plugins/db.mysql.editors/backend/mysql_table_editor.cpp (1,648 lines of code) (raw):
/*
* Copyright (c) 2009, 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
*/
#include "mysql_table_editor.h"
#include "grtdb/db_object_helpers.h"
#include "db.mysql/src/module_db_mysql.h"
#include "grt/validation_manager.h"
#include "base/string_utilities.h"
#include "mforms/code_editor.h"
#include "mforms/table.h"
#include "mforms/treeview.h"
#include "mforms/textentry.h"
#include "mforms/button.h"
#include "mforms/box.h"
#include "mforms/label.h"
#include "mforms/toolbar.h"
#include "mforms/menubar.h"
using namespace bec;
using namespace base;
using namespace parsers;
MySQLTableColumnsListBE::MySQLTableColumnsListBE(MySQLTableEditorBE *owner) : bec::TableColumnsListBE(owner) {
}
bool MySQLTableColumnsListBE::set_field(const NodeId &node, ColumnId column, const std::string &value) {
db_mysql_ColumnRef col;
if (node.is_valid() && node[0] < real_count()) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_owner->get_table());
col = table->columns().get(node[0]);
if (!col.is_valid())
return false;
switch (column) {
case Type:
// Remove auto increment for non integer types
if (is_int_datatype(value) == false)
col->autoIncrement(false);
break;
case GeneratedStorageType: {
std::string tmpValue = base::toupper(value);
if (tmpValue == "VIRTUAL" || tmpValue == "STORED") {
AutoUndoEdit undo(_owner);
col->generatedStorage(tmpValue);
undo.end(strfmt(_("Change Generated Column Storage Type of '%s.%s' to %s"), _owner->get_name().c_str(),
col->name().c_str(), value.c_str()));
return true;
} else
break;
}
case GeneratedExpression: {
AutoUndoEdit undo(_owner);
col->expression(value);
undo.end(strfmt(_("Change Generated Column Storage Type of '%s.%s'"), _owner->get_name().c_str(),
col->name().c_str()));
return true;
}
case Default:
// If a default value is set then auto increment for a column doesn't make sense.
if (!base::trim(value).empty()) {
AutoUndoEdit undo(_owner);
col->autoIncrement(false);
if (col->generated()) // TODO: this is a temporary solution, it will be split into default and expression in
// the future
{
col->expression(value);
undo.end(
strfmt(_("Set Generated Column Expression of '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
return true;
} else {
bool result = TableColumnsListBE::set_field(node, column, value);
undo.end(strfmt(_("Set Default Value and Unset Auto Increment '%s.%s'"), _owner->get_name().c_str(),
col->name().c_str()));
return result;
}
}
break;
default:
break;
}
}
return TableColumnsListBE::set_field(node, column, value);
}
bool MySQLTableColumnsListBE::set_field(const ::bec::NodeId &node, ColumnId column, ssize_t value) {
db_mysql_ColumnRef col;
if (node.is_valid() && node[0] < real_count()) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_owner->get_table());
col = table->columns().get(node[0]);
if (!col.is_valid())
return false;
db_SimpleDatatypeRef columnType;
switch ((MySQLColumnListColumns)column) {
case IsAutoIncrement:
// Determine actually used column type first.
if (col->userType().is_valid() && col->userType()->actualType().is_valid())
columnType = col->userType()->actualType();
else if (col->simpleType().is_valid() && col->simpleType()->group().is_valid())
columnType = col->simpleType();
if (columnType.is_valid() && columnType->group().is_valid()) {
// in InnoDB, with a composite PK, only the 1st column can be auto_increment
// http://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html
if (value && *table->isPrimaryKeyColumn(col) && table->tableEngine() == "InnoDB" &&
table->primaryKey()->columns()[0]->referencedColumn() != col) {
mforms::Utilities::show_error("Set AUTO_INCREMENT column",
"Only the first key column of a InnoDB table can be AUTO_INCREMENT. Please "
"reorder the columns before making this column AUTO_INCREMENT.",
"OK");
return false;
}
// Allow removing the auto inc setting even for non-numeric columns so we can
// switch that off *after* we changed the column type or for invalid/old models
// which have an auto inc set for non-numeric columns.
if (is_int_datatype(columnType->name())) {
AutoUndoEdit undo(_owner);
if (value) {
// check if there's already a column with auto-increment set and unset them
grt::ListRef<db_mysql_Column> columns(table->columns());
for (size_t c = columns.count(), i = 0; i < c; i++) {
if (*columns[i]->autoIncrement() != 0 && col != columns[i]) {
columns[i]->autoIncrement(0);
}
}
}
col->autoIncrement(value != 0);
if (value != 0)
col->generated(false);
// If auto increment is enabled then reset any default value.
if (col->autoIncrement() && !(*col->defaultValue()).empty())
bec::ColumnHelper::set_default_value(col, "");
_owner->update_change_date();
(*table->signal_refreshDisplay())("column");
undo.end(value
? strfmt(_("Set Auto Increment '%s.%s'"), _owner->get_name().c_str(), col->name().c_str())
: strfmt(_("Unset Auto Increment '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
} else
col->autoIncrement(false);
}
return true;
case IsGenerated: {
AutoUndoEdit undo(_owner);
if (value != 0)
col->autoIncrement(false);
col->generated(value != 0);
undo.end(value ? strfmt(_("Set Generated Column '%s.%s'"), _owner->get_name().c_str(), col->name().c_str())
: strfmt(_("Unset Generated Column '%s.%s'"), _owner->get_name().c_str(), col->name().c_str()));
return true;
}
case IsAutoIncrementable:
return false;
case GeneratedStorageType:
return false;
case GeneratedExpression:
return false;
}
}
return TableColumnsListBE::set_field(node, column, value);
}
bool MySQLTableColumnsListBE::get_field_grt(const ::bec::NodeId &node, ColumnId column, grt::ValueRef &value) {
db_mysql_ColumnRef col;
if (node.is_valid()) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_owner->get_table());
if (node[0] < real_count())
col = table->columns().get(node[0]);
if (col.is_valid()) {
switch (column) {
case IsGenerated:
value = col->generated();
return true;
case GeneratedStorageType:
value = col->generatedStorage();
return true;
case GeneratedExpression:
value = col->expression();
return true;
case Default:
if (col->generated()) // TODO: this is a temporary solution, it will be split into default and expression in
// the future
{
value = col->expression();
return true;
}
break;
case IsAutoIncrement:
value = col->autoIncrement();
return true;
case IsAutoIncrementable:
value = grt::IntegerRef(0);
if (col->simpleType().is_valid() && col->simpleType()->group().is_valid()) {
if (col->simpleType()->group()->name() == "numeric")
value = grt::IntegerRef(1);
}
return true;
case HasCharset:
value = grt::IntegerRef(0);
if (col->simpleType().is_valid()) {
if (col->simpleType()->name() != "JSON" &&
(col->simpleType()->group()->name() == "string" || col->simpleType()->group()->name() == "text" ||
col->simpleType()->name() == "ENUM")) {
value = grt::IntegerRef(1);
}
}
return true;
}
}
}
return TableColumnsListBE::get_field_grt(node, column, value);
}
static bool can_be_timestamp(const char *value) {
if (*value == '\'')
return true;
// accept anything that looks like a date
for (; *value; ++value)
if (!(isdigit(*value) || *value == ':' || *value == '-' || *value == '.' || *value == ' '))
return false;
return true;
}
bec::MenuItemList MySQLTableColumnsListBE::get_popup_items_for_nodes(const std::vector<bec::NodeId> &nodes) {
bec::MenuItemList items = bec::TableColumnsListBE::get_popup_items_for_nodes(nodes);
bec::MenuItem item;
if (nodes.size() == 1) {
grt::ListRef<db_Column> columns(_owner->get_table()->columns());
const size_t idx = nodes.front()[0];
db_ColumnRef col;
if (idx < columns.count())
col = columns.get(idx);
if (col.is_valid() && col->simpleType().is_valid()) {
std::string type = col->simpleType()->name();
bool improved_timestamp_support = false;
GrtVersionRef target_version =
GrtVersionRef::cast_from(bec::getModelOption(workbench_physical_ModelRef::cast_from(_owner->get_catalog()->owner()), "CatalogVersion"));
// in MySQL 5.6, CURRENT_TIMESTAMP works for TIMESTAMP and DATETIME
// and for any number of colums
if (target_version.is_valid() && (*target_version->majorNumber() > 5 ||
(*target_version->majorNumber() == 5 && *target_version->minorNumber() >= 6)))
improved_timestamp_support = true;
if (type == "TIMESTAMP" || (improved_timestamp_support && type == "DATETIME")) {
bool seen_current_ts = false;
bool current_timestamp_allowed = false; // only the 1st TIMESTAMP column can have CURRENT_TIMESTAMP, unless all
// previous ones are set to 0 or a constant
bool flag = false;
if (improved_timestamp_support)
current_timestamp_allowed = true;
else {
GRTLIST_FOREACH(db_Column, columns, c) {
if ((*c)->simpleType().is_valid() && (*c)->simpleType()->name() == "TIMESTAMP") {
if (*c == col) {
current_timestamp_allowed = !seen_current_ts;
flag = true;
}
if (!((*c)->defaultValue() == "0" || can_be_timestamp((*c)->defaultValue().c_str())))
seen_current_ts = true;
// a column before some other TS column that is already marked as CURRENT_TIMESTAMP cannot become
// CURRENT_TS
if (*c != col && flag && strstr((*c)->defaultValue().c_str(), "TIMESTAMP"))
current_timestamp_allowed = false;
}
}
}
item.caption = "Default 0";
item.internalName = "TSToolStripMenuItem";
item.accessibilityName = "ToBeDefined";
item.enabled = true;
items.push_back(item);
item.caption = "Default CURRENT_TIMESTAMP";
item.internalName = "currentTSToolStripMenuItem";
item.accessibilityName = "Current Timestamp";
item.enabled = current_timestamp_allowed;
items.push_back(item);
item.caption = "Default NULL ON UPDATE CURRENT_TIMESTAMP";
item.internalName = "currentTSNullOnUpdateToolStripMenuItem";
item.accessibilityName = "Default Null On Update";
item.enabled = current_timestamp_allowed;
items.push_back(item);
item.caption = "Default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
item.internalName = "currentTSOnUpdateToolStripMenuItem";
item.accessibilityName = "Default Current Timestamp";
item.enabled = current_timestamp_allowed;
items.push_back(item);
} else if (col->simpleType()->group()->name() == "numeric" || col->simpleType()->group()->name() == "datetime") {
item.caption = "Default 0";
item.internalName = "0ToolStripMenuItem";
item.accessibilityName = "Default Zero";
item.enabled = true;
items.push_back(item);
} else if (col->simpleType()->group()->name() == "string" || col->simpleType()->group()->name() == "text") {
item.caption = "Default ''";
item.internalName = "EmptyToolStripMenuItem";
item.accessibilityName = "Empty";
item.enabled = true;
items.push_back(item);
}
}
}
return items;
}
bool MySQLTableColumnsListBE::activate_popup_item_for_nodes(const std::string &name,
const std::vector<bec::NodeId> &orig_nodes) {
AutoUndoEdit undo(_owner);
std::string value;
bool changed = false;
if (name == "TSToolStripMenuItem" || name == "0ToolStripMenuItem")
value = "0";
else if (name == "EmptyToolStripMenuItem")
value = "''";
else if (name == "currentTSToolStripMenuItem")
value = "CURRENT_TIMESTAMP";
else if (name == "currentTSNullOnUpdateToolStripMenuItem")
value = "NULL ON UPDATE CURRENT_TIMESTAMP";
else if (name == "currentTSOnUpdateToolStripMenuItem")
value = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
if (!value.empty()) {
for (std::vector<bec::NodeId>::const_iterator iter = orig_nodes.begin(); iter != orig_nodes.end(); ++iter) {
if ((*iter)[0] < real_count()) {
db_ColumnRef col(_owner->get_table()->columns().get((*iter)[0]));
if (col.is_valid()) {
bec::ColumnHelper::set_default_value(col, value);
_owner->update_change_date();
changed = true;
}
}
}
}
if (changed) {
undo.end(_("Set Column Default"));
_owner->do_partial_ui_refresh(TableEditorBE::RefreshColumnList);
return true;
} else
undo.cancel();
return TableColumnsListBE::activate_popup_item_for_nodes(name, orig_nodes);
}
//----------------- TriggerTreeView ----------------------------------------------------------------
#define TRIGGER_DRAG_FORMAT "com.mysql.workbench.drag-trigger"
class TriggerTreeView : public mforms::TreeView {
public:
mforms::TreeNodeRef selection;
TriggerTreeView(mforms::TreeOptions options) : mforms::TreeView(options) {
}
virtual bool get_drag_data(mforms::DragDetails &details, void **data, std::string &format) {
selection = get_selected_node();
if (selection.is_valid() && selection->get_parent() != root_node()) {
format = TRIGGER_DRAG_FORMAT;
details.allowedOperations = mforms::DragOperationCopy | mforms::DragOperationMove;
*data = &selection;
return true;
} else
selection = mforms::TreeNodeRef();
return false;
}
};
//----------------- MySQLTriggerPanel --------------------------------------------------------------
class MySQLTriggerPanel : public mforms::Box, public mforms::DropDelegate {
public:
MySQLTriggerPanel(MySQLTableEditorBE *editor)
: mforms::Box(true),
_editor(editor),
_trigger_list(mforms::TreeSizeSmall | mforms::TreeNoBorder | mforms::TreeNoHeader | mforms::TreeCanBeDragSource
| mforms::TreeAllowReorderRows),
_refreshing(false) {
scoped_connect(_editor->get_table()->signal_refreshDisplay(),
std::bind(&MySQLTriggerPanel::need_refresh, this, std::placeholders::_1));
_editor_host = editor->get_sql_editor()->get_container();
scoped_connect(editor->get_catalog()->signal_changed(),
std::bind(&MySQLTriggerPanel::catalog_changed, this, std::placeholders::_1, std::placeholders::_2));
set_spacing(15);
set_padding(4);
std::vector<std::string> formats;
formats.push_back(TRIGGER_DRAG_FORMAT);
_trigger_list.register_drop_formats(this, formats);
mforms::Box *trigger_list_host = mforms::manage(new mforms::Box(false));
trigger_list_host->set_padding(4);
trigger_list_host->set_spacing(4);
_trigger_list.set_size(230, -1);
_trigger_list.set_name("Triggers List");
_trigger_list.setInternalName("triggers list");
_trigger_list.add_column(mforms::StringColumnType, _("Name"), 200, false, true);
_trigger_list.end_columns();
_trigger_list.signal_changed()->connect(std::bind(&MySQLTriggerPanel::selection_changed, this));
_trigger_list.set_row_overlay_handler(
std::bind(&MySQLTriggerPanel::overlay_icons_for_node, this, std::placeholders::_1));
scoped_connect(_trigger_list.signal_node_activated(),
std::bind(&MySQLTriggerPanel::node_activated, this, std::placeholders::_1, std::placeholders::_2));
trigger_list_host->add(&_trigger_list, true, true);
_warning_label.set_text(
_("Warning: the current server version does not allow multiple triggers "
"for the same timing/event."));
_warning_label.set_wrap_text(true);
_warning_label.set_style(mforms::SmallStyle);
_warning_label.set_front_color("#AF1F00");
trigger_list_host->add(&_warning_label, false, true);
add(trigger_list_host, false, true);
_trigger_menu.signal_will_show()->connect(
std::bind(&MySQLTriggerPanel::trigger_menu_will_show, this, std::placeholders::_1));
_trigger_menu.add_item_with_title("Move trigger up",
std::bind(&MySQLTriggerPanel::trigger_action, this, "trigger_up"), "Move Trigger Up", "trigger_up");
_trigger_menu.add_item_with_title(
"Move trigger down", std::bind(&MySQLTriggerPanel::trigger_action, this, "trigger_down"), "Move Trigger Down", "trigger_down");
_trigger_menu.add_separator();
_trigger_menu.add_item_with_title(
"Add new trigger", std::bind(&MySQLTriggerPanel::trigger_action, this, "add_trigger"), "Add New Trigger", "add_trigger");
_trigger_menu.add_item_with_title("Duplicate trigger",
std::bind(&MySQLTriggerPanel::trigger_action, this, "duplicate_trigger"),
"Duplicate Trigger", "duplicate_trigger");
_trigger_menu.add_separator();
_trigger_menu.add_item_with_title(
"Delete trigger", std::bind(&MySQLTriggerPanel::trigger_action, this, "delete_trigger"), "Delete Trigger", "delete_trigger");
_trigger_menu.add_item_with_title("Delete all triggers with this timing",
std::bind(&MySQLTriggerPanel::trigger_action, this, "delete_triggers_in_group"),
"Delete All Triggers With This Timing", "delete_triggers_in_group");
_trigger_menu.add_item_with_title(
"Delete all triggers", std::bind(&MySQLTriggerPanel::trigger_action, this, "delete_triggers"), "Delete all Triggers", "delete_triggers");
_trigger_list.set_context_menu(&_trigger_menu);
add(_editor_host, true, true);
_info_label.set_text(
_("Select an existing trigger in the tree to edit it.\nUse the context menu "
"to add and remove triggers."));
_info_label.set_front_color("#909090");
_info_label.set_text_align(mforms::MiddleCenter);
_info_label.set_font(DEFAULT_FONT_FAMILY " bold 14");
add(&_info_label, true, true);
_code_editor = _editor->get_sql_editor()->get_editor_control();
_code_editor->signal_lost_focus()->connect(std::bind(&MySQLTriggerPanel::code_edited, this));
// Sort the triggers list so that the order corresponds to the visual representation
// we establish, to ease manipulating the list later. This will not change the order of triggers
// with the same timing/event relative to each other.
// This sort order is not preserved in the model unless the user makes other changes that are saved
// (saving so also the new order, if it has changed at all).
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_editor->get_table());
grt::ListRef<db_mysql_Trigger> triggers(table->triggers());
grt::ListRef<db_mysql_Trigger> sorted_triggers(true);
_editor->freeze_refresh_on_object_change();
coalesce_triggers(triggers, sorted_triggers, "BEFORE", "INSERT");
coalesce_triggers(triggers, sorted_triggers, "AFTER", "INSERT");
coalesce_triggers(triggers, sorted_triggers, "BEFORE", "UPDATE");
coalesce_triggers(triggers, sorted_triggers, "AFTER", "UPDATE");
coalesce_triggers(triggers, sorted_triggers, "BEFORE", "DELETE");
coalesce_triggers(triggers, sorted_triggers, "AFTER", "DELETE");
grt::replace_contents(_editor->get_table()->triggers(), sorted_triggers);
_editor->thaw_refresh_on_object_change(true);
refresh();
update_warning();
}
//------------------------------------------------------------------------------------------------
~MySQLTriggerPanel() {
}
//------------------------------------------------------------------------------------------------
/**
* Moves all triggers from source to target with the given timing and event, maintaining
* their relative order.
*/
void coalesce_triggers(grt::ListRef<db_mysql_Trigger> source, grt::ListRef<db_mysql_Trigger> target,
std::string timing, std::string event) {
size_t i = 0;
while (i < source->count()) {
db_mysql_TriggerRef trigger = source[i];
if (base::same_string(trigger->timing(), timing, false) && base::same_string(trigger->event(), event, false)) {
source->remove(i);
target->insert_unchecked(trigger);
} else
++i;
}
}
//------------------------------------------------------------------------------------------------
std::vector<std::string> overlay_icons_for_node(mforms::TreeNodeRef node) {
std::vector<std::string> result;
// Add for both group nodes and triggers.
result.push_back(mforms::App::get()->get_resource_path("item_overlay_add.png"));
if (node->level() == 2)
result.push_back(mforms::App::get()->get_resource_path("item_overlay_delete.png"));
return result;
}
//------------------------------------------------------------------------------------------------
void node_activated(mforms::TreeNodeRef node, int index) {
if (!node.is_valid())
return;
switch (index) {
// Negative indices for overlay icons.
case -1: // Add button.
{
GrtVersionRef version =
GrtVersionRef::cast_from(bec::getModelOption(workbench_physical_ModelRef::cast_from(_editor->get_catalog()->owner()), "CatalogVersion"));
bool supports_multiple = bec::is_supported_mysql_version_at_least(version, 5, 7, 2);
if (node->level() == 2) // Go up to group node if this is a trigger node.
node = node->get_parent();
if (supports_multiple || node->count() == 0) {
std::string timing, event;
if (base::partition(node->get_string(0), " ", timing, event))
add_trigger(timing, event, true);
} else
mforms::Utilities::beep();
break;
}
case -2: // Delete button.
{
db_mysql_TriggerRef trigger = trigger_for_node(node);
if (trigger.is_valid()) {
_editor->freeze_refresh_on_object_change();
delete_trigger(trigger);
_editor->thaw_refresh_on_object_change(true);
}
break;
}
}
}
//------------------------------------------------------------------------------------------------
class AttachedTrigger : public mforms::TreeNodeData {
public:
db_mysql_TriggerRef _trigger;
AttachedTrigger(db_mysql_TriggerRef trigger) : _trigger(trigger){};
};
mforms::TreeNodeRef insert_trigger_in_tree(const db_mysql_TriggerRef trigger) {
int index = 0;
std::string event = base::tolower(trigger->event());
if (event == "update")
index += 2;
else if (event == "delete")
index += 4;
if (base::tolower(trigger->timing()) == "after")
++index;
mforms::TreeNodeRef parent = _trigger_list.root_node()->get_child(index);
mforms::TreeNodeRef node = parent->add_child();
node->set_string(0, trigger->name());
node->set_data(new AttachedTrigger(trigger));
parent->expand(); // Make sure the child is visible.
node->expand(); // Dummy to remove the expander icon.
return node;
}
//------------------------------------------------------------------------------------------------
void refresh() {
_refreshing = true;
_trigger_list.freeze_refresh();
mforms::TreeNodeRef selected = _trigger_list.get_selected_node();
int old_selected = 0;
if (selected)
old_selected = _trigger_list.row_for_node(selected);
_trigger_list.clear();
// Add top level nodes for each event/timing combination.
static const char *top_level_captions[] = {"BEFORE INSERT", "AFTER INSERT", "BEFORE UPDATE",
"AFTER UPDATE", "BEFORE DELETE", "AFTER DELETE"};
for (size_t i = 0; i < 6; ++i) {
mforms::TreeNodeRef node = _trigger_list.add_node();
node->set_string(0, top_level_captions[i]);
//node->set_attributes(0, mforms::TextAttributes("#303030", true, false));
node->expand();
}
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_editor->get_table());
grt::ListRef<db_mysql_Trigger> triggers(table->triggers());
GRTLIST_FOREACH(db_mysql_Trigger, triggers, trigger)
insert_trigger_in_tree(*trigger);
_refreshing = false;
_trigger_list.thaw_refresh();
_trigger_list.select_node(_trigger_list.node_at_row(old_selected));
}
//------------------------------------------------------------------------------------------------
mforms::TreeNodeRef node_for_trigger(const db_TriggerRef &trigger) {
// Find the index of the top level node based on timing and event.
int index = 0;
std::string event = base::tolower(trigger->event());
if (event == "update")
index += 2;
else if (event == "delete")
index += 4;
if (base::tolower(trigger->timing()) == "after")
++index;
// Now iterate over its children to find the one we are looking for.
mforms::TreeNodeRef parent = _trigger_list.root_node()->get_child(index);
if (!parent.is_valid())
return mforms::TreeNodeRef();
for (int i = 0; i < parent->count(); ++i) {
mforms::TreeNodeRef child = parent->get_child(i);
AttachedTrigger *data = dynamic_cast<AttachedTrigger *>(child->get_data());
if (data != NULL && data->_trigger == trigger)
return child;
}
return mforms::TreeNodeRef();
}
//------------------------------------------------------------------------------------------------
db_mysql_TriggerRef trigger_for_node(mforms::TreeNodeRef node) {
if (!node.is_valid())
return db_mysql_TriggerRef();
mforms::TreeNodeRef parent = node->get_parent();
if (!parent.is_valid())
return db_mysql_TriggerRef();
AttachedTrigger *data = dynamic_cast<AttachedTrigger *>(node->get_data());
if (data == NULL || !data->_trigger.is_valid())
return db_mysql_TriggerRef();
std::string title = node->get_string(0);
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_editor->get_table());
grt::ListRef<db_mysql_Trigger> triggers(table->triggers());
GRTLIST_FOREACH(db_mysql_Trigger, triggers, iterator) {
if (data->_trigger == *iterator)
return *iterator;
}
return db_mysql_TriggerRef();
}
//------------------------------------------------------------------------------------------------
void code_edited() {
if (_selected_trigger.is_valid()) {
bool need_refresh = false;
if (_code_editor->is_dirty() && _selected_trigger->sqlDefinition() != _code_editor->get_string_value()) {
AutoUndoEdit undo(_editor, _selected_trigger, "sql");
// Get the tree node for the trigger first, as it is based on the name.
mforms::TreeNodeRef node = node_for_trigger(_selected_trigger);
_editor->freeze_refresh_on_object_change();
std::string old_timing = _selected_trigger->timing();
std::string old_event = _selected_trigger->event();
_editor->_parserServices->parseTrigger(_editor->_parserContext, _selected_trigger,
_code_editor->get_string_value());
need_refresh = !base::same_string(old_timing, _selected_trigger->timing(), false) ||
!base::same_string(old_event, _selected_trigger->event(), false);
// Set the name before thawing the refresh, as this will update the ui and may select
// a different current trigger.
std::string name = _selected_trigger->name();
if (node)
node->set_string(0, name);
// Check if the user included a follow or precedes clause (which is valid starting with 5.7.2).
// Reorder the triggers in the current group and remove the clause if one exists.
if (!_selected_trigger->ordering().empty()) {
std::string other_trigger = _selected_trigger->otherTrigger();
if (!other_trigger.empty()) {
// Positioning information is only valid for the same timing and event.
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_editor->get_table());
grt::ListRef<db_mysql_Trigger> triggers(table->triggers());
size_t old_index = triggers->get_index(_selected_trigger);
for (size_t i = 0; i < triggers.count(); ++i) {
db_mysql_TriggerRef trigger = triggers[i];
if (trigger == _selected_trigger)
continue;
if (base::same_string(trigger->name(), other_trigger, false) &&
base::same_string(trigger->event(), _selected_trigger->event(), false) &&
base::same_string(trigger->timing(), _selected_trigger->timing(), false)) {
if (base::same_string(_selected_trigger->ordering(), "precedes", false))
triggers->reorder(old_index, i);
else
triggers->reorder(old_index, i + 1);
need_refresh = true;
break;
}
}
}
// Remove ordering clause from the sql.
// We do a token-based search-and-replace here. Keep in mind the sql text might not be valid.
std::string sql;
std::string source = _selected_trigger->sqlDefinition();
Scanner scanner = _editor->_parserContext->createScanner();
std::string orderingText = base::toupper(_selected_trigger->ordering()) + "_SYMBOL";
size_t orderingToken = _editor->_parserServices->tokenFromString(_editor->_parserContext, orderingText);
bool removalDone = false;
while (true) {
sql += scanner.tokenText();
scanner.next(false);
if (scanner.tokenType() == ParserToken::EOF)
break;
if (!removalDone && scanner.tokenType() == orderingToken) {
// The token we are looking for. Skip this and any whitespace token following it.
do {
scanner.next(false);
if (scanner.tokenChannel() == 0 || scanner.tokenType() == ParserToken::EOF)
break;
} while (true);
// See if there's an identifier following the ordering keyword and if so remove that too
// including the following whitespace).
if (_editor->_parserContext->isIdentifier(scanner.tokenType())) {
do {
scanner.next(false);
if (scanner.tokenChannel() == 0 || scanner.tokenType() == ParserToken::EOF)
break;
} while (true);
}
removalDone = true;
if (scanner.tokenType() == ParserToken::EOF)
break;
}
};
// Finally remove position information from the trigger object, regardless whether the other trigger actually
// exists (or is valid) and update the code editor.
_selected_trigger->ordering("");
_selected_trigger->otherTrigger("");
_editor->get_sql_editor()->sql(sql.c_str());
}
_editor->thaw_refresh_on_object_change();
undo.end(strfmt(_("Edit trigger `%s` of `%s`.`%s`"), name.c_str(), _editor->get_schema_name().c_str(),
_editor->get_name().c_str()));
}
if (need_refresh) {
// Reload the tree but keep the current trigger selected.
refresh();
_trigger_list.select_node(node_for_trigger(_selected_trigger));
}
} else
refresh();
}
//------------------------------------------------------------------------------------------------
bool trigger_name_exists(const std::string &name) {
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
for (size_t i = 0; i < triggers->count(); ++i) {
if (base::same_string(triggers[i]->name(), name))
return true;
}
return false;
}
//------------------------------------------------------------------------------------------------
db_mysql_TriggerRef add_trigger(const std::string &timing, const std::string &event, bool select,
std::string sql = "") {
_editor->freeze_refresh_on_object_change();
AutoUndoEdit undo(_editor);
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
db_mysql_TriggerRef trigger = db_mysql_TriggerRef(grt::Initialized);
trigger->owner(_editor->get_table());
if (sql.empty()) {
// Find a unique name for the new trigger if the default name is already taken.
std::string name = _editor->get_name() + "_" + timing + "_" + event;
if (!trigger_name_exists(name))
trigger->name(name);
else {
int counter = 1;
std::stringstream buffer;
do {
buffer.str("");
buffer << name << "_" << counter++;
} while (counter < 100 && trigger_name_exists(buffer.str()));
trigger->name(buffer.str());
}
trigger->event(event);
trigger->timing(timing);
sql = base::strfmt("CREATE DEFINER = CURRENT_USER TRIGGER `%s`.`%s` %s %s ON `%s` FOR EACH ROW\nBEGIN\n\nEND\n",
_editor->get_schema_name().c_str(), trigger->name().c_str(), timing.c_str(), event.c_str(),
_editor->get_name().c_str());
trigger->sqlDefinition(sql);
} else
_editor->_parserServices->parseTrigger(_editor->_parserContext, trigger, sql);
triggers.insert(trigger);
undo.end(base::strfmt("Add trigger to %s.%s", _editor->get_schema_name().c_str(), _editor->get_name().c_str()));
mforms::TreeNodeRef node = insert_trigger_in_tree(trigger);
if (select) {
_trigger_list.select_node(node);
selection_changed();
}
_editor->thaw_refresh_on_object_change(true);
return trigger;
}
//------------------------------------------------------------------------------------------------
void delete_trigger(db_TriggerRef trigger) {
_editor->freeze_refresh_on_object_change();
AutoUndoEdit undo(_editor);
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
triggers.remove_value(trigger);
undo.end(base::strfmt("Delete trigger %s", trigger->name().c_str()));
mforms::TreeNodeRef node = node_for_trigger(trigger);
if (node.is_valid()) {
mforms::TreeNodeRef previous = node->previous_sibling();
if (!previous.is_valid())
previous = node->get_parent();
node->remove_from_parent();
if (previous.is_valid()) {
_trigger_list.select_node(previous);
selection_changed();
}
}
_editor->thaw_refresh_on_object_change(true);
update_warning();
}
//------------------------------------------------------------------------------------------------
void selection_changed() {
if (_refreshing)
return;
if (_code_editor->is_dirty())
code_edited();
update_ui();
}
//------------------------------------------------------------------------------------------------
void need_refresh(const std::string &member) {
// Handle undo/redo changes.
if (member == "trigger" && !_editor->is_refresh_frozen()) {
refresh();
update_ui();
update_warning();
}
}
//------------------------------------------------------------------------------------------------
void update_ui() {
mforms::TreeNodeRef node = _trigger_list.get_selected_node();
db_mysql_TriggerRef trigger = trigger_for_node(node);
if (_selected_trigger != trigger) {
_selected_trigger = trigger;
if (trigger.is_valid())
_editor->get_sql_editor()->sql(trigger->sqlDefinition().c_str());
}
_editor_host->show(trigger.is_valid());
_info_label.show(!trigger.is_valid());
_code_editor->reset_dirty();
}
//------------------------------------------------------------------------------------------------
void update_warning() {
// See if there's any timing/event combination with more than one trigger definition.
bool found_multiple = false;
bool supports_multiple = bec::is_supported_mysql_version_at_least(
GrtVersionRef::cast_from(bec::getModelOption(workbench_physical_ModelRef::cast_from(_editor->get_catalog()->owner()), "CatalogVersion")), 5,
7, 2);
mforms::TreeNodeTextAttributes normal_attributes("#000000", false, false);
mforms::TreeNodeTextAttributes warning_attributes("#AF1F00", false, false);
for (int i = 0; i < _trigger_list.count(); ++i) {
mforms::TreeNodeRef node = _trigger_list.root_node()->get_child(i);
if (node->count() > 0) {
if (node->count() > 1)
found_multiple = true;
for (int j = 0; j < node->count(); ++j)
node->get_child(j)->set_attributes(
0, (!supports_multiple && node->count() > 1) ? warning_attributes : normal_attributes);
}
}
_warning_label.show(!supports_multiple && found_multiple);
}
//------------------------------------------------------------------------------------------------
void catalog_changed(const std::string &member, const grt::ValueRef &value) {
if (member == "version")
update_warning();
}
//------------------------------------------------------------------------------------------------
void trigger_menu_will_show(mforms::MenuItem *sub_menu_root) {
mforms::TreeNodeRef node = _trigger_list.get_selected_node();
if (!node.is_valid()) {
for (int i = 0; i < _trigger_menu.item_count(); ++i)
_trigger_menu.get_item(i)->set_enabled(false);
_trigger_menu.set_item_enabled("delete_triggers", true);
return;
}
// Since 5.7 we can add multiple triggers for the same timing.
GrtVersionRef version = GrtVersionRef::cast_from(bec::getModelOption(workbench_physical_ModelRef::cast_from(_editor->get_catalog()->owner()), "CatalogVersion"));
if (node->get_parent() != _trigger_list.root_node()) {
// One of the triggers.
_trigger_menu.set_item_enabled(
"trigger_up", node->previous_sibling().is_valid() || node->get_parent()->previous_sibling().is_valid());
_trigger_menu.set_item_enabled("trigger_down",
node->next_sibling().is_valid() || node->get_parent()->next_sibling().is_valid());
bool enable = bec::is_supported_mysql_version_at_least(version, 5, 7, 2);
_trigger_menu.set_item_enabled("add_trigger", enable);
_trigger_menu.set_item_enabled("duplicate_trigger", enable);
_trigger_menu.set_item_enabled("delete_trigger", true);
_trigger_menu.set_item_enabled("delete_triggers_in_group", true);
} else {
// One of the group nodes.
_trigger_menu.set_item_enabled("trigger_up", false);
_trigger_menu.set_item_enabled("trigger_down", false);
bool enable = bec::is_supported_mysql_version_at_least(version, 5, 7, 2) || node->count() == 0;
_trigger_menu.set_item_enabled("add_trigger", enable);
_trigger_menu.set_item_enabled("duplicate_trigger", false);
_trigger_menu.set_item_enabled("delete_trigger", false);
_trigger_menu.set_item_enabled("delete_triggers_in_group", node->count() > 0);
}
_trigger_menu.set_item_enabled("delete_triggers", true);
}
//------------------------------------------------------------------------------------------------
mforms::TreeNodeRef move_node_to(mforms::TreeNodeRef node, mforms::TreeNodeRef new_parent, int index) {
mforms::TreeNodeRef new_node = new_parent->insert_child(index);
new_node->set_string(0, node->get_string(0));
std::string tag = node->get_tag();
new_node->set_data(node->get_data());
node->remove_from_parent();
new_node->set_tag(tag);
return new_node;
}
//------------------------------------------------------------------------------------------------
void trigger_action(const std::string &action) {
mforms::TreeNodeRef node = _trigger_list.get_selected_node();
mforms::TreeNodeRef group_node = node;
if (node->get_parent() != _trigger_list.root_node())
group_node = node->get_parent();
if (action == "trigger_up") {
_editor->freeze_refresh_on_object_change();
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
db_mysql_TriggerRef trigger = trigger_for_node(node);
AutoUndoEdit undo(_editor);
if (node->previous_sibling().is_valid()) {
// Move within a group.
size_t index = triggers->get_index(trigger); // Index is always > 0 if the above test succeeds.
triggers->reorder(index, index - 1);
int node_index = group_node->get_child_index(node);
node = move_node_to(node, group_node, node_index - 1);
} else {
// Move to the end of the previous group.
// There's always a previous group or we wouldn't have come here.
// There's no need to move the trigger in the triggers list since at this point it is
// the first in it's group and the previous trigger is by definition the last one in the
// previous group.
group_node = group_node->previous_sibling();
std::string timing, event;
if (base::partition(group_node->get_string(0), " ", timing, event)) {
change_trigger_timing(trigger, timing, event);
node = move_node_to(node, group_node, group_node->count());
group_node->expand();
}
}
// Since we didn't destroy the node, but just moved it, selecting it this simple way works.
_trigger_list.select_node(node);
undo.end("Move trigger up in execution order");
_editor->thaw_refresh_on_object_change(true);
} else if (action == "trigger_down") {
_editor->freeze_refresh_on_object_change();
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
db_mysql_TriggerRef trigger = trigger_for_node(node);
AutoUndoEdit undo(_editor);
if (node->next_sibling().is_valid()) {
// Move within a group.
size_t index = triggers->get_index(trigger); // Index is always < count - 1 if the above test succeeds.
triggers->reorder(index, index + 1);
int node_index = group_node->get_child_index(node);
node = move_node_to(node, group_node, node_index + 2);
} else {
// Move to the beginning of the next group.
group_node = group_node->next_sibling();
std::string timing, event;
if (base::partition(group_node->get_string(0), " ", timing, event)) {
change_trigger_timing(trigger, timing, event);
node = move_node_to(node, group_node, 0);
group_node->expand();
}
}
_trigger_list.select_node(node);
undo.end("Move trigger down in execution order");
_editor->thaw_refresh_on_object_change(true);
} else if (action == "add_trigger") {
std::string timing, event;
if (base::partition(group_node->get_string(0), " ", timing, event))
add_trigger(timing, event, true);
} else if (action == "duplicate_trigger") {
db_mysql_TriggerRef trigger = trigger_for_node(node);
if (trigger.is_valid()) {
db_mysql_TriggerRef new_trigger =
add_trigger(trigger->timing(), trigger->event(), true, trigger->sqlDefinition());
}
} else if (action == "delete_trigger") {
_editor->freeze_refresh_on_object_change();
db_mysql_TriggerRef trigger = trigger_for_node(node);
delete_trigger(trigger);
_editor->thaw_refresh_on_object_change(true);
} else if (action == "delete_triggers_in_group") {
_editor->freeze_refresh_on_object_change();
AutoUndoEdit outer_undo(_editor);
while (group_node->count() > 0)
delete_trigger(trigger_for_node(group_node->get_child(0)));
outer_undo.end("Delete triggers in " + group_node->get_string(0));
_editor->thaw_refresh_on_object_change(true);
} else if (action == "delete_triggers") {
_editor->freeze_refresh_on_object_change();
AutoUndoEdit outer_undo(_editor);
for (int i = 0; i < _trigger_list.root_node()->count(); ++i) {
group_node = _trigger_list.root_node()->get_child(i);
while (group_node->count() > 0)
delete_trigger(trigger_for_node(group_node->get_child(0)));
}
outer_undo.end("Delete all triggers");
_editor->thaw_refresh_on_object_change(true);
}
update_ui();
}
//------------------------------------------------------------------------------------------------
virtual mforms::DragOperation drag_over(View *sender, base::Point p, mforms::DragOperation allowedOperations,
const std::vector<std::string> &formats) {
TriggerTreeView *tree = dynamic_cast<TriggerTreeView *>(sender);
// For now accept a drop only from our own trigger list. Might change later if we can show multiple editors at once.
if (allowedOperations != mforms::DragOperationNone && tree == &_trigger_list && tree->selection.is_valid()) {
mforms::TreeNodeRef target_node = _trigger_list.node_at_position(p);
// Don't allow dropping either if the drop position would be the same places we are currently
// (that includes dropping on the group node the target is in already).
if (!target_node.is_valid() || target_node == tree->selection || target_node == tree->selection->get_parent())
return mforms::DragOperationNone;
mforms::DropPosition position = _trigger_list.get_drop_position();
// Don't allow dropping before or after a top level node, only *on* it.
if (target_node->get_parent() == _trigger_list.root_node() && position != mforms::DropPositionOn)
return mforms::DragOperationNone;
// Check direct siblings with a position pointing to the current position.
// If selection and target are from different trees this check will not lead to early exit.
if (position == mforms::DropPositionBottom && tree->selection->previous_sibling() == target_node)
return mforms::DragOperationNone;
if (tree->selection->next_sibling().is_valid() &&
(position == mforms::DropPositionTop || position == mforms::DropPositionOn) &&
tree->selection->next_sibling() == target_node)
return mforms::DragOperationNone;
if (tree == &_trigger_list)
return allowedOperations & mforms::DragOperationMove;
return allowedOperations & mforms::DragOperationCopy;
}
return mforms::DragOperationNone;
}
//------------------------------------------------------------------------------------------------
virtual mforms::DragOperation data_dropped(View *sender, base::Point p, mforms::DragOperation allowedOperations,
void *data, const std::string &format) {
TriggerTreeView *tree = dynamic_cast<TriggerTreeView *>(sender);
if (allowedOperations != mforms::DragOperationNone && tree == &_trigger_list) {
mforms::TreeNodeRef target_node = _trigger_list.node_at_position(p);
mforms::DropPosition position = _trigger_list.get_drop_position();
if (target_node.is_valid()) {
// Note: the code below only works if the source tree is our own trigger list.
// For drag support between multiple editors code is required to get trigger-for-node
// and vice versa information from that other editor instance.
grt::ListRef<db_Trigger> triggers(_editor->get_table()->triggers());
db_mysql_TriggerRef trigger = trigger_for_node(tree->selection);
if (!trigger.is_valid())
return mforms::DragOperationNone;
_editor->freeze_refresh_on_object_change();
// If the user dropped onto a group node (which then must always be a different one than that
// it is in currently) or if the group node of source and target node differ,
// change the timing/event of the source trigger to the timing of the group it will move to.
if (target_node->get_parent() == _trigger_list.root_node() ||
tree->selection->get_parent() != target_node->get_parent()) {
mforms::TreeNodeRef group_node = target_node;
if (target_node->get_parent() != _trigger_list.root_node())
group_node = group_node->get_parent();
std::string timing, event;
if (base::partition(group_node->get_string(0), " ", timing, event))
change_trigger_timing(trigger, timing, event);
}
// After that move in the trigger list so, that it appears in the right position
// relative to the target. Dropping on a group node will move the node to the end of the
// end of the list in that group.
if (target_node->get_parent() == _trigger_list.root_node()) {
// Group node.
triggers->remove(trigger);
if (target_node->count() == 0) {
// Group node with no entries. In that case take the last child of last group node
// before this group node which has children. If there isn't any then simply insert at
// position 0.
while (target_node->previous_sibling().is_valid() && target_node->previous_sibling()->count() == 0)
target_node = target_node->previous_sibling();
}
if (target_node->count() > 0) {
mforms::TreeNodeRef last_child = target_node->get_child(target_node->count() - 1);
db_mysql_TriggerRef target_trigger = trigger_for_node(last_child);
triggers->insert_unchecked(trigger, triggers->get_index(target_trigger) + 1);
} else
triggers->insert_unchecked(trigger, 0);
} else {
// A trigger node.
db_mysql_TriggerRef trigger = trigger_for_node(tree->selection);
triggers->remove(trigger);
db_mysql_TriggerRef target_trigger = trigger_for_node(target_node);
size_t index = triggers->get_index(target_trigger);
if (position == mforms::DropPositionBottom)
++index;
triggers->insert_unchecked(trigger, index);
}
_editor->thaw_refresh_on_object_change(true);
// We will always have only small lists of triggers, so a simple refresh does the job here.
refresh();
selection_changed();
return (tree == &_trigger_list) ? mforms::DragOperationMove : mforms::DragOperationCopy;
}
}
return mforms::DragOperationNone;
}
//------------------------------------------------------------------------------------------------
void change_trigger_timing(db_mysql_TriggerRef trigger, std::string timing, std::string event) {
bool use_uppercase = (*trigger->timing())[0] >= 'A';
if (!use_uppercase) {
timing = base::tolower(timing);
event = base::tolower(event);
}
// We do a token-based search-and-replace here. Keep in mind the sql text might not be valid.
std::string sql;
std::string source = trigger->sqlDefinition();
Scanner scanner = _editor->_parserContext->createScanner();
std::string timingText = base::toupper(trigger->timing()) + "_SYMBOL";
size_t timingToken = _editor->_parserServices->tokenFromString(_editor->_parserContext, timingText);
std::string eventText = base::toupper(trigger->event()) + "_SYMBOL";
size_t eventToken = _editor->_parserServices->tokenFromString(_editor->_parserContext, eventText);
bool replace_done = false;
sql += scanner.tokenText();
do {
scanner.next(false);
if (scanner.tokenType() == ParserToken::EOF)
break;
if (!replace_done && scanner.tokenType() == timingToken) {
// The token we are looking for. Replace the timing and see if there's an event token too.
sql += timing;
do { // Add any following hidden tokens (whitespace/comment).
scanner.next(false);
if (scanner.tokenChannel() == 0 || scanner.tokenType() == ParserToken::EOF)
break;
sql += scanner.tokenText();
} while (true);
if (scanner.tokenType() == eventToken)
sql += event;
replace_done = true;
if (scanner.tokenType() == ParserToken::EOF)
break;
} else
sql += scanner.tokenText();
} while (true);
trigger->sqlDefinition(sql);
trigger->timing(timing);
trigger->event(event);
}
//------------------------------------------------------------------------------------------------
private:
MySQLTableEditorBE *_editor;
TriggerTreeView _trigger_list;
mforms::ContextMenu _trigger_menu;
mforms::Label _info_label;
mforms::Label _warning_label;
mforms::CodeEditor *_code_editor;
mforms::View *_editor_host;
db_mysql_TriggerRef _selected_trigger;
bool _refreshing;
};
//----------------- MySQLTableEditorBE -------------------------------------------------------------
MySQLTableEditorBE::MySQLTableEditorBE(db_mysql_TableRef table)
: TableEditorBE(table), _columns(this), _partitions(this), _indexes(this), _trigger_panel(0) {
_updating_triggers = false;
if (table->isStub() == 1) {
int rc;
rc = mforms::Utilities::show_warning(
_("Edit Stub Table"),
_("The table you are trying to edit is a model-only stub, created to represent missing external tables "
"referenced by foreign keys.\n"
"Such tables are ignored by forward engineering and synchronization.\n\n"
"You may convert this table to a real one that appears also in the generated SQL or keep it as stub."),
_("Convert to real table"), _("Edit as is"));
if (rc == mforms::ResultOk)
db_mysql_TableRef(table)->isStub(0);
}
}
//--------------------------------------------------------------------------------------------------
MySQLTableEditorBE::~MySQLTableEditorBE() {
delete _trigger_panel;
}
//--------------------------------------------------------------------------------------------------
void MySQLTableEditorBE::refresh_live_object() {
TableEditorBE::refresh_live_object();
load_trigger_sql();
}
//--------------------------------------------------------------------------------------------------
void MySQLTableEditorBE::commit_changes() {
_trigger_panel->code_edited();
}
//--------------------------------------------------------------------------------------------------
mforms::View *MySQLTableEditorBE::get_trigger_panel() {
if (!_trigger_panel)
_trigger_panel = new MySQLTriggerPanel(this);
return _trigger_panel;
}
//--------------------------------------------------------------------------------------------------
/**
* Programmatically add a new trigger (used for testing).
*/
void MySQLTableEditorBE::add_trigger(const std::string &timing, const std::string &event) {
get_trigger_panel();
_trigger_panel->add_trigger(timing, event, false);
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> MySQLTableEditorBE::get_index_types() {
std::vector<std::string> index_types;
GrtVersionRef version =
GrtVersionRef::cast_from(bec::getModelOption(workbench_physical_ModelRef::cast_from(get_catalog()->owner()), "CatalogVersion"));
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
index_types.push_back("INDEX");
index_types.push_back("UNIQUE");
// FULLTEXT exists only in MyISAM prior to 5.6. in 5.6+ InnoDB also supports it
if (table->tableEngine() == "MyISAM" || ((table->tableEngine() == "InnoDB" || table->tableEngine() == "") &&
bec::is_supported_mysql_version_at_least(version, 5, 6)))
index_types.push_back("FULLTEXT");
// SPATIAL is not supported by InnoDB before 5.7.5 (or maybe later)
if (table->tableEngine() == "MyISAM" || ((table->tableEngine() == "InnoDB" || table->tableEngine() == "") &&
bec::is_supported_mysql_version_at_least(version, 5, 7, 5)))
index_types.push_back("SPATIAL");
// these are special types for PK and FK
index_types.push_back("PRIMARY");
// index_types.push_back("FOREIGN");
return index_types;
}
std::vector<std::string> MySQLTableEditorBE::get_index_storage_types() {
std::vector<std::string> index_types;
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
index_types.push_back("BTREE"); // BTREE is supported by all engines
if (table->tableEngine() == "MyISAM")
index_types.push_back("RTREE"); // as of 5.7, RTREE is recognized by parser but not supported
if (table->tableEngine() == "MEMORY" || table->tableEngine() == "HEAP" || table->tableEngine() == "ndbcluster")
index_types.push_back("HASH");
return index_types;
}
std::vector<std::string> MySQLTableEditorBE::get_fk_action_options() {
std::vector<std::string> action_options;
action_options.push_back("RESTRICT");
action_options.push_back("CASCADE");
action_options.push_back("SET NULL");
action_options.push_back("NO ACTION");
return action_options;
}
std::vector<std::string> MySQLTableEditorBE::get_engines_list() {
std::vector<std::string> engines;
DbMySQLImpl *module = grt::GRT::get()->find_native_module<DbMySQLImpl>("DbMySQL");
if (!module)
throw std::runtime_error("Module DbMySQL could not be located");
grt::ListRef<db_mysql_StorageEngine> engines_ret(module->getKnownEngines());
for (size_t c = engines_ret.count(), i = 0; i < c; i++)
engines.push_back(engines_ret[i]->name());
return engines;
}
/**
* Determines if the currently set engine supports foreign keys and reports the outcome to the caller.
*/
bool MySQLTableEditorBE::engine_supports_foreign_keys() {
grt::StringRef name = db_mysql_TableRef::cast_from(get_table())->tableEngine();
if (name == "") // No engine set. Assume db default allows FKs.
return true;
db_mysql_StorageEngineRef engine = bec::TableHelper::get_engine_by_name(name);
if (engine.is_valid())
return engine->supportsForeignKeys() == 1;
return false; // Don't know anything about this engine, so assume it doesn't support FKs.
}
static struct TableOption {
const char *option_name;
const char *object_field;
bool text;
} table_options[] = {
{"PACK_KEYS", "packKeys", false},
{"PASSWORD", "password", true},
{"AUTO_INCREMENT", "nextAutoInc", true},
{"DELAY_KEY_WRITE", "delayKeyWrite", false},
{"ROW_FORMAT", "rowFormat", true},
{"KEY_BLOCK_SIZE", "keyBlockSize", true},
{"AVG_ROW_LENGTH", "avgRowLength", true},
{"MAX_ROWS", "maxRows", true},
{"MIN_ROWS", "minRows", true},
{"DATA DIRECTORY", "tableDataDir", true},
{"INDEX DIRECTORY", "tableIndexDir", true},
{"UNION", "mergeUnion", true},
{"INSERT_METHOD", "mergeInsert", true},
{"ENGINE", "tableEngine", false},
{"CHARACTER SET", "defaultCharacterSetName", false},
{"COLLATE", "defaultCollationName", false},
{"CHECKSUM", "checksum", false},
{NULL, NULL, false}
};
void MySQLTableEditorBE::set_table_option_by_name(const std::string &name, const std::string &value) {
bool found = false;
for (size_t i = 0; table_options[i].option_name; i++) {
if (name.compare(table_options[i].option_name) == 0) {
if (get_table().get_metaclass()->get_member_type(table_options[i].object_field).base.type == grt::IntegerType) {
int ivalue = base::atoi<int>(value, 0);
if (ivalue != *grt::IntegerRef::cast_from(get_table().get_member(table_options[i].object_field))) {
AutoUndoEdit undo(this);
get_table().set_member(table_options[i].object_field, grt::IntegerRef(ivalue));
update_change_date();
undo.end(strfmt(_("Change '%s' for '%s'"), name.c_str(), get_table()->name().c_str()));
}
} else {
if (value != *grt::StringRef::cast_from(get_table().get_member(table_options[i].object_field))) {
if (table_options[i].text) {
AutoUndoEdit undo(this, get_table(), table_options[i].object_field);
update_change_date();
get_table().set_member(table_options[i].object_field, grt::StringRef(value));
undo.end(strfmt(_("Change '%s' for '%s'"), name.c_str(), get_table()->name().c_str()));
} else {
AutoUndoEdit undo(this);
get_table().set_member(table_options[i].object_field, grt::StringRef(value));
update_change_date();
undo.end(strfmt(_("Change '%s' for '%s'"), name.c_str(), get_table()->name().c_str()));
}
if ("ENGINE" == name)
bec::ValidationManager::validate_instance(get_table(), "chk_fk_lgc");
}
}
found = true;
break;
}
}
if (found)
return;
if (name.compare("CHARACTER SET - COLLATE") ==
0) { // shortcut that sets both CHARACTER SET and COLLATE separated by a -
if (value != get_table_option_by_name(name)) {
std::string charset, collation;
parse_charset_collation(value, charset, collation);
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (charset != *table->defaultCharacterSetName() || collation != *table->defaultCollationName()) {
RefreshUI::Blocker blocker(*this);
AutoUndoEdit undo(this);
set_table_option_by_name("CHARACTER SET", charset);
set_table_option_by_name("COLLATE", collation);
update_change_date();
undo.end(strfmt(_("Change Charset/Collation for '%s'"), table->name().c_str()));
}
}
} else
throw std::invalid_argument("Invalid option " + name);
}
std::string MySQLTableEditorBE::get_table_option_by_name(const std::string &name) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (name.compare("PACK_KEYS") == 0)
return table->packKeys();
else if (name.compare("PASSWORD") == 0)
return table->password();
else if (name.compare("AUTO_INCREMENT") == 0)
return table->nextAutoInc();
else if (name.compare("DELAY_KEY_WRITE") == 0)
return table->delayKeyWrite().toString();
else if (name.compare("ROW_FORMAT") == 0)
return table->rowFormat();
else if (name.compare("KEY_BLOCK_SIZE") == 0)
return table->keyBlockSize();
else if (name.compare("AVG_ROW_LENGTH") == 0)
return table->avgRowLength();
else if (name.compare("MAX_ROWS") == 0)
return table->maxRows();
else if (name.compare("MIN_ROWS") == 0)
return table->minRows();
else if (name.compare("CHECKSUM") == 0)
return table->checksum().toString();
else if (name.compare("DATA DIRECTORY") == 0)
return table->tableDataDir();
else if (name.compare("INDEX DIRECTORY") == 0)
return table->tableIndexDir();
else if (name.compare("UNION") == 0)
return table->mergeUnion();
else if (name.compare("INSERT_METHOD") == 0)
return table->mergeInsert();
else if (name.compare("ENGINE") == 0)
return table->tableEngine();
else if (name.compare("CHARACTER SET - COLLATE") == 0)
return format_charset_collation(table->defaultCharacterSetName().c_str(), table->defaultCollationName().c_str());
else if (name.compare("CHARACTER SET") == 0)
return table->defaultCharacterSetName();
else if (name.compare("COLLATE") == 0)
return table->defaultCollationName();
else
throw std::invalid_argument("Invalid option " + name);
return std::string("");
}
//--------------------------------------------------------------------------------------------------
/**
* Loads the current trigger sql text into the editor control and marks that as not dirty.
* In addition the trigger UI is refreshed so that the trigger tree contains update trigger references.
*/
void MySQLTableEditorBE::load_trigger_sql() {
if (_trigger_panel && !_updating_triggers) {
_updating_triggers = true;
_trigger_panel->need_refresh("trigger");
_updating_triggers = false;
}
}
//--------------------------------------------------------------------------------------------------
bool MySQLTableEditorBE::can_close() {
_trigger_panel->code_edited(); // Same handling as for focus-lost. Might not be necessary but better safe than sorry.
return TableEditorBE::can_close();
}
//--------------------------------------------------------------------------------------------------
bool MySQLTableEditorBE::set_partition_type(const std::string &type) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (!type.empty() && type.compare(*table->partitionType()) != 0) {
if (type == "RANGE" || type == "LIST") {
AutoUndoEdit undo(this);
table->partitionType(type);
if (table->partitionCount() == 0)
table->partitionCount(1);
if (get_explicit_partitions())
reset_partition_definitions((int)table->partitionCount(),
get_explicit_subpartitions() ? (int)*table->subpartitionCount() : 0);
update_change_date();
undo.end(strfmt(_("Change Partition Type for '%s'"), get_name().c_str()));
return true;
} else if (type == "LINEAR HASH" || type == "HASH" || type == "LINEAR KEY" || type == "KEY" || type == "") {
AutoUndoEdit undo(this);
table->partitionType(type);
if (table->partitionCount() == 0)
table->partitionCount(1);
table->subpartitionCount(0);
table->subpartitionExpression("");
table->subpartitionType("");
if (get_explicit_partitions())
reset_partition_definitions((int)table->partitionCount(), 0);
update_change_date();
undo.end(strfmt(_("Change Partition Type for '%s'"), get_name().c_str()));
return true;
}
} else if (type.empty()) {
AutoUndoEdit undo(this);
table->partitionType(type);
table->partitionCount(0);
table->partitionExpression("");
table->subpartitionCount(0);
table->subpartitionExpression("");
table->subpartitionType("");
if (get_explicit_partitions())
reset_partition_definitions((int)table->partitionCount(), 0);
update_change_date();
undo.end(strfmt(_("Disable Partitioning for '%s'"), get_name().c_str()));
return true;
}
return false;
}
std::string MySQLTableEditorBE::get_partition_type() {
return *db_mysql_TableRef::cast_from(get_table())->partitionType();
}
void MySQLTableEditorBE::set_partition_expression(const std::string &expr) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
AutoUndoEdit undo(this, table, "partitionExpression");
table->partitionExpression(expr);
update_change_date();
undo.end(strfmt(_("Set Partition Expression for '%s'"), get_name().c_str()));
}
std::string MySQLTableEditorBE::get_partition_expression() {
return *db_mysql_TableRef::cast_from(get_table())->partitionExpression();
}
void MySQLTableEditorBE::set_partition_count(int count) {
AutoUndoEdit undo(this);
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (count > 0)
table->partitionCount(count);
else
table->partitionCount(1);
if (get_explicit_partitions())
reset_partition_definitions((int)table->partitionCount(),
get_explicit_partitions() ? (int)*table->subpartitionCount() : 0);
update_change_date();
undo.end(strfmt(_("Set Partition Count for '%s'"), get_name().c_str()));
}
int MySQLTableEditorBE::get_partition_count() {
return (int)*db_mysql_TableRef::cast_from(get_table())->partitionCount();
}
bool MySQLTableEditorBE::subpartition_count_allowed() {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
return (*table->partitionType() == "RANGE" || *table->partitionType() == "LIST");
}
bool MySQLTableEditorBE::set_subpartition_type(const std::string &type) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (*table->partitionType() == "RANGE" || *table->partitionType() == "LIST") {
AutoUndoEdit undo(this, table, "subpartitionType");
table->subpartitionType(type);
update_change_date();
undo.end(strfmt(_("Set Subpartition Type for '%s'"), get_name().c_str()));
return true;
}
return false;
}
std::string MySQLTableEditorBE::get_subpartition_type() {
return *db_mysql_TableRef::cast_from(get_table())->subpartitionType();
}
bool MySQLTableEditorBE::set_subpartition_expression(const std::string &expr) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (*table->partitionType() == "RANGE" || *table->partitionType() == "LIST") {
AutoUndoEdit undo(this, table, "subpartitionExpression");
table->subpartitionExpression(expr);
update_change_date();
undo.end(strfmt(_("Set Subpartition Expression for '%s'"), get_name().c_str()));
return true;
}
return false;
}
std::string MySQLTableEditorBE::get_subpartition_expression() {
return *db_mysql_TableRef::cast_from(get_table())->subpartitionExpression();
}
void MySQLTableEditorBE::set_subpartition_count(int count) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (*table->partitionType() == "RANGE" || *table->partitionType() == "LIST") {
AutoUndoEdit undo(this);
table->subpartitionCount(count);
if (get_explicit_subpartitions())
reset_partition_definitions((int)table->partitionCount(), (int)table->subpartitionCount());
update_change_date();
undo.end(strfmt(_("Set Subpartition Count for '%s'"), get_name().c_str()));
}
}
int MySQLTableEditorBE::get_subpartition_count() {
return (int)*db_mysql_TableRef::cast_from(get_table())->subpartitionCount();
}
void MySQLTableEditorBE::set_explicit_partitions(bool flag) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (flag != get_explicit_partitions()) {
AutoUndoEdit undo(this);
if (flag) {
if (table->partitionCount() == 0) {
table->partitionCount(2);
}
reset_partition_definitions((int)table->partitionCount(), (int)table->subpartitionCount());
} else
reset_partition_definitions(0, 0);
update_change_date();
undo.end(flag ? strfmt(_("Manually Define Partitions for '%s'"), get_name().c_str())
: strfmt(_("Implicitly Define Partitions for '%s'"), get_name().c_str()));
}
}
void MySQLTableEditorBE::set_explicit_subpartitions(bool flag) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
if (flag != get_explicit_subpartitions()) {
if (get_explicit_partitions()) {
AutoUndoEdit undo(this);
if (flag) {
if (table->subpartitionCount() == 0) {
table->subpartitionCount(2);
}
reset_partition_definitions((int)table->partitionCount(), (int)table->subpartitionCount());
} else
reset_partition_definitions((int)table->partitionCount(), 0);
update_change_date();
undo.end(flag ? strfmt(_("Manually Define SubPartitions for '%s'"), get_name().c_str())
: strfmt(_("Implicitly Define SubPartitions for '%s'"), get_name().c_str()));
}
}
}
bool MySQLTableEditorBE::get_explicit_partitions() {
return db_mysql_TableRef::cast_from(get_table())->partitionDefinitions().count() > 0;
}
bool MySQLTableEditorBE::get_explicit_subpartitions() {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(get_table());
return table->partitionDefinitions().count() > 0 &&
table->partitionDefinitions().get(0)->subpartitionDefinitions().count() > 0;
}
void MySQLTableEditorBE::reset_partition_definitions(int parts, int subparts) {
grt::ListRef<db_mysql_PartitionDefinition> pdefs(db_mysql_TableRef::cast_from(get_table())->partitionDefinitions());
AutoUndoEdit undo(this);
while ((int)pdefs.count() < parts) {
db_mysql_PartitionDefinitionRef part(grt::Initialized);
part->owner(db_mysql_TableRef::cast_from(get_table()));
part->name(grt::StringRef::format("part%i", pdefs.count()));
pdefs.insert(part);
}
while ((int)pdefs.count() > parts) {
pdefs.remove(pdefs.count() - 1);
}
for (size_t c = pdefs.count(), i = 0; i < c; i++) {
grt::ListRef<db_mysql_PartitionDefinition> spdefs(pdefs[i]->subpartitionDefinitions());
while ((int)spdefs.count() < subparts) {
db_mysql_PartitionDefinitionRef part(grt::Initialized);
part->owner(pdefs[i]);
part->name(grt::StringRef::format("subpart%i", i * subparts + spdefs.count()));
spdefs.insert(part);
}
while ((int)spdefs.count() > subparts) {
spdefs.remove(spdefs.count() - 1);
}
}
update_change_date();
undo.end("Reset Partitioning");
}
db_TableRef MySQLTableEditorBE::create_stub_table(const std::string &schema, const std::string &table) {
db_SchemaRef dbschema = grt::find_named_object_in_list(get_catalog()->schemata(), schema, false);
db_TableRef dbtable;
if (dbschema.is_valid())
dbtable = grt::find_named_object_in_list(dbschema->tables(), table);
else {
dbschema = db_mysql_SchemaRef(grt::Initialized);
dbschema->owner(get_catalog());
dbschema->name(schema);
dbschema->comment("This schema was created for a stub table");
get_catalog()->schemata().insert(dbschema);
}
if (!dbtable.is_valid()) {
dbtable = db_mysql_TableRef(grt::Initialized);
dbtable->owner(dbschema);
dbtable->name(table);
dbtable->isStub(1);
dbschema->tables().insert(dbtable);
}
return dbtable;
}
static db_SimpleDatatypeRef get_simple_datatype(const db_ColumnRef &column) {
if (column->simpleType().is_valid())
return column->simpleType();
if (column->userType().is_valid())
return column->userType()->actualType();
return db_SimpleDatatypeRef();
}
bool MySQLTableEditorBE::check_column_referenceable_by_fk(const db_ColumnRef &column1, const db_ColumnRef &column2) {
// from 5.1 manual:
// - Corresponding columns in the foreign key and the referenced key must have similar internal data types
// inside InnoDB so that they can be compared without a type conversion.
// - The size and sign of integer types must be the same.
// - The length of string types need not be the same.
// - For nonbinary (character) string columns, the character set and collation must be the same.
db_SimpleDatatypeRef stype1 = get_simple_datatype(column1);
db_SimpleDatatypeRef stype2 = get_simple_datatype(column2);
if (!stype1.is_valid() || !stype2.is_valid())
return false;
if (stype1 != stype2)
return false;
if (stype1->group()->name() == "numeric") {
// check sign (size is already checked by previous if)
bool unsigned1 = column1->flags().get_index("UNSIGNED") != grt::BaseListRef::npos;
bool unsigned2 = column2->flags().get_index("UNSIGNED") != grt::BaseListRef::npos;
if (unsigned1 != unsigned2)
return false;
}
if (stype1->group()->name() == "string") {
// check collation and charset
if (column1->characterSetName() != column2->characterSetName() ||
column1->collationName() != column2->collationName())
return false;
}
return true;
}
//--------------------------------------------------------------------------------
MySQLTablePartitionTreeBE::MySQLTablePartitionTreeBE(MySQLTableEditorBE *owner) {
_owner = owner;
}
bool MySQLTablePartitionTreeBE::set_field(const NodeId &node, ColumnId column, const std::string &value) {
db_mysql_PartitionDefinitionRef pdef(get_definition(node));
if (!pdef.is_valid())
return false;
switch ((Columns)column) {
case Name:
if (pdef->name() != value) {
AutoUndoEdit undo(_owner, pdef, "name");
pdef->name(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Name for '%s'"), _owner->get_name().c_str()));
}
return true;
case Value:
if (pdef->value() != value) {
AutoUndoEdit undo(_owner, pdef, "value");
pdef->value(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Parameter for '%s'"), _owner->get_name().c_str()));
}
return true;
case MinRows:
if (pdef->minRows() != value) {
AutoUndoEdit undo(_owner, pdef, "minRows");
pdef->minRows(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Min Rows for '%s'"), _owner->get_name().c_str()));
}
return true;
case MaxRows:
if (pdef->maxRows() != value) {
AutoUndoEdit undo(_owner, pdef, "maxRows");
pdef->maxRows(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Max Rows for '%s'"), _owner->get_name().c_str()));
}
return true;
case DataDirectory:
if (pdef->dataDirectory() != value) {
AutoUndoEdit undo(_owner, pdef, "dataDirectory");
pdef->dataDirectory(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Data Directory for '%s'"), _owner->get_name().c_str()));
}
return true;
case IndexDirectory:
if (pdef->indexDirectory() != value) {
AutoUndoEdit undo(_owner, pdef, "indexDirectory");
pdef->indexDirectory(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Index Directory for '%s'"), _owner->get_name().c_str()));
}
return true;
case Comment:
if (pdef->comment() != value) {
AutoUndoEdit undo(_owner, pdef, "comment");
pdef->comment(value);
_owner->update_change_date();
undo.end(strfmt(_("Change Partition Comment for '%s'"), _owner->get_name().c_str()));
}
return true;
}
return false;
}
bool MySQLTablePartitionTreeBE::get_field_grt(const NodeId &node, ColumnId column, grt::ValueRef &value) {
db_mysql_PartitionDefinitionRef pdef(get_definition(node));
if (!pdef.is_valid())
return false;
switch ((Columns)column) {
case Name:
value = pdef->name();
return true;
case Value:
value = pdef->value();
return true;
case MinRows:
value = pdef->minRows();
return true;
case MaxRows:
value = pdef->maxRows();
return true;
case DataDirectory:
value = pdef->dataDirectory();
return true;
case IndexDirectory:
value = pdef->indexDirectory();
return true;
case Comment:
value = pdef->comment();
return true;
}
return false;
}
grt::Type MySQLTablePartitionTreeBE::get_field_type(const NodeId &node, ColumnId column) {
return grt::StringType;
}
db_mysql_PartitionDefinitionRef MySQLTablePartitionTreeBE::get_definition(const NodeId &node) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_owner->get_table());
if (node.depth() == 1) {
if (node[0] < table->partitionDefinitions().count())
return table->partitionDefinitions()[node[0]];
} else if (node.depth() == 2) {
if (node[0] < table->partitionDefinitions().count()) {
db_mysql_PartitionDefinitionRef def(table->partitionDefinitions()[node[0]]);
if (node[1] < def->subpartitionDefinitions().count())
return def->subpartitionDefinitions()[node[1]];
}
}
return db_mysql_PartitionDefinitionRef();
}
size_t MySQLTablePartitionTreeBE::count_children(const NodeId &parent) {
if (parent.depth() == 1) {
db_mysql_PartitionDefinitionRef def(get_definition(parent));
if (def.is_valid())
return (int)def->subpartitionDefinitions().count();
} else if (parent.depth() == 0) {
db_mysql_TableRef table = db_mysql_TableRef::cast_from(_owner->get_table());
return (int)table->partitionDefinitions().count();
}
return 0;
}
NodeId MySQLTablePartitionTreeBE::get_child(const NodeId &parent, size_t index) {
if (count_children(parent) > index)
return NodeId(parent).append(index);
throw std::logic_error("Invalid index");
}
//-------------------------------------------------------------------------------------------------
MySQLTableIndexListBE::MySQLTableIndexListBE(MySQLTableEditorBE *owner) : IndexListBE(owner) {
}
bool MySQLTableIndexListBE::set_field(const NodeId &node, ColumnId column, const std::string &value) {
if (!index_editable(get_selected_index()))
return IndexListBE::set_field(node, column, value);
db_mysql_IndexRef index(db_mysql_IndexRef::cast_from(get_selected_index()));
if (!index.is_valid())
return IndexListBE::set_field(node, column, value);
switch (column) {
case StorageType:
if (value != *index->indexKind()) {
AutoUndoEdit undo(_owner, index, "indexKind");
index->indexKind(value);
undo.end(strfmt(_("Change Storage Type of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
}
return true;
case RowBlockSize:
if (base::atoi<int>(value, 0) != *index->keyBlockSize()) {
AutoUndoEdit undo(_owner, index, "keyBlockSize");
index->keyBlockSize(base::atoi<int>(value, 0));
undo.end(
strfmt(_("Change Key Block Size of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
}
return true;
case Parser:
if (value != *index->withParser()) {
AutoUndoEdit undo(_owner, index, "withParser");
index->withParser(value);
undo.end(strfmt(_("Change Parser of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
}
return true;
default:
return IndexListBE::set_field(node, column, value);
}
}
bool MySQLTableIndexListBE::set_field(const ::bec::NodeId &node, ColumnId column, ssize_t value) {
if(!node.is_valid() || !index_editable(get_selected_index()))
return false;
db_mysql_IndexRef index(db_mysql_IndexRef::cast_from(get_selected_index()));
if (index.is_valid()) {
switch(column) {
case Visible:
if (index->visible() != value) {
AutoUndoEdit undo(_owner, index, "Visible");
index->visible(value);
_owner->update_change_date();
undo.end(strfmt(_("Set Visibility of Index '%s.%s'"), _owner->get_name().c_str(), index->name().c_str()));
}
return true;
default:
return false;
}
}
return false;
}
bool MySQLTableIndexListBE::get_field_grt(const NodeId &node, ColumnId column, grt::ValueRef &value) {
if (node.is_valid()) {
const bool existing_node = node.end() < real_count();
db_mysql_IndexRef index = db_mysql_IndexRef::cast_from(get_selected_index());
switch (column) {
case StorageType:
value = existing_node && index.is_valid() ? index->indexKind() : grt::StringRef("");
return true;
case RowBlockSize:
value =
existing_node && index.is_valid() ? grt::StringRef(index->keyBlockSize().toString()) : grt::StringRef("");
return true;
case Parser:
value = existing_node && index.is_valid() ? index->withParser() : grt::StringRef("");
return true;
case Visible:
value = existing_node && index.is_valid() ? index->visible() : grt::IntegerRef(1);
return true;
default:
return IndexListBE::get_field_grt(node, column, value);
}
}
return false;
}