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