diff options
author | Jan Lindström <jan.lindstrom@mariadb.com> | 2019-06-04 13:19:40 +0300 |
---|---|---|
committer | Jan Lindström <jan.lindstrom@mariadb.com> | 2019-06-18 11:24:26 +0300 |
commit | 859052dfcc3da3d61e3023c7401ef009cea50bcd (patch) | |
tree | 805e62bfdfde8fe76df0057efb2b601897dd0782 | |
parent | b003b0c934cf6b59358e31144c4f69cf34622fb8 (diff) | |
download | mariadb-git-bb-10.1-MDEV-19660.tar.gz |
MDEV-19660: wsrep_rec_get_foreign_key() is dereferencing a stale pointer to a page that was previously latchedbb-10.1-MDEV-19660
In row_ins_foreign_check_on_constraint(), clustered index record is being passed to wsrep_append_foreign_key() after releasing the latch. If a record has been changed by other thread in the meantime then it could lead to a crash when
wsrep_rec_get_foreign_key () tries to access the record.
wsrep_append_foreign_key
Added call to error reporting function and debug error injection.
row_ins_foreign_report_err_low
Moved actual error reporting to a file to this low level function.
row_ins_foreign_report_err
Use new row_ins_foreign_report_err_low function for error reporting
to a file.
wsrep_report_foreign_key_error
New function to report foreign key errors when appending a key for
Galera processing.
row_ins_foreign_check_on_constraint
Use cascade->pcur->old_rec instead of clust_rec.
-rw-r--r-- | mysql-test/suite/galera/r/galera_fk_force_append_error.result | 162 | ||||
-rw-r--r-- | mysql-test/suite/galera/t/galera_fk_force_append_error.test | 161 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.cc | 110 | ||||
-rw-r--r-- | storage/innobase/include/row0ins.h | 16 | ||||
-rw-r--r-- | storage/innobase/row/row0ins.cc | 113 | ||||
-rw-r--r-- | storage/xtradb/handler/ha_innodb.cc | 110 | ||||
-rw-r--r-- | storage/xtradb/include/row0ins.h | 15 | ||||
-rw-r--r-- | storage/xtradb/row/row0ins.cc | 100 |
8 files changed, 625 insertions, 162 deletions
diff --git a/mysql-test/suite/galera/r/galera_fk_force_append_error.result b/mysql-test/suite/galera/r/galera_fk_force_append_error.result new file mode 100644 index 00000000000..48abf468a4d --- /dev/null +++ b/mysql-test/suite/galera/r/galera_fk_force_append_error.result @@ -0,0 +1,162 @@ +call mtr.add_suppression("WSREP: Appending cascaded fk row key failed:.*"); +CREATE TABLE grandparent ( +id INT NOT NULL PRIMARY KEY +) ENGINE=InnoDB; +CREATE TABLE parent ( +id INT NOT NULL PRIMARY KEY, +grandparent_id INT, +FOREIGN KEY (grandparent_id) +REFERENCES grandparent(id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE child ( +id INT NOT NULL PRIMARY KEY, +parent_id INT, +FOREIGN KEY (parent_id) +REFERENCES parent(id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO grandparent VALUES (1),(2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO parent VALUES (1,1), (2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO parent VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO child VALUES (1,1), (2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO child VALUES (1,1), (2,2); +DELETE FROM grandparent WHERE id = 1; +UPDATE grandparent SET id = 5 where id = 2; +select * from child; +id parent_id +2 2 +select * from parent; +id grandparent_id +2 5 +select * from grandparent; +id +5 +select * from child; +id parent_id +2 2 +select * from parent; +id grandparent_id +2 5 +select * from grandparent; +id +5 +drop table child, parent, grandparent; +CREATE TABLE grandparent ( +id INT NOT NULL PRIMARY KEY +) ENGINE=InnoDB; +CREATE TABLE parent ( +id INT NOT NULL, +id2 INT NOT NULL, +grandparent_id INT, +id3 INT not null, +id4 INT, +id5 INT, +PRIMARY KEY(id,id2), +unique key(id3), +key(id4,id5), +key(id4,id), +FOREIGN KEY (grandparent_id) +REFERENCES grandparent(id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE child ( +id INT NOT NULL PRIMARY KEY, +parent_id INT, +FOREIGN KEY (parent_id) +REFERENCES parent(id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE child2 ( +id INT NOT NULL PRIMARY KEY, +parent_id INT, +FOREIGN KEY (parent_id) +REFERENCES parent(id3) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE child3 ( +id INT NOT NULL PRIMARY KEY, +parent_id INT, +FOREIGN KEY (parent_id) +REFERENCES parent(id4) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +CREATE TABLE child4 ( +id INT NOT NULL PRIMARY KEY, +pid1 INT, +pid2 INT, +FOREIGN KEY (pid1,pid2) +REFERENCES parent(id4,id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO grandparent VALUES (1),(2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO parent VALUES (1,1,1,1,1,1), (2,2,2,2,2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO parent VALUES (1,1,1,1,1,1), (2,2,2,2,2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO child VALUES (1,1), (2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO child VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO child2 VALUES (1,1), (2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO child2 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO child3 VALUES (1,1), (2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO child3 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +INSERT INTO child4 VALUES (1,1,1), (2,2,2); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails +SET GLOBAL debug_dbug = ""; +INSERT INTO child4 VALUES (1,1,1), (2,2,2); +DELETE FROM grandparent WHERE id = 1; +UPDATE grandparent SET id = 5 where id = 2; +select * from child; +id parent_id +2 2 +select * from child2; +id parent_id +2 2 +select * from child3; +id parent_id +2 2 +select * from child4; +id pid1 pid2 +2 2 2 +select * from parent; +id id2 grandparent_id id3 id4 id5 +2 2 5 2 2 2 +select * from grandparent; +id +5 +select * from child; +id parent_id +2 2 +select * from child2; +id parent_id +2 2 +select * from child3; +id parent_id +2 2 +select * from child4; +id pid1 pid2 +2 2 2 +select * from parent; +id id2 grandparent_id id3 id4 id5 +2 2 5 2 2 2 +select * from grandparent; +id +5 +drop table child, child2, child3, child4, parent, grandparent; diff --git a/mysql-test/suite/galera/t/galera_fk_force_append_error.test b/mysql-test/suite/galera/t/galera_fk_force_append_error.test new file mode 100644 index 00000000000..ad3f6ebf951 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_fk_force_append_error.test @@ -0,0 +1,161 @@ +# Test different foreign keys and force galera foreign key +# append error. + +--source include/galera_cluster.inc +--source include/have_debug.inc + +--connection node_1 + +call mtr.add_suppression("WSREP: Appending cascaded fk row key failed:.*"); + +CREATE TABLE grandparent ( + id INT NOT NULL PRIMARY KEY +) ENGINE=InnoDB; + +CREATE TABLE parent ( + id INT NOT NULL PRIMARY KEY, + grandparent_id INT, + FOREIGN KEY (grandparent_id) + REFERENCES grandparent(id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE child ( + id INT NOT NULL PRIMARY KEY, + parent_id INT, + FOREIGN KEY (parent_id) + REFERENCES parent(id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO grandparent VALUES (1),(2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO parent VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO parent VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO child VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO child VALUES (1,1), (2,2); + +--connection node_2 +DELETE FROM grandparent WHERE id = 1; +UPDATE grandparent SET id = 5 where id = 2; + +--connection node_1 +select * from child; +select * from parent; +select * from grandparent; + +--connection node_2 +select * from child; +select * from parent; +select * from grandparent; + +drop table child, parent, grandparent; + +--connection node_1 + +CREATE TABLE grandparent ( + id INT NOT NULL PRIMARY KEY +) ENGINE=InnoDB; + +CREATE TABLE parent ( + id INT NOT NULL, + id2 INT NOT NULL, + grandparent_id INT, + id3 INT not null, + id4 INT, + id5 INT, + PRIMARY KEY(id,id2), + unique key(id3), + key(id4,id5), + key(id4,id), + FOREIGN KEY (grandparent_id) + REFERENCES grandparent(id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE child ( + id INT NOT NULL PRIMARY KEY, + parent_id INT, + FOREIGN KEY (parent_id) + REFERENCES parent(id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE child2 ( + id INT NOT NULL PRIMARY KEY, + parent_id INT, + FOREIGN KEY (parent_id) + REFERENCES parent(id3) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE child3 ( + id INT NOT NULL PRIMARY KEY, + parent_id INT, + FOREIGN KEY (parent_id) + REFERENCES parent(id4) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE child4 ( + id INT NOT NULL PRIMARY KEY, + pid1 INT, + pid2 INT, + FOREIGN KEY (pid1,pid2) + REFERENCES parent(id4,id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO grandparent VALUES (1),(2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO parent VALUES (1,1,1,1,1,1), (2,2,2,2,2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO parent VALUES (1,1,1,1,1,1), (2,2,2,2,2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO child VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO child VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO child2 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO child2 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO child3 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO child3 VALUES (1,1), (2,2); +SET GLOBAL debug_dbug = "+d,wsrep_append_foreign_key_error"; +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO child4 VALUES (1,1,1), (2,2,2); +SET GLOBAL debug_dbug = ""; +INSERT INTO child4 VALUES (1,1,1), (2,2,2); + +--connection node_2 +DELETE FROM grandparent WHERE id = 1; +UPDATE grandparent SET id = 5 where id = 2; + +--connection node_1 +select * from child; +select * from child2; +select * from child3; +select * from child4; +select * from parent; +select * from grandparent; + +--connection node_2 +select * from child; +select * from child2; +select * from child3; +select * from child4; +select * from parent; +select * from grandparent; + +drop table child, child2, child3, child4, parent, grandparent; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index e04d431174a..de9d4e41158 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -10353,9 +10353,17 @@ wsrep_key_type_to_str(wsrep_key_type type) return "unknown"; } +/** Append foreign key information for Galera processing. This is +local operation and no cluster communication done. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] index Clustered index +@param[in] key_type Access type of this key + (shared, exclusive, semi...) */ +UNIV_INTERN ulint wsrep_append_foreign_key( -/*===========================*/ trx_t* trx, /*!< in: trx */ dict_foreign_t* foreign, /*!< in: foreign key constraint */ const rec_t* rec, /*!<in: clustered index record */ @@ -10369,15 +10377,25 @@ wsrep_append_foreign_key( ulint rcode = DB_SUCCESS; char cache_key[513] = {'\0'}; int cache_key_len; - bool const copy = true; + bool const copy = true; + byte key[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + ulint len = WSREP_MAX_SUPPORTED_KEY_LENGTH; + int i = 0; + dict_index_t* idx_target = NULL; + dict_index_t* idx = NULL; + char *p = NULL; + wsrep_buf_t wkey_part[3]; + wsrep_key_t wkey = {wkey_part, 3}; + wsrep_t *wsrep = NULL; - if (!wsrep_on(trx->mysql_thd) || - wsrep_thd_exec_mode(thd) != LOCAL_STATE) + if (!wsrep_on(trx->mysql_thd) + || wsrep_thd_exec_mode(thd) != LOCAL_STATE) { return DB_SUCCESS; + } - if (!thd || !foreign || - (!foreign->referenced_table && !foreign->foreign_table)) - { + if (!thd + || !foreign + || (!foreign->referenced_table && !foreign->foreign_table)) { WSREP_INFO("FK: %s missing in: %s", (!thd) ? "thread" : ((!foreign) ? "constraint" : @@ -10385,44 +10403,38 @@ wsrep_append_foreign_key( "referenced table" : "foreign table")), (thd && wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - if ( !((referenced) ? - foreign->referenced_table : foreign->foreign_table)) - { + if (!((referenced) ? + foreign->referenced_table : foreign->foreign_table)) { WSREP_DEBUG("pulling %s table into cache", (referenced) ? "referenced" : "foreign"); mutex_enter(&(dict_sys->mutex)); - if (referenced) - { + if (referenced) { foreign->referenced_table = dict_table_get_low( foreign->referenced_table_name_lookup); - if (foreign->referenced_table) - { + if (foreign->referenced_table) { foreign->referenced_index = wsrep_dict_foreign_find_index( foreign->referenced_table, NULL, foreign->referenced_col_names, - foreign->n_fields, + foreign->n_fields, foreign->foreign_index, TRUE, FALSE); } - } - else - { + } else { foreign->foreign_table = dict_table_get_low( foreign->foreign_table_name_lookup); - if (foreign->foreign_table) - { + if (foreign->foreign_table) { foreign->foreign_index = wsrep_dict_foreign_find_index( foreign->foreign_table, NULL, foreign->foreign_col_names, foreign->n_fields, - foreign->referenced_index, + foreign->referenced_index, TRUE, FALSE); } } @@ -10430,30 +10442,28 @@ wsrep_append_foreign_key( } if ( !((referenced) ? - foreign->referenced_table : foreign->foreign_table)) - { + foreign->referenced_table : foreign->foreign_table)) { WSREP_WARN("FK: %s missing in query: %s", (!foreign->referenced_table) ? "referenced table" : "foreign table", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - byte key[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; - ulint len = WSREP_MAX_SUPPORTED_KEY_LENGTH; - dict_index_t *idx_target = (referenced) ? - foreign->referenced_index : index; - dict_index_t *idx = (referenced) ? + + idx_target = (referenced) ? foreign->referenced_index : index; + idx = (referenced) ? UT_LIST_GET_FIRST(foreign->referenced_table->indexes) : UT_LIST_GET_FIRST(foreign->foreign_table->indexes); - int i = 0; + while (idx != NULL && idx != idx_target) { if (innobase_strcasecmp (idx->name, innobase_index_reserve_name) != 0) { i++; } idx = UT_LIST_GET_NEXT(indexes, idx); } + ut_a(idx); key[0] = (char)i; @@ -10470,7 +10480,7 @@ wsrep_append_foreign_key( (index && index->table_name) ? index->table_name : "void table", wsrep_thd_query(thd)); - return DB_ERROR; + goto error_handling; } strncpy(cache_key, @@ -10479,17 +10489,11 @@ wsrep_append_foreign_key( foreign->referenced_table->name : foreign->foreign_table->name) : foreign->foreign_table->name, sizeof(cache_key) - 1); + cache_key_len = strlen(cache_key); -#ifdef WSREP_DEBUG_PRINT - ulint j; - fprintf(stderr, "FK parent key, table: %s %s len: %lu ", - cache_key, (shared) ? "shared" : "exclusive", len+1); - for (j=0; j<len+1; j++) { - fprintf(stderr, " %hhX, ", key[j]); - } - fprintf(stderr, "\n"); -#endif - char *p = strchr(cache_key, '/'); + + p = strchr(cache_key, '/'); + if (p) { *p = '\0'; } else { @@ -10498,8 +10502,6 @@ wsrep_append_foreign_key( foreign->foreign_table->name); } - wsrep_buf_t wkey_part[3]; - wsrep_key_t wkey = {wkey_part, 3}; if (!wsrep_prepare_key( (const uchar*)cache_key, cache_key_len + 1, @@ -10509,9 +10511,11 @@ wsrep_append_foreign_key( WSREP_WARN("key prepare failed for cascaded FK: %s", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - wsrep_t *wsrep= get_wsrep(); + + wsrep= get_wsrep(); + rcode = (int)wsrep->append_key( wsrep, wsrep_ws_handle(thd, trx), @@ -10519,15 +10523,27 @@ wsrep_append_foreign_key( 1, key_type, copy); + + /* Force error injection for testing error reporting. */ + DBUG_EXECUTE_IF("wsrep_append_foreign_key_error", + rcode = 1; + ); + if (rcode) { DBUG_PRINT("wsrep", ("row key failed: %lu", rcode)); WSREP_ERROR("Appending cascaded fk row key failed: %s, %lu", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", rcode); - return DB_ERROR; + goto error_handling; } return DB_SUCCESS; + +error_handling: + + wsrep_report_foreign_key_error(trx, foreign, rec, NULL); + + return DB_NO_REFERENCED_ROW; } static int diff --git a/storage/innobase/include/row0ins.h b/storage/innobase/include/row0ins.h index 54e7fa6d1fb..d1a0575cdb1 100644 --- a/storage/innobase/include/row0ins.h +++ b/storage/innobase/include/row0ins.h @@ -32,6 +32,7 @@ Created 4/20/1996 Heikki Tuuri #include "dict0types.h" #include "trx0types.h" #include "row0types.h" +#include "rem0types.h" /***************************************************************//** Checks if foreign key constraint fails for an index entry. Sets shared locks @@ -189,6 +190,21 @@ row_ins_step( /*=========*/ que_thr_t* thr); /*!< in: query thread */ +#ifdef WITH_WSREP +/* Report foreign key error from Galera append key. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ +UNIV_INTERN +void +wsrep_report_foreign_key_error( + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry); +#endif /* WITH_WSREP */ + /* Insert node structure */ struct ins_node_t{ diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index d60665f52ad..82c9c632a37 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -790,36 +790,26 @@ row_ins_foreign_trx_print( ut_ad(mutex_own(&dict_foreign_err_mutex)); } -/*********************************************************************//** -Reports a foreign key error associated with an update or a delete of a -parent table index entry. */ +/** Report a foreign key error to a provided file handle +@param[in] ef file handle +@param[in] errstr error string from the viewpoint + of the parent table +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ static void -row_ins_foreign_report_err( -/*=======================*/ - const char* errstr, /*!< in: error string from the viewpoint - of the parent table */ - que_thr_t* thr, /*!< in: query thread whose run_node - is an update node */ - dict_foreign_t* foreign, /*!< in: foreign key constraint */ - const rec_t* rec, /*!< in: a matching index record in the - child table */ - const dtuple_t* entry) /*!< in: index entry in the parent - table */ +row_ins_foreign_report_err_low( + FILE* ef, + const char* errstr, + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry) { std::string fk_str; - if (srv_read_only_mode) { - return; - } - - FILE* ef = dict_foreign_err_file; - trx_t* trx = thr_get_trx(thr); - - row_ins_set_detailed(trx, foreign); - - row_ins_foreign_trx_print(trx); - fputs("Foreign key constraint fails for table ", ef); ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); fputs(":\n", ef); @@ -845,6 +835,37 @@ row_ins_foreign_report_err( fputs(", the record is not available\n", ef); } putc('\n', ef); +} + +/*********************************************************************//** +Reports a foreign key error associated with an update or a delete of a +parent table index entry. */ +static +void +row_ins_foreign_report_err( +/*=======================*/ + const char* errstr, /*!< in: error string from the viewpoint + of the parent table */ + que_thr_t* thr, /*!< in: query thread whose run_node + is an update node */ + dict_foreign_t* foreign, /*!< in: foreign key constraint */ + const rec_t* rec, /*!< in: a matching index record in the + child table */ + const dtuple_t* entry) /*!< in: index entry in the parent + table */ +{ + std::string fk_str; + + if (srv_read_only_mode) { + return; + } + + FILE* ef = dict_foreign_err_file; + trx_t* trx = thr_get_trx(thr); + + row_ins_set_detailed(trx, foreign); + row_ins_foreign_trx_print(trx); + row_ins_foreign_report_err_low(ef, errstr, trx, foreign, rec, entry); mutex_exit(&dict_foreign_err_mutex); } @@ -935,6 +956,7 @@ row_ins_invalidate_query_cache( innobase_invalidate_query_cache(thr_get_trx(thr), buf, len); mem_free(buf); } + #ifdef WITH_WSREP dberr_t wsrep_append_foreign_key(trx_t *trx, dict_foreign_t* foreign, @@ -942,6 +964,25 @@ dberr_t wsrep_append_foreign_key(trx_t *trx, dict_index_t* clust_index, ibool referenced, enum wsrep_key_type key_type); + +/* Report foreign key error from Galera append key. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ +UNIV_INTERN +void +wsrep_report_foreign_key_error( + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry) +{ + mutex_enter(&dict_foreign_err_mutex); + row_ins_foreign_report_err_low(stderr, "WSREP: append foreign key failed ", + trx, foreign, rec, entry); + mutex_exit(&dict_foreign_err_mutex); +} #endif /* WITH_WSREP */ /*********************************************************************//** @@ -1272,6 +1313,15 @@ row_ins_foreign_check_on_constraint( } } +#ifdef WITH_WSREP + err = wsrep_append_foreign_key( + thr_get_trx(thr), + foreign, + clust_rec, + clust_index, + FALSE, WSREP_KEY_EXCLUSIVE); +#endif /* WITH_WSREP */ + /* Store pcur position and initialize or store the cascade node pcur stored position */ @@ -1290,17 +1340,8 @@ row_ins_foreign_check_on_constraint( cascade->state = UPD_NODE_UPDATE_CLUSTERED; #ifdef WITH_WSREP - err = wsrep_append_foreign_key( - thr_get_trx(thr), - foreign, - clust_rec, - clust_index, - FALSE, WSREP_KEY_EXCLUSIVE); - if (err != DB_SUCCESS) { - fprintf(stderr, - "WSREP: foreign key append failed: %d\n", err); - } else -#endif /* WITH_WSREP */ + if (err == DB_SUCCESS) +#endif err = row_update_cascade_for_mysql(thr, cascade, foreign->foreign_table); diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 0cdd5ac2452..0a82a4b91a9 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -10907,9 +10907,17 @@ wsrep_key_type_to_str(wsrep_key_type type) return "unknown"; } +/** Append foreign key information for Galera processing. This is +local operation and no cluster communication done. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] index Clustered index +@param[in] key_type Access type of this key + (shared, exclusive, semi...) */ +UNIV_INTERN ulint wsrep_append_foreign_key( -/*===========================*/ trx_t* trx, /*!< in: trx */ dict_foreign_t* foreign, /*!< in: foreign key constraint */ const rec_t* rec, /*!<in: clustered index record */ @@ -10923,15 +10931,25 @@ wsrep_append_foreign_key( ulint rcode = DB_SUCCESS; char cache_key[513] = {'\0'}; int cache_key_len; - bool const copy = true; + bool const copy = true; + byte key[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + ulint len = WSREP_MAX_SUPPORTED_KEY_LENGTH; + int i = 0; + dict_index_t* idx_target = NULL; + dict_index_t* idx = NULL; + char *p = NULL; + wsrep_buf_t wkey_part[3]; + wsrep_key_t wkey = {wkey_part, 3}; + wsrep_t *wsrep = NULL; - if (!wsrep_on(trx->mysql_thd) || - wsrep_thd_exec_mode(thd) != LOCAL_STATE) + if (!wsrep_on(trx->mysql_thd) + || wsrep_thd_exec_mode(thd) != LOCAL_STATE) { return DB_SUCCESS; + } - if (!thd || !foreign || - (!foreign->referenced_table && !foreign->foreign_table)) - { + if (!thd + || !foreign + || (!foreign->referenced_table && !foreign->foreign_table)) { WSREP_INFO("FK: %s missing in: %s", (!thd) ? "thread" : ((!foreign) ? "constraint" : @@ -10939,44 +10957,38 @@ wsrep_append_foreign_key( "referenced table" : "foreign table")), (thd && wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - if ( !((referenced) ? - foreign->referenced_table : foreign->foreign_table)) - { + if (!((referenced) ? + foreign->referenced_table : foreign->foreign_table)) { WSREP_DEBUG("pulling %s table into cache", (referenced) ? "referenced" : "foreign"); mutex_enter(&(dict_sys->mutex)); - if (referenced) - { + if (referenced) { foreign->referenced_table = dict_table_get_low( foreign->referenced_table_name_lookup); - if (foreign->referenced_table) - { + if (foreign->referenced_table) { foreign->referenced_index = wsrep_dict_foreign_find_index( foreign->referenced_table, NULL, foreign->referenced_col_names, - foreign->n_fields, + foreign->n_fields, foreign->foreign_index, TRUE, FALSE); } - } - else - { + } else { foreign->foreign_table = dict_table_get_low( foreign->foreign_table_name_lookup); - if (foreign->foreign_table) - { + if (foreign->foreign_table) { foreign->foreign_index = wsrep_dict_foreign_find_index( foreign->foreign_table, NULL, foreign->foreign_col_names, foreign->n_fields, - foreign->referenced_index, + foreign->referenced_index, TRUE, FALSE); } } @@ -10984,30 +10996,28 @@ wsrep_append_foreign_key( } if ( !((referenced) ? - foreign->referenced_table : foreign->foreign_table)) - { + foreign->referenced_table : foreign->foreign_table)) { WSREP_WARN("FK: %s missing in query: %s", (!foreign->referenced_table) ? "referenced table" : "foreign table", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - byte key[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; - ulint len = WSREP_MAX_SUPPORTED_KEY_LENGTH; - dict_index_t *idx_target = (referenced) ? - foreign->referenced_index : index; - dict_index_t *idx = (referenced) ? + + idx_target = (referenced) ? foreign->referenced_index : index; + idx = (referenced) ? UT_LIST_GET_FIRST(foreign->referenced_table->indexes) : UT_LIST_GET_FIRST(foreign->foreign_table->indexes); - int i = 0; + while (idx != NULL && idx != idx_target) { if (innobase_strcasecmp (idx->name, innobase_index_reserve_name) != 0) { i++; } idx = UT_LIST_GET_NEXT(indexes, idx); } + ut_a(idx); key[0] = (char)i; @@ -11024,7 +11034,7 @@ wsrep_append_foreign_key( (index && index->table_name) ? index->table_name : "void table", wsrep_thd_query(thd)); - return DB_ERROR; + goto error_handling; } strncpy(cache_key, @@ -11033,17 +11043,11 @@ wsrep_append_foreign_key( foreign->referenced_table->name : foreign->foreign_table->name) : foreign->foreign_table->name, sizeof(cache_key) - 1); + cache_key_len = strlen(cache_key); -#ifdef WSREP_DEBUG_PRINT - ulint j; - fprintf(stderr, "FK parent key, table: %s %s len: %lu ", - cache_key, (shared) ? "shared" : "exclusive", len+1); - for (j=0; j<len+1; j++) { - fprintf(stderr, " %hhX, ", key[j]); - } - fprintf(stderr, "\n"); -#endif - char *p = strchr(cache_key, '/'); + + p = strchr(cache_key, '/'); + if (p) { *p = '\0'; } else { @@ -11052,8 +11056,6 @@ wsrep_append_foreign_key( foreign->foreign_table->name); } - wsrep_buf_t wkey_part[3]; - wsrep_key_t wkey = {wkey_part, 3}; if (!wsrep_prepare_key( (const uchar*)cache_key, cache_key_len + 1, @@ -11063,9 +11065,11 @@ wsrep_append_foreign_key( WSREP_WARN("key prepare failed for cascaded FK: %s", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void"); - return DB_ERROR; + goto error_handling; } - wsrep_t *wsrep= get_wsrep(); + + wsrep= get_wsrep(); + rcode = (int)wsrep->append_key( wsrep, wsrep_ws_handle(thd, trx), @@ -11073,15 +11077,27 @@ wsrep_append_foreign_key( 1, key_type, copy); + + /* Force error injection for testing error reporting. */ + DBUG_EXECUTE_IF("wsrep_append_foreign_key_error", + rcode = 1; + ); + if (rcode) { DBUG_PRINT("wsrep", ("row key failed: %lu", rcode)); WSREP_ERROR("Appending cascaded fk row key failed: %s, %lu", (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", rcode); - return DB_ERROR; + goto error_handling; } return DB_SUCCESS; + +error_handling: + + wsrep_report_foreign_key_error(trx, foreign, rec, NULL); + + return DB_NO_REFERENCED_ROW; } static int diff --git a/storage/xtradb/include/row0ins.h b/storage/xtradb/include/row0ins.h index 54e7fa6d1fb..d5aaec9f961 100644 --- a/storage/xtradb/include/row0ins.h +++ b/storage/xtradb/include/row0ins.h @@ -189,6 +189,21 @@ row_ins_step( /*=========*/ que_thr_t* thr); /*!< in: query thread */ +#ifdef WITH_WSREP +/* Report foreign key error from Galera append key. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ +UNIV_INTERN +void +wsrep_report_foreign_key_error( + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry); +#endif /* WITH_WSREP */ + /* Insert node structure */ struct ins_node_t{ diff --git a/storage/xtradb/row/row0ins.cc b/storage/xtradb/row/row0ins.cc index 0eefdb17f39..aa2bec1ce13 100644 --- a/storage/xtradb/row/row0ins.cc +++ b/storage/xtradb/row/row0ins.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2018, MariaDB Corporation. +Copyright (c) 2017, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -796,36 +796,26 @@ row_ins_foreign_trx_print( ut_ad(mutex_own(&dict_foreign_err_mutex)); } -/*********************************************************************//** -Reports a foreign key error associated with an update or a delete of a -parent table index entry. */ +/** Report a foreign key error to a provided file handle +@param[in] ef file handle +@param[in] errstr error string from the viewpoint + of the parent table +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ static void -row_ins_foreign_report_err( -/*=======================*/ - const char* errstr, /*!< in: error string from the viewpoint - of the parent table */ - que_thr_t* thr, /*!< in: query thread whose run_node - is an update node */ - dict_foreign_t* foreign, /*!< in: foreign key constraint */ - const rec_t* rec, /*!< in: a matching index record in the - child table */ - const dtuple_t* entry) /*!< in: index entry in the parent - table */ +row_ins_foreign_report_err_low( + FILE* ef, + const char* errstr, + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry) { std::string fk_str; - if (srv_read_only_mode) { - return; - } - - FILE* ef = dict_foreign_err_file; - trx_t* trx = thr_get_trx(thr); - - row_ins_set_detailed(trx, foreign); - - row_ins_foreign_trx_print(trx); - fputs("Foreign key constraint fails for table ", ef); ut_print_name(ef, trx, TRUE, foreign->foreign_table_name); fputs(":\n", ef); @@ -851,6 +841,35 @@ row_ins_foreign_report_err( fputs(", the record is not available\n", ef); } putc('\n', ef); +} + +/*********************************************************************//** +Reports a foreign key error associated with an update or a delete of a +parent table index entry. */ +static +void +row_ins_foreign_report_err( +/*=======================*/ + const char* errstr, /*!< in: error string from the viewpoint + of the parent table */ + que_thr_t* thr, /*!< in: query thread whose run_node + is an update node */ + dict_foreign_t* foreign, /*!< in: foreign key constraint */ + const rec_t* rec, /*!< in: a matching index record in the + child table */ + const dtuple_t* entry) /*!< in: index entry in the parent + table */ +{ + if (srv_read_only_mode) { + return; + } + + FILE* ef = dict_foreign_err_file; + trx_t* trx = thr_get_trx(thr); + + row_ins_set_detailed(trx, foreign); + row_ins_foreign_trx_print(trx); + row_ins_foreign_report_err_low(ef, errstr, trx, foreign, rec, entry); mutex_exit(&dict_foreign_err_mutex); } @@ -941,6 +960,7 @@ row_ins_invalidate_query_cache( innobase_invalidate_query_cache(thr_get_trx(thr), buf, len); mem_free(buf); } + #ifdef WITH_WSREP dberr_t wsrep_append_foreign_key(trx_t *trx, dict_foreign_t* foreign, @@ -948,6 +968,25 @@ dberr_t wsrep_append_foreign_key(trx_t *trx, dict_index_t* clust_index, ibool referenced, enum wsrep_key_type key_type); + +/* Report foreign key error from Galera append key. +@param[in] trx Transaction +@param[in] foreign Foreign key constraint +@param[in] rec Index record in the child table +@param[in] entry Index entry in the parent table */ +UNIV_INTERN +void +wsrep_report_foreign_key_error( + trx_t* trx, + dict_foreign_t* foreign, + const rec_t* rec, + const dtuple_t* entry) +{ + mutex_enter(&dict_foreign_err_mutex); + row_ins_foreign_report_err_low(stderr, "WSREP: append foreign key failed ", + trx, foreign, rec, entry); + mutex_exit(&dict_foreign_err_mutex); +} #endif /* WITH_WSREP */ /*********************************************************************//** @@ -1299,14 +1338,11 @@ row_ins_foreign_check_on_constraint( err = wsrep_append_foreign_key( thr_get_trx(thr), foreign, - clust_rec, + cascade->pcur->old_rec, clust_index, FALSE, WSREP_KEY_EXCLUSIVE); - if (err != DB_SUCCESS) { - fprintf(stderr, - "WSREP: foreign key append failed: %d\n", err); - } else -#endif /* WITH_WSREP */ + if (err == DB_SUCCESS) +#endif err = row_update_cascade_for_mysql(thr, cascade, foreign->foreign_table); |