backend/wbprivate/sqlide/wb_sql_editor_result_panel.cpp (905 lines of code) (raw):

/* * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "wb_sql_editor_form.h" #include "wb_sql_editor_panel.h" #include "wb_sql_editor_result_panel.h" #include "spatial_data_view.h" #include "result_form_view.h" #include "objimpl/db.query/db_query_Resultset.h" #include "sqlide/recordset_cdbc_storage.h" #include "grtdb/db_helpers.h" #include "grtui/inserts_export_form.h" #include "objimpl/wrapper/mforms_ObjectReference_impl.h" #include "objimpl/db.query/db_query_Resultset.h" #include "objimpl/db.query/db_query_EditableResultset.h" #include "sqlide/column_width_cache.h" #include "base/sqlstring.h" #include "grt/parse_utils.h" #include "grt/spatial_handler.h" #include "base/log.h" #include "base/string_utilities.h" #include "base/boost_smart_ptr_helpers.h" #include "mforms/utilities.h" #include "mforms/treeview.h" #include "mforms/label.h" #include "mforms/box.h" #include "mforms/table.h" #include "mforms/tabview.h" #include "mforms/tabswitcher.h" #include "mforms/toolbar.h" #include "mforms/scrollpanel.h" #include "mforms/menubar.h" #include "mforms/gridview.h" #include "mforms/imagebox.h" #include "mforms/button.h" #include "mforms/selector.h" #include "mforms/textentry.h" #include <algorithm> using namespace base; DEFAULT_LOG_DOMAIN("SqlResult") //---------------------------------------------------------------------------------------------------------------------- class SqlEditorResult::DockingDelegate : public mforms::TabViewDockingPoint { mforms::TabSwitcher *_switcher; public: DockingDelegate(mforms::TabView *tabview, mforms::TabSwitcher *switcher, const std::string &name) : mforms::TabViewDockingPoint(tabview, name), _switcher(switcher) { } virtual void undock_view(AppView *view) { for (int i = 0; i < view_count(); i++) if (view_at_index(i) == view) { _switcher->remove_item(i); break; } mforms::TabViewDockingPoint::undock_view(view); } virtual void dock_view(AppView *view, const std::string &icon, int arg2) { mforms::TabViewDockingPoint::dock_view(view, icon, arg2); _switcher->add_item(view->get_title(), "", icon, ""); } }; //---------------------------------------------------------------------------------------------------------------------- SqlEditorResult::SqlEditorResult(SqlEditorPanel *owner) : mforms::AppView(true, "Query Result", "QueryResult", false), _owner(owner), _tabview(mforms::TabViewTabless), _switcher(mforms::VerticalIconSwitcher), _tabdock_delegate(new DockingDelegate(&_tabview, &_switcher, std::string("SqlResultPanel"))), _tabdock(_tabdock_delegate, true), _pinned(false) { _result_grid = NULL; _grid_header_menu = NULL; _column_info_menu = NULL; _column_info_created = false; _query_stats_created = false; _form_view_created = false; _column_info_box = nullptr; _query_stats_box = nullptr; _execution_plan_placeholder = nullptr; _query_stats_panel = nullptr; _form_result_view = nullptr; _spatial_view_initialized = false; _spatial_result_view = NULL; add(&_tabview, true, true); _switcher.set_name("Resultset View Switcher"); _switcher.attach_to_tabview(&_tabview); _switcher.set_collapsed(bec::GRTManager::get()->get_app_option_int("Recordset:SwitcherCollapsed", 0) != 0); add(&_switcher, false, true); _switcher.signal_changed()->connect(std::bind(&SqlEditorResult::switch_tab, this)); _switcher.signal_collapse_changed()->connect(std::bind(&SqlEditorResult::switcher_collapsed, this)); // put a placeholder for the resultset, which will be replaced when a resultset is actually available _resultset_placeholder = mforms::manage(new mforms::AppView(false, "Result Grid Placeholder", "ResultGridPlaceholder", false)); _resultset_placeholder->set_title("Result\nGrid"); _resultset_placeholder->set_identifier("result_grid"); _tabdock.dock_view(_resultset_placeholder, "output_type-resultset.png"); _tabdock.set_name("Resultset Views"); { db_query_QueryEditorRef editor(owner->grtobj()); _grtobj = db_query_ResultPanelRef(grt::Initialized); _grtobj->dockingPoint(mforms_to_grt(&_tabdock)); } set_on_close(std::bind(&SqlEditorResult::can_close, this)); NotificationCenter::get()->add_observer(this, "GNColorsChanged"); updateColors(); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::handle_notification(const std::string &name, void *sender, base::NotificationInfo &info) { if (name == "GNColorsChanged") { updateColors(); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::updateColors() { std::string background = base::Color::getSystemColor(TextBackgroundColor).to_html(); if (_resultset_placeholder != nullptr) _resultset_placeholder->set_back_color(background); if (_column_info_box != nullptr) _column_info_box->set_back_color(background); if (_query_stats_box != nullptr) _query_stats_box->set_back_color(background); if (_execution_plan_placeholder != nullptr) _execution_plan_placeholder->set_back_color(background); if (_query_stats_panel != nullptr) _query_stats_panel->set_back_color(background); if (_form_result_view != nullptr) _form_result_view->updateColors(); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::reset_sorting() { Recordset::Ref rset(recordset()); if (rset) rset->sort_by(0, 0, false); if (_result_grid) { for (int i = 0; i < _result_grid->get_column_count(); i++) _result_grid->set_column_header_indicator(i, mforms::NoIndicator); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::copy_column_name() { int column = _result_grid->get_clicked_header_column(); Recordset::Ref rset(recordset()); if (rset) mforms::Utilities::set_clipboard_text(rset->get_column_caption(column)); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::copy_all_column_names() { Recordset::Ref rset(recordset()); if (rset) { std::string text; size_t visibleColumnCount = rset->get_column_count(); Recordset::Column_names::const_iterator end = rset->column_names()->end(); for (Recordset::Column_names::const_iterator col = rset->column_names()->begin(); col != end && visibleColumnCount > 0; ++col, --visibleColumnCount) text.append(", ").append(*col); if (!text.empty()) text = text.substr(2); mforms::Utilities::set_clipboard_text(text); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::open_field_editor(int row, int column) { Recordset::Ref rset(recordset()); if (rset) { Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage())); if (storage) { rset->open_field_data_editor(row, column, storage->field_info()[column].type); } } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::update_selection_for_menu_extra(mforms::ContextMenu *menu, const std::vector<int> &rows, int column) { mforms::MenuItem *item = menu->find_item("edit_cell"); if (item) { if (item->signal_clicked()->empty() && !rows.empty()) item->signal_clicked()->connect(std::bind(&SqlEditorResult::open_field_editor, this, rows[0], column)); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::set_recordset(Recordset::Ref rset) { if (_resultset_placeholder) { _tabdock_delegate->undock_view(_resultset_placeholder); _resultset_placeholder = NULL; } _rset = rset; if (!rset->is_readonly()) _grtobj->resultset(grtwrap_editablerecordset(grtobj(), rset)); else _grtobj->resultset(grtwrap_recordset(grtobj(), rset)); rset->update_selection_for_menu_extra = std::bind(&SqlEditorResult::update_selection_for_menu_extra, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); rset->get_toolbar() ->find_item("record_export") ->signal_activated() ->connect(std::bind(&SqlEditorResult::show_export_recordset, this)); if (rset->get_toolbar()->find_item("record_import")) rset->get_toolbar() ->find_item("record_import") ->signal_activated() ->connect(std::bind(&SqlEditorResult::show_import_recordset, this)); // reset the column header indicators rset->get_toolbar() ->find_item("record_sort_reset") ->signal_activated() ->connect(std::bind(&SqlEditorResult::reset_sorting, this)); _grid_header_menu = new mforms::ContextMenu(); _grid_header_menu->add_item_with_title("Copy Field Name", std::bind(&SqlEditorResult::copy_column_name, this), "Copy Field Name", ""); _grid_header_menu->add_item_with_title("Copy All Field Names", std::bind(&SqlEditorResult::copy_all_column_names, this), "Copy All Field Names", ""); _grid_header_menu->add_separator(); _grid_header_menu->add_item_with_title("Reset Sorting", std::bind(&SqlEditorResult::reset_sorting, this), "Reset Sorting", ""); _grid_header_menu->add_item_with_title("Reset Column Widths", std::bind(&SqlEditorResult::reset_column_widths, this), "Reset Column Widths", ""); std::string fontDescription = bec::GRTManager::get()->get_app_option_string("workbench.general.Resultset:Font"); std::string font; float size = 0; bool bold, italic; base::parse_font_description(fontDescription, font, size, bold, italic); rset->setFont(font, size); mforms::GridView *grid = mforms::manage(mforms::GridView::create(rset)); { if (!font.empty()) grid->set_font(fontDescription); grid->set_header_menu(_grid_header_menu); } dock_result_grid(grid); Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage())); rset->caption(strfmt("%s %i", (storage->table_name().empty() ? _("Result") : storage->table_name().c_str()), ++_owner->_rs_sequence)); bec::UIForm::scoped_connect(rset->get_context_menu()->signal_will_show(), std::bind(&SqlEditorPanel::on_recordset_context_menu_show, _owner, Recordset::Ptr(rset))); restore_grid_column_widths(); bec::UIForm::scoped_connect(_result_grid->signal_column_resized(), std::bind(&SqlEditorResult::on_recordset_column_resized, this, std::placeholders::_1)); bec::UIForm::scoped_connect(_result_grid->signal_columns_resized(), std::bind(&SqlEditorResult::onRecordsetColumnsResized, this, std::placeholders::_1)); rset->data_edited_signal.connect(std::bind(&SqlEditorPanel::resultset_edited, _owner)); rset->data_edited_signal.connect(std::bind(&mforms::View::set_needs_repaint, grid)); } //---------------------------------------------------------------------------------------------------------------------- SqlEditorResult::~SqlEditorResult() { base::NotificationCenter::get()->remove_observer(this); delete _column_info_menu; delete _grid_header_menu; } //---------------------------------------------------------------------------------------------------------------------- Recordset::Ref SqlEditorResult::recordset() const { return _rset.lock(); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorResult::has_pending_changes() { Recordset::Ref rset(recordset()); if (rset) return rset->has_pending_changes(); return false; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::apply_changes() { Recordset::Ref rset(recordset()); if (rset) rset->apply_changes(); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::discard_changes() { Recordset::Ref rset(recordset()); if (rset) rset->rollback(); } //---------------------------------------------------------------------------------------------------------------------- std::string SqlEditorResult::caption() const { RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs, "") { return rs->caption(); } } //---------------------------------------------------------------------------------------------------------------------- std::vector<SpatialDataView::SpatialDataSource> SqlEditorResult::get_spatial_columns() { std::vector<SpatialDataView::SpatialDataSource> spatial_columns; int i = 0; Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(_rset.lock()->data_storage())); std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info()); for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin(); iter != field_info.end(); ++iter, ++i) { if (iter->type == "GEOMETRY") { SpatialDataView::SpatialDataSource field; field.source = get_title(); field.resultset = _rset; field.column = iter->field; field.type = iter->type; field.column_index = i; spatial_columns.push_back(field); } } return spatial_columns; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::set_title(const std::string &title) { grtobj()->name(title); mforms::AppView::set_title(title); } //---------------------------------------------------------------------------------------------------------------------- bool SqlEditorResult::can_close() { if (Recordset::Ref rs = recordset()) if (!rs->can_close(true)) return false; if (!_tabdock.close_all_views()) return false; return true; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::close() { // called by DockingPoint::close_view() if (Recordset::Ref rs = recordset()) rs->close(); _tabdock.close_all_views(); mforms::AppView::close(); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::switch_tab() { mforms::AppView *tab = _tabdock.selected_view(); if (tab) { if (tab->identifier() == "column_info" && !_column_info_created) { _column_info_created = true; create_column_info_panel(); } else if (tab->identifier() == "query_stats" && !_query_stats_created) { _query_stats_created = true; create_query_stats_panel(); } else if (tab->identifier() == "form_result") { if (!_form_view_created) { _form_view_created = true; _form_result_view->init_for_resultset(_rset, _owner->owner()); } _form_result_view->display_record(); } else if (tab->identifier() == "result_grid") { if (_resultset_placeholder) { _owner->owner()->exec_editor_sql(_owner, true, true, true, false, this); if (!_rset.expired()) set_title(_rset.lock()->caption()); } } else if (tab->identifier() == "execution_plan") { if (_execution_plan_placeholder) { _tabdock_delegate->undock_view(_execution_plan_placeholder); _execution_plan_placeholder = NULL; // if the explain tab is just a placeholder, execute visual explain, which will replace the tab when docking grt::BaseListRef args(true); args.ginsert(_owner->grtobj()); args.ginsert(_grtobj); try { // run the visual explain plugin, so it will fill the result panel grt::GRT::get()->call_module_function("SQLIDEQueryAnalysis", "visualExplain", args); } catch (std::exception &exc) { logError("Error executing visual explain: %s\n", exc.what()); mforms::Utilities::show_error( "Execution Plan", "An internal error occurred while building the execution plan, please file a bug report.", "OK"); } } } else if (tab->identifier() == "spatial_result_view") { if (!_spatial_view_initialized) { _spatial_view_initialized = true; _spatial_result_view->refresh_layers(); } _spatial_result_view->activate(); } } updateColors(); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::add_switch_toggle_toolbar_item(mforms::ToolBar *tbar) { _collapse_toggled_sig.disconnect(); mforms::App *app = mforms::App::get(); tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::ExpanderItem))); mforms::ToolBarItem *item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem)); item->set_name("Side Toggle"); item->setInternalName("sidetoggle"); item->set_icon(app->get_resource_path("output_type-toggle-on.png")); item->set_alt_icon(app->get_resource_path("output_type-toggle-off.png")); item->signal_activated()->connect(std::bind(&SqlEditorResult::toggle_switcher_collapsed, this)); item->set_checked(!_switcher.get_collapsed()); tbar->add_item(item); _collapse_toggled_sig = _collapse_toggled.connect(std::bind(&mforms::ToolBarItem::set_checked, item, std::placeholders::_1)); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::switcher_collapsed() { bool state = _switcher.get_collapsed(); for (std::list<mforms::ToolBar *>::const_iterator it = _toolbars.begin(); it != _toolbars.end(); ++it) { (*it)->find_item("sidetoggle")->set_checked(state); } relayout(); bec::GRTManager::get()->set_app_option("Recordset:SwitcherCollapsed", grt::IntegerRef(state ? 1 : 0)); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::show_export_recordset() { try { RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { grt::ValueRef option(bec::GRTManager::get()->get_app_option("Recordset:LastExportPath")); std::string path = option.is_valid() ? grt::StringRef::cast_from(option) : ""; option = bec::GRTManager::get()->get_app_option("Recordset:LastExportExtension"); std::string extension = option.is_valid() ? grt::StringRef::cast_from(option) : ""; InsertsExportForm exporter(0 /*mforms::Form::main_form()*/, rs_ref, extension); exporter.set_title(_("Export Resultset")); if (!path.empty()) exporter.set_path(path); path = exporter.run(); if (path.empty()) bec::GRTManager::get()->replace_status_text(_("Export resultset canceled")); else { bec::GRTManager::get()->replace_status_text(strfmt(_("Exported resultset to %s"), path.c_str())); bec::GRTManager::get()->set_app_option("Recordset:LastExportPath", grt::StringRef(path)); extension = base::extension(path); if (!extension.empty() && extension[0] == '.') extension = extension.substr(1); if (!extension.empty()) bec::GRTManager::get()->set_app_option("Recordset:LastExportExtension", grt::StringRef(extension)); } } } catch (const std::exception &exc) { mforms::Utilities::show_error("Error exporting recordset", exc.what(), "OK"); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::show_import_recordset() { try { RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { grt::BaseListRef args(true); grt::Module *mod = grt::GRT::get()->get_module("SQLIDEUtils"); if (mod) { args.ginsert(_owner->owner()->grtobj()); mod->call_function("launchPowerImport", args); } else logFatal("Unable to launch import wizard\n"); } } catch (const std::exception &exc) { mforms::Utilities::show_error("Error importing recordset", exc.what(), "OK"); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::toggle_switcher_collapsed() { bool flag = !_switcher.get_collapsed(); _switcher.set_collapsed(flag); _collapse_toggled(flag); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::on_recordset_column_resized(int column) { if (column >= 0) { std::string column_id = _column_width_storage_ids[column]; int width = _result_grid->get_column_width(column); _owner->owner()->column_width_cache()->save_column_width(column_id, width); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::onRecordsetColumnsResized(const std::vector<int> cols) { std::vector<int>::const_iterator it; std::map<std::string, int> widths; for (it = cols.begin(); it != cols.end(); ++it) { if (*it >= 0) { std::string column_id = _column_width_storage_ids[*it]; int width = _result_grid->get_column_width(*it); widths.insert(std::make_pair(column_id, width)); } } if (!widths.empty()) { bec::GRTManager::get()->get_dispatcher()->execute_async_function("store column widths", [this, widths]() { _owner->owner()->column_width_cache()->save_columns_width(widths); return grt::ValueRef(); }); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::reset_column_widths() { ColumnWidthCache *cache = _owner->owner()->column_width_cache(); RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage())); std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info()); for (int c = (int)field_info.size(), i = 0; i < c; i++) { std::string column_storage_id; column_storage_id = field_info[i].field + "::" + field_info[i].schema + "::" + field_info[i].table; cache->delete_column_width(column_storage_id); } } restore_grid_column_widths(); } //---------------------------------------------------------------------------------------------------------------------- std::vector<float> SqlEditorResult::get_autofit_column_widths(Recordset *rs) { std::vector<float> widths(rs->get_column_count()); std::string font = bec::GRTManager::get()->get_app_option_string("workbench.general.Resultset:Font"); for (size_t c = rs->get_column_count(), j = 0; j < c; j++) { widths[j] = (float)mforms::Utilities::get_text_width(rs->get_column_caption(j), font); } // look in 1st 10 rows for the max width of the columns for (size_t i = 0; i < 10; i++) { for (size_t c = rs->get_column_count(), j = 0; j < c; j++) { std::string value; rs->get_field(i, j, value); widths[j] = std::max(widths[j], (float)mforms::Utilities::get_text_width(value, font)); } } return widths; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::restore_grid_column_widths() { ColumnWidthCache *cache = _owner->owner()->column_width_cache(); RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage())); std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info()); std::vector<float> autofit_widths; for (int c = (int)field_info.size(), i = 0; i < c; i++) { std::string column_storage_id; column_storage_id = field_info[i].field + "::" + field_info[i].schema + "::" + field_info[i].table; _column_width_storage_ids.push_back(column_storage_id); // check if we have a remembered column width int width = cache->get_column_width(column_storage_id); if (width > 0) { _result_grid->set_column_width(i, width); } else { // if not, we set a default width based on the width of the 1st 50 rows if (autofit_widths.empty()) autofit_widths = get_autofit_column_widths(rs); int width = int(autofit_widths[i] + 10); if (width < 40) width = 40; else if (width > 250) width = 250; _result_grid->set_column_width(i, width); } } } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::dock_result_grid(mforms::GridView *view) { _result_grid = view; view->set_name("Result Grid Wrapper"); view->setInternalName("result-grid-wrapper"); mforms::AppView *grid_view; { mforms::AppView *box = grid_view = mforms::manage(new mforms::AppView(false, "Result Grid View", "ResultGridView", false)); box->set_name("Resultset Host"); box->setInternalName("resultset-host"); mforms::ToolBar *tbar = _rset.lock()->get_toolbar(); tbar->set_name("Resultset Toolbar"); tbar->setInternalName("resultset-toolbar"); _toolbars.push_back(tbar); add_switch_toggle_toolbar_item(tbar); box->add(tbar, false, true); box->add(view, true, true); box->set_title("Result\nGrid"); box->set_identifier("result_grid"); _tabdock.dock_view(box, "output_type-resultset.png"); } { bool editable = false; if (Recordset::Ref rset = _rset.lock()) editable = !rset->is_readonly(); _form_result_view = mforms::manage(new ResultFormView(editable)); add_switch_toggle_toolbar_item(_form_result_view->get_toolbar()); _form_result_view->set_title("Form\nEditor"); _form_result_view->set_identifier("form_result"); _tabdock.dock_view(_form_result_view, "output_type-formeditor.png"); } { _column_info_box = mforms::manage(new mforms::AppView(false, "Result Field Types", "ResultFieldTypes", false)); _column_info_box->set_title("Field\nTypes"); _column_info_box->set_identifier("column_info"); _tabdock.dock_view(_column_info_box, "output_type-fieldtypes.png"); } { _query_stats_box = mforms::manage(new mforms::AppView(false, "Result Query Stats", "ResultQueryStats", false)); _query_stats_box->set_title("Query\nStats"); _query_stats_box->set_identifier("query_stats"); _tabdock.dock_view(_query_stats_box, "output_type-querystats.png"); } create_spatial_view_panel_if_needed(); // reorder the explain tab to last bool has_explain_tab = false; for (int i = 0; i < _tabdock_delegate->view_count(); i++) { mforms::AppView *view = _tabdock_delegate->view_at_index(i); if (!view) continue; if (view->identifier() == "execution_plan") { has_explain_tab = true; view->retain(); _tabdock_delegate->undock_view(view); _tabdock.dock_view(view, "output_type-executionplan.png"); view->release(); break; } } if (!has_explain_tab) { _execution_plan_placeholder = mforms::manage(new mforms::AppView(false, "Execution Plan", "ExecutionPlan", false)); _execution_plan_placeholder->set_title("Execution\nPlan"); _execution_plan_placeholder->set_identifier("execution_plan"); _tabdock.dock_view(_execution_plan_placeholder, "output_type-executionplan.png"); } _switcher.set_selected(0); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::create_spatial_view_panel_if_needed() { if (Recordset::Ref rset = _rset.lock()) { Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage())); bool has_geometry = false; std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info()); for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin(); iter != field_info.end(); ++iter) { if (iter->type == "GEOMETRY") { has_geometry = true; break; } } if (has_geometry) { if (!spatial::Projection::get_instance().check_libproj_availability()) { mforms::Utilities::show_message_and_remember("Unable to initialize Spatial Viewer", "Spatial support requires the PROJ.4 library (libproj). If you " "already have it installed, please set the PROJSO environment " "variable to its location before starting Workbench.", "Ok", "", "", "SqlEditorResult.libprojcheck", ""); return; } _spatial_result_view = mforms::manage(new SpatialDataView(this)); add_switch_toggle_toolbar_item(_spatial_result_view->get_toolbar()); mforms::AppView *box = mforms::manage(new mforms::AppView(false, "Spatial View", "SpatialView", false)); box->set_title("Spatial\nView"); box->set_identifier("spatial_result_view"); box->set_name("Spatial Host"); box->setInternalName("spatial-host"); box->add(_spatial_result_view, true, true); _tabdock.dock_view(box, "output_type-spacialview.png"); } } } //---------------------------------------------------------------------------------------------------------------------- static std::string format_ps_time(std::int64_t t) { int hours, mins; double secs; secs = t / 1000000000000.0; mins = int(secs / 60) % 60; hours = mins / 3600; return base::strfmt("%i:%02i:%02.8f", hours, mins, secs); } //---------------------------------------------------------------------------------------------------------------------- static mforms::Label *bold_label(const std::string text) { mforms::Label *l = mforms::manage(new mforms::Label(text)); l->set_style(mforms::BoldStyle); return l; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::copy_column_info(mforms::TreeView *tree) { std::list<mforms::TreeNodeRef> nodes(tree->get_selection()); std::string text; for (std::list<mforms::TreeNodeRef>::const_iterator node = nodes.begin(); node != nodes.end(); ++node) { text.append(base::strfmt("%i", (*node)->get_int(0))); for (int i = 1; i < tree->get_column_count(); i++) { if (i >= 1 && i <= 5) text.append(",").append((*node)->get_string(i)); else text.append(",").append(base::strfmt("%i", (*node)->get_int(i))); } text.append("\n"); } mforms::Utilities::set_clipboard_text(text); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::copy_column_info_name(mforms::TreeView *tree) { std::list<mforms::TreeNodeRef> nodes(tree->get_selection()); std::string text; for (std::list<mforms::TreeNodeRef>::const_iterator node = nodes.begin(); node != nodes.end(); ++node) { text.append((*node)->get_string(1)).append("\n"); } mforms::Utilities::set_clipboard_text(text); } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::create_column_info_panel() { RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { Recordset_cdbc_storage::Ref storage(std::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage())); mforms::Box *box = _column_info_box; mforms::ToolBar *tbar = mforms::manage(new mforms::ToolBar(mforms::SecondaryToolBar)); _toolbars.push_back(tbar); mforms::ToolBarItem *item; item = mforms::manage(new mforms::ToolBarItem(mforms::TitleItem)); item->set_text("Field Types"); tbar->add_item(item); add_switch_toggle_toolbar_item(tbar); box->add(tbar, false, true); if (_owner->owner()->collect_field_info()) { mforms::TreeView *tree = mforms::manage(new mforms::TreeView(mforms::TreeFlatList | mforms::TreeAltRowColors | mforms::TreeShowRowLines | mforms::TreeShowColumnLines | mforms::TreeNoBorder)); tree->add_column(mforms::IntegerColumnType, "#", 50); tree->add_column(mforms::StringColumnType, "Field", 130); tree->add_column(mforms::StringColumnType, "Schema", 130); tree->add_column(mforms::StringColumnType, "Table", 130); tree->add_column(mforms::StringColumnType, "Type", 150); tree->add_column(mforms::StringColumnType, "Character Set", 100); tree->add_column(mforms::IntegerColumnType, "Display Size", 80); tree->add_column(mforms::IntegerColumnType, "Precision", 80); tree->add_column(mforms::IntegerColumnType, "Scale", 80); tree->end_columns(); tree->set_selection_mode(mforms::TreeSelectMultiple); _column_info_menu = new mforms::ContextMenu(); _column_info_menu->add_item_with_title("Copy", std::bind(&SqlEditorResult::copy_column_info, this, tree), "Copy", ""); _column_info_menu->add_item_with_title("Copy Name", std::bind(&SqlEditorResult::copy_column_info_name, this, tree), "Copy Name", ""); tree->set_context_menu(_column_info_menu); int i = 0; std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info()); for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin(); iter != field_info.end(); ++iter) { mforms::TreeNodeRef node = tree->add_node(); node->set_int(0, ++i); node->set_string(1, iter->field); node->set_string(2, iter->schema); node->set_string(3, iter->table); node->set_string(4, iter->type); node->set_string(5, iter->charset); node->set_int(6, iter->display_size); node->set_int(7, iter->precision); node->set_int(8, iter->scale); } box->add(tree, true, true); } } } //---------------------------------------------------------------------------------------------------------------------- static struct ColorDefinitions { double r, g, b; } colors[] = { {0.17, 0.34, 0.89}, {0.89, 0.34, 0.17}, {0.34, 0.89, 0.17}, {1.00, 0.37, 0.37}, {0.17, 0.89, 0.89}, {0.89, 0.89, 0.17}, {0.89, 0.17, 0.89}, {0.37, 0.64, 0.64}, {0.64, 0.37, 0.64}, {0.64, 0.64, 0.37}, }; static std::string render_stages(std::vector<SqlEditorForm::PSStage> &stages) { std::string path = mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/stages.png"; int ncolors = sizeof(colors) / sizeof(ColorDefinitions); double total = 0; for (size_t i = 0; i < stages.size(); i++) { total += stages[i].wait_time; } int rows_of_text = (int)stages.size() / 3 + 1; cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 800, 30 + 20 + rows_of_text * 25); cairo_t *cr = cairo_create(surf); cairo_set_font_size(cr, 12); cairo_set_line_width(cr, 1); double x = 0.0; for (size_t i = 0; i < stages.size(); i++) { cairo_set_source_rgb(cr, colors[i % ncolors].r, colors[i % ncolors].g, colors[i % ncolors].b); cairo_rectangle(cr, x, 0, x + stages[i].wait_time * 800 / total, 30); cairo_fill(cr); { double capx = (i % 3) * 800.0 / 3 + 1; double capy = 50 + (i / 3) * 25.0; cairo_text_extents_t ext; cairo_text_extents(cr, stages[i].name.c_str(), &ext); cairo_save(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_move_to(cr, floor(capx) + 30, capy + 25 + ext.y_bearing); if (base::hasPrefix(stages[i].name, "stage/sql/")) cairo_show_text( cr, base::strfmt("%s - %.4fms", stages[i].name.c_str() + sizeof("stage/sql/") - 1, stages[i].wait_time).c_str()); else cairo_show_text(cr, base::strfmt("%s - %.4fms", stages[i].name.c_str(), stages[i].wait_time).c_str()); cairo_rectangle(cr, floor(capx), capy, 20, 20); cairo_stroke_preserve(cr); cairo_restore(cr); cairo_fill(cr); } x += stages[i].wait_time * 800 / total; } cairo_rectangle(cr, 0, 0, 800, 30); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke(cr); cairo_set_line_width(cr, 3); cairo_set_source_rgba(cr, 0, 0, 0, 0.3); cairo_move_to(cr, 2, 28); cairo_line_to(cr, 2, 2); cairo_line_to(cr, 798, 2); cairo_stroke(cr); cairo_surface_write_to_png(surf, path.c_str()); cairo_destroy(cr); cairo_surface_destroy(surf); return path; } //---------------------------------------------------------------------------------------------------------------------- static std::string render_waits(std::vector<SqlEditorForm::PSWait> &waits) { std::string path = mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/waits.png"; int ncolors = sizeof(colors) / sizeof(ColorDefinitions); double total = 0; for (size_t i = 0; i < waits.size(); i++) { total += waits[i].wait_time; } int rows_of_text = (int)waits.size() / 2 + 1; cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 800, 30 + 20 + rows_of_text * 25); cairo_t *cr = cairo_create(surf); cairo_set_font_size(cr, 12); cairo_set_line_width(cr, 1); double x = 0.0; for (size_t i = 0; i < waits.size(); i++) { cairo_set_source_rgb(cr, colors[i % ncolors].r, colors[i % ncolors].g, colors[i % ncolors].b); cairo_rectangle(cr, x, 0, x + waits[i].wait_time * 800 / total, 30); cairo_fill(cr); { double capx = (i % 2) * 800.0 / 2 + 1; double capy = 50 + (i / 2) * 25.0; cairo_text_extents_t ext; cairo_text_extents(cr, waits[i].name.c_str(), &ext); cairo_save(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_move_to(cr, floor(capx) + 30, capy + 25 + ext.y_bearing); cairo_show_text(cr, base::strfmt("%s - %.4fms", waits[i].name.c_str(), waits[i].wait_time).c_str()); cairo_rectangle(cr, floor(capx), capy, 20, 20); cairo_stroke_preserve(cr); cairo_restore(cr); cairo_fill(cr); } x += waits[i].wait_time * 800 / total; } cairo_rectangle(cr, 0, 0, 800, 30); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke(cr); cairo_set_line_width(cr, 3); cairo_set_source_rgba(cr, 0, 0, 0, 0.3); cairo_move_to(cr, 2, 28); cairo_line_to(cr, 2, 2); cairo_line_to(cr, 798, 2); cairo_stroke(cr); cairo_surface_write_to_png(surf, path.c_str()); cairo_destroy(cr); cairo_surface_destroy(surf); return path; } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::create_query_stats_panel() { RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs) { SqlEditorForm::RecordsetData *rsdata = dynamic_cast<SqlEditorForm::RecordsetData *>(rs->client_data()); std::string info; _query_stats_panel = mforms::manage(new mforms::ScrollPanel()); mforms::Table *table = mforms::manage(new mforms::Table()); table->set_padding(20); table->set_row_count(6); table->set_column_count(2); table->set_row_spacing(4); mforms::ToolBar *tbar = mforms::manage(new mforms::ToolBar(mforms::SecondaryToolBar)); _toolbars.push_back(tbar); mforms::ToolBarItem *item; item = mforms::manage(new mforms::ToolBarItem(mforms::TitleItem)); item->set_text("Query Statistics"); tbar->add_item(item); add_switch_toggle_toolbar_item(tbar); _query_stats_box->add(tbar, false, true); mforms::Box *box = mforms::manage(new mforms::Box(false)); box->set_padding(8); // show basic stats box->add(bold_label("Timing (as measured at client side):"), false, true); info.clear(); info = strfmt("Execution time: %s\n", format_ps_time(std::int64_t(rsdata->duration * 1000000000000.0)).c_str()); box->add(mforms::manage(new mforms::Label(info)), false, true); // if we're in a server with PS, show some extra PS goodies // we need to convert this to long long it cause int64_t is not the same (long long) on the all platforms. std::map<std::string, long long int> ps_stats; for (auto &it : rsdata->ps_stat_info) ps_stats[it.first] = (long long int)it.second; if (ps_stats.size() <= 1) // "EVENT_ID" is always present { if (!rsdata->ps_stat_error.empty()) box->add(bold_label(rsdata->ps_stat_error), false, true); else box->add(bold_label("For more details, enable \"statement instrumentation\" for the Performance Schema and " "\"Query -> Collect Performance Schema Stats\"."), false, true); table->add(box, 0, 1, 1, 2, mforms::HFillFlag | mforms::HExpandFlag | mforms::VFillFlag); } else { box->add(bold_label("Timing (as measured by the server):"), false, true); info.clear(); info.append(strfmt("Execution time: %s\n", format_ps_time(ps_stats["TIMER_WAIT"]).c_str())); info.append(strfmt("Table lock wait time: %s\n", format_ps_time(ps_stats["LOCK_TIME"]).c_str())); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Errors:"), false, true); info.clear(); info.append(strfmt("Had Errors: %s\n", ps_stats["ERRORS"] ? "YES" : "NO")); info.append(strfmt("Warnings: %lli\n", ps_stats["WARNINGS"])); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Rows Processed:"), false, true); info.clear(); info.append(strfmt("Rows affected: %lli\n", ps_stats["ROWS_AFFECTED"])); info.append(strfmt("Rows sent to client: %lli\n", ps_stats["ROWS_SENT"])); info.append(strfmt("Rows examined: %lli\n", ps_stats["ROWS_EXAMINED"])); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Temporary Tables:"), false, true); info.clear(); info.append(strfmt("Temporary disk tables created: %lli\n", ps_stats["CREATED_TMP_DISK_TABLES"])); info.append(strfmt("Temporary tables created: %lli\n", ps_stats["CREATED_TMP_TABLES"])); box->add(mforms::manage(new mforms::Label(info)), false, true); table->add(box, 0, 1, 1, 2, mforms::HFillFlag | mforms::HExpandFlag | mforms::VFillFlag); box = mforms::manage(new mforms::Box(false)); box->set_padding(8); box->add(bold_label("Joins per Type:"), false, true); info.clear(); info.append(strfmt("Full table scans (Select_scan): %lli\n", ps_stats["SELECT_SCAN"])); info.append(strfmt("Joins using table scans (Select_full_join): %lli\n", ps_stats["SELECT_FULL_JOIN"])); info.append( strfmt("Joins using range search (Select_full_range_join): %lli\n", ps_stats["SELECT_FULL_RANGE_JOIN"])); info.append(strfmt("Joins with range checks (Select_range_check): %lli\n", ps_stats["SELECT_RANGE_CHECK"])); info.append(strfmt("Joins using range (Select_range): %lli\n", ps_stats["SELECT_RANGE"])); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Sorting:"), false, true); info.clear(); info.append(strfmt("Sorted rows (Sort_rows): %lli\n", ps_stats["SORT_ROWS"])); info.append(strfmt("Sort merge passes (Sort_merge_passes): %lli\n", ps_stats["SORT_MERGE_PASSES"])); info.append(strfmt("Sorts with ranges (Sort_range): %lli\n", ps_stats["SORT_RANGE"])); info.append(strfmt("Sorts with table scans (Sort_scan): %lli\n", ps_stats["SORT_SCAN"])); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Index Usage:"), false, true); info.clear(); if (ps_stats["NO_INDEX_USED"]) info.append("No Index used"); else info.append("At least one Index was used"); if (ps_stats["NO_GOOD_INDEX_USED"]) info.append("No good index used"); info.append("\n"); box->add(mforms::manage(new mforms::Label(info)), false, true); box->add(bold_label("Other Info:"), false, true); info.clear(); info.append(strfmt("Event Id: %lli\n", ps_stats["EVENT_ID"])); info.append(strfmt("Thread Id: %lli\n", ps_stats["THREAD_ID"])); box->add(mforms::manage(new mforms::Label(info)), false, true); table->add(box, 1, 2, 1, 2, mforms::HFillFlag | mforms::HExpandFlag | mforms::VFillFlag); } std::vector<SqlEditorForm::PSStage> stages(rsdata->ps_stage_info); if (!stages.empty()) { mforms::Label *l = mforms::manage(new mforms::Label("Time Spent per Execution Stage (aggregated)")); l->set_text_align(mforms::MiddleCenter); l->set_style(mforms::BoldStyle); table->add(l, 0, 2, 2, 3, mforms::HFillFlag); std::string file = render_stages(stages); mforms::ImageBox *image = mforms::manage(new mforms::ImageBox()); image->set_image(file); table->add(image, 0, 2, 3, 4, mforms::FillAndExpand); } std::vector<SqlEditorForm::PSWait> waits(rsdata->ps_wait_info); if (!waits.empty()) { mforms::Label *l = mforms::manage(new mforms::Label("Time Spent Waiting (aggregated)")); l->set_text_align(mforms::MiddleCenter); l->set_style(mforms::BoldStyle); table->add(l, 0, 2, 4, 5, mforms::HFillFlag); std::string file = render_waits(waits); mforms::ImageBox *image = mforms::manage(new mforms::ImageBox()); image->set_image(file); table->add(image, 0, 2, 5, 6, mforms::HFillFlag | mforms::VFillFlag); } _query_stats_panel->add(table); _query_stats_box->add(_query_stats_panel, true, true); } } //---------------------------------------------------------------------------------------------------------------------- void SqlEditorResult::view_record_in_form(int row_id) { if (_form_result_view) { _tabview.set_active_tab(1); switch_tab(); _form_result_view->display_record(row_id); } } //----------------------------------------------------------------------------------------------------------------------