/* Copyright (c) 2011, 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 also distributed 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 included with MySQL. 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 #include "my_dbug.h" #include "mysql/service_thd_alloc.h" #include "sql/key_spec.h" #include "sql/mysqld.h" // global_system_variables table_alias_charset ... #include "sql/sql_class.h" #include "sql/sql_lex.h" #include "sql/sql_table.h" #include "storage/ndb/plugin/ha_ndbcluster.h" #include "storage/ndb/plugin/ndb_fk_util.h" #include "storage/ndb/plugin/ndb_log.h" #include "storage/ndb/plugin/ndb_table_guard.h" #include "storage/ndb/plugin/ndb_tdc.h" #include "template_utils.h" #define ERR_RETURN(err) \ { \ const NdbError &tmp = err; \ return ndb_to_mysql_error(&tmp); \ } // Typedefs for long names typedef NdbDictionary::Dictionary NDBDICT; typedef NdbDictionary::Table NDBTAB; typedef NdbDictionary::Column NDBCOL; typedef NdbDictionary::Index NDBINDEX; typedef NdbDictionary::ForeignKey NDBFK; /* Foreign key data where this table is child or parent or both. Like indexes, these are cached under each handler instance. Unlike indexes, no references to global dictionary are kept. */ struct Ndb_fk_item { FOREIGN_KEY_INFO f_key_info; int update_action; // NDBFK::FkAction int delete_action; bool is_child; bool is_parent; }; struct Ndb_fk_data { List list; uint cnt_child; uint cnt_parent; }; /* Create all the fks for a table. The actual foreign keys are not passed in handler interface so gets them from thd->lex :-( */ static const NDBINDEX *find_matching_index( NDBDICT *dict, const NDBTAB *tab, const NDBCOL *columns[], /* OUT */ bool &matches_primary_key) { /** * First check if it matches primary key */ { matches_primary_key = false; uint cnt_pk = 0, cnt_col = 0; for (unsigned i = 0; columns[i] != 0; i++) { cnt_col++; if (columns[i]->getPrimaryKey()) cnt_pk++; } // check if all columns was part of full primary key if (cnt_col == (uint)tab->getNoOfPrimaryKeys() && cnt_col == cnt_pk) { matches_primary_key = true; return 0; } } /** * check indexes... * first choice is unique index * second choice is ordered index...with as many columns as possible */ const int noinvalidate = 0; uint best_matching_columns = 0; const NDBINDEX *best_matching_index = 0; NDBDICT::List index_list; dict->listIndexes(index_list, *tab); for (unsigned i = 0; i < index_list.count; i++) { const char *index_name = index_list.elements[i].name; const NDBINDEX *index = dict->getIndexGlobal(index_name, *tab); if (index->getType() == NDBINDEX::UniqueHashIndex) { uint cnt = 0, j; for (j = 0; columns[j] != 0; j++) { /* * Search for matching columns in any order * since order does not matter for unique index */ bool found = false; for (unsigned c = 0; c < index->getNoOfColumns(); c++) { if (!strcmp(columns[j]->getName(), index->getColumn(c)->getName())) { found = true; break; } } if (found) cnt++; else break; } if (cnt == index->getNoOfColumns() && columns[j] == 0) { /** * Full match...return this index, no need to look further */ if (best_matching_index) { // release ref to previous best candidate dict->removeIndexGlobal(*best_matching_index, noinvalidate); } return index; // NOTE: also returns reference } /** * Not full match...i.e not usable */ dict->removeIndexGlobal(*index, noinvalidate); continue; } else if (index->getType() == NDBINDEX::OrderedIndex) { uint cnt = 0; for (; columns[cnt] != 0; cnt++) { const NDBCOL *ndbcol = index->getColumn(cnt); if (ndbcol == 0) break; if (strcmp(columns[cnt]->getName(), ndbcol->getName()) != 0) break; } if (cnt > best_matching_columns) { /** * better match... */ if (best_matching_index) { dict->removeIndexGlobal(*best_matching_index, noinvalidate); } best_matching_index = index; best_matching_columns = cnt; } else { dict->removeIndexGlobal(*index, noinvalidate); } } else { // what ?? unknown index type assert(false); dict->removeIndexGlobal(*index, noinvalidate); continue; } } return best_matching_index; // NOTE: also returns reference } static void setDbName(Ndb *ndb, const char *name) { if (name && strlen(name) != 0) { ndb->setDatabaseName(name); } } template const char *lex2str(const LEX_CSTRING &str, char (&buf)[buf_size]) { snprintf(buf, buf_size, "%.*s", (int)str.length, str.str); return buf; } static void ndb_fk_casedn(char *name) { DBUG_ASSERT(name != 0); uint length = (uint)strlen(name); DBUG_ASSERT(files_charset_info != 0 && files_charset_info->casedn_multiply == 1); files_charset_info->cset->casedn(files_charset_info, name, length, name, length); } static int ndb_fk_casecmp(const char *name1, const char *name2) { if (!lower_case_table_names) { return strcmp(name1, name2); } char tmp1[FN_LEN + 1]; char tmp2[FN_LEN + 1]; strcpy(tmp1, name1); strcpy(tmp2, name2); ndb_fk_casedn(tmp1); ndb_fk_casedn(tmp2); return strcmp(tmp1, tmp2); } extern bool ndb_show_foreign_key_mock_tables(THD *thd); class Fk_util { THD *m_thd; void info(const char *fmt, ...) const MY_ATTRIBUTE((format(printf, 2, 3))); void warn(const char *fmt, ...) const MY_ATTRIBUTE((format(printf, 2, 3))); void error(const NdbDictionary::Dictionary *dict, const char *fmt, ...) const MY_ATTRIBUTE((format(printf, 3, 4))); void remove_index_global(NdbDictionary::Dictionary *dict, const NdbDictionary::Index *index) const { if (!index) return; dict->removeIndexGlobal(*index, 0); } bool copy_fk_to_new_parent(NdbDictionary::Dictionary *dict, NdbDictionary::ForeignKey &fk, const char *new_parent_name, const char *column_names[]) const { DBUG_TRACE; DBUG_PRINT("info", ("new_parent_name: %s", new_parent_name)); // Load up the new parent table Ndb_table_guard new_parent_tab(dict, new_parent_name); if (!new_parent_tab.get_table()) { error(dict, "Failed to load potentially new parent '%s'", new_parent_name); return false; } // Build new parent column list from parent column names const NdbDictionary::Column *columns[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned num_columns = 0; for (unsigned i = 0; column_names[i] != 0; i++) { DBUG_PRINT("info", ("column: %s", column_names[i])); const NdbDictionary::Column *col = new_parent_tab.get_table()->getColumn(column_names[i]); if (!col) { // Parent table didn't have any column with the given name, can happen warn( "Could not resolve '%s' as fk parent for '%s' since it didn't " "have " "all the referenced columns", new_parent_name, fk.getChildTable()); return false; } columns[num_columns++] = col; } columns[num_columns] = 0; } NdbDictionary::ForeignKey new_fk(fk); // Create name for the new fk by splitting the fk's name and replacing // the part in format "//" { char name[FN_REFLEN + 1]; unsigned parent_id, child_id; if (sscanf(fk.getName(), "%u/%u/%s", &parent_id, &child_id, name) != 3) { warn("Skip, failed to parse name of fk: %s", fk.getName()); return false; } char fk_name[FN_REFLEN + 1]; snprintf(fk_name, sizeof(fk_name), "%s", name); DBUG_PRINT("info", ("Setting new fk name: %s", fk_name)); new_fk.setName(fk_name); } // Find matching index bool parent_primary_key = false; const NdbDictionary::Index *parent_index = find_matching_index( dict, new_parent_tab.get_table(), columns, parent_primary_key); DBUG_PRINT("info", ("parent_primary_key: %d", parent_primary_key)); // Check if either pk or index matched if (!parent_primary_key && parent_index == 0) { warn( "Could not resolve '%s' as fk parent for '%s' since no matching " "index " "could be found", new_parent_name, fk.getChildTable()); return false; } if (parent_index != 0) { DBUG_PRINT("info", ("Setting parent with index %s", parent_index->getName())); new_fk.setParent(*new_parent_tab.get_table(), parent_index, columns); } else { DBUG_PRINT("info", ("Setting parent without index")); new_fk.setParent(*new_parent_tab.get_table(), 0, columns); } // Old fk is dropped by cascading when the mock table is dropped // Create new fk referencing the new table DBUG_PRINT("info", ("Create new fk: %s", new_fk.getName())); int flags = 0; if (thd_test_options(m_thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { flags |= NdbDictionary::Dictionary::CreateFK_NoVerify; } NdbDictionary::ObjectId objid; if (dict->createForeignKey(new_fk, &objid, flags) != 0) { error(dict, "Failed to create foreign key '%s'", new_fk.getName()); remove_index_global(dict, parent_index); return false; } remove_index_global(dict, parent_index); return true; } void resolve_mock(NdbDictionary::Dictionary *dict, const char *new_parent_name, const char *mock_name) const { DBUG_TRACE; DBUG_PRINT("enter", ("mock_name '%s'", mock_name)); DBUG_ASSERT(is_mock_name(mock_name)); // Load up the mock table Ndb_table_guard mock_tab(dict, mock_name); if (!mock_tab.get_table()) { error(dict, "Failed to load the listed mock table '%s'", mock_name); DBUG_ASSERT(false); return; } // List dependent objects of mock table NdbDictionary::Dictionary::List list; if (dict->listDependentObjects(list, *mock_tab.get_table()) != 0) { error(dict, "Failed to list dependent objects for mock table '%s'", mock_name); return; } for (unsigned i = 0; i < list.count; i++) { const NdbDictionary::Dictionary::List::Element &element = list.elements[i]; if (element.type != NdbDictionary::Object::ForeignKey) continue; DBUG_PRINT("info", ("fk: %s", element.name)); NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, element.name) != 0) { error(dict, "Could not find the listed fk '%s'", element.name); continue; } // Build column name list for parent const char *col_names[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned num_columns = 0; for (unsigned j = 0; j < fk.getParentColumnCount(); j++) { const NdbDictionary::Column *col = mock_tab.get_table()->getColumn(fk.getParentColumnNo(j)); if (!col) { error(NULL, "Could not find column %d in mock table '%s'", fk.getParentColumnNo(j), mock_name); continue; } col_names[num_columns++] = col->getName(); } col_names[num_columns] = 0; if (num_columns != fk.getParentColumnCount()) { error( NULL, "Could not find all columns referenced by fk in mock table '%s'", mock_name); continue; } } if (!copy_fk_to_new_parent(dict, fk, new_parent_name, col_names)) continue; // New fk has been created between child and new parent, drop the mock // table and it's related fk const int drop_flags = NDBDICT::DropTableCascadeConstraints; if (dict->dropTableGlobal(*mock_tab.get_table(), drop_flags) != 0) { error(dict, "Failed to drop mock table '%s'", mock_name); continue; } info("Dropped mock table '%s' - resolved by '%s'", mock_name, new_parent_name); } return; } bool create_mock_tables_and_drop(Ndb *ndb, NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table) { DBUG_TRACE; DBUG_PRINT("enter", ("table: %s", table->getName())); /* List all foreign keys referencing the table to be dropped and recreate those to point at a new mock */ NdbDictionary::Dictionary::List list; if (dict->listDependentObjects(list, *table) != 0) { error(dict, "Failed to list dependent objects for table '%s'", table->getName()); return false; } uint fk_index = 0; for (unsigned i = 0; i < list.count; i++) { const NdbDictionary::Dictionary::List::Element &element = list.elements[i]; if (element.type != NdbDictionary::Object::ForeignKey) continue; DBUG_PRINT("fk", ("name: %s, type: %d", element.name, element.type)); NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, element.name) != 0) { // Could not find the listed fk DBUG_ASSERT(false); continue; } // Parent of the found fk should be the table to be dropped DBUG_PRINT("info", ("fk.parent: %s", fk.getParentTable())); char parent_db_and_name[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db_and_name, fk.getParentTable()); if (strcmp(parent_db_and_name, ndb->getDatabaseName()) != 0 || strcmp(parent_name, table->getName()) != 0) { DBUG_PRINT("info", ("fk is not parent, skip")); continue; } DBUG_PRINT("info", ("fk.child: %s", fk.getChildTable())); char child_db_and_name[FN_LEN + 1]; const char *child_name = fk_split_name(child_db_and_name, fk.getChildTable()); // Open child table Ndb_db_guard db_guard(ndb); setDbName(ndb, child_db_and_name); Ndb_table_guard child_tab(dict, child_name); if (child_tab.get_table() == 0) { error(dict, "Failed to open child table '%s'", child_name); return false; } /* Format mock table name */ char mock_name[FN_REFLEN]; if (!format_name(mock_name, sizeof(mock_name), child_tab.get_table()->getObjectId(), fk_index, parent_name)) { error(NULL, "Failed to create mock parent table, too long mock name"); return false; } // Build both column name and column type list from parent(which will be // dropped) const char *col_names[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; const NdbDictionary::Column *col_types[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned num_columns = 0; for (unsigned j = 0; j < fk.getParentColumnCount(); j++) { const NdbDictionary::Column *col = table->getColumn(fk.getParentColumnNo(j)); DBUG_PRINT("col", ("[%u] %s", i, col->getName())); if (!col) { error(NULL, "Could not find column %d in parent table '%s'", fk.getParentColumnNo(j), table->getName()); continue; } col_names[num_columns] = col->getName(); col_types[num_columns] = col; num_columns++; } col_names[num_columns] = 0; col_types[num_columns] = 0; if (num_columns != fk.getParentColumnCount()) { error(NULL, "Could not find all columns referenced by fk in parent table " "'%s'", table->getName()); continue; } } db_guard.restore(); // restore db // Create new mock if (!create(dict, mock_name, child_name, col_names, col_types)) { error(dict, "Failed to create mock parent table '%s", mock_name); DBUG_ASSERT(false); return false; } // Recreate fks to point at new mock if (!copy_fk_to_new_parent(dict, fk, mock_name, col_names)) { return false; } fk_index++; } // Drop the requested table and all foreign keys refering to it // i.e the old fks const int drop_flags = NDBDICT::DropTableCascadeConstraints; if (dict->dropTableGlobal(*table, drop_flags) != 0) { error(dict, "Failed to drop the requested table"); return false; } return true; } public: Fk_util(THD *thd) : m_thd(thd) {} static bool split_mock_name(const char *name, unsigned *child_id_ptr = NULL, unsigned *child_index_ptr = NULL, const char **parent_name = NULL) { const struct { const char *str; size_t len; } prefix = {STRING_WITH_LEN("NDB$FKM_")}; if (strncmp(name, prefix.str, prefix.len) != 0) return false; char *end; const char *ptr = name + prefix.len + 1; // Parse child id long child_id = strtol(ptr, &end, 10); if (ptr == end || child_id < 0 || *end == 0 || *end != '_') return false; ptr = end + 1; // Parse child index long child_index = strtol(ptr, &end, 10); if (ptr == end || child_id < 0 || *end == 0 || *end != '_') return false; ptr = end + 1; // Assign and return OK if (child_id_ptr) *child_id_ptr = child_id; if (child_index_ptr) *child_index_ptr = child_index; if (parent_name) *parent_name = ptr; return true; } static bool is_mock_name(const char *name) { return split_mock_name(name); } static const char *format_name(char buf[], size_t buf_size, int child_id, uint fk_index, const char *parent_name) { DBUG_TRACE; DBUG_PRINT("enter", ("child_id: %d, fk_index: %u, parent_name: %s", child_id, fk_index, parent_name)); const size_t len = snprintf(buf, buf_size, "NDB$FKM_%d_%u_%s", child_id, fk_index, parent_name); if (len >= buf_size - 1) { DBUG_PRINT("info", ("Size of buffer too small")); return NULL; } DBUG_PRINT("exit", ("buf: '%s'", buf)); return buf; } // Adaptor function for calling create() with Mem_root_array bool create(NDBDICT *dict, const char *mock_name, const char *child_name, const Mem_root_array &key_part_list, const NDBCOL *col_types[]) { // Convert List into null terminated const char* array const char *col_names[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned i = 0; for (const Key_part_spec *key : key_part_list) { col_names[i++] = strdup(key->get_field_name()); } col_names[i] = 0; } const bool ret = create(dict, mock_name, child_name, col_names, col_types); // Free the strings in col_names array for (unsigned i = 0; col_names[i] != 0; i++) { const char *col_name = col_names[i]; free(const_cast(col_name)); } return ret; } bool create(NDBDICT *dict, const char *mock_name, const char *child_name, const char *col_names[], const NDBCOL *col_types[]) { NDBTAB mock_tab; DBUG_TRACE; DBUG_PRINT("enter", ("mock_name: %s", mock_name)); DBUG_ASSERT(is_mock_name(mock_name)); if (mock_tab.setName(mock_name)) { return false; } mock_tab.setLogging(false); unsigned i = 0; while (col_names[i]) { NDBCOL mock_col; const char *col_name = col_names[i]; DBUG_PRINT("info", ("name: %s", col_name)); if (mock_col.setName(col_name)) { DBUG_ASSERT(false); return false; } const NDBCOL *col = col_types[i]; if (!col) { // Internal error, the two lists should be same size DBUG_ASSERT(col); return false; } // Use column spec as requested(normally built from child table) mock_col.setType(col->getType()); mock_col.setPrecision(col->getPrecision()); mock_col.setScale(col->getScale()); mock_col.setLength(col->getLength()); mock_col.setCharset(col->getCharset()); // Make column part of primary key and thus not nullable mock_col.setPrimaryKey(true); mock_col.setNullable(false); if (mock_tab.addColumn(mock_col)) { return false; } i++; } // Create the table in NDB if (dict->createTable(mock_tab) != 0) { // Error is available to caller in dict* return false; } info("Created mock table '%s' referenced by '%s'", mock_name, child_name); return true; } bool build_mock_list(NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table, List &mock_list) { DBUG_TRACE; NdbDictionary::Dictionary::List list; if (dict->listDependentObjects(list, *table) != 0) { error(dict, "Failed to list dependent objects for table '%s'", table->getName()); return false; } for (unsigned i = 0; i < list.count; i++) { const NdbDictionary::Dictionary::List::Element &element = list.elements[i]; if (element.type != NdbDictionary::Object::ForeignKey) continue; NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, element.name) != 0) { // Could not find the listed fk DBUG_ASSERT(false); continue; } char parent_db_and_name[FN_LEN + 1]; const char *name = fk_split_name(parent_db_and_name, fk.getParentTable()); if (!Fk_util::is_mock_name(name)) continue; mock_list.push_back(thd_strdup(m_thd, fk.getParentTable())); } return true; } void drop_mock_list(Ndb *ndb, NdbDictionary::Dictionary *dict, List &drop_list) { const char *full_name; List_iterator_fast it(drop_list); while ((full_name = it++)) { DBUG_PRINT("info", ("drop table: '%s'", full_name)); char db_name[FN_LEN + 1]; const char *table_name = fk_split_name(db_name, full_name); Ndb_db_guard db_guard(ndb); setDbName(ndb, db_name); Ndb_table_guard mocktab_g(dict, table_name); if (!mocktab_g.get_table()) { // Could not open the mock table DBUG_PRINT("error", ("Could not open the listed mock table, ignore it")); DBUG_ASSERT(false); continue; } if (dict->dropTableGlobal(*mocktab_g.get_table()) != 0) { DBUG_PRINT("error", ("Failed to drop the mock table '%s'", mocktab_g.get_table()->getName())); DBUG_ASSERT(false); continue; } info("Dropped mock table '%s' - referencing table dropped", table_name); } } bool drop(Ndb *ndb, NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table) { DBUG_TRACE; // Start schema transaction to make this operation atomic if (dict->beginSchemaTrans() != 0) { error(dict, "Failed to start schema transaction"); return false; } bool result = true; if (!create_mock_tables_and_drop(ndb, dict, table)) { // Operation failed, set flag to abort when ending trans result = false; } // End schema transaction const Uint32 end_trans_flag = result ? 0 : NdbDictionary::Dictionary::SchemaTransAbort; if (dict->endSchemaTrans(end_trans_flag) != 0) { error(dict, "Failed to end schema transaction"); result = false; } return result; } bool count_fks(NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table, uint &count) const { DBUG_TRACE; NdbDictionary::Dictionary::List list; if (dict->listDependentObjects(list, *table) != 0) { error(dict, "Failed to list dependent objects for table '%s'", table->getName()); return false; } for (unsigned i = 0; i < list.count; i++) { if (list.elements[i].type == NdbDictionary::Object::ForeignKey) count++; } DBUG_PRINT("exit", ("count: %u", count)); return true; } bool drop_fk(Ndb *ndb, NdbDictionary::Dictionary *dict, const char *fk_name) { DBUG_TRACE; NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, fk_name) != 0) { error(dict, "Could not find fk '%s'", fk_name); DBUG_ASSERT(false); return false; } char parent_db_and_name[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db_and_name, fk.getParentTable()); if (Fk_util::is_mock_name(parent_name)) { // Fk is referencing a mock table, drop the table // and the constraint at the same time Ndb_db_guard db_guard(ndb); setDbName(ndb, parent_db_and_name); Ndb_table_guard mocktab_g(dict, parent_name); if (mocktab_g.get_table()) { const int drop_flags = NDBDICT::DropTableCascadeConstraints; if (dict->dropTableGlobal(*mocktab_g.get_table(), drop_flags) != 0) { error(dict, "Failed to drop fk mock table '%s'", parent_name); DBUG_ASSERT(false); return false; } // table and fk dropped return true; } else { warn("Could not open the fk mock table '%s', ignoring it...", parent_name); DBUG_ASSERT(false); // fallthrough and try to drop only the fk, } } if (dict->dropForeignKey(fk) != 0) { error(dict, "Failed to drop fk '%s'", fk_name); return false; } return true; } void resolve_mock_tables(NdbDictionary::Dictionary *dict, const char *new_parent_db, const char *new_parent_name) const { DBUG_TRACE; DBUG_PRINT("enter", ("new_parent_db: %s, new_parent_name: %s", new_parent_db, new_parent_name)); /* List all tables in NDB and look for mock tables which could potentially be resolved to the new table */ NdbDictionary::Dictionary::List table_list; if (dict->listObjects(table_list, NdbDictionary::Object::UserTable, true) != 0) { DBUG_ASSERT(false); return; } for (unsigned i = 0; i < table_list.count; i++) { const NdbDictionary::Dictionary::List::Element &el = table_list.elements[i]; DBUG_ASSERT(el.type == NdbDictionary::Object::UserTable); // Check if table is in same database as the potential new parent if (strcmp(new_parent_db, el.database) != 0) { DBUG_PRINT("info", ("Skip, '%s.%s' is in different database", el.database, el.name)); continue; } const char *parent_name; if (!Fk_util::split_mock_name(el.name, NULL, NULL, &parent_name)) continue; // Check if this mock table should reference the new table if (strcmp(parent_name, new_parent_name) != 0) { DBUG_PRINT("info", ("Skip, parent of this mock table is not the new table")); continue; } resolve_mock(dict, new_parent_name, el.name); } return; } bool truncate_allowed(NdbDictionary::Dictionary *dict, const char *db, const NdbDictionary::Table *table, bool &allow) const { DBUG_TRACE; NdbDictionary::Dictionary::List list; if (dict->listDependentObjects(list, *table) != 0) { error(dict, "Failed to list dependent objects for table '%s'", table->getName()); return false; } allow = true; for (unsigned i = 0; i < list.count; i++) { const NdbDictionary::Dictionary::List::Element &element = list.elements[i]; if (element.type != NdbDictionary::Object::ForeignKey) continue; DBUG_PRINT("info", ("fk: %s", element.name)); NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, element.name) != 0) { error(dict, "Could not find the listed fk '%s'", element.name); DBUG_ASSERT(false); continue; } // Refuse if table is parent of fk char parent_db_and_name[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db_and_name, fk.getParentTable()); if (strcmp(db, parent_db_and_name) != 0 || strcmp(parent_name, table->getName()) != 0) { // Not parent of the fk, skip continue; } allow = false; break; } DBUG_PRINT("exit", ("allow: %u", allow)); return true; } /** Generate FK info string from the NDBFK object. This can be called either by ha_ndbcluster::get_error_message or ha_ndbcluster:get_foreign_key_create_info. @param thd Current thread. @param ndb Pointer to the Ndb Object @param fk The foreign key object whose info has to be printed. @param tab_id If this is > 0, the FK is printed only if the table with this table id, is the child table of the passed fk. This is > 0 only if the caller is ha_ndbcluster:get_foreign_key_create_info(). @param print_mock_table_names If true, mock tables names are printed rather than the real parent names. @param fk_string String in which the fk info is to be printed. @retval true on success false on failure. */ bool generate_fk_constraint_string(Ndb *ndb, const NdbDictionary::ForeignKey &fk, const int tab_id, const bool print_mock_table_names, String &fk_string) { DBUG_TRACE; const NDBTAB *parenttab = 0; const NDBTAB *childtab = 0; NDBDICT *dict = ndb->getDictionary(); Ndb_db_guard db_guard(ndb); /* The function generates fk constraint strings for * showing fk info in error and in show create table. * child_tab_id is non zero only for generating show create info */ bool generating_for_show_create = (tab_id != 0); /* Fetch parent db and name and load it */ Ndb_table_guard parent_table_guard(dict); char parent_db_and_name[FN_LEN + 1]; { const char *name = fk_split_name(parent_db_and_name, fk.getParentTable()); setDbName(ndb, parent_db_and_name); parent_table_guard.init(name); parenttab = parent_table_guard.get_table(); if (parenttab == 0) { NdbError err = dict->getNdbError(); warn("Unable to load parent table : error %d, %s", err.code, err.message); return false; } } /* Fetch child db and name and load it */ Ndb_table_guard child_table_guard(dict); char child_db_and_name[FN_LEN + 1]; { const char *name = fk_split_name(child_db_and_name, fk.getChildTable()); setDbName(ndb, child_db_and_name); child_table_guard.init(name); childtab = child_table_guard.get_table(); if (childtab == 0) { NdbError err = dict->getNdbError(); err = dict->getNdbError(); warn("Unable to load child table : error %d, %s", err.code, err.message); return false; } if (!generating_for_show_create) { /* Print child table name if printing error */ fk_string.append("`"); fk_string.append(child_db_and_name); fk_string.append("`.`"); fk_string.append(name); fk_string.append("`, "); } } if (generating_for_show_create) { if (childtab->getTableId() != tab_id) { /** * This was on parent table (fk are shown on child table in SQL) * Skip printing this fk */ assert(parenttab->getTableId() == tab_id); return true; } fk_string.append(","); fk_string.append("\n "); } fk_string.append("CONSTRAINT `"); { char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, fk.getName()); fk_string.append(name); } fk_string.append("` FOREIGN KEY ("); { const char *separator = ""; for (unsigned j = 0; j < fk.getChildColumnCount(); j++) { const int child_col_index = fk.getChildColumnNo(j); fk_string.append(separator); fk_string.append("`"); fk_string.append(childtab->getColumn(child_col_index)->getName()); fk_string.append("`"); separator = ","; } } fk_string.append(") REFERENCES `"); if (strcmp(parent_db_and_name, child_db_and_name) != 0) { /* Print db name only if the parent and child are from different dbs */ fk_string.append(parent_db_and_name); fk_string.append("`.`"); } const char *real_parent_name; if (!print_mock_table_names && Fk_util::split_mock_name(parenttab->getName(), NULL, NULL, &real_parent_name)) { /* print the real table name */ DBUG_PRINT("info", ("real_parent_name: %s", real_parent_name)); fk_string.append(real_parent_name); } else { fk_string.append(parenttab->getName()); } fk_string.append("` ("); { const char *separator = ""; for (unsigned j = 0; j < fk.getParentColumnCount(); j++) { const int parent_col_index = fk.getParentColumnNo(j); fk_string.append(separator); fk_string.append("`"); fk_string.append(parenttab->getColumn(parent_col_index)->getName()); fk_string.append("`"); separator = ","; } } fk_string.append(")"); /* print action strings */ switch (fk.getOnDeleteAction()) { case NdbDictionary::ForeignKey::NoAction: fk_string.append(" ON DELETE NO ACTION"); break; case NdbDictionary::ForeignKey::Restrict: fk_string.append(" ON DELETE RESTRICT"); break; case NdbDictionary::ForeignKey::Cascade: fk_string.append(" ON DELETE CASCADE"); break; case NdbDictionary::ForeignKey::SetNull: fk_string.append(" ON DELETE SET NULL"); break; case NdbDictionary::ForeignKey::SetDefault: fk_string.append(" ON DELETE SET DEFAULT"); break; } switch (fk.getOnUpdateAction()) { case NdbDictionary::ForeignKey::NoAction: fk_string.append(" ON UPDATE NO ACTION"); break; case NdbDictionary::ForeignKey::Restrict: fk_string.append(" ON UPDATE RESTRICT"); break; case NdbDictionary::ForeignKey::Cascade: fk_string.append(" ON UPDATE CASCADE"); break; case NdbDictionary::ForeignKey::SetNull: fk_string.append(" ON UPDATE SET NULL"); break; case NdbDictionary::ForeignKey::SetDefault: fk_string.append(" ON UPDATE SET DEFAULT"); break; } return true; } }; void Fk_util::info(const char *fmt, ...) const { va_list args; char msg[MYSQL_ERRMSG_SIZE]; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); // Push as warning if user has turned on ndb_show_foreign_key_mock_tables if (ndb_show_foreign_key_mock_tables(m_thd)) { push_warning(m_thd, Sql_condition::SL_WARNING, ER_YES, msg); } // Print info to log ndb_log_info("%s", msg); } void Fk_util::warn(const char *fmt, ...) const { va_list args; char msg[MYSQL_ERRMSG_SIZE]; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); push_warning(m_thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, msg); // Print warning to log ndb_log_warning("%s", msg); } void Fk_util::error(const NdbDictionary::Dictionary *dict, const char *fmt, ...) const { va_list args; char msg[MYSQL_ERRMSG_SIZE]; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); push_warning(m_thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, msg); char ndb_msg[MYSQL_ERRMSG_SIZE] = {0}; if (dict) { // Extract message from Ndb const NdbError &error = dict->getNdbError(); snprintf(ndb_msg, sizeof(ndb_msg), "%d '%s'", error.code, error.message); push_warning_printf(m_thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Ndb error: %s", ndb_msg); } // Print error to log ndb_log_error("%s, Ndb error: %s", msg, ndb_msg); } bool ndb_fk_util_build_list(THD *thd, NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table, List &mock_list) { Fk_util fk_util(thd); return fk_util.build_mock_list(dict, table, mock_list); } void ndb_fk_util_drop_list(THD *thd, Ndb *ndb, NdbDictionary::Dictionary *dict, List &drop_list) { Fk_util fk_util(thd); fk_util.drop_mock_list(ndb, dict, drop_list); } bool ndb_fk_util_drop_table(THD *thd, Ndb *ndb, NdbDictionary::Dictionary *dict, const NdbDictionary::Table *table) { Fk_util fk_util(thd); return fk_util.drop(ndb, dict, table); } bool ndb_fk_util_is_mock_name(const char *table_name) { return Fk_util::is_mock_name(table_name); } void ndb_fk_util_resolve_mock_tables(THD *thd, NdbDictionary::Dictionary *dict, const char *new_parent_db, const char *new_parent_name) { Fk_util fk_util(thd); fk_util.resolve_mock_tables(dict, new_parent_db, new_parent_name); } bool ndb_fk_util_truncate_allowed(THD *thd, NdbDictionary::Dictionary *dict, const char *db, const NdbDictionary::Table *table, bool &allowed) { Fk_util fk_util(thd); if (!fk_util.truncate_allowed(dict, db, table, allowed)) return false; return true; } bool ndb_fk_util_generate_constraint_string(THD *thd, Ndb *ndb, const NdbDictionary::ForeignKey &fk, const int tab_id, const bool print_mock_table_names, String &fk_string) { Fk_util fk_util(thd); return fk_util.generate_fk_constraint_string( ndb, fk, tab_id, print_mock_table_names, fk_string); } /** @brief Flush the parent table after a successful addition/deletion to the Foreign Key. This is done to force reload the Parent table's metadata. @param thd thread handle @param parent_db Parent table's database name @param parent_name Parent table's name @return Void */ static void flush_parent_table_for_fk(THD *thd, const char *parent_db, const char *parent_name) { DBUG_TRACE; if (Fk_util::is_mock_name(parent_name)) { /* Parent table is mock - no need to flush */ DBUG_PRINT("debug", ("Parent table is a mock - skipped flushing")); return; } DBUG_PRINT("debug", ("Flushing table : `%s`.`%s` ", parent_db, parent_name)); ndb_tdc_close_cached_table(thd, parent_db, parent_name); } /* @brief Guard class for references to indexes in the global NdbApi dictionary cache which need to be released(and sometimes invalidated) when guard goes out of scope */ template class Ndb_index_release_guard { NdbDictionary::Dictionary *const m_dict; std::vector m_indexes; public: Ndb_index_release_guard(NdbDictionary::Dictionary *dict) : m_dict(dict) {} Ndb_index_release_guard(const Ndb_index_release_guard &) = delete; ~Ndb_index_release_guard() { for (const NdbDictionary::Index *index : m_indexes) { DBUG_PRINT("info", ("Releasing index: '%s'", index->getName())); m_dict->removeIndexGlobal(*index, invalidate_index); } } // Register index to be released void add_index_to_release(const NdbDictionary::Index *index) { DBUG_PRINT("info", ("Adding index '%s' to release", index->getName())); m_indexes.push_back(index); } }; int ha_ndbcluster::create_fks(THD *thd, Ndb *ndb) { DBUG_TRACE; NdbDictionary::Dictionary *dict = ndb->getDictionary(); // Releaser for child(i.e the table being created/altered) which // need to be invalidated when released Ndb_index_release_guard child_index_releaser(dict); // Releaser for parent(i.e the _other_ table) which is not modified // and thus need not be invalidated Ndb_index_release_guard parent_index_releaser(dict); // return real mysql error to avoid total randomness.. const int err_default = HA_ERR_CANNOT_ADD_FOREIGN; assert(thd->lex != 0); for (const Key_spec *key : thd->lex->alter_info->key_list) { if (key->type != KEYTYPE_FOREIGN) continue; const Foreign_key_spec *fk = down_cast(key); // Open the table to create foreign keys for Ndb_table_guard child_tab(dict, m_tabname); if (child_tab.get_table() == 0) { ERR_RETURN(dict->getNdbError()); } /** * NOTE 2: we mark the table as invalid * so that it gets removed from GlobalDictCache if * the schema transaction later fails... * * TODO: This code currently fetches table definition from data-nodes * once per FK...which could be improved to once if a FK */ child_tab.invalidate(); /** * Get table columns columns... */ const NDBCOL *childcols[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned pos = 0; const NDBTAB *tab = child_tab.get_table(); for (const Key_part_spec *col : fk->columns) { const NDBCOL *ndbcol = tab->getColumn(col->get_field_name()); if (ndbcol == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Child table %s has no column %s in NDB", child_tab.get_table()->getName(), col->get_field_name()); return err_default; } childcols[pos++] = ndbcol; } childcols[pos] = 0; // NULL terminate } bool child_primary_key = false; const NDBINDEX *child_index = find_matching_index( dict, child_tab.get_table(), childcols, child_primary_key); if (child_index) { child_index_releaser.add_index_to_release(child_index); } if (!child_primary_key && child_index == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Child table %s foreign key columns match no index in NDB", child_tab.get_table()->getName()); return err_default; } Ndb_db_guard db_guard(ndb); // save db char parent_db[FN_REFLEN]; char parent_name[FN_REFLEN]; /* * Looking at Table_ident, testing for db.str first is safer * for valgrind. Do same with table.str too. */ if (fk->ref_db.str != 0 && fk->ref_db.length != 0) { snprintf(parent_db, sizeof(parent_db), "%*s", (int)fk->ref_db.length, fk->ref_db.str); } else { /* parent db missing - so the db is same as child's */ snprintf(parent_db, sizeof(parent_db), "%*s", (int)sizeof(m_dbname), m_dbname); } if (fk->ref_table.str != 0 && fk->ref_table.length != 0) { snprintf(parent_name, sizeof(parent_name), "%*s", (int)fk->ref_table.length, fk->ref_table.str); } else { parent_name[0] = 0; } if (lower_case_table_names) { ndb_fk_casedn(parent_db); ndb_fk_casedn(parent_name); } setDbName(ndb, parent_db); Ndb_table_guard parent_tab(dict, parent_name); if (parent_tab.get_table() == 0) { if (!thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { const NdbError &error = dict->getNdbError(); push_warning_printf(thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Parent table %s not found in NDB: %d: %s", parent_name, error.code, error.message); return err_default; } DBUG_PRINT("info", ("No parent and foreign_key_checks=0")); Fk_util fk_util(thd); /* Count the number of existing fks on table */ uint existing = 0; if (!fk_util.count_fks(dict, child_tab.get_table(), existing)) { return err_default; } /* Format mock table name */ char mock_name[FN_REFLEN]; if (!fk_util.format_name(mock_name, sizeof(mock_name), child_tab.get_table()->getObjectId(), existing, parent_name)) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Failed to create mock parent table, too long mock name"); return err_default; } if (!fk_util.create(dict, mock_name, m_tabname, fk->ref_columns, childcols)) { const NdbError &error = dict->getNdbError(); push_warning_printf(thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Failed to create mock parent table in NDB: %d: %s", error.code, error.message); return err_default; } parent_tab.init(mock_name); parent_tab.invalidate(); // invalidate mock table when releasing if (parent_tab.get_table() == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "INTERNAL ERROR: Could not find created mock table '%s'", mock_name); // Internal error, should be able to load the just created mock table DBUG_ASSERT(parent_tab.get_table()); return err_default; } } const NDBCOL *parentcols[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned pos = 0; const NDBTAB *tab = parent_tab.get_table(); for (const Key_part_spec *col : fk->ref_columns) { const NDBCOL *ndbcol = tab->getColumn(col->get_field_name()); if (ndbcol == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Parent table %s has no column %s in NDB", parent_tab.get_table()->getName(), col->get_field_name()); return err_default; } parentcols[pos++] = ndbcol; } parentcols[pos] = 0; // NULL terminate } bool parent_primary_key = false; const NDBINDEX *parent_index = find_matching_index( dict, parent_tab.get_table(), parentcols, parent_primary_key); if (parent_index) { parent_index_releaser.add_index_to_release(parent_index); } db_guard.restore(); // restore db if (!parent_primary_key && parent_index == 0) { my_error(ER_FK_NO_INDEX_PARENT, MYF(0), fk->name.str ? fk->name.str : "", parent_tab.get_table()->getName()); return err_default; } { /** * Check that columns match...this happens to be same * condition as the one for SPJ... */ for (unsigned i = 0; parentcols[i] != 0; i++) { if (parentcols[i]->isBindable(*childcols[i]) == -1) { // Should never happen thanks to SQL-layer doing compatibility check. DBUG_ASSERT(0); push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Parent column %s.%s is incompatible with child column %s.%s in " "NDB", parent_tab.get_table()->getName(), parentcols[i]->getName(), child_tab.get_table()->getName(), childcols[i]->getName()); return err_default; } } } NdbDictionary::ForeignKey ndbfk; char fk_name[FN_REFLEN]; /* In 8.0 we rely on SQL-layer to always provide foreign key name, either by using the name provided by the user, or by generating an unique name. In either case, the name has already been prepared at this point. */ DBUG_ASSERT(fk->name.str && fk->name.length); lex2str(fk->name, fk_name); if (lower_case_table_names) ndb_fk_casedn(fk_name); ndbfk.setName(fk_name); ndbfk.setParent(*parent_tab.get_table(), parent_index, parentcols); ndbfk.setChild(*child_tab.get_table(), child_index, childcols); switch ((fk_option)fk->delete_opt) { case FK_OPTION_UNDEF: case FK_OPTION_NO_ACTION: ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::NoAction); break; case FK_OPTION_RESTRICT: ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::Restrict); break; case FK_OPTION_CASCADE: ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::Cascade); break; case FK_OPTION_SET_NULL: ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::SetNull); break; case FK_OPTION_DEFAULT: ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::SetDefault); break; default: assert(false); ndbfk.setOnDeleteAction(NdbDictionary::ForeignKey::NoAction); } switch ((fk_option)fk->update_opt) { case FK_OPTION_UNDEF: case FK_OPTION_NO_ACTION: ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::NoAction); break; case FK_OPTION_RESTRICT: ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::Restrict); break; case FK_OPTION_CASCADE: ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::Cascade); break; case FK_OPTION_SET_NULL: ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::SetNull); break; case FK_OPTION_DEFAULT: ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::SetDefault); break; default: assert(false); ndbfk.setOnUpdateAction(NdbDictionary::ForeignKey::NoAction); } int flags = 0; if (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { flags |= NdbDictionary::Dictionary::CreateFK_NoVerify; } NdbDictionary::ObjectId objid; const int err = dict->createForeignKey(ndbfk, &objid, flags); if (err) { const NdbError err = dict->getNdbError(); if (err.code == 721) { /* An FK constraint with same name exists */ my_error(ER_FK_DUP_NAME, MYF(0), ndbfk.getName()); return err_default; } else { /* Return the error returned by dict */ ERR_RETURN(err); } } /* Flush the parent table out if parent is different from child */ if (parent_tab.get_table()->getObjectId() != child_tab.get_table()->getObjectId()) { /* flush parent table */ flush_parent_table_for_fk(thd, parent_db, parent_name); } } ndb_fk_util_resolve_mock_tables(thd, ndb->getDictionary(), m_dbname, m_tabname); return 0; } uint ha_ndbcluster::referenced_by_foreign_key() { DBUG_TRACE; Ndb_fk_data *data = m_fk_data; if (data == 0) { DBUG_ASSERT(false); return 0; } DBUG_PRINT("info", ("count FKs total %u child %u parent %u", data->list.elements, data->cnt_child, data->cnt_parent)); return data->cnt_parent != 0; } struct Ndb_mem_root_guard { Ndb_mem_root_guard(MEM_ROOT *new_root) { root_ptr = THR_MALLOC; DBUG_ASSERT(root_ptr != 0); old_root = *root_ptr; *root_ptr = new_root; } ~Ndb_mem_root_guard() { *root_ptr = old_root; } private: MEM_ROOT **root_ptr; MEM_ROOT *old_root; }; int ha_ndbcluster::get_fk_data(THD *thd, Ndb *ndb) { DBUG_TRACE; MEM_ROOT *mem_root = &m_fk_mem_root; Ndb_mem_root_guard mem_root_guard(mem_root); free_root(mem_root, 0); m_fk_data = 0; init_alloc_root(PSI_INSTRUMENT_ME, mem_root, fk_root_block_size, 0); NdbError err_OOM; err_OOM.code = 4000; // should we check OOM errors at all? NdbError err_API; err_API.code = 4011; // API internal should not happen Ndb_fk_data *data = new (mem_root) Ndb_fk_data; if (data == 0) ERR_RETURN(err_OOM); data->cnt_child = 0; data->cnt_parent = 0; DBUG_PRINT("info", ("%s.%s: list dependent objects", m_dbname, m_tabname)); int res; NDBDICT *dict = ndb->getDictionary(); NDBDICT::List obj_list; res = dict->listDependentObjects(obj_list, *m_table); if (res != 0) ERR_RETURN(dict->getNdbError()); DBUG_PRINT("info", ("found %u dependent objects", obj_list.count)); for (unsigned i = 0; i < obj_list.count; i++) { const NDBDICT::List::Element &e = obj_list.elements[i]; if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) { DBUG_PRINT("info", ("skip non-FK %s type %d", e.name, e.type)); continue; } DBUG_PRINT("info", ("found FK %s", e.name)); NdbDictionary::ForeignKey fk; res = dict->getForeignKey(fk, e.name); if (res != 0) ERR_RETURN(dict->getNdbError()); Ndb_fk_item *item = new (mem_root) Ndb_fk_item; if (item == 0) ERR_RETURN(err_OOM); FOREIGN_KEY_INFO &f_key_info = item->f_key_info; { char fk_full_name[FN_LEN + 1]; const char *name = fk_split_name(fk_full_name, fk.getName()); f_key_info.foreign_id = thd_make_lex_string(thd, 0, name, (uint)strlen(name), 1); } { char child_db_and_name[FN_LEN + 1]; const char *child_name = fk_split_name(child_db_and_name, fk.getChildTable()); /* Dependent (child) database name */ f_key_info.foreign_db = thd_make_lex_string( thd, 0, child_db_and_name, (uint)strlen(child_db_and_name), 1); /* Dependent (child) table name */ f_key_info.foreign_table = thd_make_lex_string(thd, 0, child_name, (uint)strlen(child_name), 1); Ndb_db_guard db_guard(ndb); setDbName(ndb, child_db_and_name); Ndb_table_guard child_tab(dict, child_name); if (child_tab.get_table() == 0) { DBUG_ASSERT(false); ERR_RETURN(dict->getNdbError()); } for (unsigned i = 0; i < fk.getChildColumnCount(); i++) { const NdbDictionary::Column *col = child_tab.get_table()->getColumn(fk.getChildColumnNo(i)); if (col == 0) ERR_RETURN(err_API); LEX_STRING *name = thd_make_lex_string(thd, 0, col->getName(), (uint)strlen(col->getName()), 1); f_key_info.foreign_fields.push_back(name); } } { char parent_db_and_name[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db_and_name, fk.getParentTable()); /* Referenced (parent) database name */ f_key_info.referenced_db = thd_make_lex_string( thd, 0, parent_db_and_name, (uint)strlen(parent_db_and_name), 1); /* Referenced (parent) table name */ f_key_info.referenced_table = thd_make_lex_string( thd, 0, parent_name, (uint)strlen(parent_name), 1); Ndb_db_guard db_guard(ndb); setDbName(ndb, parent_db_and_name); Ndb_table_guard parent_tab(dict, parent_name); if (parent_tab.get_table() == 0) { DBUG_ASSERT(false); ERR_RETURN(dict->getNdbError()); } for (unsigned i = 0; i < fk.getParentColumnCount(); i++) { const NdbDictionary::Column *col = parent_tab.get_table()->getColumn(fk.getParentColumnNo(i)); if (col == 0) ERR_RETURN(err_API); LEX_STRING *name = thd_make_lex_string(thd, 0, col->getName(), (uint)strlen(col->getName()), 1); f_key_info.referenced_fields.push_back(name); } } { const char *update_method = ""; switch (item->update_action = fk.getOnUpdateAction()) { case NdbDictionary::ForeignKey::NoAction: update_method = "NO ACTION"; break; case NdbDictionary::ForeignKey::Restrict: update_method = "RESTRICT"; break; case NdbDictionary::ForeignKey::Cascade: update_method = "CASCADE"; break; case NdbDictionary::ForeignKey::SetNull: update_method = "SET NULL"; break; case NdbDictionary::ForeignKey::SetDefault: update_method = "SET DEFAULT"; break; } f_key_info.update_method = thd_make_lex_string( thd, 0, update_method, (uint)strlen(update_method), 1); } { const char *delete_method = ""; switch (item->delete_action = fk.getOnDeleteAction()) { case NdbDictionary::ForeignKey::NoAction: delete_method = "NO ACTION"; break; case NdbDictionary::ForeignKey::Restrict: delete_method = "RESTRICT"; break; case NdbDictionary::ForeignKey::Cascade: delete_method = "CASCADE"; break; case NdbDictionary::ForeignKey::SetNull: delete_method = "SET NULL"; break; case NdbDictionary::ForeignKey::SetDefault: delete_method = "SET DEFAULT"; break; } f_key_info.delete_method = thd_make_lex_string( thd, 0, delete_method, (uint)strlen(delete_method), 1); } if (fk.getParentIndex() != 0) { // sys/def/10/xb1$unique char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, fk.getParentIndex(), true); f_key_info.referenced_key_name = thd_make_lex_string(thd, 0, name, (uint)strlen(name), 1); } else { const char *name = "PRIMARY"; f_key_info.referenced_key_name = thd_make_lex_string(thd, 0, name, (uint)strlen(name), 1); } item->is_child = strcmp(m_dbname, f_key_info.foreign_db->str) == 0 && strcmp(m_tabname, f_key_info.foreign_table->str) == 0; item->is_parent = strcmp(m_dbname, f_key_info.referenced_db->str) == 0 && strcmp(m_tabname, f_key_info.referenced_table->str) == 0; data->cnt_child += item->is_child; data->cnt_parent += item->is_parent; res = data->list.push_back(item); if (res != 0) ERR_RETURN(err_OOM); } DBUG_PRINT("info", ("count FKs total %u child %u parent %u", data->list.elements, data->cnt_child, data->cnt_parent)); m_fk_data = data; return 0; } void ha_ndbcluster::release_fk_data() { DBUG_TRACE; Ndb_fk_data *data = m_fk_data; if (data != 0) { DBUG_PRINT("info", ("count FKs total %u child %u parent %u", data->list.elements, data->cnt_child, data->cnt_parent)); } MEM_ROOT *mem_root = &m_fk_mem_root; free_root(mem_root, 0); m_fk_data = 0; } int ha_ndbcluster::get_child_or_parent_fk_list( List *f_key_list, bool is_child, bool is_parent) { DBUG_TRACE; DBUG_PRINT("info", ("table %s.%s", m_dbname, m_tabname)); Ndb_fk_data *data = m_fk_data; if (data == 0) { DBUG_ASSERT(false); return 0; } DBUG_PRINT("info", ("count FKs total %u child %u parent %u", data->list.elements, data->cnt_child, data->cnt_parent)); Ndb_fk_item *item = 0; List_iterator iter(data->list); while ((item = iter++)) { FOREIGN_KEY_INFO &f_key_info = item->f_key_info; DBUG_PRINT( "info", ("FK %s ref %s -> %s is_child %d is_parent %d", f_key_info.foreign_id->str, f_key_info.foreign_table->str, f_key_info.referenced_table->str, item->is_child, item->is_parent)); if (is_child && !item->is_child) continue; if (is_parent && !item->is_parent) continue; DBUG_PRINT("info", ("add %s to list", f_key_info.foreign_id->str)); f_key_list->push_back(&f_key_info); } return 0; } int ha_ndbcluster::get_foreign_key_list(THD *, List *f_key_list) { DBUG_TRACE; int res = get_child_or_parent_fk_list(f_key_list, true, false); DBUG_PRINT("info", ("count FKs child %u", f_key_list->elements)); return res; } int ha_ndbcluster::get_parent_foreign_key_list( THD *, List *f_key_list) { DBUG_TRACE; int res = get_child_or_parent_fk_list(f_key_list, false, true); DBUG_PRINT("info", ("count FKs parent %u", f_key_list->elements)); return res; } namespace { struct cmp_fk_name { bool operator()(const NDBDICT::List::Element &e0, const NDBDICT::List::Element &e1) const { int res; if ((res = strcmp(e0.name, e1.name)) != 0) return res < 0; if ((res = strcmp(e0.database, e1.database)) != 0) return res < 0; if ((res = strcmp(e0.schema, e1.schema)) != 0) return res < 0; return e0.id < e1.id; } }; } // namespace char *ha_ndbcluster::get_foreign_key_create_info() { DBUG_TRACE; /** * List foreigns for this table */ if (m_table == 0) { return 0; } if (table == 0) { return 0; } THD *thd = table->in_use; if (thd == 0) { return 0; } Ndb *ndb = get_ndb(thd); if (ndb == 0) { return 0; } NDBDICT *dict = ndb->getDictionary(); NDBDICT::List obj_list; dict->listDependentObjects(obj_list, *m_table); /** * listDependentObjects will return FK's in order that they * are stored in hash-table in Dbdict (i.e random) * * sort them to make MTR and similar happy */ std::sort(obj_list.elements, obj_list.elements + obj_list.count, cmp_fk_name()); String fk_string; for (unsigned i = 0; i < obj_list.count; i++) { if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) continue; NdbDictionary::ForeignKey fk; int res = dict->getForeignKey(fk, obj_list.elements[i].name); if (res != 0) { // Push warning?? return 0; } if (!ndb_fk_util_generate_constraint_string( thd, ndb, fk, m_table->getTableId(), ndb_show_foreign_key_mock_tables(thd), fk_string)) { return 0; // How to report error ?? } } return strdup(fk_string.c_ptr()); } void ha_ndbcluster::free_foreign_key_create_info(char *str) { if (str != 0) { free(str); } } int ha_ndbcluster::copy_fk_for_offline_alter(THD *thd, Ndb *ndb, const char *tabname) { DBUG_TRACE; DBUG_PRINT("enter", ("tabname: '%s'", tabname)); if (thd->lex == 0) { assert(false); return 0; } Ndb_db_guard db_guard(ndb); const char *src_db = thd->lex->select_lex->table_list.first->db; const char *src_tab = thd->lex->select_lex->table_list.first->table_name; if (src_db == 0 || src_tab == 0) { assert(false); return 0; } NDBDICT *dict = ndb->getDictionary(); setDbName(ndb, src_db); Ndb_table_guard srctab(dict, src_tab); if (srctab.get_table() == 0) { /** * when doign alter table engine=ndb this can happen */ return 0; } db_guard.restore(); Ndb_table_guard dsttab(dict, tabname); if (dsttab.get_table() == 0) { ERR_RETURN(dict->getNdbError()); } setDbName(ndb, src_db); NDBDICT::List obj_list; if (dict->listDependentObjects(obj_list, *srctab.get_table()) != 0) { ERR_RETURN(dict->getNdbError()); } // check if fk to drop exists { for (const Alter_drop *drop_item : thd->lex->alter_info->drop_list) { if (drop_item->type != Alter_drop::FOREIGN_KEY) continue; bool found = false; for (unsigned i = 0; i < obj_list.count; i++) { // Skip if the element is not a foreign key if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) continue; // Check if this is the fk being dropped char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, obj_list.elements[i].name); if (ndb_fk_casecmp(drop_item->name, name) != 0) continue; NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, obj_list.elements[i].name) != 0) { // should never happen DBUG_ASSERT(false); push_warning_printf(thd, Sql_condition::SL_WARNING, ER_CANT_DROP_FIELD_OR_KEY, "INTERNAL ERROR: Could not find foreign key '%s'", obj_list.elements[i].name); ERR_RETURN(dict->getNdbError()); } // The FK we are looking for is on src_tab. char child_db_and_name[FN_LEN + 1]; const char *child_name = fk_split_name(child_db_and_name, fk.getChildTable()); if (strcmp(child_db_and_name, src_db) == 0 && strcmp(child_name, src_tab) == 0) { found = true; break; } } if (!found) { // FK not found my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop_item->name); return ER_CANT_DROP_FIELD_OR_KEY; } } } for (unsigned i = 0; i < obj_list.count; i++) { if (obj_list.elements[i].type == NdbDictionary::Object::ForeignKey) { NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, obj_list.elements[i].name) != 0) { // should never happen DBUG_ASSERT(false); push_warning_printf(thd, Sql_condition::SL_WARNING, ER_ALTER_INFO, "INTERNAL ERROR: Could not find foreign key '%s'", obj_list.elements[i].name); ERR_RETURN(dict->getNdbError()); } { /** * Check if it should be copied */ char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, obj_list.elements[i].name); bool found = false; for (const Alter_drop *drop_item : thd->lex->alter_info->drop_list) { if (drop_item->type != Alter_drop::FOREIGN_KEY) continue; if (ndb_fk_casecmp(drop_item->name, name) != 0) continue; char child_db_and_name[FN_LEN + 1]; const char *child_name = fk_split_name(child_db_and_name, fk.getChildTable()); if (strcmp(child_db_and_name, src_db) == 0 && strcmp(child_name, src_tab) == 0) { found = true; break; } } if (found) { /** * Item is on drop list... * don't copy it */ continue; } } { char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, fk.getParentTable()); setDbName(ndb, db_and_name); Ndb_table_guard org_parent(dict, name); if (org_parent.get_table() == 0) { ERR_RETURN(dict->getNdbError()); } } { char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, fk.getChildTable()); setDbName(ndb, db_and_name); Ndb_table_guard org_child(dict, name); if (org_child.get_table() == 0) { ERR_RETURN(dict->getNdbError()); } } /** * flags for CreateForeignKey */ int flags = 0; char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, fk.getParentTable()); if (strcmp(name, src_tab) == 0 && strcmp(db_and_name, src_db) == 0) { /** * We used to be parent... */ const NDBCOL *cols[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; for (unsigned j = 0; j < fk.getParentColumnCount(); j++) { const int parent_col_index = fk.getParentColumnNo(j); const NDBCOL *orgcol = srctab.get_table()->getColumn(parent_col_index); cols[j] = dsttab.get_table()->getColumn(orgcol->getName()); } cols[fk.getParentColumnCount()] = 0; if (fk.getParentIndex() != 0) { name = fk_split_name(db_and_name, fk.getParentIndex(), true); setDbName(ndb, db_and_name); const NDBINDEX *idx = dict->getIndexGlobal(name, *dsttab.get_table()); if (idx == 0) { ERR_RETURN(dict->getNdbError()); } fk.setParent(*dsttab.get_table(), idx, cols); dict->removeIndexGlobal(*idx, 0); } else { /* The parent column was previously the primary key. Make sure it still is a primary key as implicit pks might change during the alter. If not, get a better matching index. */ bool parent_primary = false; const NDBINDEX *idx = find_matching_index(dict, dsttab.get_table(), cols, parent_primary); if (!parent_primary && idx == 0) { my_error(ER_FK_NO_INDEX_PARENT, MYF(0), fk.getName(), dsttab.get_table()->getName()); return HA_ERR_CANNOT_ADD_FOREIGN; } fk.setParent(*dsttab.get_table(), idx, cols); } /** * We're parent, and this is offline alter table * then we can't verify that FK cause the new parent will * be populated later during copy data between tables * * However, iff FK is consistent when this alter starts, * it should remain consistent since mysql does not * allow the alter to modify the columns referenced */ flags |= NdbDictionary::Dictionary::CreateFK_NoVerify; } else { name = fk_split_name(db_and_name, fk.getChildTable()); assert(strcmp(name, src_tab) == 0 && strcmp(db_and_name, src_db) == 0); const NDBCOL *cols[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; for (unsigned j = 0; j < fk.getChildColumnCount(); j++) { const int child_col_index = fk.getChildColumnNo(j); const NDBCOL *orgcol = srctab.get_table()->getColumn(child_col_index); cols[j] = dsttab.get_table()->getColumn(orgcol->getName()); } cols[fk.getChildColumnCount()] = 0; if (fk.getChildIndex() != 0) { name = fk_split_name(db_and_name, fk.getChildIndex(), true); setDbName(ndb, db_and_name); bool child_primary_key = false; const NDBINDEX *idx = find_matching_index(dict, dsttab.get_table(), cols, child_primary_key); if (!child_primary_key && idx == 0) { ERR_RETURN(dict->getNdbError()); } fk.setChild(*dsttab.get_table(), idx, cols); if (idx) dict->removeIndexGlobal(*idx, 0); } else { fk.setChild(*dsttab.get_table(), 0, cols); } } char new_name[FN_LEN + 1]; name = fk_split_name(db_and_name, fk.getName()); snprintf(new_name, sizeof(new_name), "%s", name); fk.setName(new_name); setDbName(ndb, db_and_name); if (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { flags |= NdbDictionary::Dictionary::CreateFK_NoVerify; } NdbDictionary::ObjectId objid; if (dict->createForeignKey(fk, &objid, flags) != 0) { ERR_RETURN(dict->getNdbError()); } } } return 0; } int ha_ndbcluster::inplace__drop_fks(THD *thd, Ndb *ndb, NDBDICT *dict, const NDBTAB *tab) { DBUG_TRACE; if (thd->lex == 0) { assert(false); return 0; } Ndb_table_guard srctab(dict, tab->getName()); if (srctab.get_table() == 0) { DBUG_ASSERT(false); // Why ?? return 0; } NDBDICT::List obj_list; if (dict->listDependentObjects(obj_list, *srctab.get_table()) != 0) { ERR_RETURN(dict->getNdbError()); } for (const Alter_drop *drop_item : thd->lex->alter_info->drop_list) { if (drop_item->type != Alter_drop::FOREIGN_KEY) continue; bool found = false; for (unsigned i = 0; i < obj_list.count; i++) { if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) { continue; } char db_and_name[FN_LEN + 1]; const char *name = fk_split_name(db_and_name, obj_list.elements[i].name); if (ndb_fk_casecmp(drop_item->name, name) != 0) continue; NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, obj_list.elements[i].name) != 0) { ERR_RETURN(dict->getNdbError()); } char child_db_and_name[FN_LEN + 1]; const char *child_name = fk_split_name(child_db_and_name, fk.getChildTable()); if (strcmp(child_db_and_name, ndb->getDatabaseName()) == 0 && strcmp(child_name, tab->getName()) == 0) { found = true; Fk_util fk_util(thd); if (!fk_util.drop_fk(ndb, dict, obj_list.elements[i].name)) { ERR_RETURN(dict->getNdbError()); } /* Flush the parent table out if parent is different from child */ if (ndb_fk_casecmp(fk.getParentTable(), fk.getChildTable()) != 0) { char parent_db[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db, fk.getParentTable()); flush_parent_table_for_fk(thd, parent_db, parent_name); } break; } } if (!found) { // FK not found my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop_item->name); return ER_CANT_DROP_FIELD_OR_KEY; } } return 0; } /** Save all fk data into a fk_list - Build list of foreign keys for which the given table is child @retval 0 ok @retval != 0 failure in saving the fk data */ int ha_ndbcluster::get_fk_data_for_truncate(NdbDictionary::Dictionary *dict, const NdbDictionary::Table *ndbtab, Ndb_fk_list &fk_list) { DBUG_TRACE; NDBDICT::List obj_list; if (dict->listDependentObjects(obj_list, *ndbtab) != 0) { ERR_RETURN(dict->getNdbError()); } for (unsigned i = 0; i < obj_list.count; i++) { DBUG_PRINT("debug", ("DependentObject %d : %s, Type : %d", i, obj_list.elements[i].name, obj_list.elements[i].type)); if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) continue; /* obj is an fk. Fetch it */ NDBFK fk; if (dict->getForeignKey(fk, obj_list.elements[i].name) != 0) { ERR_RETURN(dict->getNdbError()); } DBUG_PRINT("debug", ("Retrieving FK : %s", fk.getName())); fk_list.push_back(new NdbDictionary::ForeignKey(fk)); DBUG_PRINT("info", ("Foreign Key added to list : %s", fk.getName())); } return 0; } /** Restore foreign keys into the child table from fk_list - for all foreign keys in the given fk list, re-assign child object ids to reflect the newly created child table/indexes - create the fk in the child table @retval 0 ok @retval != 0 failure in recreating the fk data */ int ha_ndbcluster::recreate_fk_for_truncate(THD *thd, Ndb *ndb, const char *tab_name, Ndb_fk_list &fk_list) { DBUG_TRACE; int flags = 0; const int err_default = HA_ERR_CANNOT_ADD_FOREIGN; NDBDICT *dict = ndb->getDictionary(); /* fetch child table */ Ndb_table_guard child_tab(dict, tab_name); if (child_tab.get_table() == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "INTERNAL ERROR: Could not find created child table '%s'", tab_name); // Internal error, should be able to load the just created child table DBUG_ASSERT(child_tab.get_table()); return err_default; } NDBFK *fk; List_iterator fk_iterator(fk_list); while ((fk = fk_iterator++)) { DBUG_PRINT("info", ("Parsing foreign key : %s", fk->getName())); /* Get child table columns and index */ const NDBCOL *child_cols[NDB_MAX_ATTRIBUTES_IN_INDEX + 1]; { unsigned pos = 0; const NDBTAB *tab = child_tab.get_table(); for (unsigned i = 0; i < fk->getChildColumnCount(); i++) { const NDBCOL *ndbcol = tab->getColumn(fk->getChildColumnNo(i)); if (ndbcol == 0) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Child table %s has no column referred by the FK %s", tab->getName(), fk->getName()); DBUG_ASSERT(ndbcol); return err_default; } child_cols[pos++] = ndbcol; } child_cols[pos] = 0; } bool child_primary_key = false; const NDBINDEX *child_index = find_matching_index( dict, child_tab.get_table(), child_cols, child_primary_key); if (!child_primary_key && child_index == 0) { my_error(ER_FK_NO_INDEX_CHILD, MYF(0), fk->getName(), child_tab.get_table()->getName()); return err_default; } /* update the fk's child references */ fk->setChild(*child_tab.get_table(), child_index, child_cols); /* the name of "fk" seems to be different when you read it up compared to when you create it. (Probably a historical artifact) So update fk's name */ { char name[FN_REFLEN + 1]; unsigned parent_id, child_id; if (sscanf(fk->getName(), "%u/%u/%s", &parent_id, &child_id, name) != 3) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_CANNOT_ADD_FOREIGN, "Skip, failed to parse name of fk: %s", fk->getName()); return err_default; } char fk_name[FN_REFLEN + 1]; snprintf(fk_name, sizeof(fk_name), "%s", name); DBUG_PRINT("info", ("Setting new fk name: %s", fk_name)); fk->setName(fk_name); } if (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) { flags |= NdbDictionary::Dictionary::CreateFK_NoVerify; } NdbDictionary::ObjectId objid; int err = dict->createForeignKey(*fk, &objid, flags); if (child_index) { dict->removeIndexGlobal(*child_index, 0); } if (err) { ERR_RETURN(dict->getNdbError()); } /* Flush the parent table out if parent is different from child */ char parent_db[FN_LEN + 1]; const char *parent_name = fk_split_name(parent_db, fk->getParentTable()); if (ndb_fk_casecmp(parent_name, tab_name) != 0 || ndb_fk_casecmp(parent_db, ndb->getDatabaseName()) != 0) { flush_parent_table_for_fk(thd, parent_db, parent_name); } } return 0; } bool ha_ndbcluster::has_fk_dependency( THD *thd, const NdbDictionary::Column *column) const { DBUG_TRACE; Ndb *ndb = get_ndb(thd); NDBDICT *dict = ndb->getDictionary(); NdbDictionary::Dictionary::List obj_list; DBUG_PRINT("info", ("Searching for column %s", column->getName())); if (dict->listDependentObjects(obj_list, *m_table) == 0) { for (unsigned i = 0; i < obj_list.count; i++) { const NDBDICT::List::Element &e = obj_list.elements[i]; if (obj_list.elements[i].type != NdbDictionary::Object::ForeignKey) { DBUG_PRINT("info", ("skip non-FK %s type %d", e.name, e.type)); continue; } DBUG_PRINT("info", ("found FK %s", e.name)); NdbDictionary::ForeignKey fk; if (dict->getForeignKey(fk, e.name) != 0) { DBUG_PRINT("error", ("Could not find the listed fk '%s'", e.name)); continue; } for (unsigned j = 0; j < fk.getParentColumnCount(); j++) { const NdbDictionary::Column *col = m_table->getColumn(fk.getParentColumnNo(j)); DBUG_PRINT("col", ("[%u] %s", i, col->getName())); if (col == column) return true; } for (unsigned j = 0; j < fk.getChildColumnCount(); j++) { const NdbDictionary::Column *col = m_table->getColumn(fk.getChildColumnNo(j)); DBUG_PRINT("col", ("[%u] %s", i, col->getName())); if (col == column) return true; } } } return false; }