Commit 10590dd3 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-15199 Referential integrity broken in ON DELETE CASCADE

MDEV-14222 Unnecessary 'cascade' memory allocation for every updated row
when there is no FOREIGN KEY

This reverts the MySQL 5.7.2 change
https://github.com/mysql/mysql-server/commit/377774689bf6a16af74182753fe950d514c2c6dd
which introduced these problems. MariaDB 10.2.2 inherited these problems
in commit 2e814d47.

The FOREIGN KEY CASCADE and SET NULL operations implemented as
procedural recursion are consuming more than 8 kilobytes of stack
(9 stack frames) per iteration in a non-debug GNU/Linux AMD64 build.
This is why we need to limit the maximum recursion depth to 15 steps
instead of the 255 that it used to be in MySQL 5.7 and MariaDB 10.2.

A corresponding change was made in MySQL 5.7.21 in
https://github.com/mysql/mysql-server/commit/7b26dc98a624d5cdf79cd5eee455cb03e3bccb5a
parent 5d7e9fd4
......@@ -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;
......@@ -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;
......
......@@ -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
......
......@@ -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;
......
......@@ -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
......@@ -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;
......
......@@ -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.
......
......@@ -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. */
......
......@@ -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
......
......@@ -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);
......
......@@ -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);
}
......
......@@ -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 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
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 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
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. */
......
......@@ -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 @@ row_upd_check_references_constraints(
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 @@ row_upd_step(
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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment