backend/wbprivate/sqlide/wb_sql_editor_form.h (411 lines of code) (raw):
/*
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "workbench/wb_backend_public_interface.h"
#include "base/file_utilities.h"
#include "base/ui_form.h"
#include "base/threaded_timer.h"
#include "grts/structs.workbench.h"
#include "grts/structs.db.mgmt.h"
#include "grtpp_notifications.h"
#include "sqlide/recordset_be.h"
#include "sqlide/sql_editor_be.h"
#include "sqlide/db_sql_editor_log.h"
#include "sqlide/db_sql_editor_history_be.h"
#include "sqlide/wb_context_sqlide.h"
#include "sqlide/wb_live_schema_tree.h"
#include "cppdbc.h"
#include "mforms/view.h"
#include "SymbolTable.h"
namespace mforms {
class ToolBar;
class AppView;
class View;
class MenuItem;
class DockingPoint;
};
namespace bec {
class DBObjectEditorBE;
}
#define MAIN_DOCKING_POINT "db.query.Editor:main"
#define RESULT_DOCKING_POINT "db.Query.QueryEditor:result"
class QuerySidePalette;
class SqlEditorTreeController;
class ColumnWidthCache;
class SqlEditorPanel;
class SqlEditorResult;
namespace wb {
class SSHTunnel;
}
typedef std::vector<Recordset::Ref> Recordsets;
typedef std::shared_ptr<Recordsets> RecordsetsRef;
db_mgmt_ServerInstanceRef getServerInstance(const db_mgmt_ConnectionRef &connection);
class MYSQLWBBACKEND_PUBLIC_FUNC SqlEditorForm :
public bec::UIForm,
grt::GRTObserver,
public std::enable_shared_from_this<SqlEditorForm>,
mforms::DropDelegate {
public:
#if defined(ENABLE_TESTING)
friend class EditorFormTester;
friend class LocalEditorFormTester;
#endif
enum ServerState { UnknownState, RunningState, PossiblyStoppedState, OfflineState };
struct PSStage {
std::string name;
double wait_time;
};
struct PSWait {
std::string name;
double wait_time;
};
class RecordsetData : public Recordset::ClientData {
public:
SqlEditorResult *result_panel;
std::string generator_query;
double duration;
std::string ps_stat_error;
std::map<std::string, std::int64_t> ps_stat_info;
std::vector<PSStage> ps_stage_info;
std::vector<PSWait> ps_wait_info;
};
public:
typedef std::shared_ptr<SqlEditorForm> Ref;
typedef std::weak_ptr<SqlEditorForm> Ptr;
static SqlEditorForm::Ref create(wb::WBContextSQLIDE *wbsql, const db_mgmt_ConnectionRef &conn);
static void report_connection_failure(const std::string &error, const db_mgmt_ConnectionRef &target);
static void report_connection_failure(const grt::server_denied &info, const db_mgmt_ConnectionRef &target);
void set_tab_dock(mforms::DockingPoint *dp);
/* Callback must be set by frontend to show a busy indicator on the tab with the given index. -1 means remove it from
* all */
std::function<void(int)> set_busy_tab;
parsers::SymbolTable *databaseSymbols() { return &_databaseSymbols; }
protected:
SqlEditorForm(wb::WBContextSQLIDE *wbsql);
void update_menu_and_toolbar();
void update_toolbar_icons();
void save_workspace_order(const std::string &prefix);
std::string find_workspace_state(const std::string &workspace_name, std::unique_ptr<base::LockFile> &lock_file);
public:
virtual ~SqlEditorForm();
void cancel_connect();
virtual void close();
virtual bool is_main_form() {
return true;
}
virtual std::string get_form_context_name() const;
virtual mforms::MenuBar *get_menubar();
virtual mforms::ToolBar *get_toolbar();
std::string get_session_name();
void auto_save();
void save_workspace(const std::string &workspace_name, bool is_autosave);
bool load_workspace(const std::string &workspace_name);
void restore_last_workspace();
public:
wb::WBContextSQLIDE *wbsql() const {
return _wbsql;
}
db_query_EditorRef grtobj();
void validate_menubar();
void handle_tab_menu_action(const std::string &action, int tab_index);
void handle_history_action(const std::string &action, const std::string &sql);
public:
// do NOT use rdbms->version().. it's not specific for this connection
db_mgmt_RdbmsRef rdbms();
GrtVersionRef rdbms_version() const;
std::string get_connection_info() const {
return _connectionInfo;
}
public:
SqlEditorPanel *active_sql_editor_panel();
void sql_editor_reordered(SqlEditorPanel *editor, int new_index);
bool is_closing() const {
return _closing;
}
private:
int _sql_editors_serial = 0;
int _scratch_editors_serial = 0;
std::shared_ptr<wb::SSHTunnel> _tunnel;
db_mgmt_SSHConnectionRef _sshConnection;
void sql_editor_panel_switched();
void sql_editor_panel_closed(mforms::AppView *view);
void set_editor_tool_items_enbled(const std::string &name, bool flag);
void set_editor_tool_items_checked(const std::string &name, bool flag);
public:
void set_tool_item_checked(const std::string &name, bool flag);
boost::signals2::signal<void(MySQLEditor::Ref, bool)> sql_editor_list_changed;
SqlEditorPanel *run_sql_in_scratch_tab(const std::string &sql, bool reuse_if_possible, bool start_collapsed);
SqlEditorPanel *add_sql_editor(bool scratch = false,
bool start_collapsed = false); // returns index of the added sql_editor
void remove_sql_editor(SqlEditorPanel *panel);
SqlEditorPanel *sql_editor_panel(int index);
int sql_editor_count();
int sql_editor_panel_index(SqlEditorPanel *panel);
virtual mforms::DragOperation drag_over(mforms::View *sender, base::Point p, mforms::DragOperation allowedOperations,
const std::vector<std::string> &formats);
virtual mforms::DragOperation files_dropped(mforms::View *sender, base::Point p,
mforms::DragOperation allowedOperations,
const std::vector<std::string> &file_names);
private:
int count_connection_editors(const std::string &conn_name);
protected:
std::string create_title();
void title_changed();
void check_server_problems();
public:
virtual std::string get_title() {
return _title;
}
void update_title();
int getTunnelPort() const;
std::map<std::string, std::string> &connection_details() {
return _connection_details;
}
int server_version();
std::set<std::string> valid_charsets();
private:
grt::StringRef do_connect(std::shared_ptr<wb::SSHTunnel> tunnel, sql::Authentication::Ref &auth,
struct ConnectionErrorInfo *autherr_ptr);
std::string get_client_lib_version();
grt::StringRef do_disconnect();
void update_connected_state();
public:
bool connect(std::shared_ptr<wb::SSHTunnel> tunnel);
bool connected() const;
bool connectionIsValid() const {
return _connection.is_valid();
}
void checkIfOffline();
bool offline();
bool ping() const;
void finish_startup();
void cancel_query();
void reset();
void commit();
void rollback();
bool auto_commit();
void auto_commit(bool value);
void toggle_autocommit();
void toggle_collect_field_info();
bool collect_field_info() const;
void toggle_collect_ps_statement_events();
bool collect_ps_statement_events() const;
void set_connection(db_mgmt_ConnectionRef conn);
void run_editor_contents(bool current_statement_only);
void limit_rows(const std::string &limit_text);
std::string sql_mode() const {
return _sql_mode;
};
int lower_case_table_names() const {
return _lower_case_table_names;
}
private:
void do_commit();
public:
db_mgmt_ConnectionRef connection_descriptor() const {
return _connection;
}
db_mgmt_SSHConnectionRef getSSHConnection();
bool get_session_variable(sql::Connection *dbc_conn, const std::string &name, std::string &value);
private:
void cache_sql_mode();
void update_sql_mode_for_editors();
void query_ps_statistics(std::int64_t conn_id, std::map<std::string, std::int64_t> &stats);
std::vector<SqlEditorForm::PSStage> query_ps_stages(std::int64_t stmt_event_id);
std::vector<SqlEditorForm::PSWait> query_ps_waits(std::int64_t stmt_event_id);
private:
void create_connection(sql::Dbc_connection_handler::Ref &dbc_conn, db_mgmt_ConnectionRef db_mgmt_conn,
std::shared_ptr<wb::SSHTunnel> tunnel, sql::Authentication::Ref auth,
bool autocommit_mode, bool user_connection);
void init_connection(sql::Connection *dbc_conn_ref, const db_mgmt_ConnectionRef &connectionProperties,
sql::Dbc_connection_handler::Ref &dbc_conn, bool user_connection);
void close_connection(sql::Dbc_connection_handler::Ref &dbc_conn);
base::RecMutexLock ensure_valid_dbc_connection(sql::Dbc_connection_handler::Ref &dbc_conn,
base::RecMutex &dbc_conn_mutex, bool throw_on_block = false,
bool lockOnly = false);
base::RecMutexLock ensure_valid_usr_connection(bool throw_on_block = false, bool lockOnly = false);
base::RecMutexLock ensure_valid_aux_connection(bool throw_on_block = false, bool lockOnly = false);
std::vector<std::pair<std::string, std::string>> runQueryForCache(const std::string &query);
public:
base::RecMutexLock ensure_valid_aux_connection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly = false);
parsers::MySQLParserContext::Ref work_parser_context() {
return _work_parser_context;
};
private:
void send_message_keep_alive();
bool send_message_keep_alive_bool_wrapper() {
send_message_keep_alive();
return false;
} // need it for ThreadedTimer, which expects callbacks to return bool
void reset_keep_alive_thread();
base::RecMutexLock getAuxConnection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly = false);
base::RecMutexLock getUserConnection(sql::Dbc_connection_handler::Ref &conn, bool lockOnly = false);
void onCacheAction(bool active);
public:
ColumnWidthCache *column_width_cache() {
return _column_width_cache;
}
bool exec_editor_sql(SqlEditorPanel *editor, bool sync, bool current_statement_only = false,
bool wrap_with_non_std_delimiter = false, bool dont_add_limit_clause = false,
SqlEditorResult *into_result = NULL);
void exec_sql_retaining_editor_contents(const std::string &sql_script, SqlEditorPanel *editor, bool sync,
bool dont_add_limit_clause = false);
RecordsetsRef exec_sql_returning_results(const std::string &sql_script, bool dont_add_limit_clause);
void exec_management_sql(const std::string &sql, bool log);
db_query_ResultsetRef exec_management_query(const std::string &sql, bool log);
void exec_main_sql(const std::string &sql, bool log);
db_query_ResultsetRef exec_main_query(const std::string &sql, bool log);
void explain_current_statement();
bool is_running_query();
sql::Authentication::Ref dbc_auth_data() {
return _dbc_auth;
}
private:
enum ExecFlags { NeedNonStdDelimiter = 1 << 1, DontAddLimitClause = 1 << 2, ShowWarnings = 1 << 3 };
void update_live_schema_tree(const std::string &sql);
grt::StringRef do_exec_sql(Ptr self_ptr, std::shared_ptr<std::string> sql, SqlEditorPanel *editor, ExecFlags flags,
RecordsetsRef result_list);
void handle_command_side_effects(const std::string &sql);
public:
GrtThreadedTask::Ref exec_sql_task;
std::function<void()> post_query_slot; // called after a query is executed
private:
int on_exec_sql_finished();
public:
bool continue_on_error() {
return _continueOnError;
}
void continue_on_error(bool val);
private:
typedef boost::signals2::signal<int(long long, const std::string &, const std::string &),
boost::signals2::last_value<int>>
Error_cb;
typedef boost::signals2::signal<int(float), boost::signals2::last_value<int>> Batch_exec_progress_cb;
typedef boost::signals2::signal<int(long, long), boost::signals2::last_value<int>> Batch_exec_stat_cb;
public:
Error_cb on_sql_script_run_error;
private:
int sql_script_apply_error(long long, const std::string &, const std::string &, std::string &);
int sql_script_apply_progress(float);
int sql_script_stats(long, long);
void abort_apply_object_alter_script();
public:
void apply_object_alter_script(const std::string &alter_script, bec::DBObjectEditorBE *obj_editor, RowId log_id);
bool run_live_object_alteration_wizard(const std::string &alter_script, bec::DBObjectEditorBE *obj_editor,
RowId log_id, const std::string &log_context);
private:
void apply_changes_to_recordset(Recordset::Ptr rs_ptr);
bool run_data_changes_commit_wizard(Recordset::Ptr rs_ptr, bool skip_commit);
void apply_data_changes_commit(const std::string &sql_script_text, Recordset::Ptr rs_ptr, bool skip_commit);
void update_editor_title_schema(const std::string &schema);
public:
bool can_close();
bool can_close_(bool interactive);
void check_external_file_changes();
public:
SqlEditorPanel *new_sql_script_file();
SqlEditorPanel *new_sql_scratch_area(bool start_collapsed = false);
void new_scratch_area() {
new_sql_scratch_area(false);
}
void open_file(const std::string &path, bool in_new_tab, bool askForFile = true);
void open_file(const std::string &path = "") {
open_file(path, true, !path.empty());
}
public:
void active_schema(const std::string &value);
std::string active_schema() const;
void schemaListRefreshed(std::vector<std::string> const &schemas);
void schema_meta_data_refreshed(const std::string &schema_name, base::StringListPtr tables, base::StringListPtr views,
base::StringListPtr procedures, base::StringListPtr functions);
private:
void cache_active_schema_name();
public:
void request_refresh_schema_tree();
public:
std::string fetch_data_from_stored_procedure(std::string proc_call, std::shared_ptr<sql::ResultSet> &rs);
DbSqlEditorLog::Ref log() {
return _log;
}
DbSqlEditorHistory::Ref history() {
return _history;
}
std::string restore_sql_from_history(int entry_index, std::list<int> &detail_indexes);
int exec_sql_error_count() {
return _exec_sql_error_count;
}
std::shared_ptr<SqlEditorTreeController> get_live_tree() {
return _live_tree;
}
void schema_tree_did_populate();
std::function<void(const std::string &, bool)> output_text_slot;
public:
// Result should be RowId but that requires to change the task callback type (at least for 64bit builds).
int add_log_message(int msg_type, const std::string &msg, const std::string &context, const std::string &duration);
void set_log_message(RowId log_message_index, int msg_type, const std::string &msg, const std::string &context,
const std::string &duration);
void refresh_log_messages(bool ignore_last_message_timestamp);
protected:
DbSqlEditorLog::Ref _log;
DbSqlEditorHistory::Ref _history;
bool _serverIsOffline = false;
std::string _title;
private:
virtual void handle_grt_notification(const std::string &name, grt::ObjectRef sender, grt::DictRef info);
virtual void handle_notification(const std::string &name, void *sender, base::NotificationInfo &info);
void setup_side_palette();
void schema_row_selected();
void side_bar_filter_changed(const std::string &filter);
void note_connection_open_outcome(int error);
public:
void inspect_object(const std::string &name, const std::string &object, const std::string &type);
void toolbar_command(const std::string &command);
bool save_snippet();
void show_output_area();
mforms::View *get_sidebar();
mforms::View *get_side_palette();
void set_autosave_disabled(const bool autosave_disabled);
bool get_autosave_disabled(void);
private:
wb::WBContextSQLIDE *_wbsql;
GrtVersionRef _version;
mforms::MenuBar *_menu = nullptr;
mforms::ToolBar *_toolbar = nullptr;
std::string _connectionInfo;
base::LockFile *_autosave_lock = nullptr;
std::string _autosave_path;
mforms::DockingPoint *_tabdock = nullptr;
// Set when we triggered a refresh asynchronously.
boost::signals2::connection _overviewRefreshPending;
boost::signals2::connection _editorRefreshPending;
int _keep_alive_task_id = 0;
base::Mutex _keep_alive_thread_mutex;
Batch_exec_progress_cb on_sql_script_run_progress;
Batch_exec_stat_cb on_sql_script_run_statistics;
bool _autosave_disabled = false;
bool _loading_workspace = false;
bool _cancel_connect = false;
bool _closing = false;
bool _startup_done = false;
bool _is_running_query = false;
bool _continueOnError = false;
bool _has_pending_log_messages = false;
double _last_log_message_timestamp;
int _exec_sql_error_count;
std::shared_ptr<SqlEditorTreeController> _live_tree;
mforms::View *_side_palette_host = nullptr;
QuerySidePalette *_side_palette = nullptr;
std::string _pending_expand_nodes;
std::map<std::string, std::string> _connection_details;
std::set<std::string> _charsets;
std::string _sql_mode;
int _lower_case_table_names;
parsers::MySQLParserContext::Ref _work_parser_context; // Never use in a background thread.
db_mgmt_ConnectionRef _connection;
// connection for maintenance operations, fetching schema contents & live editors (DDL only)
sql::Dbc_connection_handler::Ref _aux_dbc_conn;
base::RecMutex _aux_dbc_conn_mutex;
// connection for running sql scripts
sql::Dbc_connection_handler::Ref _usr_dbc_conn;
mutable base::RecMutex _usr_dbc_conn_mutex;
sql::Authentication::Ref _dbc_auth;
ServerState _last_server_running_state = UnknownState;
ColumnWidthCache *_column_width_cache = nullptr;
parsers::SymbolTable _staticServerSymbols; // Charsets, collations, engines.
parsers::SymbolTable _databaseSymbols; // All available db objects reachable via the current connection.
void activate_command(const std::string &command);
void readStaticServerSymbols();
// workaround for managed code windows
struct PrivateMutex;
std::unique_ptr<PrivateMutex> _pimplMutex;
};