backend/wbpublic/grtdb/editor_dbobject.cpp (353 lines of code) (raw):
/*
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "editor_dbobject.h"
#include "base/util_functions.h"
#include "grt/validation_manager.h"
#include "grtpp_notifications.h"
#include "base/string_utilities.h"
#include "base/notifications.h"
#include "mforms/utilities.h"
#include "mforms/code_editor.h"
#include "sqlide/sql_editor_be.h"
#include "db_helpers.h"
#include "mysql/MySQLRecognizerCommon.h"
const char *DEFAULT_CHARSET_CAPTION = "Default Charset";
const char *DEFAULT_COLLATION_CAPTION = "Default Collation";
using namespace bec;
using namespace parsers;
//--------------------------------------------------------------------------------------------------
DBObjectEditorBE::DBObjectEditorBE(const db_DatabaseObjectRef &object)
: BaseEditor(object) {
_ignored_object_fields_for_ui_refresh.insert("lastChangeDate");
// Get the owning catalog.
GrtObjectRef run = object;
while (run.is_valid() && !run.is_instance(db_Catalog::static_class_name()))
run = run->owner();
_catalog = db_CatalogRef::cast_from(run);
_parserServices = MySQLParserServices::get();
bool case_sensitive = true;
if (object->customData().get_int("CaseSensitive", 1) == 0)
case_sensitive = false;
// Assume a default version if the given catalog is incomplete.
GrtVersionRef version = get_catalog()->version();
if (!version.is_valid())
version = bec::parse_version("8.0.16");
_globalSymbols = parsers::functionSymbolsForVersion(bec::versionToEnum(version));
std::string sqlMode;
if (object->customData().has_key("sqlMode"))
sqlMode = object->customData().get_string("sqlMode");
_parserContext =
_parserServices->createParserContext(get_catalog()->characterSets(), version, sqlMode, case_sensitive);
// Because syntax checks and auto completion are done in different threads we need 2 different parser contexts.
_autocompletionContext =
_parserServices->createParserContext(get_catalog()->characterSets(), version, sqlMode, case_sensitive);
_val_notify_conn = ValidationManager::signal_notify()->connect(
std::bind(&DBObjectEditorBE::notify_from_validation, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
// Get notified about version number changes.
grt::GRTNotificationCenter::get()->add_grt_observer(this, "GRNPreferencesDidClose");
grt::DictRef info(true);
info.gset("form", form_id());
info.set("object", object);
// Must be delayed, because observer will probably need the form to be finished constructing
grt::GRTNotificationCenter::get()->send_grt("GRNDBObjectEditorCreated", grt::ObjectRef(), info);
}
//--------------------------------------------------------------------------------------------------
DBObjectEditorBE::~DBObjectEditorBE() {
grt::GRTNotificationCenter::get()->remove_grt_observer(this);
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::handle_grt_notification(const std::string &name, grt::ObjectRef sender, grt::DictRef info) {
if (info.get_int("saved") == 1) {
if (name == "GRNPreferencesDidClose") {
// We want to see changes for the server version.
GrtVersionRef version = get_catalog()->version();
_parserContext->updateServerVersion(version);
get_sql_editor()->setServerVersion(version);
}
}
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::is_editing_live_object() {
return get_dbobject()->customData().get("liveRdbms").is_valid();
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::apply_changes_to_live_object() {
BaseEditor::apply_changes_to_live_object();
if (on_apply_changes_to_live_object(this, false))
refresh_live_object();
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::refresh_live_object() {
BaseEditor::refresh_live_object();
on_refresh_live_object(this);
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::can_close() {
// Editing in a model always allows to close the editor. Save checks are done when the model
// is closed.
if (!is_editing_live_object())
return true;
bool res = BaseEditor::can_close();
// Note: the result of the BaseEditor::can_close() is only used if there's no apply callback set.
// Otherwise we always use the callback for checks (because there can be other changes than
// just the code editor which is checked in the BaseEditor).
if (on_apply_changes_to_live_object) {
bool is_object_modified = on_apply_changes_to_live_object(this, true);
if (is_object_modified) {
int user_choice =
mforms::Utilities::show_warning(base::strfmt(_("Object %s was changed"), get_name().c_str()),
base::strfmt(_("Do you want to save changes made to %s?"), get_name().c_str()),
_("Save"), _("Cancel"), _("Don't Save"));
if (mforms::ResultOk == user_choice)
res = on_apply_changes_to_live_object(this, false);
else if (mforms::ResultCancel == user_choice)
res = false;
else
res = true;
} else
res = true;
}
return res;
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::should_close_on_delete_of(const std::string &oid) {
if (get_object().id() == oid)
return true;
db_SchemaRef schema(get_schema());
if (schema.is_valid() && schema.id() == oid)
return true;
return false;
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::update_change_date() {
get_object().set_member("lastChangeDate", grt::StringRef(base::fmttime(0, DATETIME_FMT)));
}
//--------------------------------------------------------------------------------------------------
std::string DBObjectEditorBE::get_name() {
return get_object()->name();
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::set_name(const std::string &name) {
if (get_object()->name() != name) {
RefreshUI::Blocker refresh_block(*this);
AutoUndoEdit undo(this, get_dbobject(), "name");
std::string name_ = base::trim(name);
get_dbobject()->name(name_);
update_change_date();
undo.end(base::strfmt(_("Rename to '%s'"), name_.c_str()));
}
}
//--------------------------------------------------------------------------------------------------
std::string DBObjectEditorBE::get_comment() {
return get_dbobject()->comment();
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::set_comment(const std::string &descr) {
if (get_dbobject()->comment() != descr) {
RefreshUI::Blocker blocker(*this);
AutoUndoEdit undo(this, get_dbobject(), "comment");
get_dbobject()->comment(descr);
update_change_date();
undo.end(_("Edit Comment"));
}
}
//--------------------------------------------------------------------------------------------------
std::string DBObjectEditorBE::get_sql() {
if (db_DatabaseDdlObjectRef::can_wrap(get_object())) {
db_DatabaseDdlObjectRef object = db_DatabaseDdlObjectRef::cast_from(get_object());
return object->sqlDefinition();
}
return "";
}
//--------------------------------------------------------------------------------------------------
/**
* Called from outside to set new sql text in our editor.
*/
void DBObjectEditorBE::set_sql(const std::string &sql) {
get_sql_editor()->sql(sql.c_str());
commit_changes();
send_refresh();
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::is_sql_commented() {
return (*get_dbobject()->commentedOut() != 0);
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::set_sql_commented(bool flag) {
RefreshUI::Blocker blocker(*this);
AutoUndoEdit undo(this, get_dbobject(), "commentedOut");
get_dbobject()->commentedOut(flag ? 1 : 0);
update_change_date();
undo.end(_("Comment Out SQL"));
}
//--------------------------------------------------------------------------------------------------
db_CatalogRef DBObjectEditorBE::get_catalog() {
return _catalog;
}
//--------------------------------------------------------------------------------------------------
db_SchemaRef DBObjectEditorBE::get_schema() {
GrtObjectRef object = get_dbobject();
while (object.is_valid() && !object.is_instance(db_Schema::static_class_name()))
object = object->owner();
return db_SchemaRef::cast_from(object);
}
//--------------------------------------------------------------------------------------------------
std::string DBObjectEditorBE::get_schema_name() {
return get_schema()->name();
}
//--------------------------------------------------------------------------------------------------
db_SchemaRef DBObjectEditorBE::get_schema_with_name(const std::string &schema_name) {
return grt::find_named_object_in_list(_catalog->schemata(), schema_name);
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_all_schema_names() {
std::vector<std::string> names;
if (is_editing_live_object()) {
names.push_back(get_schema()->name());
return names;
}
grt::ListRef<db_Schema> schema_list = _catalog->schemata();
for (size_t sc = schema_list.count(), s = 0; s < sc; s++)
names.push_back(schema_list[s]->name());
return names;
}
//--------------------------------------------------------------------------------------------------
/**
* Collects the FQN for all tables not in our own schema.
*/
std::vector<std::string> DBObjectEditorBE::get_all_table_names() {
if (is_editing_live_object())
on_create_live_table_stubs(this);
grt::ListRef<db_Schema> schema_list = _catalog->schemata();
db_SchemaRef myschema = get_schema();
std::vector<std::string> table_list;
// Construct FQN for all tables. This will sort all tables within the same schema together.
table_list = get_schema_table_names();
for (size_t i = 0; i < schema_list.count(); ++i) {
if (schema_list[i] == myschema)
continue;
db_SchemaRef schema = schema_list[i];
std::string schema_name = schema_list[i]->name();
for (size_t j = 0; j < schema->tables().count(); ++j)
table_list.push_back("`" + schema_name + "`.`" + *schema->tables()[j]->name() + "`");
}
std::sort(table_list.begin(), table_list.end());
table_list.push_back("Specify Manually...");
return table_list;
}
//--------------------------------------------------------------------------------------------------
/**
* Collects the FQN for all tables in our schema.
*/
std::vector<std::string> DBObjectEditorBE::get_schema_table_names() {
db_SchemaRef schema = get_schema();
std::vector<std::string> table_list;
std::string schema_name = schema->name();
if (schema.is_valid()) {
for (size_t i = 0; i < schema->tables().count(); ++i)
table_list.push_back("`" + schema_name + "`.`" + *schema->tables()[i]->name() + "`");
}
std::sort(table_list.begin(), table_list.end());
return table_list;
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_table_column_names(const std::string &fq_table_name) {
db_SchemaRef schema;
std::vector<std::string> columns;
if (fq_table_name.empty())
return columns;
std::vector<std::string> parts = base::split_qualified_identifier(fq_table_name);
std::string table_name;
if (parts.size() == 1) {
table_name = parts[0];
schema = get_schema();
} else if (!parts.empty()) {
schema = get_schema_with_name(parts[0]);
table_name = parts[1];
}
if (schema.is_valid()) {
db_TableRef table(grt::find_named_object_in_list(schema->tables(), table_name));
if (table.is_valid()) {
for (size_t c = table->columns().count(), i = 0; i < c; i++)
columns.push_back(*table->columns()[i]->name());
}
}
return columns;
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_table_column_names(const db_TableRef &table) {
std::vector<std::string> columns;
if (table.is_valid())
for (size_t c = table->columns().count(), i = 0; i < c; i++)
columns.push_back(*table->columns()[i]->name());
return columns;
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_charset_list() {
std::vector<std::string> result;
grt::ListRef<db_CharacterSet> charsets = _catalog->characterSets();
for (size_t j = 0; j < charsets.count(); ++j) {
db_CharacterSetRef cs = charsets.get(j);
std::string cs_name(cs->name().c_str());
result.push_back(cs_name);
}
result.push_back(DEFAULT_CHARSET_CAPTION);
std::sort(result.begin(), result.end());
return result;
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_charset_collation_list(const std::string &charset) {
std::vector<std::string> result;
grt::ListRef<db_CharacterSet> charsets = _catalog->characterSets();
for (size_t j = 0; j < charsets.count(); ++j) {
db_CharacterSetRef cs = charsets.get(j);
if (cs->name() != charset)
continue;
grt::StringListRef collations(cs->collations());
for (size_t k = 0; k < collations.count(); ++k) {
result.push_back(collations.get(k));
}
}
result.push_back(DEFAULT_COLLATION_CAPTION);
std::sort(result.begin(), result.end());
return result;
}
//--------------------------------------------------------------------------------------------------
std::vector<std::string> DBObjectEditorBE::get_charset_collation_list() {
std::vector<std::string> collation_list;
grt::ListRef<db_CharacterSet> charsets = _catalog->characterSets();
for (size_t j = 0; j < charsets.count(); ++j) {
db_CharacterSetRef cs = charsets.get(j);
grt::StringListRef collations(cs->collations());
std::string cs_name(cs->name().c_str());
collation_list.push_back(format_charset_collation(cs_name, ""));
for (size_t k = 0; k < collations.count(); ++k)
collation_list.push_back(format_charset_collation(cs_name, collations.get(k)));
}
return collation_list;
}
//--------------------------------------------------------------------------------------------------
std::string DBObjectEditorBE::format_charset_collation(const std::string &charset, const std::string &collation) {
if (collation.empty()) {
if (charset.empty())
return " - ";
else
return charset + " - " + DEFAULT_COLLATION_CAPTION;
} else
return charset + " - " + collation;
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::parse_charset_collation(const std::string &str, std::string &charset, std::string &collation) {
std::string::size_type pos;
if ((pos = str.find(" - ")) != std::string::npos) {
charset = str.substr(0, pos);
collation = str.substr(pos + 3);
if (collation == DEFAULT_COLLATION_CAPTION)
collation = "";
return true;
}
charset = "";
collation = "";
return false;
}
//--------------------------------------------------------------------------------------------------
bool DBObjectEditorBE::has_editor() {
if (_sql_editor)
return true;
return false;
}
//--------------------------------------------------------------------------------------------------
MySQLEditor::Ref DBObjectEditorBE::get_sql_editor() {
if (!_sql_editor) {
_sql_editor = MySQLEditor::create(_parserContext, _autocompletionContext, { _globalSymbols });
grt::DictRef obj_options = get_dbobject()->customData();
if (obj_options.has_key("sqlMode"))
_sql_editor->set_sql_mode(obj_options.get_string("sqlMode"));
}
return _sql_editor;
}
//--------------------------------------------------------------------------------------------------
void bec::DBObjectEditorBE::reset_editor_undo_stack() {
// Don't create an editor control if we don't need one (e.g. for the schema editor).
if (_sql_editor)
_sql_editor->get_editor_control()->reset_dirty();
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::notify_from_validation(const grt::Validator::Tag &tag, const grt::ObjectRef &obj,
const std::string &msg, const int level) {
bool notify_is_for_us = false;
if (obj.is_valid()) {
// Get edited object
const GrtObjectRef our_obj = get_object();
const GrtObjectRef val_obj = GrtObjectRef::cast_from(obj);
// Check if passed object is ours
if (our_obj == val_obj)
notify_is_for_us = true;
else {
GrtObjectRef parent = val_obj->owner();
while (parent.is_valid()) {
if (parent == our_obj) {
notify_is_for_us = true;
break;
}
parent = parent->owner();
}
// Scan owners upwards
}
} else {
if (tag == "*")
notify_is_for_us = true;
}
if (notify_is_for_us) {
_last_validation_check_status = (grt::MessageType)level;
_last_validation_message = msg;
}
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::send_refresh() {
db_DatabaseObjectRef db_object = get_dbobject();
(*db_object->signal_changed())("", grt::ValueRef());
}
//--------------------------------------------------------------------------------------------------
void DBObjectEditorBE::set_sql_mode(const std::string &value) {
MySQLEditor::Ref sql_editor = get_sql_editor();
if (sql_editor)
sql_editor->set_sql_mode(value);
}
//--------------------------------------------------------------------------------------------------