Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/mysql/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ int thd_in_lock_tables(const MYSQL_THD thd);
int thd_tablespace_op(const MYSQL_THD thd);
long long thd_test_options(const MYSQL_THD thd, long long test_options);
int thd_sql_command(const MYSQL_THD thd);

struct DDL_options_st;
struct DDL_options_st *thd_ddl_options(const MYSQL_THD thd);
void thd_storage_lock_wait(MYSQL_THD thd, long long value);
Expand Down
47 changes: 47 additions & 0 deletions include/mysql/service_thd_binlog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* Copyright (c) 2026, 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 Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

struct TABLE;
class Event_log;
class binlog_cache_data;

int thd_is_current_stmt_binlog_format_row(const MYSQL_THD thd);

int thd_rpl_use_binlog_events_for_fk_cascade(const MYSQL_THD thd);

void thd_binlog_mark_fk_cascade_events(MYSQL_THD thd);

int thd_binlog_update_row(MYSQL_THD thd, struct TABLE *table,
class Event_log *bin_log,
class binlog_cache_data *cache_data,
int is_trans, unsigned long row_image,
const unsigned char *before_record,
const unsigned char *after_record);

int thd_binlog_delete_row(MYSQL_THD thd, struct TABLE *table,
class Event_log *bin_log,
class binlog_cache_data *cache_data,
int is_trans, unsigned long row_image,
const unsigned char *before_record);

#ifdef __cplusplus
}
#endif
2 changes: 2 additions & 0 deletions include/mysql/service_wsrep.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ extern struct wsrep_service_st {
#define wsrep_report_bf_lock_wait(T,I) wsrep_service->wsrep_report_bf_lock_wait(T,I)
#define wsrep_thd_set_PA_unsafe(T) wsrep_service->wsrep_thd_set_PA_unsafe_func(T)
#define wsrep_get_domain_id(T) wsrep_service->wsrep_get_domain_id_func(T)
#define wsrep_emulate_binlog(T) wsrep_service->wsrep_emulate_binlog_func(T)
#else

#define MYSQL_SERVICE_WSREP_STATIC_INCLUDED
Expand Down Expand Up @@ -265,5 +266,6 @@ extern "C" void wsrep_report_bf_lock_wait(const THD *thd,
/* declare parallel applying unsafety for the THD */
extern "C" void wsrep_thd_set_PA_unsafe(MYSQL_THD thd);
extern "C" uint32 wsrep_get_domain_id();
extern "C" my_bool wsrep_emulate_binlog(const MYSQL_THD thd);
#endif
#endif /* MYSQL_SERVICE_WSREP_INCLUDED */
4 changes: 4 additions & 0 deletions mysql-test/main/mysqld--help.result
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,9 @@ The following specify which files/extra groups are read (specified before remain
play when stop slave is executed
--rpl-semi-sync-slave-trace-level=#
The tracing level for semi-sync replication
--rpl-use-binlog-events-for-fk-cascade
If enabled, the master will write row events for foreign
key cascade operations
--safe-mode Skip some optimize stages (for testing). Deprecated, will
be removed in a future release.
--safe-user-create Don't allow new user creation by the user who has no
Expand Down Expand Up @@ -2096,6 +2099,7 @@ rpl-semi-sync-slave-delay-master FALSE
rpl-semi-sync-slave-enabled FALSE
rpl-semi-sync-slave-kill-conn-timeout 5
rpl-semi-sync-slave-trace-level 32
rpl-use-binlog-events-for-fk-cascade FALSE
safe-user-create FALSE
secure-file-priv (No default value)
secure-timestamp NO
Expand Down
85 changes: 85 additions & 0 deletions mysql-test/suite/rpl/r/rpl_fk_cascade_binlog_row.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
include/master-slave.inc
[connection master]
SET @old_binlog_format := @@session.binlog_format;
SET @old_default_storage_engine := @@session.default_storage_engine;
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
SET SESSION default_storage_engine=InnoDB;
SET SESSION binlog_format=ROW;
SET SESSION rpl_use_binlog_events_for_fk_cascade=0;
CREATE TABLE p (
id INT PRIMARY KEY,
v INT
) ENGINE=InnoDB;
CREATE TABLE c (
id INT PRIMARY KEY,
pid INT,
v INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 10);
INSERT INTO c VALUES (100, 1, 20);
UPDATE p SET id=2 WHERE id=1;
connection slave;
connection slave;
SELECT pid FROM c ORDER BY id;
pid
2
connection master;
DELETE FROM p WHERE id=2;
connection slave;
connection slave;
SELECT COUNT(*) FROM p;
COUNT(*)
0
SELECT COUNT(*) FROM c;
COUNT(*)
0
connection master;
FLUSH BINARY LOGS;
NOT FOUND /### UPDATE `test`.`c`/ in fk_cascade_binlog_row_off.sql
NOT FOUND /### DELETE FROM `test`.`c`/ in fk_cascade_binlog_row_off.sql
DROP TABLE c, p;
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
CREATE TABLE p (
id INT PRIMARY KEY,
v INT
) ENGINE=InnoDB;
CREATE TABLE c (
id INT PRIMARY KEY,
pid INT,
v INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 10);
INSERT INTO c VALUES (100, 1, 20);
UPDATE p SET id=2 WHERE id=1;
connection slave;
connection slave;
SELECT pid FROM c ORDER BY id;
pid
2
connection master;
DELETE FROM p WHERE id=2;
connection slave;
connection slave;
SELECT COUNT(*) FROM p;
COUNT(*)
0
SELECT COUNT(*) FROM c;
COUNT(*)
0
connection master;
FLUSH BINARY LOGS;
FOUND 1 /### UPDATE `test`.`c`/ in fk_cascade_binlog_row_on.sql
FOUND 1 /### DELETE FROM `test`.`c`/ in fk_cascade_binlog_row_on.sql
DROP TABLE c, p;
SET SESSION default_storage_engine=@old_default_storage_engine;
SET SESSION binlog_format=@old_binlog_format;
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
include/rpl_end.inc
140 changes: 140 additions & 0 deletions mysql-test/suite/rpl/r/rpl_fk_cascade_binlog_row_rollback.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
include/master-slave.inc
[connection master]
SET @old_binlog_format := @@session.binlog_format;
SET @old_default_storage_engine := @@session.default_storage_engine;
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
SET SESSION default_storage_engine=InnoDB;
SET SESSION binlog_format=ROW;
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
CREATE TABLE p (
id INT PRIMARY KEY,
v INT
) ENGINE=InnoDB;
CREATE TABLE c (
id INT PRIMARY KEY,
pid INT,
v INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 10);
INSERT INTO c VALUES (100, 1, 20);
connection slave;
connection master;
#
# Phase 1: full transaction ROLLBACK
#
FLUSH BINARY LOGS;
BEGIN;
UPDATE p SET id=2 WHERE id=1;
ROLLBACK;
# master: parent and child unchanged
SELECT * FROM p ORDER BY id;
id v
1 10
SELECT * FROM c ORDER BY id;
id pid v
100 1 20
connection slave;
connection slave;
# slave: child unchanged
SELECT * FROM c ORDER BY id;
id pid v
100 1 20
connection master;
FLUSH BINARY LOGS;
# no cascade child row event must be present
NOT FOUND /### UPDATE `test`.`c`/ in fk_cascade_rollback_full.sql
#
# Phase 2: ROLLBACK TO SAVEPOINT (surviving work before the savepoint)
#
FLUSH BINARY LOGS;
BEGIN;
INSERT INTO p VALUES (3, 30);
SAVEPOINT sp1;
UPDATE p SET id=2 WHERE id=1;
ROLLBACK TO SAVEPOINT sp1;
COMMIT;
# master: cascade undone, INSERT before savepoint kept
SELECT * FROM p ORDER BY id;
id v
1 10
3 30
SELECT * FROM c ORDER BY id;
id pid v
100 1 20
connection slave;
connection slave;
# slave: child unchanged, parent has surviving row
SELECT * FROM p ORDER BY id;
id v
1 10
3 30
SELECT * FROM c ORDER BY id;
id pid v
100 1 20
connection master;
FLUSH BINARY LOGS;
# cascade child row event must be absent
NOT FOUND /### UPDATE `test`.`c`/ in fk_cascade_rollback_sp.sql
# surviving INSERT before the savepoint must be replicated
FOUND 1 /### INSERT INTO `test`.`p`/ in fk_cascade_rollback_sp.sql
DROP TABLE c, p;
#
# Phase 3: statement rollback, transaction continues and commits
#
CREATE TABLE p (
id INT PRIMARY KEY,
u INT,
UNIQUE KEY uq_u(u)
) ENGINE=InnoDB;
CREATE TABLE c (
cid INT PRIMARY KEY,
pid INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 100), (2, 200);
INSERT INTO c VALUES (11, 1);
connection slave;
connection master;
FLUSH BINARY LOGS;
BEGIN;
UPDATE p SET id=id+10, u=100 WHERE id IN (1,2) ORDER BY id;
ERROR 23000: Duplicate entry '100' for key 'uq_u'
INSERT INTO p VALUES (9, 900);
COMMIT;
# master: failed UPDATE rolled back, INSERT committed
SELECT * FROM p ORDER BY id;
id u
1 100
2 200
9 900
SELECT * FROM c ORDER BY cid;
cid pid
11 1
connection slave;
connection slave;
# slave: child unchanged, parent has committed row
SELECT * FROM p ORDER BY id;
id u
1 100
2 200
9 900
SELECT * FROM c ORDER BY cid;
cid pid
11 1
connection master;
FLUSH BINARY LOGS;
# discarded cascade child row event must be absent
NOT FOUND /### UPDATE `test`.`c`/ in fk_cascade_rollback_stmt.sql
# committed INSERT must be replicated
FOUND 1 /### INSERT INTO `test`.`p`/ in fk_cascade_rollback_stmt.sql
DROP TABLE c, p;
SET SESSION default_storage_engine=@old_default_storage_engine;
SET SESSION binlog_format=@old_binlog_format;
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
include/rpl_end.inc
61 changes: 61 additions & 0 deletions mysql-test/suite/rpl/r/rpl_fk_set_null_binlog_row.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
include/master-slave.inc
[connection master]
SET @old_binlog_format := @@session.binlog_format;
SET @old_default_storage_engine := @@session.default_storage_engine;
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
SET SESSION default_storage_engine=InnoDB;
SET SESSION binlog_format=ROW;
SET SESSION rpl_use_binlog_events_for_fk_cascade=0;
CREATE TABLE p (
id INT PRIMARY KEY,
v INT
) ENGINE=InnoDB;
CREATE TABLE c (
id INT PRIMARY KEY,
pid INT NULL,
v INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON DELETE SET NULL
ON UPDATE SET NULL
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 10);
INSERT INTO c VALUES (100, 1, 20);
UPDATE p SET id=2 WHERE id=1;
connection slave;
SELECT pid FROM c ORDER BY id;
pid
NULL
connection master;
FLUSH BINARY LOGS;
NOT FOUND /### UPDATE `test`.`c`/ in fk_set_null_binlog_row_off.sql
DROP TABLE c, p;
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
CREATE TABLE p (
id INT PRIMARY KEY,
v INT
) ENGINE=InnoDB;
CREATE TABLE c (
id INT PRIMARY KEY,
pid INT NULL,
v INT,
KEY(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
ON DELETE SET NULL
ON UPDATE SET NULL
) ENGINE=InnoDB;
INSERT INTO p VALUES (1, 10);
INSERT INTO c VALUES (100, 1, 20);
UPDATE p SET id=2 WHERE id=1;
connection slave;
SELECT pid FROM c ORDER BY id;
pid
NULL
connection master;
FLUSH BINARY LOGS;
FOUND 1 /### UPDATE `test`.`c`/ in fk_set_null_binlog_row_on.sql
DROP TABLE c, p;
SET SESSION default_storage_engine=@old_default_storage_engine;
SET SESSION binlog_format=@old_binlog_format;
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
include/rpl_end.inc
Loading