diff options
-rw-r--r-- | mysql-test/suite/innodb/r/foreign_key.result | 43 | ||||
-rw-r--r-- | mysql-test/suite/innodb/r/innodb-index-online-fk.result | 1 | ||||
-rw-r--r-- | mysql-test/suite/innodb/r/innodb.result | 75 | ||||
-rw-r--r-- | mysql-test/suite/innodb/r/update-cascade.result | 2 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/foreign_key.test | 40 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/innodb.test | 72 | ||||
-rw-r--r-- | storage/innobase/include/dict0mem.h | 2 | ||||
-rw-r--r-- | storage/innobase/include/row0mysql.h | 12 | ||||
-rw-r--r-- | storage/innobase/include/row0upd.h | 63 | ||||
-rw-r--r-- | storage/innobase/que/que0que.cc | 9 | ||||
-rw-r--r-- | storage/innobase/row/row0ins.cc | 65 | ||||
-rw-r--r-- | storage/innobase/row/row0mysql.cc | 331 | ||||
-rw-r--r-- | storage/innobase/row/row0upd.cc | 75 |
13 files changed, 281 insertions, 509 deletions
diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index da0e16b86ac..e569fc7dba7 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -263,3 +263,46 @@ SELECT * FROM t2; id ref_id f 2 2 20 DROP TABLE t2, t1; +# +# MDEV-15199 Referential integrity broken in ON DELETE CASCADE +# +CREATE TABLE member (id int AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO member VALUES (1); +CREATE TABLE address ( +id int AUTO_INCREMENT PRIMARY KEY, +member_id int NOT NULL, +KEY address_FI_1 (member_id), +CONSTRAINT address_FK_1 FOREIGN KEY (member_id) REFERENCES member (id) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO address VALUES (2,1); +CREATE TABLE payment_method ( +id int AUTO_INCREMENT PRIMARY KEY, +member_id int NOT NULL, +cardholder_address_id int DEFAULT NULL, +KEY payment_method_FI_1 (member_id), +KEY payment_method_FI_2 (cardholder_address_id), +CONSTRAINT payment_method_FK_1 FOREIGN KEY (member_id) REFERENCES member (id) ON DELETE CASCADE ON UPDATE CASCADE, +CONSTRAINT payment_method_FK_2 FOREIGN KEY (cardholder_address_id) REFERENCES address (id) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO payment_method VALUES (3,1,2); +BEGIN; +UPDATE member SET id=42; +SELECT * FROM member; +id +42 +SELECT * FROM address; +id member_id +2 42 +SELECT * FROM payment_method; +id member_id cardholder_address_id +3 42 2 +DELETE FROM member; +COMMIT; +SELECT * FROM member; +id +SELECT * FROM address; +id member_id +SELECT * FROM payment_method; +id member_id cardholder_address_id +DROP TABLE payment_method,address,member; diff --git a/mysql-test/suite/innodb/r/innodb-index-online-fk.result b/mysql-test/suite/innodb/r/innodb-index-online-fk.result index 359ce4a717c..4bd4b060ab3 100644 --- a/mysql-test/suite/innodb/r/innodb-index-online-fk.result +++ b/mysql-test/suite/innodb/r/innodb-index-online-fk.result @@ -86,6 +86,7 @@ child CREATE TABLE `child` ( DELETE FROM parent where a = 1; SELECT * FROM child; a1 a2 +1 NULL 2 3 10 20 SET foreign_key_checks = 0; diff --git a/mysql-test/suite/innodb/r/innodb.result b/mysql-test/suite/innodb/r/innodb.result index c1bbc2c68d9..ed8720a0df9 100644 --- a/mysql-test/suite/innodb/r/innodb.result +++ b/mysql-test/suite/innodb/r/innodb.result @@ -1320,80 +1320,17 @@ INSERT INTO t2 VALUES (10, 'old'), (20, 'other'); UPDATE t1 SET c1 = 'other' WHERE c1 = 'old'; ERROR 23000: Foreign key constraint for table 't1', record 'other-somevalu' would lead to a duplicate entry in table 't2', key 'c1' DROP TABLE t2,t1; -call mtr.add_suppression("Cannot delete/update rows with cascading foreign key constraints that exceed max depth of 255. Please drop excessive foreign constraints and try again"); +call mtr.add_suppression("Cannot delete/update rows with cascading foreign key constraints that exceed max depth of 15\\. Please drop excessive foreign constraints and try again"); create table t1( id int primary key, pid int, index(pid), foreign key(pid) references t1(id) on delete cascade) engine=innodb; -insert into t1 values -( 0, 0), ( 1, 0), ( 2, 1), ( 3, 2), -( 4, 3), ( 5, 4), ( 6, 5), ( 7, 6), -( 8, 7), ( 9, 8), ( 10, 9), ( 11, 10), -( 12, 11), ( 13, 12), ( 14, 13), ( 15, 14), -( 16, 15), ( 17, 16), ( 18, 17), ( 19, 18), -( 20, 19), ( 21, 20), ( 22, 21), ( 23, 22), -( 24, 23), ( 25, 24), ( 26, 25), ( 27, 26), -( 28, 27), ( 29, 28), ( 30, 29), ( 31, 30), -( 32, 31), ( 33, 32), ( 34, 33), ( 35, 34), -( 36, 35), ( 37, 36), ( 38, 37), ( 39, 38), -( 40, 39), ( 41, 40), ( 42, 41), ( 43, 42), -( 44, 43), ( 45, 44), ( 46, 45), ( 47, 46), -( 48, 47), ( 49, 48), ( 50, 49), ( 51, 50), -( 52, 51), ( 53, 52), ( 54, 53), ( 55, 54), -( 56, 55), ( 57, 56), ( 58, 57), ( 59, 58), -( 60, 59), ( 61, 60), ( 62, 61), ( 63, 62), -( 64, 63), ( 65, 64), ( 66, 65), ( 67, 66), -( 68, 67), ( 69, 68), ( 70, 69), ( 71, 70), -( 72, 71), ( 73, 72), ( 74, 73), ( 75, 74), -( 76, 75), ( 77, 76), ( 78, 77), ( 79, 78), -( 80, 79), ( 81, 80), ( 82, 81), ( 83, 82), -( 84, 83), ( 85, 84), ( 86, 85), ( 87, 86), -( 88, 87), ( 89, 88), ( 90, 89), ( 91, 90), -( 92, 91), ( 93, 92), ( 94, 93), ( 95, 94), -( 96, 95), ( 97, 96), ( 98, 97), ( 99, 98), -(100, 99), (101, 100), (102, 101), (103, 102), -(104, 103), (105, 104), (106, 105), (107, 106), -(108, 107), (109, 108), (110, 109), (111, 110), -(112, 111), (113, 112), (114, 113), (115, 114), -(116, 115), (117, 116), (118, 117), (119, 118), -(120, 119), (121, 120), (122, 121), (123, 122), -(124, 123), (125, 124), (126, 125), (127, 126), -(128, 127), (129, 128), (130, 129), (131, 130), -(132, 131), (133, 132), (134, 133), (135, 134), -(136, 135), (137, 136), (138, 137), (139, 138), -(140, 139), (141, 140), (142, 141), (143, 142), -(144, 143), (145, 144), (146, 145), (147, 146), -(148, 147), (149, 148), (150, 149), (151, 150), -(152, 151), (153, 152), (154, 153), (155, 154), -(156, 155), (157, 156), (158, 157), (159, 158), -(160, 159), (161, 160), (162, 161), (163, 162), -(164, 163), (165, 164), (166, 165), (167, 166), -(168, 167), (169, 168), (170, 169), (171, 170), -(172, 171), (173, 172), (174, 173), (175, 174), -(176, 175), (177, 176), (178, 177), (179, 178), -(180, 179), (181, 180), (182, 181), (183, 182), -(184, 183), (185, 184), (186, 185), (187, 186), -(188, 187), (189, 188), (190, 189), (191, 190), -(192, 191), (193, 192), (194, 193), (195, 194), -(196, 195), (197, 196), (198, 197), (199, 198), -(200, 199), (201, 200), (202, 201), (203, 202), -(204, 203), (205, 204), (206, 205), (207, 206), -(208, 207), (209, 208), (210, 209), (211, 210), -(212, 211), (213, 212), (214, 213), (215, 214), -(216, 215), (217, 216), (218, 217), (219, 218), -(220, 219), (221, 220), (222, 221), (223, 222), -(224, 223), (225, 224), (226, 225), (227, 226), -(228, 227), (229, 228), (230, 229), (231, 230), -(232, 231), (233, 232), (234, 233), (235, 234), -(236, 235), (237, 236), (238, 237), (239, 238), -(240, 239), (241, 240), (242, 241), (243, 242), -(244, 243), (245, 244), (246, 245), (247, 246), -(248, 247), (249, 248), (250, 249), (251, 250), -(252, 251), (253, 252), (254, 253), (255, 254); +insert into t1 values(0,0),(1,0),(2,1),(3,2),(4,3),(5,4),(6,5),(7,6), +(8,7),(9,8),(10,9),(11,10),(12,11),(13,12),(14,13),(15,14); delete from t1 where id=0; Got one of the listed errors -delete from t1 where id=255; +delete from t1 where id=15; delete from t1 where id=0; drop table t1; CREATE TABLE t1 (col1 int(1))ENGINE=InnoDB; @@ -1758,10 +1695,10 @@ variable_value 16384 SELECT variable_value - @innodb_rows_deleted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_deleted'; variable_value - @innodb_rows_deleted_orig -311 +71 SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted'; variable_value - @innodb_rows_inserted_orig -1204 +964 SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated'; variable_value - @innodb_rows_updated_orig 866 diff --git a/mysql-test/suite/innodb/r/update-cascade.result b/mysql-test/suite/innodb/r/update-cascade.result index 6ec7a8cae50..a3c8fed931e 100644 --- a/mysql-test/suite/innodb/r/update-cascade.result +++ b/mysql-test/suite/innodb/r/update-cascade.result @@ -176,7 +176,6 @@ set session transaction isolation level read uncommitted; start transaction; select f1, right(f2, 20) as p2 from t1; f1 p2 -10 -------------------- select f1, right(f2, 20) as p2 from t2; f1 p2 select f1, right(f2, 20) as p2 from t3; @@ -266,7 +265,6 @@ set session transaction isolation level read uncommitted; start transaction; select f1, f2 from t1; f1 f2 -2 28 select f1, f2 from t2; f1 f2 select f1, f2 from t3; diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index 3d3466c90f0..862717647b5 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -236,4 +236,44 @@ DELETE FROM t1 WHERE id = 1; SELECT * FROM t2; DROP TABLE t2, t1; +--echo # +--echo # MDEV-15199 Referential integrity broken in ON DELETE CASCADE +--echo # + +CREATE TABLE member (id int AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO member VALUES (1); +CREATE TABLE address ( + id int AUTO_INCREMENT PRIMARY KEY, + member_id int NOT NULL, + KEY address_FI_1 (member_id), + CONSTRAINT address_FK_1 FOREIGN KEY (member_id) REFERENCES member (id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO address VALUES (2,1); +CREATE TABLE payment_method ( + id int AUTO_INCREMENT PRIMARY KEY, + member_id int NOT NULL, + cardholder_address_id int DEFAULT NULL, + KEY payment_method_FI_1 (member_id), + KEY payment_method_FI_2 (cardholder_address_id), + CONSTRAINT payment_method_FK_1 FOREIGN KEY (member_id) REFERENCES member (id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT payment_method_FK_2 FOREIGN KEY (cardholder_address_id) REFERENCES address (id) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO payment_method VALUES (3,1,2); + +BEGIN; +UPDATE member SET id=42; +SELECT * FROM member; +SELECT * FROM address; +SELECT * FROM payment_method; +DELETE FROM member; +COMMIT; +SELECT * FROM member; +SELECT * FROM address; +SELECT * FROM payment_method; + +DROP TABLE payment_method,address,member; + --source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/innodb/t/innodb.test b/mysql-test/suite/innodb/t/innodb.test index 47fb3210807..4ebd4f2fa73 100644 --- a/mysql-test/suite/innodb/t/innodb.test +++ b/mysql-test/suite/innodb/t/innodb.test @@ -1058,82 +1058,18 @@ DROP TABLE t2,t1; # # test for FK cascade depth limit # -call mtr.add_suppression("Cannot delete/update rows with cascading foreign key constraints that exceed max depth of 255. Please drop excessive foreign constraints and try again"); +call mtr.add_suppression("Cannot delete/update rows with cascading foreign key constraints that exceed max depth of 15\\. Please drop excessive foreign constraints and try again"); create table t1( id int primary key, pid int, index(pid), foreign key(pid) references t1(id) on delete cascade) engine=innodb; -insert into t1 values -( 0, 0), ( 1, 0), ( 2, 1), ( 3, 2), -( 4, 3), ( 5, 4), ( 6, 5), ( 7, 6), -( 8, 7), ( 9, 8), ( 10, 9), ( 11, 10), -( 12, 11), ( 13, 12), ( 14, 13), ( 15, 14), -( 16, 15), ( 17, 16), ( 18, 17), ( 19, 18), -( 20, 19), ( 21, 20), ( 22, 21), ( 23, 22), -( 24, 23), ( 25, 24), ( 26, 25), ( 27, 26), -( 28, 27), ( 29, 28), ( 30, 29), ( 31, 30), -( 32, 31), ( 33, 32), ( 34, 33), ( 35, 34), -( 36, 35), ( 37, 36), ( 38, 37), ( 39, 38), -( 40, 39), ( 41, 40), ( 42, 41), ( 43, 42), -( 44, 43), ( 45, 44), ( 46, 45), ( 47, 46), -( 48, 47), ( 49, 48), ( 50, 49), ( 51, 50), -( 52, 51), ( 53, 52), ( 54, 53), ( 55, 54), -( 56, 55), ( 57, 56), ( 58, 57), ( 59, 58), -( 60, 59), ( 61, 60), ( 62, 61), ( 63, 62), -( 64, 63), ( 65, 64), ( 66, 65), ( 67, 66), -( 68, 67), ( 69, 68), ( 70, 69), ( 71, 70), -( 72, 71), ( 73, 72), ( 74, 73), ( 75, 74), -( 76, 75), ( 77, 76), ( 78, 77), ( 79, 78), -( 80, 79), ( 81, 80), ( 82, 81), ( 83, 82), -( 84, 83), ( 85, 84), ( 86, 85), ( 87, 86), -( 88, 87), ( 89, 88), ( 90, 89), ( 91, 90), -( 92, 91), ( 93, 92), ( 94, 93), ( 95, 94), -( 96, 95), ( 97, 96), ( 98, 97), ( 99, 98), -(100, 99), (101, 100), (102, 101), (103, 102), -(104, 103), (105, 104), (106, 105), (107, 106), -(108, 107), (109, 108), (110, 109), (111, 110), -(112, 111), (113, 112), (114, 113), (115, 114), -(116, 115), (117, 116), (118, 117), (119, 118), -(120, 119), (121, 120), (122, 121), (123, 122), -(124, 123), (125, 124), (126, 125), (127, 126), -(128, 127), (129, 128), (130, 129), (131, 130), -(132, 131), (133, 132), (134, 133), (135, 134), -(136, 135), (137, 136), (138, 137), (139, 138), -(140, 139), (141, 140), (142, 141), (143, 142), -(144, 143), (145, 144), (146, 145), (147, 146), -(148, 147), (149, 148), (150, 149), (151, 150), -(152, 151), (153, 152), (154, 153), (155, 154), -(156, 155), (157, 156), (158, 157), (159, 158), -(160, 159), (161, 160), (162, 161), (163, 162), -(164, 163), (165, 164), (166, 165), (167, 166), -(168, 167), (169, 168), (170, 169), (171, 170), -(172, 171), (173, 172), (174, 173), (175, 174), -(176, 175), (177, 176), (178, 177), (179, 178), -(180, 179), (181, 180), (182, 181), (183, 182), -(184, 183), (185, 184), (186, 185), (187, 186), -(188, 187), (189, 188), (190, 189), (191, 190), -(192, 191), (193, 192), (194, 193), (195, 194), -(196, 195), (197, 196), (198, 197), (199, 198), -(200, 199), (201, 200), (202, 201), (203, 202), -(204, 203), (205, 204), (206, 205), (207, 206), -(208, 207), (209, 208), (210, 209), (211, 210), -(212, 211), (213, 212), (214, 213), (215, 214), -(216, 215), (217, 216), (218, 217), (219, 218), -(220, 219), (221, 220), (222, 221), (223, 222), -(224, 223), (225, 224), (226, 225), (227, 226), -(228, 227), (229, 228), (230, 229), (231, 230), -(232, 231), (233, 232), (234, 233), (235, 234), -(236, 235), (237, 236), (238, 237), (239, 238), -(240, 239), (241, 240), (242, 241), (243, 242), -(244, 243), (245, 244), (246, 245), (247, 246), -(248, 247), (249, 248), (250, 249), (251, 250), -(252, 251), (253, 252), (254, 253), (255, 254); +insert into t1 values(0,0),(1,0),(2,1),(3,2),(4,3),(5,4),(6,5),(7,6), +(8,7),(9,8),(10,9),(11,10),(12,11),(13,12),(14,13),(15,14); --error ER_GET_ERRMSG,ER_ROW_IS_REFERENCED_2 delete from t1 where id=0; -delete from t1 where id=255; ---error 0,ER_ROW_IS_REFERENCED_2 +delete from t1 where id=15; delete from t1 where id=0; drop table t1; diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 4ba1556f3b8..7f90cf0ddbe 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -300,7 +300,7 @@ result in recursive cascading calls. This defines the maximum number of such cascading deletes/updates allowed. When exceeded, the delete from parent table will fail, and user has to drop excessive foreign constraint before proceeds. */ -#define FK_MAX_CASCADE_DEL 255 +#define FK_MAX_CASCADE_DEL 15 /**********************************************************************//** Creates a table memory object. diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 85f763f07a9..7a34c025dab 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -297,6 +297,18 @@ row_create_update_node_for_mysql( /*=============================*/ dict_table_t* table, /*!< in: table to update */ mem_heap_t* heap); /*!< in: mem heap from which allocated */ + +/**********************************************************************//** +Does a cascaded delete or set null in a foreign key operation. +@return error code or DB_SUCCESS */ +dberr_t +row_update_cascade_for_mysql( +/*=========================*/ + que_thr_t* thr, /*!< in: query thread */ + upd_node_t* node, /*!< in: update node used in the cascade + or set null operation */ + dict_table_t* table) /*!< in: table where we do the operation */ + MY_ATTRIBUTE((nonnull, warn_unused_result)); /*********************************************************************//** Locks the data dictionary exclusively for performing a table create or other data dictionary modification operation. */ diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index ec7995dd096..a174fd03751 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -32,20 +32,10 @@ Created 12/27/1996 Heikki Tuuri #include "btr0types.h" #include "dict0types.h" #include "trx0types.h" -#include <stack> #include "btr0pcur.h" #include "que0types.h" #include "pars0types.h" -/** The std::deque to store cascade update nodes, that uses mem_heap_t -as allocator. */ -typedef std::deque<upd_node_t*, mem_heap_allocator<upd_node_t*> > - deque_mem_heap_t; - -/** Double-ended queue of update nodes to be processed for cascade -operations */ -typedef deque_mem_heap_t upd_cascade_t; - /*********************************************************************//** Creates an update vector object. @return own: update vector object */ @@ -523,38 +513,12 @@ struct upd_node_t{ dict_foreign_t* foreign;/* NULL or pointer to a foreign key constraint if this update node is used in doing an ON DELETE or ON UPDATE operation */ - - bool cascade_top; - /*!< true if top level in cascade */ - - upd_cascade_t* cascade_upd_nodes; - /*!< Queue of update nodes to handle the - cascade of update and delete operations in an - iterative manner. Their parent/child - relations are properly maintained. All update - nodes point to this same queue. All these - nodes are allocated in heap pointed to by - upd_node_t::cascade_heap. */ - - upd_cascade_t* new_upd_nodes; - /*!< Intermediate list of update nodes in a - cascading update/delete operation. After - processing one update node, this will be - concatenated to cascade_upd_nodes. This extra - list is needed so that retry because of - DB_LOCK_WAIT works corrrectly. */ - - upd_cascade_t* processed_cascades; - /*!< List of processed update nodes in a - cascading update/delete operation. All the - cascade nodes are stored here, so that memory - can be freed. */ - + upd_node_t* cascade_node;/* NULL or an update node template which + is used to implement ON DELETE/UPDATE CASCADE + or ... SET NULL for foreign keys */ mem_heap_t* cascade_heap; - /*!< NULL or a mem heap where cascade_upd_nodes - are created. This heap is owned by the node - that has cascade_top=true. */ - + /*!< NULL or a mem heap where cascade + node is created.*/ sel_node_t* select; /*!< query graph subtree implementing a base table cursor: the rows returned will be updated */ @@ -601,25 +565,8 @@ struct upd_node_t{ sym_node_t* table_sym;/* table node in symbol table */ que_node_t* col_assign_list; /* column assignment list */ - - doc_id_t fts_doc_id; - /* The FTS doc id of the row that is now - pointed to by the pcur. */ - - doc_id_t fts_next_doc_id; - /* The new fts doc id that will be used - in update operation */ - ulint magic_n; -#ifndef DBUG_OFF - /** Print information about this object into the trace log file. */ - void dbug_trace(); - - /** Ensure that the member cascade_upd_nodes has only one update node - for each of the tables. This is useful for testing purposes. */ - void check_cascade_only_once(); -#endif /* !DBUG_OFF */ }; #define UPD_NODE_MAGIC_N 1579975 diff --git a/storage/innobase/que/que0que.cc b/storage/innobase/que/que0que.cc index 5f9f57dc08a..839f7ca0100 100644 --- a/storage/innobase/que/que0que.cc +++ b/storage/innobase/que/que0que.cc @@ -482,20 +482,17 @@ que_graph_free_recursive( case QUE_NODE_UPDATE: upd = static_cast<upd_node_t*>(node); - DBUG_PRINT("que_graph_free_recursive", - ("QUE_NODE_UPDATE: %p, processed_cascades: %p", - upd, upd->processed_cascades)); - if (upd->in_mysql_interface) { btr_pcur_free_for_mysql(upd->pcur); upd->in_mysql_interface = FALSE; } - if (upd->cascade_top) { + que_graph_free_recursive(upd->cascade_node); + + if (upd->cascade_heap) { mem_heap_free(upd->cascade_heap); upd->cascade_heap = NULL; - upd->cascade_top = false; } que_graph_free_recursive(upd->select); diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 75ddac440b9..c771d2edb0e 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -475,9 +475,9 @@ row_ins_cascade_calc_update_vec( type is != 0 */ mem_heap_t* heap, /*!< in: memory heap to use as temporary storage */ - trx_t* trx, /*!< in: update transaction */ - upd_node_t* cascade) /*!< in: cascade update node */ + trx_t* trx) /*!< in: update transaction */ { + upd_node_t* cascade = node->cascade_node; dict_table_t* table = foreign->foreign_table; dict_index_t* index = foreign->foreign_index; upd_t* update; @@ -702,13 +702,13 @@ row_ins_cascade_calc_update_vec( fts_get_next_doc_id(table, next_doc_id); doc_id = fts_update_doc_id(table, ufield, next_doc_id); n_fields_updated++; - cascade->fts_next_doc_id = doc_id; + fts_trx_add_op(trx, table, doc_id, FTS_INSERT, NULL); } else { if (doc_id_updated) { ut_ad(new_doc_id); - cascade->fts_next_doc_id = new_doc_id; + fts_trx_add_op(trx, table, new_doc_id, + FTS_INSERT, NULL); } else { - cascade->fts_next_doc_id = FTS_NULL_DOC_ID; ib::error() << "FTS Doc ID must be updated" " along with FTS indexed column for" " table " << table->name; @@ -1121,18 +1121,14 @@ row_ins_foreign_check_on_constraint( DBUG_RETURN(DB_ROW_IS_REFERENCED); } - cascade = row_create_update_node_for_mysql(table, node->cascade_heap); - que_node_set_parent(cascade, node); - - /* For the cascaded operation, all the update nodes are allocated in - the same heap. All the update nodes will point to the same heap. - This heap is owned by the first update node. And it must be freed - only in the first update node */ - cascade->cascade_heap = node->cascade_heap; - cascade->cascade_upd_nodes = node->cascade_upd_nodes; - cascade->new_upd_nodes = node->new_upd_nodes; - cascade->processed_cascades = node->processed_cascades; + if (node->cascade_node == NULL) { + node->cascade_heap = mem_heap_create(128); + node->cascade_node = row_create_update_node_for_mysql( + table, node->cascade_heap); + que_node_set_parent(node->cascade_node, node); + } + cascade = node->cascade_node; cascade->table = table; cascade->foreign = foreign; if (!(cascade->is_delete = node->is_delete @@ -1274,17 +1270,8 @@ row_ins_foreign_check_on_constraint( if (node->is_delete ? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) : (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { - /* Build the appropriate update vector which sets foreign->n_fields first fields in rec to SQL NULL */ - if (table->fts) { - - /* For the clause ON DELETE SET NULL, the cascade - operation is actually an update operation with the new - values being null. For FTS, this means that the old - values be deleted and no new values to be added.*/ - cascade->fts_next_doc_id = FTS_NULL_DOC_ID; - } update = cascade->update; @@ -1323,7 +1310,7 @@ row_ins_foreign_check_on_constraint( } if (affects_fulltext) { - cascade->fts_doc_id = doc_id; + fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } if (foreign->v_cols != NULL @@ -1353,7 +1340,7 @@ row_ins_foreign_check_on_constraint( } if (affects_fulltext) { - cascade->fts_doc_id = doc_id; + fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } } @@ -1364,7 +1351,7 @@ row_ins_foreign_check_on_constraint( foreign->n_fields first fields in rec to new values */ bool affects_fulltext = row_ins_cascade_calc_update_vec( - node, foreign, cascade->cascade_heap, trx, cascade); + node, foreign, tmp_heap, trx); if (foreign->v_cols && !foreign->v_cols->empty()) { row_ins_foreign_fill_virtual( @@ -1403,7 +1390,7 @@ row_ins_foreign_check_on_constraint( /* Mark the old Doc ID as deleted */ if (affects_fulltext) { ut_ad(table->fts); - cascade->fts_doc_id = doc_id; + fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } } @@ -1432,11 +1419,8 @@ row_ins_foreign_check_on_constraint( "WSREP: foreign key append failed: %d\n", err); } else #endif /* WITH_WSREP */ - node->new_upd_nodes->push_back(cascade); - - my_atomic_addlint(&table->n_foreign_key_checks_running, 1); - - ut_ad(foreign->foreign_table->n_foreign_key_checks_running > 0); + err = row_update_cascade_for_mysql(thr, cascade, + foreign->foreign_table); /* Release the data dictionary latch for a while, so that we do not starve other threads from doing CREATE TABLE etc. if we have a huge @@ -1463,7 +1447,6 @@ row_ins_foreign_check_on_constraint( DBUG_RETURN(err); nonstandard_exit_func: - que_graph_free_recursive(cascade); if (tmp_heap) { mem_heap_free(tmp_heap); @@ -1948,6 +1931,12 @@ row_ins_check_foreign_constraints( row_mysql_freeze_data_dictionary(trx); } + if (referenced_table) { + my_atomic_addlint( + &foreign->foreign_table + ->n_foreign_key_checks_running, 1); + } + /* NOTE that if the thread ends up waiting for a lock we will release dict_operation_lock temporarily! But the counter on the table protects the referenced @@ -1956,6 +1945,12 @@ row_ins_check_foreign_constraints( err = row_ins_check_foreign_constraint( TRUE, foreign, table, entry, thr); + if (referenced_table) { + my_atomic_addlint( + &foreign->foreign_table + ->n_foreign_key_checks_running, -1); + } + if (got_s_lock) { row_mysql_unfreeze_data_dictionary(trx); } diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index b9f8ec3376a..2f41d322681 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -1598,8 +1598,6 @@ row_create_update_node_for_mysql( node->table_sym = NULL; node->col_assign_list = NULL; - node->fts_doc_id = FTS_NULL_DOC_ID; - node->fts_next_doc_id = UINT64_UNDEFINED; DBUG_RETURN(node); } @@ -1647,9 +1645,9 @@ row_fts_do_update( doc_id_t old_doc_id, /* in: old document id */ doc_id_t new_doc_id) /* in: new document id */ { - fts_trx_add_op(trx, table, old_doc_id, FTS_DELETE, NULL); - - if (new_doc_id != FTS_NULL_DOC_ID) { + if(trx->fts_next_doc_id) { + fts_trx_add_op(trx, table, old_doc_id, FTS_DELETE, NULL); + if(new_doc_id != FTS_NULL_DOC_ID) fts_trx_add_op(trx, table, new_doc_id, FTS_INSERT, NULL); } } @@ -1661,24 +1659,30 @@ static dberr_t row_fts_update_or_delete( /*=====================*/ - trx_t* trx, - upd_node_t* node) /* in: prebuilt struct in MySQL + row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL handle */ { - dict_table_t* table = node->table; - doc_id_t old_doc_id = node->fts_doc_id; + trx_t* trx = prebuilt->trx; + dict_table_t* table = prebuilt->table; + upd_node_t* node = prebuilt->upd_node; + doc_id_t old_doc_id = prebuilt->fts_doc_id; + DBUG_ENTER("row_fts_update_or_delete"); - ut_a(dict_table_has_fts_index(node->table)); + ut_a(dict_table_has_fts_index(prebuilt->table)); /* Deletes are simple; get them out of the way first. */ if (node->is_delete) { /* A delete affects all FTS indexes, so we pass NULL */ fts_trx_add_op(trx, table, old_doc_id, FTS_DELETE, NULL); } else { - doc_id_t new_doc_id = node->fts_next_doc_id; - ut_ad(new_doc_id != UINT64_UNDEFINED); + doc_id_t new_doc_id; + new_doc_id = fts_read_doc_id((byte*) &trx->fts_next_doc_id); + if (new_doc_id == 0) { + ib::error() << "InnoDB FTS: Doc ID cannot be 0"; + return(DB_FTS_INVALID_DOCID); + } row_fts_do_update(trx, table, old_doc_id, new_doc_id); } @@ -1727,19 +1731,6 @@ init_fts_doc_id_for_ref( } } -/* A functor for decrementing counters. */ -class ib_dec_counter { -public: - ib_dec_counter() {} - - void operator() (upd_node_t* node) { - ut_ad(node->table->n_foreign_key_checks_running > 0); - my_atomic_addlint( - &node->table->n_foreign_key_checks_running, -1); - } -}; - - /** Does an update or delete of a row for MySQL. @param[in,out] prebuilt prebuilt struct in MySQL handle @return error code or DB_SUCCESS */ @@ -1749,15 +1740,11 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) trx_savept_t savept; dberr_t err; que_thr_t* thr; - ibool was_lock_wait; dict_index_t* clust_index; upd_node_t* node; dict_table_t* table = prebuilt->table; trx_t* trx = prebuilt->trx; ulint fk_depth = 0; - upd_cascade_t* cascade_upd_nodes; - upd_cascade_t* new_upd_nodes; - upd_cascade_t* processed_cascades; bool got_s_lock = false; DBUG_ENTER("row_update_for_mysql"); @@ -1802,26 +1789,6 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) const bool is_delete = node->is_delete; ut_ad(node->table == table); - if (node->cascade_heap) { - mem_heap_empty(node->cascade_heap); - } else { - node->cascade_heap = mem_heap_create(128); - } - - mem_heap_allocator<upd_node_t*> mem_heap_ator(node->cascade_heap); - - cascade_upd_nodes = new - (mem_heap_ator.allocate(sizeof(upd_cascade_t))) - upd_cascade_t(deque_mem_heap_t(mem_heap_ator)); - - new_upd_nodes = new - (mem_heap_ator.allocate(sizeof(upd_cascade_t))) - upd_cascade_t(deque_mem_heap_t(mem_heap_ator)); - - processed_cascades = new - (mem_heap_ator.allocate(sizeof(upd_cascade_t))) - upd_cascade_t(deque_mem_heap_t(mem_heap_ator)); - clust_index = dict_table_get_first_index(table); if (prebuilt->pcur->btr_cur.index == clust_index) { @@ -1846,121 +1813,52 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) node->state = UPD_NODE_UPDATE_CLUSTERED; - node->cascade_top = true; - node->cascade_upd_nodes = cascade_upd_nodes; - node->new_upd_nodes = new_upd_nodes; - node->processed_cascades = processed_cascades; - node->fts_doc_id = prebuilt->fts_doc_id; - - if (trx->fts_next_doc_id != UINT64_UNDEFINED) { - node->fts_next_doc_id = fts_read_doc_id( - (byte*) &trx->fts_next_doc_id); - } else { - node->fts_next_doc_id = UINT64_UNDEFINED; - } - ut_ad(!prebuilt->sql_stat_start); que_thr_move_to_run_state_for_mysql(thr, trx); - thr->fk_cascade_depth = 0; - -run_again: - if (thr->fk_cascade_depth == 1 && trx->dict_operation_lock_mode == 0) { - got_s_lock = true; - row_mysql_freeze_data_dictionary(trx); - } - - thr->run_node = node; - thr->prev_node = node; - - row_upd_step(thr); + for (;;) { + thr->run_node = node; + thr->prev_node = node; + thr->fk_cascade_depth = 0; - DBUG_EXECUTE_IF("dml_cascade_only_once", node->check_cascade_only_once();); + row_upd_step(thr); - err = trx->error_state; + err = trx->error_state; - if (err != DB_SUCCESS) { + if (err == DB_SUCCESS) { + break; + } que_thr_stop_for_mysql(thr); if (err == DB_RECORD_NOT_FOUND) { trx->error_state = DB_SUCCESS; - trx->op_info = ""; - - if (thr->fk_cascade_depth > 0) { - que_graph_free_recursive(node); - } goto error; } - /* Since reporting a plain "duplicate key" error message to - the user in cases where a long CASCADE operation would lead - to a duplicate key in some other table is very confusing, - map duplicate key errors resulting from FK constraints to a - separate error code. */ - if (err == DB_DUPLICATE_KEY && thr->fk_cascade_depth > 0) { - err = DB_FOREIGN_DUPLICATE_KEY; - trx->error_state = err; - } - thr->lock_state= QUE_THR_LOCK_ROW; DEBUG_SYNC(trx->mysql_thd, "row_update_for_mysql_error"); - was_lock_wait = row_mysql_handle_errors(&err, trx, thr, - &savept); + bool was_lock_wait = row_mysql_handle_errors( + &err, trx, thr, &savept); thr->lock_state= QUE_THR_LOCK_NOLOCK; - if (was_lock_wait) { - std::for_each(new_upd_nodes->begin(), - new_upd_nodes->end(), - ib_dec_counter()); - std::for_each(new_upd_nodes->begin(), - new_upd_nodes->end(), - que_graph_free_recursive); - node->new_upd_nodes->clear(); - goto run_again; - } - - trx->op_info = ""; - - if (thr->fk_cascade_depth > 0) { - que_graph_free_recursive(node); + if (!was_lock_wait) { + goto error; } - goto error; - } else { - - std::copy(node->new_upd_nodes->begin(), - node->new_upd_nodes->end(), - std::back_inserter(*node->cascade_upd_nodes)); - - node->new_upd_nodes->clear(); - } - - if (dict_table_has_fts_index(node->table) - && node->fts_doc_id != FTS_NULL_DOC_ID - && node->fts_next_doc_id != UINT64_UNDEFINED) { - err = row_fts_update_or_delete(trx, node); - ut_a(err == DB_SUCCESS); - } - - if (thr->fk_cascade_depth > 0) { - /* Processing cascade operation */ - ut_ad(node->table->n_foreign_key_checks_running > 0); - my_atomic_addlint( - &node->table->n_foreign_key_checks_running, -1); - node->processed_cascades->push_back(node); } - if (!cascade_upd_nodes->empty()) { - DEBUG_SYNC_C("foreign_constraint_update_cascade"); - node = cascade_upd_nodes->front(); - node->cascade_upd_nodes = cascade_upd_nodes; - cascade_upd_nodes->pop_front(); - thr->fk_cascade_depth++; + que_thr_stop_for_mysql_no_error(thr, trx); - goto run_again; + if (dict_table_has_fts_index(table) + && trx->fts_next_doc_id != UINT64_UNDEFINED) { + err = row_fts_update_or_delete(prebuilt); + if (UNIV_UNLIKELY(err != DB_SUCCESS)) { + ut_ad(!"unexpected error"); + goto error; + } } /* Completed cascading operations (if any) */ @@ -1968,45 +1866,10 @@ run_again: row_mysql_unfreeze_data_dictionary(trx); } - thr->fk_cascade_depth = 0; - - /* Update the statistics of each involved table - only after completing all operations, including - FOREIGN KEY...ON...CASCADE|SET NULL. */ bool update_statistics; + ut_ad(node->is_delete == is_delete); - for (upd_cascade_t::iterator i = processed_cascades->begin(); - i != processed_cascades->end(); - ++i) { - - node = *i; - - if (node->is_delete) { - /* Not protected by dict_table_stats_lock() for - performance reasons, we would rather get garbage - in stat_n_rows (which is just an estimate anyway) - than protecting the following code with a latch. */ - dict_table_n_rows_dec(node->table); - - update_statistics = !srv_stats_include_delete_marked; - srv_stats.n_rows_deleted.inc(size_t(trx->id)); - } else { - update_statistics - = !(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE); - srv_stats.n_rows_updated.inc(size_t(trx->id)); - } - - if (update_statistics) { - dict_stats_update_if_needed(node->table); - } else { - /* Always update the table modification counter. */ - node->table->stat_modified_counter++; - } - - que_graph_free_recursive(node); - } - - if (is_delete) { + if (/*node->*/is_delete) { /* Not protected by dict_table_stats_lock() for performance reasons, we would rather get garbage in stat_n_rows (which is just an estimate anyway) than protecting the following code @@ -2040,45 +1903,14 @@ run_again: trx->op_info = ""; - que_thr_stop_for_mysql_no_error(thr, trx); - - DBUG_ASSERT(cascade_upd_nodes->empty()); - DBUG_RETURN(err); error: + trx->op_info = ""; if (got_s_lock) { row_mysql_unfreeze_data_dictionary(trx); } - if (thr->fk_cascade_depth > 0) { - ut_ad(node->table->n_foreign_key_checks_running > 0); - my_atomic_addlint( - &node->table->n_foreign_key_checks_running, -1); - thr->fk_cascade_depth = 0; - } - - /* Reset the table->n_foreign_key_checks_running counter */ - std::for_each(cascade_upd_nodes->begin(), - cascade_upd_nodes->end(), - ib_dec_counter()); - - std::for_each(new_upd_nodes->begin(), - new_upd_nodes->end(), - ib_dec_counter()); - - std::for_each(cascade_upd_nodes->begin(), - cascade_upd_nodes->end(), - que_graph_free_recursive); - - std::for_each(new_upd_nodes->begin(), - new_upd_nodes->end(), - que_graph_free_recursive); - - std::for_each(processed_cascades->begin(), - processed_cascades->end(), - que_graph_free_recursive); - DBUG_RETURN(err); } @@ -2245,6 +2077,89 @@ row_mysql_unfreeze_data_dictionary( trx->dict_operation_lock_mode = 0; } +/**********************************************************************//** +Does a cascaded delete or set null in a foreign key operation. +@return error code or DB_SUCCESS */ +dberr_t +row_update_cascade_for_mysql( +/*=========================*/ + que_thr_t* thr, /*!< in: query thread */ + upd_node_t* node, /*!< in: update node used in the cascade + or set null operation */ + dict_table_t* table) /*!< in: table where we do the operation */ +{ + /* Increment fk_cascade_depth to record the recursive call depth on + a single update/delete that affects multiple tables chained + together with foreign key relations. */ + + if (++thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) { + return(DB_FOREIGN_EXCEED_MAX_CASCADE); + } + + const trx_t* trx = thr_get_trx(thr); + + for (;;) { + thr->run_node = node; + thr->prev_node = node; + + DEBUG_SYNC_C("foreign_constraint_update_cascade"); + { + TABLE *mysql_table = thr->prebuilt->m_mysql_table; + thr->prebuilt->m_mysql_table = NULL; + row_upd_step(thr); + thr->prebuilt->m_mysql_table = mysql_table; + } + + switch (trx->error_state) { + case DB_LOCK_WAIT: + que_thr_stop_for_mysql(thr); + lock_wait_suspend_thread(thr); + + if (trx->error_state == DB_SUCCESS) { + continue; + } + + /* fall through */ + default: + /* Other errors are handled for the parent node. */ + thr->fk_cascade_depth = 0; + return trx->error_state; + + case DB_SUCCESS: + thr->fk_cascade_depth = 0; + bool stats; + + if (node->is_delete) { + /* Not protected by + dict_table_stats_lock() for + performance reasons, we would rather + get garbage in stat_n_rows (which is + just an estimate anyway) than + protecting the following code with a + latch. */ + dict_table_n_rows_dec(node->table); + + stats = !srv_stats_include_delete_marked; + srv_stats.n_rows_deleted.inc(size_t(trx->id)); + } else { + stats = !(node->cmpl_info + & UPD_NODE_NO_ORD_CHANGE); + srv_stats.n_rows_updated.inc(size_t(trx->id)); + } + + if (stats) { + dict_stats_update_if_needed(node->table); + } else { + /* Always update the table + modification counter. */ + node->table->stat_modified_counter++; + } + + return(DB_SUCCESS); + } + } +} + /*********************************************************************//** Locks the data dictionary exclusively for performing a table create or other data dictionary modification operation. */ diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index ed03af11110..5009ac02408 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -319,9 +319,20 @@ row_upd_check_references_constraints( But the counter on the table protects 'foreign' from being dropped while the check is running. */ + if (foreign_table) { + my_atomic_addlint( + &foreign_table->n_foreign_key_checks_running, + 1); + } + err = row_ins_check_foreign_constraint( FALSE, foreign, table, entry, thr); + if (foreign_table) { + my_atomic_addlint( + &foreign_table->n_foreign_key_checks_running, + -1); + } if (ref_table != NULL) { dict_table_close(ref_table, FALSE, FALSE); } @@ -342,11 +353,6 @@ func_exit: mem_heap_free(heap); DEBUG_SYNC_C("foreign_constraint_check_for_update_done"); - - DBUG_EXECUTE_IF("row_upd_cascade_lock_wait_err", - err = DB_LOCK_WAIT; - DBUG_SET("-d,row_upd_cascade_lock_wait_err");); - DBUG_RETURN(err); } @@ -469,9 +475,8 @@ wsrep_must_process_fk(const upd_node_t* node, const trx_t* trx) return false; } - const upd_node_t* parent = static_cast<const upd_node_t*>(node->common.parent); - - return parent->cascade_upd_nodes->empty(); + return static_cast<upd_node_t*>(node->common.parent)->cascade_node + == node; } #endif /* WITH_WSREP */ @@ -2151,7 +2156,6 @@ row_upd_store_v_row( cascade update. And virtual column can't be affected, so it is Ok to set it to NULL */ - ut_ad(!node->cascade_top); dfield_set_null(dfield); } else { dfield_t* vfield @@ -3426,56 +3430,3 @@ error_handling: DBUG_RETURN(thr); } - -#ifndef DBUG_OFF - -/** Ensure that the member cascade_upd_nodes has only one update node -for each of the tables. This is useful for testing purposes. */ -void upd_node_t::check_cascade_only_once() -{ - DBUG_ENTER("upd_node_t::check_cascade_only_once"); - - dbug_trace(); - - for (upd_cascade_t::const_iterator i = cascade_upd_nodes->begin(); - i != cascade_upd_nodes->end(); ++i) { - - const upd_node_t* update_node = *i; - std::string table_name(update_node->table->name.m_name); - ulint count = 0; - - for (upd_cascade_t::const_iterator j - = cascade_upd_nodes->begin(); - j != cascade_upd_nodes->end(); ++j) { - - const upd_node_t* node = *j; - - if (table_name == node->table->name.m_name) { - DBUG_ASSERT(count++ == 0); - } - } - } - - DBUG_VOID_RETURN; -} - -/** Print information about this object into the trace log file. */ -void upd_node_t::dbug_trace() -{ - DBUG_ENTER("upd_node_t::dbug_trace"); - - for (upd_cascade_t::const_iterator i = cascade_upd_nodes->begin(); - i != cascade_upd_nodes->end(); ++i) { - DBUG_LOG("upd_node_t", "cascade_upd_nodes: Cascade to table: " - << (*i)->table->name); - } - - for (upd_cascade_t::const_iterator j = new_upd_nodes->begin(); - j != new_upd_nodes->end(); ++j) { - DBUG_LOG("upd_node_t", "new_upd_nodes: Cascade to table: " - << (*j)->table->name); - } - - DBUG_VOID_RETURN; -} -#endif /* !DBUG_OFF */ |