From 8ee69168a8a5c72a2859cbdfef4d638f998b5622 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 22 Jul 2026 18:42:22 +0200 Subject: [PATCH 001/150] cleanup: silence (wrong) maybe-uninitialized warning gcc 15.2.1 with -Og: filamdbf.cpp:355:33: error: 'tfp' may be used uninitialized [-Werror=maybe-uninitialized] 355 | } else if (fread(tfp, HEADLEN, 1, infile) != 1) { | ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~ --- storage/connect/filamdbf.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/storage/connect/filamdbf.cpp b/storage/connect/filamdbf.cpp index e2f90694989..45b26fa5940 100644 --- a/storage/connect/filamdbf.cpp +++ b/storage/connect/filamdbf.cpp @@ -246,7 +246,7 @@ PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, PTOS topt, bool info) bool bad, mul; PCSZ target, pwd; DBFHEADER mainhead, *hp = NULL; - DESCRIPTOR thisfield, *tfp; + DESCRIPTOR thisfield, *tfp= &thisfield; FILE *infile = NULL; UNZIPUTL *zutp = NULL; PQRYRES qrp; @@ -308,7 +308,6 @@ PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, PTOS topt, bool info) return NULL; } // endif dbfhead - tfp = &thisfield; } // endif zipped /************************************************************************/ From 728a4e7b5d4c943b7e0b92f94068784c368da91d Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Tue, 14 Jul 2026 21:11:28 -0600 Subject: [PATCH 002/150] MDEV-40298 Use-After-Free when SQL Thread stops in the middle of SHOW SLAVE STATUS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MDEV-36287 acknowledged that SHOW SLAVE STATUS needed to acquire a mutex lock before accessing the SQL Thread’s THD, as otherwise it may access invalid memory if a concurrent STOP SLAVE deletes the THD. But it missed that the SQL Thread’s mutex is `mi->rli.run_lock`, not `mi->run_lock`, so the bug was still not fixed. This commit fills in the oversight by acquiring the correct corresponding lock for each of `Slave_IO_State` & `Slave_SQL_State`. Reviewed-by: Kristian Nielsen --- mysql-test/main/rli_run_lock.result | 18 ++++++++++++ mysql-test/main/rli_run_lock.test | 43 +++++++++++++++++++++++++++++ sql/slave.cc | 5 +++- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 mysql-test/main/rli_run_lock.result create mode 100644 mysql-test/main/rli_run_lock.test diff --git a/mysql-test/main/rli_run_lock.result b/mysql-test/main/rli_run_lock.result new file mode 100644 index 00000000000..e2776a356d0 --- /dev/null +++ b/mysql-test/main/rli_run_lock.result @@ -0,0 +1,18 @@ +CHANGE MASTER TO master_host='127.0.0.1', master_user='root'; +START SLAVE SQL_THREAD; +include/wait_for_slave_sql_to_start.inc +SET @@SESSION.debug_sync= +'hold_sss_with_run_lock SIGNAL sss_got_run_lock WAIT_FOR sss_continue'; +SHOW SLAVE STATUS; +connect stopper, 127.0.0.1, root, , , $SERVER_MYPORT_1; +SET @@SESSION.debug_sync= 'now WAIT_FOR sss_got_run_lock'; +STOP SLAVE SQL_THREAD; +connect continuer, 127.0.0.1, root, , , $SERVER_MYPORT_1; +SET @@SESSION.debug_sync= 'now SIGNAL sss_continue'; +connection stopper; +disconnect continuer; +connection default; +disconnect stopper; +SET @@SESSION.debug_sync= RESET; +include/wait_for_slave_sql_to_stop.inc +RESET SLAVE ALL; diff --git a/mysql-test/main/rli_run_lock.test b/mysql-test/main/rli_run_lock.test new file mode 100644 index 00000000000..ca8eaf4d929 --- /dev/null +++ b/mysql-test/main/rli_run_lock.test @@ -0,0 +1,43 @@ +# MDEV-40298 +# Use-After-Free when SQL Thread stops in the middle of SHOW SLAVE STATUS +# +# Test that the correct synchronization is performed + +--source include/have_debug_sync.inc +--source include/have_binlog_format_mixed.inc # No actual replication required +CHANGE MASTER TO master_host='127.0.0.1', master_user='root'; # basic setup + +START SLAVE SQL_THREAD; +--source include/wait_for_slave_sql_to_start.inc + + +SET @@SESSION.debug_sync= + 'hold_sss_with_run_lock SIGNAL sss_got_run_lock WAIT_FOR sss_continue'; +--send SHOW SLAVE STATUS + +--connect (stopper, 127.0.0.1, root, , , $SERVER_MYPORT_1) + SET @@SESSION.debug_sync= 'now WAIT_FOR sss_got_run_lock'; + --send STOP SLAVE SQL_THREAD + # Wait a bit for the thread to "shut down" + # (It should not shut down, but block waiting for the ongoing SSS; + # the tested bug instead pulled this carpet under the SSS's feet.) + --sleep 1 + + --connect (continuer, 127.0.0.1, root, , , $SERVER_MYPORT_1) + SET @@SESSION.debug_sync= 'now SIGNAL sss_continue'; + --connection stopper + --disconnect continuer + + --reap +--connection default +--disconnect stopper + + +--disable_result_log + --reap +--enable_result_log + +# Clean-up +SET @@SESSION.debug_sync= RESET; +--source include/wait_for_slave_sql_to_stop.inc +RESET SLAVE ALL; diff --git a/sql/slave.cc b/sql/slave.cc index 370b2509a1e..9ca95dee938 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -3301,10 +3301,13 @@ static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, protocol->store(mi->connection_name.str, mi->connection_name.length, &my_charset_bin); - mysql_mutex_lock(&mi->run_lock); + mysql_mutex_lock(&mi->rli.run_lock); THD *sql_thd= mi->rli.sql_driver_thd; + DEBUG_SYNC(thd, "hold_sss_with_run_lock"); const char *slave_sql_running_state= sql_thd ? sql_thd->get_proc_info() : ""; + mysql_mutex_unlock(&mi->rli.run_lock); + mysql_mutex_lock(&mi->run_lock); THD *io_thd= mi->io_thd; const char *slave_io_running_state= io_thd ? io_thd->get_proc_info() : ""; mysql_mutex_unlock(&mi->run_lock); From 9c8f08a1a3a9a4151fb41b25efb56b1364b61b56 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Fri, 17 Jul 2026 14:41:22 +0200 Subject: [PATCH 003/150] MDEV-38722 server crash hp_rec_key_cmp In an extended set operation (select_unit_ext, used when EXCEPT ALL or INTERSECT ALL is present) the HEAP unique index is released once, at the node pointed to by union_distinct, assuming every operation after it is a plain UNION ALL that can be unfolded. That does not hold for a trailing EXCEPT ALL: select_unit_ext::send_data() still calls find_unique_row() for it (only the UNION ALL branch checks is_index_enabled), so it dereferenced the already released index and the server crashed in hp_rec_key_cmp(). optimize_bag_operation() now postpones the index release past such a trailing EXCEPT ALL, so the index is available while it is executed. Keeping the index alive uncovered a second problem. A leading INTERSECT that stays inline in the extended operation (instead of being materialized in a derived table) left the rows that did not match every INTERSECT operand in the temporary table. select_unit::send_eof() removes them for a materialized subsequence, but the select_unit_ext override did not, so once the crash was gone the following operations counted those stale rows and produced a wrong multiset. select_unit_ext::send_eof() already scans the temporary table (to reset the duplicate counter, to fold INTERSECT ALL counters, or to unfold), so the filtering of the non-matching INTERSECT records is done inside those loops (guarded by the filter_intersect flag) and the table is still scanned only once. Co-Authored-By: Claude Opus 4.8 (1M context) --- mysql-test/main/intersect_all.result | 66 ++++++++++++++++++++++++++++ mysql-test/main/intersect_all.test | 31 +++++++++++++ sql/sql_union.cc | 35 +++++++++++++++ 3 files changed, 132 insertions(+) diff --git a/mysql-test/main/intersect_all.result b/mysql-test/main/intersect_all.result index 2bda1bb37a5..46e4f2d82c6 100644 --- a/mysql-test/main/intersect_all.result +++ b/mysql-test/main/intersect_all.result @@ -1030,3 +1030,69 @@ id select_type table type possible_keys key key_len ref rows Extra NULL UNIT RESULT ALL NULL NULL NULL NULL NULL drop tables t1, t2; set sql_mode= default; +# +# MDEV-38722 server crash hp_rec_key_cmp +# +# original reproducer (used to crash), returns a single row +SELECT 1 INTERSECT SELECT 1 UNION ALL SELECT 1 EXCEPT ALL SELECT 1; +1 +1 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT i FROM t1 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3 ORDER BY i; +i +1 +2 +2 +(((SELECT i FROM t1) INTERSECT (SELECT i FROM t3)) UNION ALL (SELECT i FROM t2)) +EXCEPT ALL (SELECT i FROM t3) ORDER BY i; +i +1 +2 +2 +# trailing UNION ALL after the EXCEPT ALL +SELECT i FROM t1 INTERSECT SELECT i FROM t2 UNION ALL SELECT i FROM t2 +EXCEPT ALL SELECT i FROM t3 UNION ALL SELECT i FROM t1 ORDER BY i; +i +1 +1 +1 +2 +2 +2 +2 +3 +((((SELECT i FROM t1) INTERSECT (SELECT i FROM t2)) UNION ALL (SELECT i FROM t2)) +EXCEPT ALL (SELECT i FROM t3)) UNION ALL (SELECT i FROM t1) ORDER BY i; +i +1 +1 +1 +2 +2 +2 +2 +3 +# a leading INTERSECT ALL keeps the operation extended while the last +# INTERSECT DISTINCT node is the index-release point +SELECT i FROM t1 INTERSECT ALL SELECT i FROM t2 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 ORDER BY i; +i +1 +1 +2 +2 +(((SELECT i FROM t1) INTERSECT ALL (SELECT i FROM t2)) INTERSECT (SELECT i FROM t3)) +UNION ALL (SELECT i FROM t2) ORDER BY i; +i +1 +1 +2 +2 +DROP TABLE t1,t2,t3; +# End of 10.6 tests diff --git a/mysql-test/main/intersect_all.test b/mysql-test/main/intersect_all.test index 8217e4ebcbc..41055b04a72 100644 --- a/mysql-test/main/intersect_all.test +++ b/mysql-test/main/intersect_all.test @@ -392,3 +392,34 @@ select * from t1 intersect all (select * from t2 union values (1, 2)); explain select * from t1 intersect all select * from t2 union values (1, 2); drop tables t1, t2; set sql_mode= default; + +--echo # +--echo # MDEV-38722 server crash hp_rec_key_cmp +--echo # +--echo # original reproducer (used to crash), returns a single row +SELECT 1 INTERSECT SELECT 1 UNION ALL SELECT 1 EXCEPT ALL SELECT 1; + +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT i FROM t1 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3 ORDER BY i; +(((SELECT i FROM t1) INTERSECT (SELECT i FROM t3)) UNION ALL (SELECT i FROM t2)) + EXCEPT ALL (SELECT i FROM t3) ORDER BY i; +--echo # trailing UNION ALL after the EXCEPT ALL +SELECT i FROM t1 INTERSECT SELECT i FROM t2 UNION ALL SELECT i FROM t2 + EXCEPT ALL SELECT i FROM t3 UNION ALL SELECT i FROM t1 ORDER BY i; +((((SELECT i FROM t1) INTERSECT (SELECT i FROM t2)) UNION ALL (SELECT i FROM t2)) + EXCEPT ALL (SELECT i FROM t3)) UNION ALL (SELECT i FROM t1) ORDER BY i; +--echo # a leading INTERSECT ALL keeps the operation extended while the last +--echo # INTERSECT DISTINCT node is the index-release point +SELECT i FROM t1 INTERSECT ALL SELECT i FROM t2 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 ORDER BY i; +(((SELECT i FROM t1) INTERSECT ALL (SELECT i FROM t2)) INTERSECT (SELECT i FROM t3)) + UNION ALL (SELECT i FROM t2) ORDER BY i; +DROP TABLE t1,t2,t3; + +--echo # End of 10.6 tests diff --git a/sql/sql_union.cc b/sql/sql_union.cc index ca73f5e006e..87a7bd49eee 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -781,10 +781,22 @@ bool select_unit_ext::send_eof() next_sl && next_sl->get_linkage() == INTERSECT_TYPE && !next_sl->distinct; + /* + True at the last INTERSECT of an inline INTERSECT DISTINCT subsequence: the + records that did not match every operand (their intersect counter differs + from curr_step) must be dropped. Instead of a separate table scan this is + folded into the scan/unfold loops below, so the table is scanned only once + (MDEV-38722). When the subsequence is materialized in a derived table this + filtering is done by select_unit::send_eof() instead. + */ + bool filter_intersect= curr_op_type == INTERSECT_DISTINCT && + !(next_sl && + next_sl->get_linkage() == INTERSECT_TYPE); bool need_unfold= disable_index_if_needed(curr_sl); if (((curr_sl->distinct && !is_next_distinct) || curr_op_type == INTERSECT_ALL || + filter_intersect || is_next_intersect_all) && !need_unfold) { @@ -806,6 +818,13 @@ bool select_unit_ext::send_eof() } break; } + if (filter_intersect && additional_cnt->val_int() != curr_step) + { + /* drop a record that did not match every INTERSECT operand */ + if (unlikely(error= delete_record())) + break; + continue; + } store_record(table, record[1]); if (curr_sl->distinct && !is_next_distinct) @@ -859,6 +878,13 @@ bool select_unit_ext::send_eof() } break; } + if (filter_intersect && additional_cnt->val_int() != curr_step) + { + /* drop a record that did not match every INTERSECT operand */ + if (unlikely(error= delete_record())) + break; + continue; + } dup_cnt= (ha_rows)duplicate_cnt->val_int(); /* delete record if not exist in the second operand */ if (dup_cnt == 0) @@ -2000,6 +2026,15 @@ void st_select_lex_unit::optimize_bag_operation(bool is_outer_distinct) sl->distinct= true; disable_index= sl; } + else if (disable_index) + { + /* + EXCEPT ALL needs the unique index in select_unit_ext::send_data(). + If the index would otherwise be released at an earlier node, + postpone the release until (at least) this operation. + */ + disable_index= sl; + } } } else From cf03f61aa45d00a6ac822e86cec2706679cb4346 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Tue, 20 Jul 2021 16:32:57 +0900 Subject: [PATCH 004/150] MDEV-39556 SIGSEGV in ha_mroonga::storage_set_keys_in_use on SELECT Many pathes exist for Mroonga to open tables without opening indexes. Altering from another storage engine and accessing the information schema statistics is one mechanism. The fix is a backport from upstream. Backport from https://github.com/mroonga/mroonga/commit/0b3541910ede845f6ccb62241a633310f2c27f52#diff-c026dc94c7f79d426e7a68e1e69fdc0f7743296a37d6cd19c9e7117a763f242e Fix a crash bug that may be caused after MySQL/MariaDB upgrade GitHub: fix GH-423 MySQL/MariaDB may open Mroonga tables by "CHECK TABLES" when MySQL/MariaDB is upgraded. Mroonga didn't open indexes when a table is opened by "CHECK TABLES". If "REPAIR TABLE" is needed, a table is reopened. If "REPAIR TABLE" isn't needed, a table is NOT reopened. The table doesn't open indexes. "SELECT ... MATCH AGAINST" for the table causes a crash. Reported by Vincent Pelletier. Thanks!!! Backported to MariaDB by Daniel Black Reviews by: Sutou Kouhei and Oleksandr Byelkin --- storage/mroonga/ha_mroonga.cpp | 76 +++++++++---------- .../storage/r/check_table_broken.result | 7 ++ .../storage/r/check_table_innodb.result | 13 ++++ .../r/repair_table_no_index_file.result | 1 + .../mroonga/storage/t/check_table_broken.test | 6 +- .../mroonga/storage/t/check_table_innodb.test | 16 ++++ .../storage/t/repair_table_no_index_file.test | 1 + .../wrapper/r/repair_table_no_files.result | 1 + .../r/repair_table_no_index_file.result | 1 + .../wrapper/t/repair_table_no_index_file.test | 1 + 10 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result create mode 100644 storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test diff --git a/storage/mroonga/ha_mroonga.cpp b/storage/mroonga/ha_mroonga.cpp index a306aef83c5..d5a66798f25 100644 --- a/storage/mroonga/ha_mroonga.cpp +++ b/storage/mroonga/ha_mroonga.cpp @@ -4291,18 +4291,15 @@ int ha_mroonga::wrapper_open(const char *name, int mode, uint open_options) if (error) DBUG_RETURN(error); - if (!(open_options & HA_OPEN_FOR_REPAIR)) { - error = open_table(name); - if (error) - DBUG_RETURN(error); + error = open_table(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) + goto error_exit; - error = wrapper_open_indexes(name); - if (error) { - grn_obj_unlink(ctx, grn_table); - grn_table = NULL; - DBUG_RETURN(error); - } - } + error = wrapper_open_indexes(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) + goto error_exit; + + error = 0; mrn_init_alloc_root(&mem_root, 1024, 0, MYF(0)); wrap_key_info = mrn_create_key_info_for_table(share, table, &error); @@ -4390,6 +4387,7 @@ int ha_mroonga::wrapper_open(const char *name, int mode, uint open_options) } } +error_exit: if (error) { grn_obj_unlink(ctx, grn_table); @@ -4662,37 +4660,35 @@ int ha_mroonga::storage_open(const char *name, int mode, uint open_options) DBUG_RETURN(error); } + error = storage_open_indexes(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) { + storage_close_columns(); + grn_obj_unlink(ctx, grn_table); + grn_table = NULL; + DBUG_RETURN(error); + } + + storage_set_keys_in_use(); + if (!(open_options & HA_OPEN_FOR_REPAIR)) { - error = storage_open_indexes(name); - if (error) { - storage_close_columns(); - grn_obj_unlink(ctx, grn_table); - grn_table = NULL; - DBUG_RETURN(error); - } - - storage_set_keys_in_use(); - - { - mrn::Lock lock(&mrn_operations_mutex); - mrn::PathMapper mapper(name); - const char *table_name = mapper.table_name(); - size_t table_name_size = strlen(table_name); - if (db->is_broken_table(table_name, table_name_size)) { - GRN_LOG(ctx, GRN_LOG_NOTICE, - "Auto repair is started: <%s>", - name); - error = operations_->repair(table_name, table_name_size); + mrn::Lock lock(&mrn_operations_mutex); + mrn::PathMapper mapper(name); + const char *table_name = mapper.table_name(); + size_t table_name_size = strlen(table_name); + if (db->is_broken_table(table_name, table_name_size)) { + GRN_LOG(ctx, GRN_LOG_NOTICE, + "Auto repair is started: <%s>", + name); + error = operations_->repair(table_name, table_name_size); + if (!error) + db->mark_table_repaired(table_name, table_name_size); + if (!share->disable_keys) { if (!error) - db->mark_table_repaired(table_name, table_name_size); - if (!share->disable_keys) { - if (!error) - error = storage_reindex(); - } - GRN_LOG(ctx, GRN_LOG_NOTICE, - "Auto repair is done: <%s>: %s", - name, error == 0 ? "success" : "failure"); + error = storage_reindex(); } + GRN_LOG(ctx, GRN_LOG_NOTICE, + "Auto repair is done: <%s>: %s", + name, error == 0 ? "success" : "failure"); } } @@ -5237,7 +5233,7 @@ void ha_mroonga::storage_set_keys_in_use() if (i == table_share->primary_key) { continue; } - if (!grn_index_tables[i]) { + if (grn_index_tables && !grn_index_tables[i]) { /* disabled */ table_share->keys_in_use.clear_bit(i); DBUG_PRINT("info", ("mroonga: key %u disabled", i)); diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result index 4926a72a77a..af85e783cd5 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result +++ b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result @@ -9,10 +9,17 @@ INSERT INTO diaries VALUES ('Hello'); FLUSH TABLES; CHECK TABLE diaries; Table Op Msg_type Msg_text +check_test.diaries check Error system call error: No such file or directory: failed to open path: check_test.diaries check error Corrupt REPAIR TABLE diaries; Table Op Msg_type Msg_text +check_test.diaries repair Error system call error: No such file or directory: failed to open path: check_test.diaries repair status OK +SELECT * +FROM diaries +WHERE MATCH(title) AGAINST('+Hello' IN BOOLEAN MODE); +title +Hello DROP TABLE diaries; DROP DATABASE check_test; USE test; diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result new file mode 100644 index 00000000000..628a33af7f9 --- /dev/null +++ b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result @@ -0,0 +1,13 @@ +# +# MDEV-39556 SIGSEGV in ha_mroonga::storage_set_keys_in_use on SELECT +# +CREATE TABLE t (c INT KEY,c2 GEOMETRY NOT NULL,SPATIAL INDEX idx_sp (c2)) ENGINE=InnoDB; +ALTER TABLE t ENGINE=Mroonga; +CHECK TABLE t; +Table Op Msg_type Msg_text +test.t check status OK +SELECT COUNT(*) > 0 AS r FROM information_schema.STATISTICS; +r +1 +DROP TABLE t; +# End of 10.6 tests diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result b/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result index 24d427ed2ab..f58b3916eaf 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result +++ b/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("+starting" IN BOOLEAN MODE); ERROR HY000: system call error: No such file or directory: failed to open path: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error system call error: No such file or directory: failed to open path: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test index d438a912ae2..d4666e60055 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test +++ b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test @@ -31,12 +31,16 @@ CREATE TABLE diaries ( INSERT INTO diaries VALUES ('Hello'); --remove_file $MYSQLD_DATADIR/check_test.mrn.000010C - FLUSH TABLES; CHECK TABLE diaries; REPAIR TABLE diaries; + +SELECT * + FROM diaries + WHERE MATCH(title) AGAINST('+Hello' IN BOOLEAN MODE); + DROP TABLE diaries; DROP DATABASE check_test; diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test new file mode 100644 index 00000000000..e34d30ec29d --- /dev/null +++ b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test @@ -0,0 +1,16 @@ +--source include/have_innodb.inc +--source ../../include/mroonga/have_mroonga.inc + +--echo # +--echo # MDEV-39556 SIGSEGV in ha_mroonga::storage_set_keys_in_use on SELECT +--echo # + +CREATE TABLE t (c INT KEY,c2 GEOMETRY NOT NULL,SPATIAL INDEX idx_sp (c2)) ENGINE=InnoDB; + +ALTER TABLE t ENGINE=Mroonga; +CHECK TABLE t; +SELECT COUNT(*) > 0 AS r FROM information_schema.STATISTICS; + +DROP TABLE t; + +--echo # End of 10.6 tests diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test b/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test index 0f04bd3e423..63624a9d56e 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test +++ b/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test @@ -43,6 +43,7 @@ FLUSH TABLES; --error ER_CANT_OPEN_FILE SELECT * FROM diaries WHERE MATCH(body) AGAINST("+starting" IN BOOLEAN MODE); +--replace_result "[io][open] " "" REPAIR TABLE diaries; SELECT * FROM diaries; diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result index 8258a03bf42..98de2e89518 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result +++ b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); ERROR HY000: mroonga: failed to open table: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error mroonga: failed to open table: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result index cca7aee95de..271c086c4b3 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result +++ b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); ERROR HY000: system call error: No such file or directory: failed to open path: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error system call error: No such file or directory: failed to open path: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test b/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test index 2fefadc254d..5061a268d8a 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test +++ b/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test @@ -44,6 +44,7 @@ FLUSH TABLES; --error ER_CANT_OPEN_FILE SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); +--replace_result "[io][open] " "" REPAIR TABLE diaries; SELECT * FROM diaries; From 228e5b506b770789a824141cd381cc427f692d7a Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Fri, 17 Apr 2026 15:22:41 +0200 Subject: [PATCH 005/150] MDEV-24245 REPAIR/OPTIMIZE on ARCHIVE table corrupts blob/text data pack_row() and unpack_row() both used record_buffer. After unpack_row(), blob field pointers in record[0] reference data stored in record_buffer. When pack_row() then writes the new packed row into the same record_buffer, it overwrites the blob data that those pointers still reference. Subsequent Field_blob::pack() calls follow the stale pointers and read corrupted data. This only matters in the optimize/repair loop where the same handler reads then writes each row. Normal INSERT has no prior unpack_row() on the same handler. Fix: in the optimize loop, before writing, copy blob data out of record_buffer (the same approach already used by get_row_version2),so that blob pointers no longer reference record_buffer when pack_row() overwrites it. Free the temporary blob buffer after the loop. --- mysql-test/suite/archive/repair_text.result | 12 +++++ mysql-test/suite/archive/repair_text.test | 10 ++++ storage/archive/ha_archive.cc | 52 ++++++++++++++++++++- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 mysql-test/suite/archive/repair_text.result create mode 100644 mysql-test/suite/archive/repair_text.test diff --git a/mysql-test/suite/archive/repair_text.result b/mysql-test/suite/archive/repair_text.result new file mode 100644 index 00000000000..67926d923cf --- /dev/null +++ b/mysql-test/suite/archive/repair_text.result @@ -0,0 +1,12 @@ +CREATE TABLE t1( +t TEXT NULL DEFAULT NULL +) COLLATE='utf8_general_ci' ENGINE=archive; +INSERT INTO t1(t) VALUES ('Testtext'); +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT t = 'Testtext' as Expect_1 from t1; +Expect_1 +1 +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/archive/repair_text.test b/mysql-test/suite/archive/repair_text.test new file mode 100644 index 00000000000..842b1edb201 --- /dev/null +++ b/mysql-test/suite/archive/repair_text.test @@ -0,0 +1,10 @@ +--source include/have_archive.inc +CREATE TABLE t1( + t TEXT NULL DEFAULT NULL +) COLLATE='utf8_general_ci' ENGINE=archive; +INSERT INTO t1(t) VALUES ('Testtext'); +REPAIR TABLE t1; +SELECT t = 'Testtext' as Expect_1 from t1; +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/storage/archive/ha_archive.cc b/storage/archive/ha_archive.cc index 2d623507fd3..5fbc05e1d95 100644 --- a/storage/archive/ha_archive.cc +++ b/storage/archive/ha_archive.cc @@ -945,14 +945,21 @@ uint32 ha_archive::max_row_length(const uchar *record) unsigned int ha_archive::pack_row(const uchar *record, azio_stream *writer) { uchar *ptr; + uint32 max_len; my_ptrdiff_t const rec_offset= record - table->record[0]; DBUG_ENTER("ha_archive::pack_row"); - if (fix_rec_buff(max_row_length(record))) - DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ + max_len= max_row_length(record); if (writer->version == 1) + { + if (fix_rec_buff(max_len)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ DBUG_RETURN(pack_row_v1(record)); + } + + if (fix_rec_buff(max_len + ARCHIVE_ROW_HEADER_SIZE)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* Copy null bits */ memcpy(record_buffer->buffer+ARCHIVE_ROW_HEADER_SIZE, @@ -1580,6 +1587,46 @@ int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt) while (!(rc= get_row(&archive, table->record[0]))) { + /* + If the source is version 3, unpack_row() stores blob data in + record_buffer. Since pack_row() also uses record_buffer for + packing, we must copy blob data out first to avoid corruption. + Version 2 already stores blobs in the 'buffer' member. + */ + if (archive.version >= 3) + { + uint *blob_ptr, *blob_end; + size_t total_blob_length= 0; + for (blob_ptr= table->s->blob_field, + blob_end= blob_ptr + table->s->blob_fields; + blob_ptr != blob_end; blob_ptr++) + { + Field_blob *blob= (Field_blob*) table->field[*blob_ptr]; + if (!blob->is_null()) + total_blob_length+= blob->get_length(); + } + if (buffer.alloc(total_blob_length)) + { + rc= HA_ERR_OUT_OF_MEM; + break; + } + char *pos= (char*) buffer.ptr(); + for (blob_ptr= table->s->blob_field, + blob_end= blob_ptr + table->s->blob_fields; + blob_ptr != blob_end; blob_ptr++) + { + Field_blob *blob= (Field_blob*) table->field[*blob_ptr]; + if (blob->is_null()) + continue; + uint32 length= blob->get_length(); + if (length) + { + memcpy(pos, blob->get_ptr(), length); + blob->set_ptr(length, (uchar*) pos); + pos+= length; + } + } + } real_write_row(table->record[0], &writer); /* Long term it should be possible to optimize this so that @@ -1599,6 +1646,7 @@ int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt) tmp_restore_column_map(&table->read_set, org_bitmap); share->rows_recorded= (ha_rows)writer.rows; + buffer.free(); } DBUG_PRINT("info", ("recovered %llu archive rows", From 2dcfc13c69f0c0d25ee7cf072e7d892f00c02afb Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 20 Jul 2026 14:56:20 +0200 Subject: [PATCH 006/150] MDEV-37000 disable optimization for Aria remove_key() on affected MSVC The Aria remove_key() helper can miscompile in x64 MSVC builds when optimization is enabled, leading to 'Index is corrupt' during DELETE. Apply the same #pragma optimize("g", off) workaround already used in MyISAM, and limit it to _MSC_VER >= 1930 && _MSC_VER < 1951. The upper bound for affected MSVC is just the newest MSVC version in VS2026, where the bug is no longer reproducible. Also, MSVC release notes https://learn.microsoft.com/en-us/visualstudio/releases/2026/release-notes list multiple fixes for 19.50 x64 optimizer and codegen bugs. --- mysql-test/main/mdev-37000.result | 8 ++++++++ mysql-test/main/mdev-37000.test | 9 +++++++++ storage/maria/ma_delete.c | 6 ++++++ storage/myisam/mi_delete.c | 4 ++-- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/mdev-37000.result create mode 100644 mysql-test/main/mdev-37000.test diff --git a/mysql-test/main/mdev-37000.result b/mysql-test/main/mdev-37000.result new file mode 100644 index 00000000000..73e6812382a --- /dev/null +++ b/mysql-test/main/mdev-37000.result @@ -0,0 +1,8 @@ +# +# MDEV-37000 Deleting a row from Aria table results to 'Index is corrupt' +# +CREATE OR REPLACE TABLE t (a INT, b CHAR(8) NOT NULL, KEY(b)) ENGINE=Aria DEFAULT CHARSET=utf8; +INSERT INTO t VALUES (1,'20070101'), (2,'20070101'); +DELETE FROM t LIMIT 2; +DROP TABLE t; +# End of 10.6 tests diff --git a/mysql-test/main/mdev-37000.test b/mysql-test/main/mdev-37000.test new file mode 100644 index 00000000000..631be073f8c --- /dev/null +++ b/mysql-test/main/mdev-37000.test @@ -0,0 +1,9 @@ +--echo # +--echo # MDEV-37000 Deleting a row from Aria table results to 'Index is corrupt' +--echo # +CREATE OR REPLACE TABLE t (a INT, b CHAR(8) NOT NULL, KEY(b)) ENGINE=Aria DEFAULT CHARSET=utf8; +INSERT INTO t VALUES (1,'20070101'), (2,'20070101'); +DELETE FROM t LIMIT 2; +DROP TABLE t; + +--echo # End of 10.6 tests diff --git a/storage/maria/ma_delete.c b/storage/maria/ma_delete.c index b24cfcc6842..3002b06c281 100644 --- a/storage/maria/ma_delete.c +++ b/storage/maria/ma_delete.c @@ -1335,6 +1335,9 @@ err: @retval # How many chars was removed */ +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 +#pragma optimize("g", off) +#endif static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, uchar *keypos, uchar *lastkey, uchar *page_end, my_off_t *next_block, @@ -1475,6 +1478,9 @@ static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, DBUG_RETURN((uint) s_length); } /* remove_key */ +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 +#pragma optimize("", on) +#endif /**************************************************************************** Logging of redos diff --git a/storage/myisam/mi_delete.c b/storage/myisam/mi_delete.c index 62409a15a46..aa9196dc0a4 100644 --- a/storage/myisam/mi_delete.c +++ b/storage/myisam/mi_delete.c @@ -767,7 +767,7 @@ err: returns how many chars was removed or 0 on error */ -#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 #pragma optimize("g", off) #endif @@ -896,6 +896,6 @@ static uint remove_key(MI_KEYDEF *keyinfo, uint nod_flag, DBUG_RETURN((uint) s_length); } /* remove_key */ -#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 #pragma optimize("",on) #endif From 768796e81a1a52e9baa9cd403ed4b0b54713093e Mon Sep 17 00:00:00 2001 From: KhaledR57 Date: Tue, 21 Jul 2026 10:09:42 +0300 Subject: [PATCH 007/150] MDEV-37865: IF() function is returning incorrect error get_expr_function_type() compared a parsed function name against function_table[] using strncasecmp() bounded by the length of the parsed name. The comparison stopped at that length and never checked that the table entry ended there, so any name that is a prefix of a known function resolved to that function. "if" matched "ifnull" and produced a misleading arity error. Names that prefixed a function with a compatible signature were worse and dispatched silently: l("AB") ran lcase(),t(" x ") ran trim(). A typo in a test script ran a different function and the test still passed. Store each entry's length in function_table[] with STRING_WITH_LEN() and require it to match the parsed length. Report the unmatched name in the "Unknown function" error. --- client/mysqltest.cc | 76 ++++++++++--------- .../mysqltest_expression_evaluation.result | 7 ++ .../main/mysqltest_expression_evaluation.test | 13 ++++ 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index f78b469d160..d52878a859a 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -5543,7 +5543,7 @@ static void primary(Expression_value *result, const char **s) enum func_type func_type= get_expr_function_type(start, end - start); if (func_type == FUNC_UNKNOWN) - die("Syntax error: Unknown function"); + die("Syntax error: Unknown function '%.*s'", (int) (end - start), start); *s= end + 1; // skip '(' handle_expr_function_call(func_type, result, s); @@ -5990,47 +5990,48 @@ static void expr(Expression_value *result, const char **s) static struct { const char *name; + size_t length; enum func_type type; } function_table[]= { // Numeric functions - {"abs", FUNC_ABS}, - {"bin", FUNC_BIN}, - {"conv", FUNC_CONV}, - {"hex", FUNC_HEX}, - {"oct", FUNC_OCT}, + {STRING_WITH_LEN("abs"), FUNC_ABS}, + {STRING_WITH_LEN("bin"), FUNC_BIN}, + {STRING_WITH_LEN("conv"), FUNC_CONV}, + {STRING_WITH_LEN("hex"), FUNC_HEX}, + {STRING_WITH_LEN("oct"), FUNC_OCT}, // String functions - {"concat", FUNC_CONCAT}, - {"concat_ws", FUNC_CONCAT_WS}, - {"greatest", FUNC_GREATEST}, - {"insert", FUNC_INSERT}, - {"instr", FUNC_INSTR}, - {"lcase", FUNC_LOWER}, - {"least", FUNC_LEAST}, - {"length", FUNC_LENGTH}, - {"locate", FUNC_LOCATE}, - {"lower", FUNC_LOWER}, - {"lpad", FUNC_LPAD}, - {"ltrim", FUNC_LTRIM}, - {"repeat", FUNC_REPEAT}, - {"replace", FUNC_REPLACE}, - {"reverse", FUNC_REVERSE}, - {"rpad", FUNC_RPAD}, - {"rtrim", FUNC_RTRIM}, - {"substr", FUNC_SUBSTR}, - {"substring", FUNC_SUBSTR}, - {"substring_index", FUNC_SUBSTR_IDX}, - {"trim", FUNC_TRIM}, - {"ucase", FUNC_UPPER}, - {"upper", FUNC_UPPER}, + {STRING_WITH_LEN("concat"), FUNC_CONCAT}, + {STRING_WITH_LEN("concat_ws"), FUNC_CONCAT_WS}, + {STRING_WITH_LEN("greatest"), FUNC_GREATEST}, + {STRING_WITH_LEN("insert"), FUNC_INSERT}, + {STRING_WITH_LEN("instr"), FUNC_INSTR}, + {STRING_WITH_LEN("lcase"), FUNC_LOWER}, + {STRING_WITH_LEN("least"), FUNC_LEAST}, + {STRING_WITH_LEN("length"), FUNC_LENGTH}, + {STRING_WITH_LEN("locate"), FUNC_LOCATE}, + {STRING_WITH_LEN("lower"), FUNC_LOWER}, + {STRING_WITH_LEN("lpad"), FUNC_LPAD}, + {STRING_WITH_LEN("ltrim"), FUNC_LTRIM}, + {STRING_WITH_LEN("repeat"), FUNC_REPEAT}, + {STRING_WITH_LEN("replace"), FUNC_REPLACE}, + {STRING_WITH_LEN("reverse"), FUNC_REVERSE}, + {STRING_WITH_LEN("rpad"), FUNC_RPAD}, + {STRING_WITH_LEN("rtrim"), FUNC_RTRIM}, + {STRING_WITH_LEN("substr"), FUNC_SUBSTR}, + {STRING_WITH_LEN("substring"), FUNC_SUBSTR}, + {STRING_WITH_LEN("substring_index"), FUNC_SUBSTR_IDX}, + {STRING_WITH_LEN("trim"), FUNC_TRIM}, + {STRING_WITH_LEN("ucase"), FUNC_UPPER}, + {STRING_WITH_LEN("upper"), FUNC_UPPER}, // Regexp functions - {"regexp_instr", FUNC_REGEXP_INSTR}, - {"regexp_replace", FUNC_REGEXP_REPLACE}, - {"regexp_substr", FUNC_REGEXP_SUBSTR}, + {STRING_WITH_LEN("regexp_instr"), FUNC_REGEXP_INSTR}, + {STRING_WITH_LEN("regexp_replace"), FUNC_REGEXP_REPLACE}, + {STRING_WITH_LEN("regexp_substr"), FUNC_REGEXP_SUBSTR}, // Null functions - {"coalesce", FUNC_COALESCE}, - {"ifnull", FUNC_IFNULL}, - {"nullif", FUNC_NULLIF}, - {NULL, FUNC_UNKNOWN} + {STRING_WITH_LEN("coalesce"), FUNC_COALESCE}, + {STRING_WITH_LEN("ifnull"), FUNC_IFNULL}, + {STRING_WITH_LEN("nullif"), FUNC_NULLIF}, + {NULL, 0, FUNC_UNKNOWN} }; @@ -7546,7 +7547,8 @@ enum func_type get_expr_function_type(const char *name, size_t len) { for (int i= 0; function_table[i].name; ++i) { - if (!strncasecmp(function_table[i].name, name, len)) + if (function_table[i].length == len && + !strncasecmp(function_table[i].name, name, len)) return function_table[i].type; } return FUNC_UNKNOWN; diff --git a/mysql-test/main/mysqltest_expression_evaluation.result b/mysql-test/main/mysqltest_expression_evaluation.result index d9fa0ccb3b8..b74fc767d47 100644 --- a/mysql-test/main/mysqltest_expression_evaluation.result +++ b/mysql-test/main/mysqltest_expression_evaluation.result @@ -505,4 +505,11 @@ mysqltest: At line 1: Evaluation error: Modulo by zero # Test case: Overflow mysqltest: At line 1: Range error: 18446744073709551616 value out of range for Integer type mysqltest: At line 1: Range error: 18446744073709551616 value out of range for Integer type +# +# MDEV-37865: IF() function is returning incorrect error +# +# Test case: unknown function name that is a prefix of a known one +mysqltest: At line 1: Syntax error: Unknown function 'if' +mysqltest: At line 1: Syntax error: Unknown function 'l' # Expression evaluation tests completed successfully +# End of 10.6 tests diff --git a/mysql-test/main/mysqltest_expression_evaluation.test b/mysql-test/main/mysqltest_expression_evaluation.test index 7a108f2fe17..b76648284fc 100644 --- a/mysql-test/main/mysqltest_expression_evaluation.test +++ b/mysql-test/main/mysqltest_expression_evaluation.test @@ -894,4 +894,17 @@ let $large_xor = $(0x7FFFFFFFFFFFFFFF ^ 0x7FFFFFFFFFFFFFFE); --error 1 --exec echo "let \$a = \$(-18446744073709551616);" | $MYSQL_TEST 2>&1 +--echo # +--echo # MDEV-37865: IF() function is returning incorrect error +--echo # + +--echo # Test case: unknown function name that is a prefix of a known one +--error 1 +--exec echo "let \$a = \$(if(null, 'fallback', 'actual'));" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "let \$a = \$(l('AB'));" | $MYSQL_TEST 2>&1 + --echo # Expression evaluation tests completed successfully + +--echo # End of 10.6 tests From 39569c6606dced86d96605bf052497d9f7163bc8 Mon Sep 17 00:00:00 2001 From: KhaledR57 Date: Tue, 21 Jul 2026 18:30:36 +0300 Subject: [PATCH 008/150] MDEV-37859 mysqltest hex() fails on string arguments func_hex() passed every argument through convert_base_helper(), which parses the argument as a base 10 number. A string argument therefore died with "invalid number 'abc' for base 10" instead of being converted. HEX() is the only one of the base conversion functions that accepts a string. In the server BIN() and OCT() are built as Item_func_conv() with fixed bases and are numeric only, while HEX() has a dedicated Item with a separate string path, because HEX() is the counterpart of UNHEX() and has to serialise bytes. Dispatch on the argument type: numeric arguments keep the existing CONV(N, 10, 16) behaviour, string arguments are converted byte by byte with String::set_hex(), which is the same call the server uses in Item_func_hex::val_str_ascii_from_val_str(). --- client/mysqltest.cc | 12 ++++++++++-- mysql-test/main/mysqltest_string_functions.result | 7 +++++++ mysql-test/main/mysqltest_string_functions.test | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index d52878a859a..24a0298d28c 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -6271,9 +6271,9 @@ void func_oct(Expression_value args[], int count, Expression_value *result) @param[out] result Expression_value to store result @details - Converts a number to hexadecimal representation. HEX(N) returns a string representation of the hexadecimal value of N. This is equivalent to CONV(N, 10, 16). + HEX(str) returns a hexadecimal representation of the bytes of str. @note Dies if argument count != 1 */ @@ -6283,7 +6283,15 @@ void func_hex(Expression_value args[], int count, Expression_value *result) if (count != 1) die("hex() expects 1 argument, got %d", count); - convert_base_helper(args[0].to_string(), 10, 16, result); + if (args[0].is_numeric) + convert_base_helper(args[0].to_string(), 10, 16, result); + else + { + My_string str= args[0].to_string(); + My_string hex_str; + hex_str.set_hex(str.ptr(), str.length()); + result->set_string(hex_str.ptr(), hex_str.length()); + } } diff --git a/mysql-test/main/mysqltest_string_functions.result b/mysql-test/main/mysqltest_string_functions.result index eb3d56e6554..2f4eb0c94b0 100644 --- a/mysql-test/main/mysqltest_string_functions.result +++ b/mysql-test/main/mysqltest_string_functions.result @@ -1116,4 +1116,11 @@ insert(upper('hello world'), 7, 5, lower('MARIADB')) -> HELLO mariadb concat_ws(' ', upper('hello'), lower('WORLD')) -> HELLO world ifnull(nullif('same', 'same'), 'default') -> default coalesce(nullif('test', 'test'), 'backup') -> backup +# +# MDEV-37859: Mysqltest hex() not working for string values +# +hex("abc") -> 616263 +hex("255") -> 323535 +hex('') -> # Expression evaluation string functions tests completed successfully +# End of 10.6 tests diff --git a/mysql-test/main/mysqltest_string_functions.test b/mysql-test/main/mysqltest_string_functions.test index 10e0d546e15..4ccdfec59c0 100644 --- a/mysql-test/main/mysqltest_string_functions.test +++ b/mysql-test/main/mysqltest_string_functions.test @@ -2128,4 +2128,19 @@ let $result5 = $(ifnull(nullif('same', 'same'), 'default')); let $result6 = $(coalesce(nullif('test', 'test'), 'backup')); --echo coalesce(nullif('test', 'test'), 'backup') -> $result6 +--echo # +--echo # MDEV-37859: Mysqltest hex() not working for string values +--echo # + +let $hex_str1 = $(hex("abc")); +--echo hex("abc") -> $hex_str1 + +let $hex_str2 = $(hex("255")); +--echo hex("255") -> $hex_str2 + +let $hex_str3 = $(hex('')); +--echo hex('') -> $hex_str3 + --echo # Expression evaluation string functions tests completed successfully + +--echo # End of 10.6 tests From 41cdc5630eb53e0e184cb685202c1ad7a69bb6a6 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Tue, 28 Jul 2026 15:01:16 +0530 Subject: [PATCH 009/150] MDEV-39468 Mariabackup: redo log copier stalls and fails with misleading error message Problem: ======== During backup process, log copier thread stalls at lsn and fails with the misleading "Was only able to copy log from X to Y, not Z; try increasing innodb_log_file_size", even when the redo was intact and the log far from full. Problem is that copier advances recv_sys.lsn only on CRC-validated mtr, but it parses the redo at the server is concurrently writing to it. InnoDB rewrites the current partial write block repeatedly as mini-transaction fills it, so an mmap read (or) pread of a page being written can observe a torn/intermediate image of the tail block. If that tail block happens to parse as a longer mtr which is still valid, has a correct CRC then recv_sys.lsn, recv_sys.offset gets advanced wrongly and points to middle of the mini-transaction. In that case, later parse return GOT_EOF always and copier thread never reaches target and fails Solution: ========= Poll the server's durably-flushed LSN (status var Innodb_lsn_flushed) and limit the redo log copier based on it. backup_log_parse(): parses one mtr and if it exceeds the limit rolls back recv_sys.lsn/offset and reports GOT_EOF; the copier re-parses those bytes on a later pass once the fence has advanced past them. Used on both the mmap and buffered paths. log_copying_thread(): opens its own connection (it runs concurrently with the main thread, which owns mysql_connection) and refreshes the max_limit for parsing of redo log in each pass. In the final backup phase (BACKUP STAGE BLOCK_COMMIT) it gates on the exact target max(metadata_last_lsn, metadata_to_lsn). so the last partial block is copied in full up to the target. The stall diagnostic / retry spin is suppressed on a reached_parse_limit wait so a normal "caught up, wait" is not misreported as drift. get_log_flushed_lsn(): Added to get log_flushed_lsn from SHOW STATUS outout; --- extra/mariabackup/backup_mysql.cc | 24 +++ extra/mariabackup/backup_mysql.h | 3 + extra/mariabackup/xtrabackup.cc | 154 ++++++++++++++++-- .../mariabackup/innodb_redo_overwrite.result | 2 +- .../mariabackup/innodb_redo_overwrite.test | 2 +- 5 files changed, 170 insertions(+), 15 deletions(-) diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc index 7fa02b1128c..453e9bb052e 100644 --- a/extra/mariabackup/backup_mysql.cc +++ b/extra/mariabackup/backup_mysql.cc @@ -346,6 +346,30 @@ read_mysql_one_value(MYSQL *mysql, const char *query) return read_mysql_one_value(mysql, query, 0/*offset*/, 1/*total columns*/); } +/** Poll InnoDB's durably flushed redo LSN from the running server. +Log copier uses this as its parse limit; it never accepts a redo log +record whose end exceeds this LSN +@param connection MariaDB client connection +@retval 0 on failure, lsn on success */ +uint64_t get_log_flushed_lsn(MYSQL *connection) noexcept +{ + if (!connection) + return 0; + + uint64_t lsn= 0; + MYSQL_RES *res= xb_mysql_query(connection, + "SHOW STATUS LIKE 'Innodb_lsn_flushed'", + true, false); + if (!res) + return 0; + + if (MYSQL_ROW row= mysql_fetch_row(res)) + if (row[1]) + lsn= strtoull(row[1], nullptr, 10); + mysql_free_result(res); + return lsn; +} + static bool diff --git a/extra/mariabackup/backup_mysql.h b/extra/mariabackup/backup_mysql.h index 55700dddf6d..76f4bc666b3 100644 --- a/extra/mariabackup/backup_mysql.h +++ b/extra/mariabackup/backup_mysql.h @@ -2,6 +2,7 @@ #define XTRABACKUP_BACKUP_MYSQL_H #include +#include #include #include #include "datasink.h" @@ -97,4 +98,6 @@ bool write_slave_info(ds_ctxt *datasink, MYSQL *connection); ulonglong get_current_lsn(MYSQL *connection); + +uint64_t get_log_flushed_lsn(MYSQL *connection) noexcept; #endif diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index 3ac3fe608f6..345624fcbf9 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -205,7 +205,7 @@ struct xb_filter_entry_t{ /** whether log_copying_thread() is active; protected by recv_sys.mutex */ static bool log_copying_running; /** the log parsing function for --backup */ -static recv_sys_t::parser backup_log_parse; +static recv_sys_t::parser backup_log_parse_low; /** for --backup, target LSN to copy the log to; protected by recv_sys.mutex */ lsn_t metadata_to_lsn; @@ -252,6 +252,7 @@ const char *defaults_group = "mysqld"; #define HA_INNOBASE_ROWS_IN_TABLE 10000 /* to get optimization right */ #define HA_INNOBASE_RANGE_COUNT 100 +#define METADATA_LSN_ERROR 1 /* The default values for the following, type long or longlong, start-up parameters are declared in mysqld.cc: */ @@ -3443,6 +3444,41 @@ skip: return(FALSE); } +/* Maximum LSN the copier may parse up to. +Usually derived from the server's durably-flushed redo LSN. +When a final target LSN is set, this limit is raised to that +target so the last partial block can be copied in full. +The copier refuses to accept a mini-transaction whose end +exceeds this, so it never parses the volatile tail block +the server is still rewriting +(which mmap can read torn, leading to a mis-computed mtr +length and permanent mid-mtr drift). A pread() of the same +still-being-written tail block can likewise return a torn read +which can be observed on ext4, though not on XFS). +Read/written only under recv_sys.mutex. */ +static lsn_t max_parse_lsn; + +/* Set by backup_log_parse() when the last parse was +rejected because the mtr crossed max_parse_lsn. It means +"caught up, wait" condition, not a stall. */ +static bool reached_parse_limit; + +static recv_sys_t::parse_mtr_result backup_log_parse() +{ + const lsn_t prev_lsn= recv_sys.lsn; + const size_t prev_offset= recv_sys.offset; + reached_parse_limit= false; + recv_sys_t::parse_mtr_result r= backup_log_parse_low(false); + if (r == recv_sys_t::OK && max_parse_lsn && recv_sys.lsn > max_parse_lsn) + { + recv_sys.lsn= prev_lsn; + recv_sys.offset= prev_offset; + reached_parse_limit= true; + return recv_sys_t::GOT_EOF; + } + return r; +} + static int xtrabackup_copy_mmap_snippet(ds_file_t *ds, const byte *start, const byte *end) { @@ -3468,7 +3504,7 @@ static bool xtrabackup_copy_mmap_logfile() const byte *start= &log_sys.buf[recv_sys.offset]; ut_d(recv_sys_t::parse_mtr_result r); - if ((ut_d(r=) backup_log_parse(false)) == recv_sys_t::OK) + if ((ut_d(r=) backup_log_parse()) == recv_sys_t::OK) { do { @@ -3486,7 +3522,7 @@ static bool xtrabackup_copy_mmap_logfile() start = seq + 1; } } - while ((ut_d(r=) backup_log_parse(false)) == recv_sys_t::OK); + while ((ut_d(r=) backup_log_parse()) == recv_sys_t::OK); if (xtrabackup_copy_mmap_snippet(dst_log_file, start, &log_sys.buf[recv_sys.offset])) @@ -3559,7 +3595,7 @@ static bool xtrabackup_copy_logfile(bool early_exit) if (log_sys.buf[recv_sys.offset] <= 1) break; - if (backup_log_parse(false) == recv_sys_t::OK) + if (backup_log_parse() == recv_sys_t::OK) { do { @@ -3569,7 +3605,7 @@ static bool xtrabackup_copy_logfile(bool early_exit) sequence_offset)); *seq= 1; } - while ((r= backup_log_parse(false)) == recv_sys_t::OK); + while ((r= backup_log_parse()) == recv_sys_t::OK); if (ds_write(dst_log_file, log_sys.buf + start_offset, recv_sys.offset - start_offset)) @@ -3599,6 +3635,9 @@ static bool xtrabackup_copy_logfile(bool early_exit) if (retry_count == 100) break; + if (reached_parse_limit) + break; + mysql_mutex_unlock(&recv_sys.mutex); if (!retry_count++) msg("Retrying read of log at LSN=" LSN_PF, recv_sys.lsn); @@ -3613,13 +3652,32 @@ static bool xtrabackup_copy_logfile(bool early_exit) return false; } +/** Handles backup log copy timeout or incomplete LSN copy. +Checks if the target LSN was reached. If not, +logs diagnostic messages advising whether to increase +innodb_log_file_size (log wrapped around) or to check +server/backup configuration mismatches. +@param lsn Target LSN expected by the backup. +@param last_lsn Actual maximum LSN copied so far. +@return true if target LSN was reached, false otherwise. */ static bool backup_wait_timeout(lsn_t lsn, lsn_t last_lsn) { if (last_lsn >= lsn) return true; + + const lsn_t checkpoint_lsn= log_sys.last_checkpoint_lsn.load(); + const lsn_t capacity= log_sys.file_size - log_sys.START_OFFSET; + const lsn_t needed= lsn - checkpoint_lsn; + msg("Was only able to copy log from " LSN_PF " to " LSN_PF - ", not " LSN_PF "; try increasing innodb_log_file_size", - log_sys.last_checkpoint_lsn.load(), last_lsn, lsn); + ", not " LSN_PF, checkpoint_lsn, last_lsn, lsn); + + if (needed <= capacity) + msg("mariabackup: The required redo still fits within the log " + "capacity, so it has not been overwritten; check whether the " + "server and backup configuration are the same."); + else + msg("mariabackup: Try increasing the innodb_log_file_size."); return false; } @@ -3674,16 +3732,80 @@ static bool backup_wait_for_lsn(lsn_t lsn) static void log_copying_thread() { my_thread_init(); - mysql_mutex_lock(&recv_sys.mutex); - while (!xtrabackup_copy_logfile(false) && - (!metadata_last_lsn || metadata_last_lsn > recv_sys.lsn)) + MYSQL *limit_con= xb_mysql_connect(); + if (!limit_con) { + /* Without this connection we cannot poll the durably-flushed + LSN, so max_parse_lsn could never advance and the copier + would either stall or parse the volatile tail block. + Fail the copy gracefully and let the main thread report + it via backup_wait_timeout(). */ + msg("mariabackup: Error: cannot open a server connection for " + "the log copying thread; the backup will fail."); + mysql_mutex_lock(&recv_sys.mutex); + log_copying_running= false; + pthread_cond_broadcast(&scanned_lsn_cond); + mysql_mutex_unlock(&recv_sys.mutex); + my_thread_end(); + return; + } + + mysql_mutex_lock(&recv_sys.mutex); + for (;;) + { + /* metadata_last_lsn is set to 1 when error was encountered. + Abort the log copier if an error was signaled */ + if (metadata_last_lsn == METADATA_LSN_ERROR) + break; + /* Refresh the max_parse_lsn before each copy pass */ + const lsn_t final_target= metadata_last_lsn > metadata_to_lsn + ? metadata_last_lsn : metadata_to_lsn; + if (final_target) + /* Final phase (BLOCK_DDL/BLOCK_COMMIT): fence at the exact target + instead of the polled flushed LSN. This is only safe because the + target was derived from get_current_lsn(), which runs + FLUSH ENGINE LOGS and thus makes the redo durable up to at least + final_target; so final_target <= Innodb_lsn_flushed holds here and + the bytes up to it are stable. Do not feed this branch an LSN that + has not been flushed by the server, or the copier will parse the + volatile tail block again (MDEV-39468). */ + max_parse_lsn= final_target; + else + { + mysql_mutex_unlock(&recv_sys.mutex); + lsn_t flushed= get_log_flushed_lsn(limit_con); + mysql_mutex_lock(&recv_sys.mutex); + if (flushed > log_sys.get_first_lsn()) + max_parse_lsn= flushed; + else if (!max_parse_lsn) + { + /* We could not obtain a flushed LSN and none was seeded before, + so the parse limit is unset and the copier would fall back to + parsing the volatile tail block (the MDEV-39468 failure). Warn + once so this is diagnosable rather than a silent stall. */ + static bool warned; + if (!warned) + { + warned= true; + msg("mariabackup: Warning: could not read Innodb_lsn_flushed; " + "the redo log copier has no parse limit."); + } + } + } + + if (xtrabackup_copy_logfile(false)) + break; + if (final_target && final_target <= recv_sys.lsn) + break; + timespec abstime; set_timespec_nsec(abstime, 1000000ULL * xtrabackup_log_copy_interval); mysql_cond_timedwait(&log_copying_stop, &recv_sys.mutex, &abstime); } log_copying_running= false; mysql_mutex_unlock(&recv_sys.mutex); + if (limit_con) + mysql_close(limit_con); my_thread_end(); } @@ -4888,7 +5010,7 @@ static bool backup_wait_for_commit_lsn() else { msg("Error: recv_sys.find_checkpoint() failed."); - metadata_last_lsn= 1; + metadata_last_lsn= METADATA_LSN_ERROR; stop_backup_threads(); mysql_mutex_unlock(&recv_sys.mutex); return false; @@ -5487,7 +5609,7 @@ static bool xtrabackup_backup_func() fail: if (log_copying_running) { mysql_mutex_lock(&recv_sys.mutex); - metadata_last_lsn = 1; + metadata_last_lsn = METADATA_LSN_ERROR; stop_backup_threads(); mysql_mutex_unlock(&recv_sys.mutex); } @@ -5614,9 +5736,15 @@ fail: /* copy log file by current position */ mysql_mutex_lock(&recv_sys.mutex); - backup_log_parse = recv_sys.get_backup_parser(); + backup_log_parse_low = recv_sys.get_backup_parser(); recv_sys.lsn = log_sys.last_checkpoint_lsn; + if (lsn_t flushed= get_log_flushed_lsn(mysql_connection)) + { + if (flushed > log_sys.get_first_lsn()) + max_parse_lsn= flushed; + } + const bool log_copy_failed = xtrabackup_copy_logfile(true); mysql_mutex_unlock(&recv_sys.mutex); diff --git a/mysql-test/suite/mariabackup/innodb_redo_overwrite.result b/mysql-test/suite/mariabackup/innodb_redo_overwrite.result index bab5d4331e0..cbc7f44cab3 100644 --- a/mysql-test/suite/mariabackup/innodb_redo_overwrite.result +++ b/mysql-test/suite/mariabackup/innodb_redo_overwrite.result @@ -1,5 +1,5 @@ CREATE TABLE t ENGINE=INNODB SELECT seq%10 i FROM seq_0_to_204796; # xtrabackup backup -FOUND 1 /Was only able to copy log from \d+ to \d+, not \d+; try increasing innodb_log_file_size\b/ in backup.log +FOUND 1 /mariabackup: Try increasing the innodb_log_file_size./ in backup.log NOT FOUND /failed: redo log block checksum does not match/ in backup.log DROP TABLE t; diff --git a/mysql-test/suite/mariabackup/innodb_redo_overwrite.test b/mysql-test/suite/mariabackup/innodb_redo_overwrite.test index ab5993d232c..8c26acf4141 100644 --- a/mysql-test/suite/mariabackup/innodb_redo_overwrite.test +++ b/mysql-test/suite/mariabackup/innodb_redo_overwrite.test @@ -17,7 +17,7 @@ CREATE TABLE t ENGINE=INNODB SELECT seq%10 i FROM seq_0_to_204796; --exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir --dbug=+d,mariabackup_events > $backuplog --enable_result_log ---let SEARCH_PATTERN=Was only able to copy log from \\d+ to \\d+, not \\d+; try increasing innodb_log_file_size\\b +--let SEARCH_PATTERN=mariabackup: Try increasing the innodb_log_file_size. --let SEARCH_FILE=$backuplog --source include/search_pattern_in_file.inc --remove_file $backuplog From 129e80972d96539667b8a4be953dcc54c990e968 Mon Sep 17 00:00:00 2001 From: Hemant Dangi Date: Mon, 22 Jun 2026 17:37:06 +0530 Subject: [PATCH 010/150] MDEV-28233: rsync SST script silently runs unencrypted if stunnel is not installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: When ssl-mode required encryption but the means to perform it was missing, the SST scripts silently fell back to a cleartext transfer: - wsrep_sst_rsync: ran over plain TCP when the 'stunnel' binary was absent. - wsrep_sst_mariabackup: socat used a cleartext socket when ssl-mode was set but no usable cert/key was found (encrypt stayed 0). Solution: Abort the SST instead of falling back to an unencrypted transfer when ssl-mode is not DISABLED but encryption cannot be set up: - wsrep_sst_rsync: derive the implicit ssl-mode from the SSL config even when stunnel is absent, then abort with ENOENT if ssl-mode is active and the stunnel binary is not found. - wsrep_sst_mariabackup: after reading the SSL configuration, abort with EINVAL if ssl-mode is not DISABLED but encrypt resolved to 0 (no usable cert/key). Reviewed-by: Jan Lindström --- .../galera_sst_mariabackup_missing_ssl.result | 27 ++++++++ .../r/galera_sst_rsync_missing_stunnel.result | 27 ++++++++ .../t/galera_sst_mariabackup_missing_ssl.cnf | 11 +++ .../t/galera_sst_mariabackup_missing_ssl.test | 69 +++++++++++++++++++ .../t/galera_sst_rsync_missing_stunnel.cnf | 10 +++ .../t/galera_sst_rsync_missing_stunnel.test | 66 ++++++++++++++++++ scripts/wsrep_sst_mariabackup.sh | 15 ++++ scripts/wsrep_sst_rsync.sh | 23 +++++-- 8 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result create mode 100644 mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test create mode 100644 mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result new file mode 100644 index 00000000000..0424988061e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result @@ -0,0 +1,27 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_1; +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +connection node_2; +# Force a fresh SST on node_2 +# Start node_2 with SSL cert/key simulated as missing +# node_2 failed to start, as expected +# Joiner refused the SST instead of running unencrypted +FOUND 1 /ssl-mode is set to .REQUIRED., but no usable SSL/ in mysqld.2.err +# Restart node_2 normally so it rejoins +connection node_2; +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); +connection node_1; diff --git a/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result b/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result new file mode 100644 index 00000000000..771d6e0fd75 --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result @@ -0,0 +1,27 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_1; +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +connection node_2; +# Force a fresh SST on node_2 +# Start node_2 with stunnel simulated as missing +# node_2 failed to start, as expected +# Joiner refused the SST instead of running unencrypted +FOUND 1 /ssl-mode is set to .REQUIRED., but the .stunnel. binary was not found/ in mysqld.2.err +# Restart node_2 normally so it rejoins +connection node_2; +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); +connection node_1; diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf new file mode 100644 index 00000000000..5c9ea2149a3 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf @@ -0,0 +1,11 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +ssl-cert=@ENV.MYSQL_TEST_DIR/std_data/server-cert.pem +ssl-key=@ENV.MYSQL_TEST_DIR/std_data/server-key.pem +ssl-ca=@ENV.MYSQL_TEST_DIR/std_data/cacert.pem + +[sst] +ssl-mode=REQUIRED diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test new file mode 100644 index 00000000000..a636634cd50 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test @@ -0,0 +1,69 @@ +# MDEV-28233: with ssl-mode requiring encryption but no usable cert/key, +# wsrep_sst_mariabackup used to fall back to an unencrypted transfer +# WITHOUT any error or warning. Before the fix the joiner only logged the +# informational line: +# WSREP_SST: [INFO] SSL configuration: ... MODE='REQUIRED' ... encrypt='0' +# and then opened a cleartext socket. +# +# Steps: +# 1. Form a 2-node cluster with ssl-mode=REQUIRED and SSL cert/key. +# 2. Shut node_2 down and force a fresh SST. +# 3. Restart node_2 with cert/key simulated as missing +# (MTR_SST_SIMULATE_NO_SSL_CERT=1). +# 4. Check the joiner refuses the SST and logs the error. +# 5. Restart node_2 normally so it rejoins. +# +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +# Save auto_increment_offset values. +--let $node_1=node_1 +--let $node_2=node_2 +--source include/auto_increment_offset_save.inc + +--connection node_1 +# Donor-side noise when the joiner aborts the SST. +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); + +--connection node_2 +--source include/shutdown_mysqld.inc + +--echo # Force a fresh SST on node_2 +--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat + +--echo # Start node_2 with SSL cert/key simulated as missing +# Abort exit code varies by platform. +--error 1,134 +--exec MTR_SST_SIMULATE_NO_SSL_CERT=1 $MYSQLD_LAST_CMD +--echo # node_2 failed to start, as expected + +--echo # Joiner refused the SST instead of running unencrypted +--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let SEARCH_PATTERN = ssl-mode is set to .REQUIRED., but no usable SSL +--source include/search_pattern_in_file.inc + +--echo # Restart node_2 normally so it rejoins +--source include/start_mysqld.inc +--source include/wait_until_connected_again.inc + +--connection node_2 +# Joiner-side noise from the failed SST attempt. +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--source include/auto_increment_offset_restore.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf new file mode 100644 index 00000000000..39fbab6cb5c --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf @@ -0,0 +1,10 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=rsync +ssl-cert=@ENV.MYSQL_TEST_DIR/std_data/server-cert.pem +ssl-key=@ENV.MYSQL_TEST_DIR/std_data/server-key.pem +ssl-ca=@ENV.MYSQL_TEST_DIR/std_data/cacert.pem + +[sst] +ssl-mode=REQUIRED diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test new file mode 100644 index 00000000000..1310c27c6db --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test @@ -0,0 +1,66 @@ +# MDEV-28233: the rsync SST script must NOT silently fall back to an +# unencrypted transfer when encryption is requested (ssl-mode is set to a +# value other than DISABLED) but the stunnel binary is not installed. +# +# Steps: +# 1. Form a 2-node cluster with ssl-mode=REQUIRED (stunnel present). +# 2. Shut node_2 down and force a fresh SST. +# 3. Restart node_2 with stunnel hidden (MTR_SST_SIMULATE_NO_STUNNEL=1). +# 4. Check the joiner refuses the SST and logs the error. +# 5. Restart node_2 normally so it rejoins. +# +--source include/galera_cluster.inc +--source include/have_innodb.inc +# Needed for the initial SST; only simulated as missing below. +--source include/have_stunnel.inc + +# Save auto_increment_offset values. +--let $node_1=node_1 +--let $node_2=node_2 +--source include/auto_increment_offset_save.inc + +--connection node_1 +# Donor-side noise when the joiner aborts the SST. +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); + +--connection node_2 +--source include/shutdown_mysqld.inc + +--echo # Force a fresh SST on node_2 +--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat + +--echo # Start node_2 with stunnel simulated as missing +# Abort exit code +--error 1,134 +--exec MTR_SST_SIMULATE_NO_STUNNEL=1 $MYSQLD_LAST_CMD +--echo # node_2 failed to start, as expected + +--echo # Joiner refused the SST instead of running unencrypted +--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let SEARCH_PATTERN = ssl-mode is set to .REQUIRED., but the .stunnel. binary was not found +--source include/search_pattern_in_file.inc + +--echo # Restart node_2 normally so it rejoins +--source include/start_mysqld.inc +--source include/wait_until_connected_again.inc + +--connection node_2 +# Joiner-side noise from the failed SST attempt. +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--source include/auto_increment_offset_restore.inc diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 734fc2dd42c..ad937bb8775 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -571,6 +571,11 @@ read_cnf() if [ "$tmode" != 'DISABLED' -o $encrypt -ge 2 ]; then check_server_ssl_config fi + # MTR test hook: simulate a missing SSL certificate and key. + if [ -n "${MTR_SST_SIMULATE_NO_SSL_CERT:-}" ]; then + tpem="" + tkey="" + fi if [ "$tmode" != 'DISABLED' ]; then if [ 0 -eq $encrypt -a -n "$tpem" -a -n "$tkey" ] then @@ -599,6 +604,16 @@ read_cnf() "CERT='$tpem', KEY='$tkey', MODE='$tmode'," \ "encrypt='$encrypt'" + # ssl-mode requires encryption but none could be set up (no usable + # cert/key): abort instead of silently transferring in cleartext. + if [ "$tmode" != 'DISABLED' -a $encrypt -eq 0 ]; then + wsrep_log_error "ssl-mode is set to '$tmode', but no usable SSL" \ + "certificate and key were found. Cannot perform an" \ + "encrypted transfer. Please configure ssl-cert and" \ + "ssl-key, or set ssl-mode to DISABLED." + exit 22 # EINVAL + fi + if [ $encrypt -ge 2 ]; then ssl_dhparams=$(parse_cnf "$encgroups" 'ssl-dhparams') fi diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 93bef66244e..61ed3d5aee3 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -192,14 +192,11 @@ SSTCAP="$tcap" SSLMODE=$(parse_cnf "$encgroups" 'ssl-mode' | tr '[[:lower:]]' '[[:upper:]]') if [ -z "$SSLMODE" ]; then - # Implicit verification if CA is set and the SSL mode - # is not specified by user: + # ssl-mode not set: derive it from the SSL config. Set it even when + # stunnel is absent, so the check below aborts instead of silently + # falling back to an unencrypted transfer. if [ -n "$SSTCA$SSTCAP" ]; then - STUNNEL_BIN=$(commandex 'stunnel') - if [ -n "$STUNNEL_BIN" ]; then - SSLMODE='VERIFY_CA' - fi - # Require SSL by default if SSL key and cert are present: + SSLMODE='VERIFY_CA' elif [ -n "$SSTKEY" -a -n "$SSTCERT" ]; then SSLMODE='REQUIRED' fi @@ -274,10 +271,22 @@ if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then if [ -z "${STUNNEL_BIN+x}" ]; then STUNNEL_BIN=$(commandex 'stunnel') fi + # MTR test hook: simulate a missing stunnel binary. + if [ -n "${MTR_SST_SIMULATE_NO_STUNNEL:-}" ]; then + STUNNEL_BIN="" + fi if [ -n "$STUNNEL_BIN" ]; then wsrep_log_info "Using stunnel for SSL encryption: CA: '$SSTCA'," \ "CAPATH='$SSTCAP', ssl-mode='$SSLMODE'" STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + else + # Encryption required but stunnel missing: abort instead of + # silently falling back to an unencrypted transfer. + wsrep_log_error "ssl-mode is set to '$SSLMODE', but the 'stunnel'" \ + "binary was not found in the path. Cannot perform" \ + "an encrypted transfer. Please install stunnel or" \ + "set ssl-mode to DISABLED." + exit 2 # ENOENT fi fi From 8856f9fe196af14754e18360d5ecefc998f39db0 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 28 Jul 2026 18:35:25 +0200 Subject: [PATCH 011/150] MDEV-40540 ST_GeomFromWKB stack overrun on deeply nested GeometryCollection --- mysql-test/main/lotofstack.result | 5 +++++ mysql-test/main/lotofstack.test | 9 +++++++++ sql/spatial.cc | 3 +++ 3 files changed, 17 insertions(+) diff --git a/mysql-test/main/lotofstack.result b/mysql-test/main/lotofstack.result index 6f776eb6733..dfe12c3f796 100644 --- a/mysql-test/main/lotofstack.result +++ b/mysql-test/main/lotofstack.result @@ -103,4 +103,9 @@ drop table t3| # MDEV-40409 ST_GeomFromText stack overrun on deeply nested GeometryCollection # SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',REPEAT(')',5000))); +# +# MDEV-40540 ST_GeomFromWKB stack overrun on deeply nested GeometryCollection +# +set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); +SELECT ST_GeomFromWKB(@a); # End of 10.6 tests diff --git a/mysql-test/main/lotofstack.test b/mysql-test/main/lotofstack.test index 10b3e762082..768c87160b6 100644 --- a/mysql-test/main/lotofstack.test +++ b/mysql-test/main/lotofstack.test @@ -140,4 +140,13 @@ delimiter ;| SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',REPEAT(')',5000))); --enable_result_log +--echo # +--echo # MDEV-40540 ST_GeomFromWKB stack overrun on deeply nested GeometryCollection +--echo # +set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); +--disable_result_log +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ST_GeomFromWKB(@a); +--enable_result_log + --echo # End of 10.6 tests diff --git a/sql/spatial.cc b/sql/spatial.cc index 20b8a551ef5..79e36bf6215 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -3406,6 +3406,9 @@ uint Gis_geometry_collection::init_from_wkb(const char *wkb, uint len, return 0; n_geom= wkb_get_uint(wkb, bo); + if (check_stack_overrun(current_thd, STACK_MIN_SIZE, (uchar*)&wkb_orig)) + return 1; + if (res->reserve(4, 512)) return 0; res->q_append(n_geom); From 9beedae93c5c12d612bf3039e4c74042e8c51de0 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 1 Jul 2026 18:51:24 +0200 Subject: [PATCH 012/150] github copilot review instructions Assisted-By: Claude:claude-4.8-opus --- .github/copilot-instructions.md | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..1d5935f981a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,114 @@ +# MariaDB Server — Code Review Instructions + +You are reviewing a change to MariaDB Server, a large C/C++ codebase with +strict conventions. **Correctness** comes first, but style matters too: no +automated formatter runs on this repo, so review is the only gate that +enforces the coding style — flag style violations as well as bugs. + +## Priorities (in order) +1. Correctness bugs and crashes (see checklist below). +2. Backward/forward compatibility (on-disk formats, protocol, replication). +3. Performance and scalability — but only where it plausibly matters (a hot + path, or large/unbounded data), not speculative micro-optimization. Look + for: + - Wrong algorithmic complexity for the expected scale — e.g. an O(N²) loop + where a hash or sort gives O(N)/O(N log N) and N can be large. Conversely, + do NOT flag a simple O(N²) over small, bounded input where a heavier data + structure would be slower and less readable — call out the trade-off, not + just the asymptotics. + - Expensive or blocking work inside a critical section — disk/network I/O, + `fsync`, memory allocation, logging, or acquiring another lock while + holding a mutex/latch/rwlock. Shrink the critical section instead. + - Avoidable per-row cost in a hot loop — repeated allocation, redundant + recomputation of a loop-invariant, needless charset/string conversion, or + a full scan where an index lookup exists. + - Unnecessary copying of large records/buffers where a reference, move, or + in-place operation suffices. +4. Missing or inadequate test coverage. +5. Style deviations from CODING_STANDARDS.md. These are NOT enforced by any + automated formatter, so review is the only gate — do flag them, as nits. + +## MariaDB-specific bug checklist +- **Memory lifetime.** Allocator must match lifetime: `MEM_ROOT` + (`new (thd->mem_root) T`, `alloc_root`) for query-scoped; `my_malloc`/plain + `new` for large or long-lived. Flag: pointers into a `MEM_ROOT`/`String`/`blob_heap` + buffer that survive `free_root` or a record re-read; mismatched alloc/free + families — handing `MEM_ROOT` memory to `free`/`my_free` (it is bulk-freed at + `free_root`), or `my_free`-ing something from `new`. Note `delete obj` on a + `MEM_ROOT` object is fine and often required — classes like `Item` make + `operator delete` a no-op, so `delete` runs the destructor without freeing. +- **Prepared-statement arena.** Objects that must survive re-execution belong + on `thd->stmt_arena` (`Query_arena_stmt`), not the runtime mem_root. + Statements whose shape can change need `CF_REEXECUTION_FRAGILE` / + `needs_reprepare`, or PS reuse crashes or returns stale results. Item + tree changes are either permanent on the stmt_arena or temporary + and must be registered. +- **Error contract.** `bool`: false=success, true=error; int: 0=success. + Every fallible call checked; `my_error()` issued before returning true; + error path frees what it allocated (`goto err`). +- **Scoped error interception.** `thd->is_error()` checked after an operation, + or `thd->clear_error()`, is usually an anti-pattern: both act on the whole + THD, so they also see (or wipe) a legitimate error raised *before* this + operation. To detect or suppress errors from a specific bounded operation, + push an `Internal_error_handler` (`thd->push_internal_handler()` / + `pop_internal_handler()`) around exactly that scope. +- **NULL.** SQL NULL (`null_value`/`is_null()`) vs C NULL pointer; `maybe_null` + propagation; `val_str()`/`val_*` may return NULL. `item->null_value` / + `is_null()` is valid ONLY after the item has been evaluated by a `val_*()` + call in the current row — it is set as a side effect of evaluation, so + checking it before (or without) evaluating reads a stale/undefined flag. +- **Replication.** Non-deterministic constructs and their SBR vs RBR logging + safety; binlog side effects; cross-version compatibility. +- **Concurrency.** Mutex acquisition order and init order; `LOCK_*` globals; + init-once races. Respect `thd->killed`, `thd->check_killed()` is preferred. +- **Compatibility.** `.frm`, redo/undo, system-table schema, wire protocol, + sysvar defaults — a format change without upgrade handling is blocking. +- **Portability.** No `long`/`ulong` (use fixed-width or `size_t`); don't rely + on char being signed; alignment, endianness; integer overflow/truncation in + size arithmetic. +- **Assertions.** `DBUG_ASSERT` is compiled out in release — for invariants + only, never to validate external/untrusted input. Use them also as a + self-enforcing code documentation ("note, ptr is never NULL here"). + +## Testing +- Expect an `mysql-test/` `.test`; it must FAIL without the code change + (demonstrate the regression), not merely pass with it. +- **Check the test is adequate, not just present.** It must exercise the + logic the fix actually adds. For every new condition/branch, expect a case + that hits each side — a fix guarded by `if (value is NULL)` needs both a + NULL case (takes the new path) and a non-NULL case (doesn't). A new loop + bound, error path, or type case is untested until an input reaches it. Flag + new conditionals with no covering test. +- **A `.result`-only change is a red flag, not noise.** When a `.test` is + unchanged but its `.result` is, ask: (1) Is the new output actually + correct, or does it silently bless a regression? (2) Did the change gut the + test — e.g. an `EXPLAIN` that once exercised a specific optimizer path now + shows a different plan, so the test no longer covers what it was written to + cover? Flag both. + +## PR hygiene +- Subject line starts with `MDEV-NNNNN`; body wrapped at 72 cols. +- Bug fix targets the oldest maintained branch that reproduces (≤3y since GA); + new feature targets the main branch. +- Bug fix commit is minimal; cleanups (unrelated and related - prerequisite + cleanups) belong in separate commits, even if they are allowed to be in the + same PR + +## Do NOT +- Restate what the diff does. +- Report pure compiler/CI failures (they are already visible) — but style is + NOT auto-enforced here, so style review is expected, not redundant. +- Suggest STL/`std::` replacements — this codebase uses `List`, + `Dynamic_array`, `Hash_set`, `String`, `LEX_CSTRING`, etc. by policy. +- Assert about code outside the diff — ask a question instead. Do not invent + API/symbol names; verify against the code shown. + +## Output +- Cite `file:line`. Tag each finding **blocking / should-fix / nit**. +- Prefer a few high-confidence findings over many speculative ones. + +## Skip +Submodules and bundled third-party code (`zlib/`). +Note: `.result` files are NOT skipped — see Testing. + +See CODING_STANDARDS.md for full style rules. From eccf9ca5f15bdca5b8e887396d9a570d580eb4ee Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 28 Jul 2026 16:30:05 +0200 Subject: [PATCH 013/150] move DuckDB to a separate package --- debian/autobake-deb.sh | 7 +++++-- storage/duckdb/CMakeLists.txt | 7 ++++++- storage/duckdb/debian/mariadb-plugin-duckdb.install | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/debian/autobake-deb.sh b/debian/autobake-deb.sh index 1249a097bcc..9b3e12a3c2e 100755 --- a/debian/autobake-deb.sh +++ b/debian/autobake-deb.sh @@ -179,9 +179,12 @@ then fi # Enable DuckDB storage engine plugin packaging -if grep -q "$architecture" storage/duckdb/debian/control +if grep -q "$architecture" storage/duckdb/debian/control && \ + [[ ! -f debian/mariadb-plugin-duckdb.install ]] then - cat storage/duckdb/debian/mariadb-plugin-duckdb.install >> debian/mariadb-server.install + echo >> debian/control + cat storage/duckdb/debian/control >> debian/control + cp -v storage/duckdb/debian/mariadb-plugin-duckdb.install debian/ fi if [ -n "${AUTOBAKE_PREP_CONTROL_RULES_ONLY:-}" ] diff --git a/storage/duckdb/CMakeLists.txt b/storage/duckdb/CMakeLists.txt index 2105ed5fc71..f3a2242f96e 100644 --- a/storage/duckdb/CMakeLists.txt +++ b/storage/duckdb/CMakeLists.txt @@ -37,7 +37,12 @@ SET(DUCKDB_PLUGIN_SOURCES duckdb_udf.cc ) -MYSQL_ADD_PLUGIN(duckdb ${DUCKDB_PLUGIN_SOURCES} STORAGE_ENGINE MODULE_ONLY) +MYSQL_ADD_PLUGIN(duckdb ${DUCKDB_PLUGIN_SOURCES} STORAGE_ENGINE MODULE_ONLY + COMPONENT duckdb-engine) +SET(CPACK_RPM_duckdb-engine_PACKAGE_SUMMARY "DuckDB storage engine for MariaDB server" PARENT_SCOPE) +SET(CPACK_RPM_duckdb-engine_PACKAGE_DESCRIPTION "The MariaDB DuckDB storage +engine embeds DuckDB as a pluggable storage engine, enabling fast analytical +(OLAP) query processing directly inside MariaDB." PARENT_SCOPE) IF(TARGET duckdb) diff --git a/storage/duckdb/debian/mariadb-plugin-duckdb.install b/storage/duckdb/debian/mariadb-plugin-duckdb.install index 248ff92bd02..45065aa3854 100644 --- a/storage/duckdb/debian/mariadb-plugin-duckdb.install +++ b/storage/duckdb/debian/mariadb-plugin-duckdb.install @@ -1 +1,2 @@ +etc/mysql/mariadb.conf.d/duckdb.cnf usr/lib/mysql/plugin/ha_duckdb.so From 178a612b397af330d53763823c7392a81e7ff15d Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 20 Jul 2026 19:22:57 +0200 Subject: [PATCH 014/150] apply reasonable limits to initid.max_length as provided by UDF UDF can set max_length incorrectly, e.g. to 2138 for an integer. let's cap UDF-provided value by a type-specific limit. --- sql/item_func.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index 0dbe467bc04..07ae0fe99c4 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3669,7 +3669,8 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, u_d->name.str, init_msg_buff); goto err_exit; } - func->max_length=MY_MIN(initid.max_length,MAX_BLOB_WIDTH); + func->max_length= MY_MIN(initid.max_length,MAX_BLOB_WIDTH); + func->max_length= func->type_handler()->max_display_length(func); func->set_maybe_null(initid.maybe_null); /* The above call for init() can reset initid.const_item to "false", From d5fa60c345e5ef3304df8d5b548d8f8003fcd44a Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Tue, 28 Jul 2026 13:08:01 +0200 Subject: [PATCH 015/150] New CC 3.3 --- libmariadb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmariadb b/libmariadb index 93bb9266666..85c322f70e4 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit 93bb9266666a0a838187679ad2cc14af169895da +Subproject commit 85c322f70e477c5a7bd42c8344754d9335e56f58 From 67ea07dd3400004e4f7ae01bf977f4344bbe064b Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 28 Jul 2026 23:36:22 +0200 Subject: [PATCH 016/150] MDEV-40541 role vs user@localhost acl_cache key confusion localhost connections have ip=0, host="localhost". roles have ip="", host="" --- mysql-test/main/grant5.result | 28 ++++++++++++++++++++++++++++ mysql-test/main/grant5.test | 29 +++++++++++++++++++++++++++++ sql/sql_acl.cc | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/grant5.result b/mysql-test/main/grant5.result index b72b15ce19e..3a354e2ef25 100644 --- a/mysql-test/main/grant5.result +++ b/mysql-test/main/grant5.result @@ -495,4 +495,32 @@ CREATE USER for u2@localhost CREATE USER `u2`@`localhost` IDENTIFIED BY PASSWORD '*975B2CD4FF9AE554FE8AD33168FBFC326D2021DD' drop user u1@localhost; drop user u2@localhost; +# +# MDEV-40541 role vs user@localhost acl_cache key confusion +# +create database db; +create table db.t1(a varchar(40)); +insert into db.t1 values ('role-cache-collision-row'); +create role r1; +create user r1@localhost identified by 'r1pass'; +create user u1@'127.0.0.1' identified by 'u1pass'; +grant select on db.* to r1@localhost; +grant r1 to u1@'127.0.0.1'; +connect r1,localhost,r1,r1pass; +select a as precache from db.t1; +precache +role-cache-collision-row +connect u1,127.0.0.1,u1,u1pass; +set role r1; +show grants; +Grants for u1@127.0.0.1 +GRANT `r1` TO `u1`@`127.0.0.1` +GRANT USAGE ON *.* TO `u1`@`127.0.0.1` IDENTIFIED BY PASSWORD '*4C5C0A2C80D7567C7CFBDFC09409901AADC4B390' +GRANT USAGE ON *.* TO `r1` +select a from db.t1; +ERROR 42000: SELECT command denied to user 'u1'@'localhost' for table `db`.`t1` +connection default; +drop database db; +drop role r1; +drop user r1@localhost, u1@'127.0.0.1'; # End of 10.6 tests diff --git a/mysql-test/main/grant5.test b/mysql-test/main/grant5.test index ca25db100d4..51654c3f874 100644 --- a/mysql-test/main/grant5.test +++ b/mysql-test/main/grant5.test @@ -456,4 +456,33 @@ show create user u2@localhost; drop user u1@localhost; drop user u2@localhost; +--echo # +--echo # MDEV-40541 role vs user@localhost acl_cache key confusion +--echo # +create database db; +create table db.t1(a varchar(40)); +insert into db.t1 values ('role-cache-collision-row'); + +create role r1; +create user r1@localhost identified by 'r1pass'; +create user u1@'127.0.0.1' identified by 'u1pass'; + +grant select on db.* to r1@localhost; +grant r1 to u1@'127.0.0.1'; +#flush privileges; + +connect r1,localhost,r1,r1pass; +select a as precache from db.t1; + +connect u1,127.0.0.1,u1,u1pass; +set role r1; +show grants; +--error ER_TABLEACCESS_DENIED_ERROR +select a from db.t1; + +connection default; +drop database db; +drop role r1; +drop user r1@localhost, u1@'127.0.0.1'; + --echo # End of 10.6 tests diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index d4a7606d012..3dc5f391f9d 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -3704,7 +3704,7 @@ privilege_t acl_get(const char *host, const char *ip, acl_entry *entry; DBUG_ENTER("acl_get"); - tmp_db= strmov(strmov(key, safe_str(ip)) + 1, user) + 1; + tmp_db= strmov(strmov(key, safe_str(ip ? ip : host)) + 1, user) + 1; end= strnmov(tmp_db, db, key + sizeof(key) - tmp_db); if (end >= key + sizeof(key)) // db name was truncated From e97544b5c526316ddb886c148ed07cd0814092c5 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 16 Jul 2026 19:09:45 +0200 Subject: [PATCH 017/150] MDEV-40411 SFORMAT ignores max_allowed_packet do our own allocator that implements std::allocator interface but uses our memory accounting (my_malloc) and limits max allocation (e.g. to max_allowed_packet). use it for fmt::vformat_to. later can be used for various std:: stuff too. --- mysql-test/main/func_sformat.result | 37 +++++++++++++++--- mysql-test/main/func_sformat.test | 32 +++++++++++++--- sql/item_strfunc.cc | 59 ++++++++++++++++++++++++----- 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/mysql-test/main/func_sformat.result b/mysql-test/main/func_sformat.result index 053717ce705..7877ce7913c 100644 --- a/mysql-test/main/func_sformat.result +++ b/mysql-test/main/func_sformat.result @@ -459,13 +459,8 @@ select sformat('{:f}', @a); sformat('{:f}', @a) 3.140000 set names latin1; -# # End of 10.7 tests # -# -# Start of 10.8 tests -# -# # MDEV-29646 sformat('Num [{:20}]', 42) gives incorrect result in view # create view v1 as select sformat('Num [{:20}]', 42); @@ -498,4 +493,36 @@ bar 2 foo 1 drop view v; drop table t; +# +# MDEV-40411 SFORMAT ignores max_allowed_packet +# +set @old_max_allowed_packet=@@global.max_allowed_packet; +set @@global.max_allowed_packet=512*1024; +connect u,localhost,root; +select length(sformat('{:2147483647}', 1)); +length(sformat('{:2147483647}', 1)) +NULL +select length(sformat('{:524288}', 1)); +length(sformat('{:524288}', 1)) +524288 +select length(sformat('{:524289}', 1)); +length(sformat('{:524289}', 1)) +NULL +select length(sformat('{:524287}.', 1)); +length(sformat('{:524287}.', 1)) +524288 +select length(sformat('{:524287}..', 1)); +length(sformat('{:524287}..', 1)) +NULL +disconnect u; +connection default; +set @@global.max_allowed_packet= @old_max_allowed_packet; +set max_session_mem_used=512*1024; +select length(sformat('{:524288}', 1)); +length(sformat('{:524288}', 1)) +524288 +select length(sformat('{:524289}', 1)); +length(sformat('{:524289}', 1)) +NULL +set max_session_mem_used=default; # End of 10.11 tests diff --git a/mysql-test/main/func_sformat.test b/mysql-test/main/func_sformat.test index 9e86615d268..332d5a67027 100644 --- a/mysql-test/main/func_sformat.test +++ b/mysql-test/main/func_sformat.test @@ -236,13 +236,7 @@ set @a=3.14; select sformat('{:f}', @a); set names latin1; -echo #; echo # End of 10.7 tests; -echo #; - -echo #; -echo # Start of 10.8 tests; -echo #; echo #; echo # MDEV-29646 sformat('Num [{:20}]', 42) gives incorrect result in view; @@ -274,4 +268,30 @@ select * from v where a != sformat('{}', 'qux'); drop view v; drop table t; +echo #; +echo # MDEV-40411 SFORMAT ignores max_allowed_packet; +echo #; +set @old_max_allowed_packet=@@global.max_allowed_packet; +set @@global.max_allowed_packet=512*1024; +connect u,localhost,root; +# these three case: fmt allocates the requested size at once +select length(sformat('{:2147483647}', 1)); +select length(sformat('{:524288}', 1)); +select length(sformat('{:524289}', 1)); +# here it allocates 524287 and then grows by *1.5 +select length(sformat('{:524287}.', 1)); +select length(sformat('{:524287}..', 1)); +disconnect u; +connection default; +set @@global.max_allowed_packet= @old_max_allowed_packet; + +disable_cursor_protocol; +disable_view_protocol; +set max_session_mem_used=512*1024; +select length(sformat('{:524288}', 1)); +select length(sformat('{:524289}', 1)); +set max_session_mem_used=default; +enable_view_protocol; +enable_cursor_protocol; + echo # End of 10.11 tests; diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index b5682cf9019..9b31b501cad 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -59,6 +59,38 @@ C_MODE_END #define FMT_HEADER_ONLY 1 #include "fmt/args.h" +/* + an allocator that implements std::allocator interface. + can be used with std or fmt. + but it uses our memory accounting + and allows to limit strings to max_allowed_packet + to be more generic needs to take MY_THREAD_SPECIFIC and PSI key as arguments, + and limit the amount of memory allocated, not just size of one allocation +*/ +template struct my_allocator { + using value_type= T; + std::size_t max_alloc; + + my_allocator(std::size_t max_alloc) : max_alloc(max_alloc) {}; + template my_allocator(const my_allocator&o) + : max_alloc(o.max_alloc) {} + + T* allocate(std::size_t n) + { + void* p= n > max_alloc / sizeof(T) + ? nullptr : my_malloc(PSI_INSTRUMENT_MEM, n * sizeof(T), + MYF(MY_THREAD_SPECIFIC)); + if (!p) + throw std::bad_alloc(); + return static_cast(p); + } + + std::size_t max_size() { return max_alloc / sizeof(T); } + void deallocate(T* p, std::size_t) noexcept { my_free(p); } + bool operator==(const my_allocator&) const noexcept { return true; } + bool operator!=(const my_allocator&) const noexcept { return false; } +}; + size_t username_char_length= USERNAME_CHAR_LENGTH; /* @@ -1357,8 +1389,6 @@ bool Item_func_sformat::fix_length_and_dec(THD *thd) if (!val_arg) return TRUE; - ulonglong char_length= 0; - uint flags= MY_COLL_ALLOW_SUPERSET_CONV | MY_COLL_ALLOW_COERCIBLE_CONV | MY_COLL_ALLOW_NUMERIC_CONV; @@ -1379,8 +1409,8 @@ bool Item_func_sformat::fix_length_and_dec(THD *thd) return TRUE; } - char_length= MAX_BLOB_WIDTH; - fix_char_length_ulonglong(char_length); + max_length= MY_MIN((uint32)thd->variables.max_allowed_packet, + (uint32)thd->variables.max_mem_used); return FALSE; } @@ -1412,6 +1442,8 @@ struct fmt_locale_comma : std::numpunct }; static std::locale fmt_locale(std::locale(), new fmt_locale_comma); +using fmt_buffer= fmt::basic_memory_buffer >; /* SFORMAT(format_string, ...) This function receives a formatting specification string and N parameters @@ -1460,14 +1492,19 @@ String *Item_func_sformat::val_str(String *res) } } - null_value= false; /* Create the string output */ try { - auto text = fmt::vformat(fmt_locale, fmt_arg->c_ptr_safe(), arg_store); - res->length(0); - res->set_charset(collation.collation); - res->append(text.c_str(), text.size(), fmt_arg->charset()); + /* have to use *1.5 to match fmt realloc strategy */ + fmt_buffer text{my_allocator(max_length + max_length/2)}; + fmt::vformat_to(std::back_inserter(text), fmt_locale, + fmt_arg->c_ptr_safe(), arg_store); + if (!((null_value= text.size() > max_length))) + { + res->length(0); + res->set_charset(collation.collation); + res->append(text.data(), text.size(), fmt_arg->charset()); + } } catch (const fmt::format_error &ex) { @@ -1477,6 +1514,10 @@ String *Item_func_sformat::val_str(String *res) ER_THD(thd, WARN_SFORMAT_ERROR), ex.what()); null_value= true; } + catch (const std::bad_alloc&) + { + null_value= true; // OOM + } return null_value ? NULL : res; } From 2e842cbb1698e16060d0b44f3b02a92a8c8ddaa8 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 24 Jul 2026 17:27:28 +0200 Subject: [PATCH 018/150] cleanup: main.view test --- mysql-test/main/view.result | 85 ----------- mysql-test/main/view.test | 285 ------------------------------------ 2 files changed, 370 deletions(-) diff --git a/mysql-test/main/view.result b/mysql-test/main/view.result index a2fa623b9a8..ec35fac9e4a 100644 --- a/mysql-test/main/view.result +++ b/mysql-test/main/view.result @@ -1906,8 +1906,6 @@ test.v5 check error Corrupt test.v6 check status OK drop view v1, v2, v3, v4, v5, v6; drop table t2; -drop function if exists f1; -drop function if exists f2; CREATE TABLE t1 (col1 time); CREATE TABLE t2 (col1 time); CREATE TABLE t3 (col1 time); @@ -2144,8 +2142,6 @@ CREATE VIEW v1 AS SELECT f1(); ERROR HY000: View's SELECT refers to a temporary table 't1' DROP FUNCTION f1; DROP TABLE t1; -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; CREATE TABLE t1 (f4 CHAR(5)); CREATE VIEW v1 AS SELECT * FROM t1; DESCRIBE v1; @@ -2431,7 +2427,6 @@ f1 sum(f2) NULL 12 drop view v1; drop table t1; -drop procedure if exists p1; create procedure p1 () deterministic begin create view v1 as select 1; @@ -2444,7 +2439,6 @@ v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VI drop view v1; drop procedure p1; CREATE VIEW v1 AS SELECT 42 AS Meaning; -DROP FUNCTION IF EXISTS f1; CREATE FUNCTION f1() RETURNS INTEGER BEGIN DECLARE retn INTEGER; @@ -2866,7 +2860,6 @@ SHOW TABLES; Tables_in_test t1 DROP TABLE t1; -DROP VIEW IF EXISTS v1; set GLOBAL sql_mode=""; set LOCAL sql_mode=""; CREATE DATABASE bug21261DB; @@ -2910,9 +2903,6 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`f1` AS `f1` from `t1` where `t1`.`f1` between current_timestamp() and current_timestamp() + interval 1 minute latin1 latin1_swedish_ci drop view v1; drop table t1; -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; -DROP VIEW IF EXISTS v2; CREATE TABLE t1(a INT, b INT); CREATE DEFINER=longer_than_80_456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789@localhost VIEW v1 AS SELECT a FROM t1; @@ -2921,10 +2911,6 @@ CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890 VIEW v2 AS SELECT b FROM t1; ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTYa...' is too long for host name (should be no longer than 255) DROP TABLE t1; -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT); CREATE VIEW v1 AS SELECT * FROM t1; CREATE FUNCTION f1() RETURNS INT @@ -2987,9 +2973,6 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=MERGE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`pk` AS `pk` from (`t1` join `t2` on(`t2`.`fk` = `t1`.`pk` and `t2`.`ver` = (select max(`t`.`ver`) from `t2` `t` where `t`.`org` = `t2`.`org`))) latin1 latin1_swedish_ci DROP VIEW v1; DROP TABLE t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT); INSERT INTO t1 VALUES (1); CREATE VIEW v1 AS SELECT MAX(i) FROM t1; @@ -3011,8 +2994,6 @@ UPDATE v1 SET val=6 WHERE id=2; ERROR 44000: CHECK OPTION failed `test`.`v1` DROP VIEW v1; DROP TABLE t1; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT); CREATE VIEW v1 AS SELECT j FROM t1; CREATE VIEW v2 AS SELECT * FROM t1; @@ -3056,7 +3037,6 @@ SELECT * FROM v; x 5 DROP VIEW v; -DROP VIEW IF EXISTS v1; CREATE VIEW v1 AS SELECT 'The\ZEnd'; SELECT * FROM v1; TheEnd @@ -3167,7 +3147,6 @@ code COUNT(DISTINCT country) 100 2 DROP VIEW v1; DROP TABLE t1; -DROP VIEW IF EXISTS v1; SELECT * FROM (SELECT 1) AS t into @w; Warnings: Warning 1287 ' INTO FROM...' instead @@ -3177,9 +3156,6 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp SELECT * FROM (SELECT 1) AS t into @w; Warnings: Warning 1287 ' INTO FROM...' instead -drop view if exists view_24532_a; -drop view if exists view_24532_b; -drop table if exists table_24532; create table table_24532 ( a int, b bigint, @@ -3643,38 +3619,24 @@ DROP TABLE t1; # ----------------------------------------------------------------- # -- Bug#34337 Server crash when Altering a view using a table name. # ----------------------------------------------------------------- - -DROP TABLE IF EXISTS t1; - CREATE TABLE t1(c1 INT); - SELECT * FROM t1; c1 ALTER ALGORITHM=TEMPTABLE SQL SECURITY INVOKER VIEW t1 (c2) AS SELECT (1); ERROR HY000: 'test.t1' is not of type 'VIEW' - DROP TABLE t1; - -# -- End of test case for Bug#34337. - # ----------------------------------------------------------------- # -- Bug#35193 VIEW query is rewritten without "FROM DUAL", # -- causing syntax error # ----------------------------------------------------------------- - CREATE VIEW v1 AS SELECT 1 FROM DUAL WHERE 1; - SELECT * FROM v1; 1 1 SHOW CREATE TABLE v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select 1 AS `1` from DUAL where 1 latin1 latin1_swedish_ci - DROP VIEW v1; - -# -- End of test case for Bug#35193. - CREATE VIEW v1 AS SELECT 1; DROP VIEW v1; CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 INT, INDEX (c2)); @@ -3835,7 +3797,6 @@ DROP TABLE t1; # ----------------------------------------------------------------- # -- End of 5.0 tests. # ----------------------------------------------------------------- -DROP DATABASE IF EXISTS `d-1`; CREATE DATABASE `d-1`; USE `d-1`; CREATE TABLE `t-1` (c1 INT); @@ -3854,93 +3815,61 @@ DROP TABLE `t-2`; DROP VIEW `v-2`; DROP DATABASE `d-1`; USE test; - # # Bug#26676 VIEW using old table schema in a session. # - -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; CREATE TABLE t1(c1 INT, c2 INT); INSERT INTO t1 VALUES (1, 2), (3, 4); - SELECT * FROM t1; c1 c2 1 2 3 4 - CREATE VIEW v1 AS SELECT * FROM t1; - SELECT * FROM v1; c1 c2 1 2 3 4 - ALTER TABLE t1 ADD COLUMN c3 INT AFTER c2; - SELECT * FROM t1; c1 c2 c3 1 2 NULL 3 4 NULL - SELECT * FROM v1; c1 c2 1 2 3 4 - SHOW CREATE VIEW v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`c1` AS `c1`,`t1`.`c2` AS `c2` from `t1` latin1 latin1_swedish_ci - DROP VIEW v1; DROP TABLE t1; - -# End of test case for Bug#26676. - # ----------------------------------------------------------------- # -- Bug#32538 View definition picks up character set, but not collation # ----------------------------------------------------------------- - -DROP VIEW IF EXISTS v1; - SET collation_connection = latin1_general_ci; CREATE VIEW v1 AS SELECT _latin1 'text1' AS c1, 'text2' AS c2; - SELECT COLLATION(c1), COLLATION(c2) FROM v1; COLLATION(c1) COLLATION(c2) latin1_swedish_ci latin1_general_ci - SHOW CREATE VIEW v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select _latin1'text1' AS `c1`,'text2' AS `c2` latin1 latin1_general_ci - SELECT * FROM v1 WHERE c1 = 'text1'; ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin1_general_ci,COERCIBLE) for operation '=' - SELECT * FROM v1 WHERE c2 = 'text2'; c1 c2 text1 text2 - use test; SET names latin1; - SELECT COLLATION(c1), COLLATION(c2) FROM v1; COLLATION(c1) COLLATION(c2) latin1_swedish_ci latin1_general_ci - SELECT * FROM v1 WHERE c1 = 'text1'; c1 c2 text1 text2 - SELECT * FROM v1 WHERE c2 = 'text2'; ERROR HY000: Illegal mix of collations (latin1_general_ci,COERCIBLE) and (latin1_swedish_ci,COERCIBLE) for operation '=' - DROP VIEW v1; - -# -- End of test case for Bug#32538. - -drop view if exists a; -drop procedure if exists p; create procedure p() begin declare continue handler for sqlexception begin end; @@ -3963,9 +3892,6 @@ DROP TABLE t1; # Bug#48449: hang on show create view after upgrading when # view contains function of view # -DROP VIEW IF EXISTS v1,v2; -DROP TABLE IF EXISTS t1,t2; -DROP FUNCTION IF EXISTS f1; CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); CREATE FUNCTION f1() RETURNS INT @@ -4713,8 +4639,6 @@ c a b c 0 93 1 0 DROP VIEW v4; DROP TABLE t1,t2,t3,t4; -drop table if exists t_9801; -drop view if exists v_9801; create table t_9801 (s1 int); create view v_9801 as select sum(s1) from t_9801 with check option; @@ -4729,8 +4653,6 @@ drop table t_9801; # # Bug #47335 assert in get_table_share # -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; CREATE TEMPORARY TABLE t1 (id INT); ALTER VIEW t1 AS SELECT 1 AS f1; ERROR 42S02: Table 'test.t1' doesn't exist @@ -4747,8 +4669,6 @@ DROP VIEW v1; # Bug #47635 assert in start_waiting_global_read_lock # during CREATE VIEW # -DROP TABLE IF EXISTS t1, t2; -DROP VIEW IF EXISTS t2; CREATE TABLE t1 (f1 integer); CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); CREATE TEMPORARY TABLE t2 (f1 integer); @@ -4762,8 +4682,6 @@ DROP TABLE t1, t2; # Bug#48315 Metadata lock is not taken for merged views that # use an INFORMATION_SCHEMA table # -DROP VIEW IF EXISTS v1; -DROP PROCEDURE IF EXISTS p1; connect con2, localhost, root; connect con3, localhost, root; connection default; @@ -4806,7 +4724,6 @@ disconnect con3; # Bug#12626844: WRONG ERROR MESSAGE WHILE CREATING A VIEW ON A # NON EXISTING DATABASE # -DROP DATABASE IF EXISTS nodb; CREATE VIEW nodb.a AS SELECT 1; ERROR 42000: Unknown database 'nodb' # @@ -6931,10 +6848,8 @@ SELECT * FROM v1; b'' DROP VIEW v1; -# # End of 10.3 tests # -# # MDEV-25206: view specification contains unknown column reference # CREATE TABLE t1 (a int); diff --git a/mysql-test/main/view.test b/mysql-test/main/view.test index cb120bb26ad..a43af9244c6 100644 --- a/mysql-test/main/view.test +++ b/mysql-test/main/view.test @@ -70,7 +70,6 @@ create view v3 (c) as select a+1 from v1; -- error ER_BAD_FIELD_ERROR create view v3 (c) as select b+1 from v1; - # VIEW on VIEW test with mixing different algorithms on different order create view v3 (c) as select c+1 from v1; select c from v3; @@ -159,7 +158,6 @@ select * from v2 natural left join v1; drop view v1, v2; drop table t1; - # # DISTINCT option for VIEW # @@ -758,7 +756,6 @@ show create view v1; drop view v1; drop table t1; - # # VIEW with floating point (long number) as column # @@ -1678,10 +1675,6 @@ CHECK TABLE v1, v2, v3, v4, v5, v6; drop view v1, v2, v3, v4, v5, v6; drop table t2; ---disable_warnings -drop function if exists f1; -drop function if exists f2; ---enable_warnings CREATE TABLE t1 (col1 time); CREATE TABLE t2 (col1 time); CREATE TABLE t3 (col1 time); @@ -1735,7 +1728,6 @@ show create view v1; drop view v1; drop table t1; - # # Correct inserting data check (absence of default value) for view # underlying tables (Bug#6443) @@ -1754,7 +1746,6 @@ set sql_mode=default; drop view v2,v1; drop table t1; - # # Bug#11399 Use an alias in a select statement on a view # @@ -1765,7 +1756,6 @@ select f1 as alias from v1; drop view v1; drop table t1; - # # Test for Bug#6120 SP cache to be invalidated when altering a view # @@ -1786,7 +1776,6 @@ DROP PROCEDURE p1; DROP VIEW v1; DROP TABLE t1; - # # Test for Bug#11709 View was ordered by wrong column # @@ -1797,7 +1786,6 @@ select * from v1 order by f1; drop view v1; drop table t1; - # # Test for Bug#11771 wrong query_id in SELECT * FROM # @@ -1812,7 +1800,6 @@ SELECT * FROM t1; DROP VIEW v1; DROP TABLE t1; - # # opening table in correct locking mode (Bug#9597) # @@ -1831,7 +1818,6 @@ DROP PROCEDURE p1; DROP VIEW v1; DROP TABLE t1; - # # Bug#11760 Typo in Item_func_add_time::print() results in NULLs returned # subtime() in view @@ -1842,7 +1828,6 @@ select * from v1; drop view v1; drop table t1; - # # Test for Bug#11412 query over a multitable view with GROUP_CONCAT # @@ -1867,7 +1852,6 @@ SELECT pid,GROUP_CONCAT(CONCAT(fn,' ',ln) ORDER BY 1) FROM v1 GROUP BY pid; DROP VIEW v1; DROP TABLE t1,t2; - # # Test for Bug#12382 SELECT * FROM view after INSERT command # @@ -1883,7 +1867,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Test for Bug#12470 crash for a simple select from a view defined # as a join over 5 tables @@ -1903,7 +1886,6 @@ SELECT a FROM v1; DROP VIEW v1; DROP TABLE t1,t2,t3,t4,t5; - # # Bug#12298 Typo in function name results in erroneous view being created. # @@ -1923,7 +1905,6 @@ call p1(); call p1(); drop procedure p1; - # # Bug#10624 Views with multiple UNION and UNION ALL produce incorrect results # @@ -1936,7 +1917,6 @@ select * from v1; drop view v1; drop table t1,t2; - # # Test for Bug#10970 view referring a temporary table indirectly # @@ -1949,14 +1929,9 @@ CREATE VIEW v1 AS SELECT f1(); DROP FUNCTION f1; DROP TABLE t1; - # # Bug#12533 (crash on DESCRIBE after renaming base table column) # ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; ---enable_warnings CREATE TABLE t1 (f4 CHAR(5)); CREATE VIEW v1 AS SELECT * FROM t1; @@ -1968,7 +1943,6 @@ DESCRIBE v1; DROP TABLE t1; DROP VIEW v1; - # # Bug#12489 wrongly printed strcmp() function results in creation of broken # view @@ -1978,7 +1952,6 @@ select * from v1; drop view v1; drop table t1; - # # Bug#12922 if(sum(),...) with group from view returns wrong results # @@ -1989,7 +1962,6 @@ select if(sum(f1)>1,f2,f3) from v1 group by f1; drop view v1; drop table t1; - # Bug#12941 # create table t1 ( @@ -2022,7 +1994,6 @@ order by users_names; drop view v1, v2; drop table t1, t2; - # # Bug#6808 Views: CREATE VIEW v ... FROM t AS v fails # @@ -2032,7 +2003,6 @@ create view abc as select * from t1 as abc; drop table t1; drop view abc; - # # Bug#12993 View column rename broken in subselect # @@ -2072,7 +2042,6 @@ create view v1 as SELECT CONVERT_TZ('2004-01-01 12:00:00','GMT','MET'); select * from v1; drop view v1; - # # Bugs#12963, #13000 wrong creation of VIEW with DAYNAME, DAYOFWEEK, and WEEKDAY # @@ -2104,7 +2073,6 @@ SELECT * FROM v3; DROP TABLE t1; DROP VIEW v1, v2, v3; - # # Bug#13411 crash when using non-qualified view column in HAVING clause # @@ -2118,7 +2086,6 @@ SELECT v1.a FROM v1 GROUP BY v1.a HAVING a > 1; DROP VIEW v1; DROP TABLE t1; - # # Bug#13410 failed name resolution for qualified view column in HAVING # @@ -2134,7 +2101,6 @@ SELECT v_1.a FROM v1 AS v_1 GROUP BY v_1.a HAVING v_1.a IN (1,2,3); DROP VIEW v1; DROP TABLE t1; - # # Bug#13327 view wasn't using index for const condition # @@ -2153,7 +2119,6 @@ EXPLAIN SELECT * FROM v2 WHERE a=1; DROP VIEW v1,v2; DROP TABLE t1,t2,t3; - # # Bug#13622 Wrong view .frm created if some field's alias contain \n # @@ -2164,7 +2129,6 @@ select * from v1; drop view v1; drop table t1; - # Bug#14466 lost sort order in GROUP_CONCAT() in a view # create table t1 (f1 int, f2 int); @@ -2176,7 +2140,6 @@ select * from v2; drop view v1,v2; drop table t1; - # # Bug#14026 Crash on second PS execution when using views # @@ -2201,7 +2164,6 @@ execute stmt1 using @parm1; drop view v1; drop table t1,t2,t3,t4; - # # Bug#14540 OPTIMIZE, ANALYZE, REPAIR applied to not a view # @@ -2220,7 +2182,6 @@ REPAIR TABLE v1; DROP VIEW v1; - # # Bug#14719 Views DEFINER grammar is incorrect # @@ -2233,7 +2194,6 @@ create definer = current_user sql security invoker view v1 as select 1; show create view v1; drop view v1; - # # Bug#14816 test_if_order_by_key() expected only Item_fields. # @@ -2244,7 +2204,6 @@ explain select id from v1 order by id; drop view v1; drop table t1; - # # Bug#14850 Item_ref's values wasn't updated # @@ -2256,14 +2215,10 @@ select f1, sum(f2) from v1 group by f1; drop view v1; drop table t1; - # # Bug#14885 incorrect SOURCE in view created in a procedure # TODO: here SOURCE string must be shown when it will be possible # ---disable_warnings -drop procedure if exists p1; ---enable_warnings delimiter //; create procedure p1 () deterministic begin @@ -2276,14 +2231,10 @@ show create view v1; drop view v1; drop procedure p1; - # # Bug#15096 using function with view for view creation # CREATE VIEW v1 AS SELECT 42 AS Meaning; ---disable_warnings -DROP FUNCTION IF EXISTS f1; ---enable_warnings DELIMITER //; --enable_prepare_warnings CREATE FUNCTION f1() RETURNS INTEGER @@ -2300,7 +2251,6 @@ select * from v2; drop view v2,v1; drop function f1; - # # Bug#14861 aliased column names are not preserved. # @@ -2320,7 +2270,6 @@ order by v2.receipt_id; drop view v2, v1; drop table t1; - # # Bug#16016 MIN/MAX optimization for views # @@ -2345,7 +2294,6 @@ EXPLAIN SELECT MIN(a) FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#16382 grouping name is resolved against a view column name # which coincides with a select column name @@ -2363,7 +2311,6 @@ SELECT IF(x IS NULL, 'blank', 'not blank') AS x FROM v1 GROUP BY x; DROP VIEW v1; DROP TABLE t1; - # # Bug#15943 mysql_next_result hangs on invalid SHOW CREATE VIEW # @@ -2381,7 +2328,6 @@ drop view v1; delimiter ;// --enable_ps_protocol - # # Bug#17726 Not checked empty list caused endless loop # @@ -2396,7 +2342,6 @@ select * from v2; drop view v2, v1; drop table t1; - # # Bug#18386 select from view over a table with ORDER BY view_col clause # given view_col is not an image of any column from the base table @@ -2411,7 +2356,6 @@ SELECT my_sqrt FROM v1 ORDER BY my_sqrt; DROP VIEW v1; DROP TABLE t1; - # # Bug#18237 invalid count optimization applied to an outer join with a view # @@ -2433,7 +2377,6 @@ DROP VIEW v2; DROP TABLE t1, t2; - # # Bug#16069 VIEW does return the same results as underlying SELECT # with WHERE condition containing BETWEEN over dates @@ -2455,7 +2398,6 @@ SELECT * FROM v1 WHERE td BETWEEN CAST('2005.01.02' AS DATE) AND CAST('2005.01.0 DROP VIEW v1; DROP TABLE t1; - # # Bug#14308 Recursive view definitions # @@ -2489,7 +2431,6 @@ select * from v1; drop function f1; drop view t1, v1; - # # Bug#15153 CONVERT_TZ() is not allowed in all places in VIEWs # @@ -2517,7 +2458,6 @@ select * from v2; drop view v1, v2; drop table t1; - # # Bug#19490 usage of view specified by a query with GROUP BY # an expression containing non-constant interval @@ -2534,7 +2474,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#19077 A nested materialized view is used before being populated. # @@ -2546,7 +2485,6 @@ SELECT * FROM v2; DROP VIEW v2, v1; DROP TABLE t1; - # # Bug#19573 VIEW with HAVING that refers an alias name # @@ -2574,7 +2512,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#19089 wrong inherited dafault values in temp table views # @@ -2606,7 +2543,6 @@ SELECT * FROM t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#16110 insert permitted into view col w/o default value # @@ -2625,7 +2561,6 @@ SELECT * FROM t1; DROP VIEW v1; DROP TABLE t1; - # # Bug#18243 expression over a view column that with the REVERSE function # @@ -2642,7 +2577,6 @@ SELECT CONCAT(LEFT(name,LENGTH(name)-INSTR(REVERSE(name)," ")), DROP VIEW v1; DROP TABLE t1; - # # Bug#19714 wrong type of a view column specified by an expressions over ints # @@ -2656,7 +2590,6 @@ DESCRIBE t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#17526 views with TRIM functions # @@ -2681,7 +2614,6 @@ DROP VIEW v1; DROP TABLE t1; - # # Bug#21080 ALTER VIEW makes user restate SQL SECURITY mode, and ALGORITHM # @@ -2695,7 +2627,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1; - # Bug#21086 server crashes when VIEW defined with a SELECT with COLLATE # clause is called # @@ -2714,7 +2645,6 @@ SELECT s1 FROM t1; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#11551 Asymmetric + undocumented behaviour of DROP VIEW and DROP TABLE # @@ -2733,10 +2663,6 @@ show warnings; SHOW TABLES; DROP TABLE t1; ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - # # Bug#21261 Wrong access rights was required for an insert to a view @@ -2789,19 +2715,12 @@ show create view v1; drop view v1; drop table t1; - # # Test for Bug#16899 Possible buffer overflow in handling of DEFINER-clause. # # Prepare. ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; -DROP VIEW IF EXISTS v2; ---enable_warnings - CREATE TABLE t1(a INT, b INT); --error ER_WRONG_STRING_LENGTH @@ -2816,19 +2735,12 @@ CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890 DROP TABLE t1; - # # Bug#17591 Updatable view not possible with trigger or stored function # # During prelocking phase we didn't update lock type of view tables, # hence READ lock was always requested. # ---disable_warnings -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; ---enable_warnings CREATE TABLE t1 (i INT); @@ -2862,7 +2774,6 @@ DROP FUNCTION f2; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#5500 wrong select_type in EXPLAIN output for queries over views # @@ -2881,7 +2792,6 @@ EXPLAIN SELECT * FROM v1 t WHERE t.s1+1 < (SELECT MAX(t1.s1) FROM t1); DROP VIEW v1; DROP TABLE t1; - # # Bug#5505 Wrong error message on INSERT into a view # @@ -2893,7 +2803,6 @@ update v1 set a = 5; drop view v1; drop table t1; - # # Bug#21646 view qith a subquery in ON expression # @@ -2912,19 +2821,12 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1, t2; - # # Bug#19111 TRIGGERs selecting from a VIEW on the firing base table fail # # Allow to select from a view on a table being modified in a trigger # and stored function, since plain select is allowed there. # ---disable_warnings -DROP FUNCTION IF EXISTS f1; -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; ---enable_warnings - CREATE TABLE t1 (i INT); INSERT INTO t1 VALUES (1); @@ -2945,7 +2847,6 @@ DROP FUNCTION f1; DROP VIEW v1; DROP TABLE t1; - # # Bug#16813 (WITH CHECK OPTION doesn't work with UPDATE) # @@ -2960,7 +2861,6 @@ UPDATE v1 SET val=6 WHERE id=2; DROP VIEW v1; DROP TABLE t1; - # # Bug#22584 last_insert_id not updated after inserting a record # through a updatable view @@ -2969,11 +2869,6 @@ DROP TABLE t1; # not accessible through a view. However, we do not reset the value # of LAST_INSERT_ID, but keep it unchanged. # ---disable_warnings -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; ---enable_warnings - CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT); CREATE VIEW v1 AS SELECT j FROM t1; CREATE VIEW v2 AS SELECT * FROM t1; @@ -2998,7 +2893,6 @@ SELECT * FROM t1; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#25580 !0 as an operand in a select expression of a view # @@ -3011,15 +2905,9 @@ SELECT * FROM v; DROP VIEW v; - # # Bug#24293 '\Z' token is not handled correctly in views # - ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - CREATE VIEW v1 AS SELECT 'The\ZEnd'; SELECT * FROM v1; @@ -3027,7 +2915,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; - # # Bug#26124 BETWEEN over a view column of the DATETIME type # @@ -3044,7 +2931,6 @@ SELECT * FROM v1 WHERE mydate BETWEEN '2007-01-01' AND '2007-01-31'; DROP VIEW v1; DROP TABLE t1; - # # Bug#25931 update of a multi-table view with check option # @@ -3067,7 +2953,6 @@ SELECT * FROM t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#12122 Views with ORDER BY can't be resolved using MERGE algorithm. # @@ -3104,14 +2989,9 @@ SELECT code, COUNT(DISTINCT country) FROM v1 GROUP BY code ORDER BY MAX(id); DROP VIEW v1; DROP TABLE t1; - # # Bug#25897 Some queries are no longer possible after a CREATE VIEW fails # ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - let $query = SELECT * FROM (SELECT 1) AS t into @w; --disable_cursor_protocol @@ -3127,13 +3007,6 @@ eval $query; # # Bug#24532 The return data type of IS TRUE is different from similar operations # - ---disable_warnings -drop view if exists view_24532_a; -drop view if exists view_24532_b; -drop table if exists table_24532; ---enable_warnings - create table table_24532 ( a int, b bigint, @@ -3210,7 +3083,6 @@ drop view view_24532_a; drop view view_24532_b; drop table table_24532; - # # Bug#26560 view using subquery with a reference to an outer alias # @@ -3251,7 +3123,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP table t1,t2; - # # Bug#27786 Inconsistent Operation Performing UNION On View With ORDER BY # @@ -3268,7 +3139,6 @@ EXPLAIN SELECT * FROM t1 UNION SELECT * FROM v1 ORDER BY a; DROP VIEW v1; DROP TABLE t1; - # # Bug#27921 View ignores precision for CAST() # @@ -3281,7 +3151,6 @@ CREATE VIEW v1 AS SELECT CAST(1.23456789 AS DECIMAL(8,0)) AS col; SHOW CREATE VIEW v1; DROP VIEW v1; - # # Bug#28716 CHECK OPTION expression is evaluated over expired record buffers # when VIEW is updated via temporary tables @@ -3298,7 +3167,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#28561 update on multi-table view with CHECK OPTION and a subquery # in WHERE condition @@ -3318,7 +3186,6 @@ UPDATE v1 SET c=1; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#27827 CHECK OPTION ignores ON conditions when updating # a multi-table view with CHECK OPTION. @@ -3371,7 +3238,6 @@ SELECT * FROM t1; DROP VIEW v1,v2; DROP TABLE t1,t2,t3,t4; - # # Bug#29104 assertion abort for a query with a view column reference # in the GROUP BY list and a condition requiring the value @@ -3383,7 +3249,6 @@ INSERT INTO t1 VALUES (1,2), (2,2), (1,3), (1,2); CREATE VIEW v1 AS SELECT a, b+1 as b FROM t1; - SELECT b, SUM(a) FROM v1 WHERE b=3 GROUP BY b; EXPLAIN SELECT b, SUM(a) FROM v1 WHERE b=3 GROUP BY b; @@ -3396,7 +3261,6 @@ EXPLAIN SELECT a, SUM(b) FROM v1 WHERE a=1 GROUP BY a; DROP VIEW v1; DROP TABLE t1; - # # Bug#29392 SELECT over a multi-table view with ORDER BY # selecting the same view column with two different aliases @@ -3450,7 +3314,6 @@ SELECT t.person_id AS a, t.person_id AS b FROM v1 t WHERE t.person_id=6; DROP VIEW v1; DROP TABLE t1,t2,t3; - # # Bug#30020 Insufficient check led to a wrong info provided by the # information schema table. @@ -3464,7 +3327,6 @@ select table_name, is_updatable from information_schema.views drop view v1; drop table t1; - # # Bug#28701 SELECTs from VIEWs completely ignore USE/FORCE KEY, allowing # invalid statements @@ -3483,7 +3345,6 @@ SELECT * FROM v1 IGNORE KEY(non_existant); DROP VIEW v1; DROP TABLE t1; - # # Bug#28702 VIEWs defined with USE/FORCE KEY ignore that request # @@ -3505,7 +3366,6 @@ DROP VIEW v2; DROP VIEW v3; DROP TABLE t1; - --echo # --echo # Bug#29477 Not all fields of the target table were checked to have --echo # a default value when inserting into a view. @@ -3521,7 +3381,6 @@ set @@sql_mode=@old_mode; drop view v1; drop table t1; - # # Bug#33389 Selecting from a view into a table from within SP or trigger # crashes server @@ -3543,7 +3402,6 @@ execute stmt; drop view v1, v2; drop table t1, t2; - # # Bug#33049 Assert while running test-as3ap test(mysql-bench suite) # @@ -3562,54 +3420,29 @@ DROP TABLE t1; --echo # ----------------------------------------------------------------- --echo # -- Bug#34337 Server crash when Altering a view using a table name. --echo # ----------------------------------------------------------------- ---echo - ---disable_warnings -DROP TABLE IF EXISTS t1; ---enable_warnings - ---echo CREATE TABLE t1(c1 INT); - ---echo - SELECT * FROM t1; --error ER_WRONG_OBJECT ALTER ALGORITHM=TEMPTABLE SQL SECURITY INVOKER VIEW t1 (c2) AS SELECT (1); ---echo - DROP TABLE t1; ---echo ---echo # -- End of test case for Bug#34337. ---echo - ########################################################################### --echo # ----------------------------------------------------------------- --echo # -- Bug#35193 VIEW query is rewritten without "FROM DUAL", --echo # -- causing syntax error --echo # ----------------------------------------------------------------- ---echo CREATE VIEW v1 AS SELECT 1 FROM DUAL WHERE 1; ---echo - SELECT * FROM v1; SHOW CREATE TABLE v1; ---echo - DROP VIEW v1; ---echo ---echo # -- End of test case for Bug#35193. ---echo - ########################################################################### # @@ -3620,7 +3453,6 @@ DROP VIEW v1; CREATE VIEW v1 AS SELECT 1; DROP VIEW v1; - # # Bug#33461 SELECT ... FROM USE INDEX (...) throws an error # @@ -3710,9 +3542,6 @@ DROP TABLE t1; # # Bug#21370 View renaming lacks tablename_to_filename encoding # ---disable_warnings -DROP DATABASE IF EXISTS `d-1`; ---enable_warnings CREATE DATABASE `d-1`; USE `d-1`; CREATE TABLE `t-1` (c1 INT); @@ -3726,125 +3555,64 @@ DROP VIEW `v-2`; DROP DATABASE `d-1`; USE test; ---echo --echo # --echo # Bug#26676 VIEW using old table schema in a session. --echo # ---echo - ---disable_warnings -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; ---enable_warnings CREATE TABLE t1(c1 INT, c2 INT); INSERT INTO t1 VALUES (1, 2), (3, 4); ---echo - SELECT * FROM t1; ---echo - CREATE VIEW v1 AS SELECT * FROM t1; ---echo - SELECT * FROM v1; ---echo - ALTER TABLE t1 ADD COLUMN c3 INT AFTER c2; ---echo - SELECT * FROM t1; ---echo - SELECT * FROM v1; ---echo - SHOW CREATE VIEW v1; ---echo - DROP VIEW v1; DROP TABLE t1; ---echo ---echo # End of test case for Bug#26676. ---echo - ########################################################################### --echo # ----------------------------------------------------------------- --echo # -- Bug#32538 View definition picks up character set, but not collation --echo # ----------------------------------------------------------------- ---echo - ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - ---echo SET collation_connection = latin1_general_ci; CREATE VIEW v1 AS SELECT _latin1 'text1' AS c1, 'text2' AS c2; ---echo - SELECT COLLATION(c1), COLLATION(c2) FROM v1; ---echo - SHOW CREATE VIEW v1; ---echo - --error ER_CANT_AGGREGATE_2COLLATIONS SELECT * FROM v1 WHERE c1 = 'text1'; ---echo - SELECT * FROM v1 WHERE c2 = 'text2'; ---echo - use test; SET names latin1; ---echo - SELECT COLLATION(c1), COLLATION(c2) FROM v1; ---echo - SELECT * FROM v1 WHERE c1 = 'text1'; ---echo - --error ER_CANT_AGGREGATE_2COLLATIONS SELECT * FROM v1 WHERE c2 = 'text2'; ---echo - DROP VIEW v1; ---echo ---echo # -- End of test case for Bug#32538. ---echo - # # Bug#34587 Creating a view inside a stored procedure leads to a server crash # ---disable_warnings -drop view if exists a; -drop procedure if exists p; ---enable_warnings - delimiter |; create procedure p() begin @@ -3859,7 +3627,6 @@ drop procedure p; ########################################################################### - --echo # --echo # Bug #44860: ALTER TABLE on view crashes server --echo # @@ -3875,12 +3642,6 @@ DROP TABLE t1; --echo # view contains function of view --echo # ---disable_warnings -DROP VIEW IF EXISTS v1,v2; -DROP TABLE IF EXISTS t1,t2; -DROP FUNCTION IF EXISTS f1; ---enable_warnings - CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); @@ -3908,7 +3669,6 @@ DROP VIEW v1,v2; DROP TABLE t1,t2; DROP FUNCTION f1; - # # Bug#48294 assertion when creating a view based on some row() construct in select query # @@ -4500,7 +4260,6 @@ SELECT * FROM t3 , t4 WHERE t4.c <= (SELECT t2.e FROM t2 LEFT JOIN t1 ON ( t1.a = t2.d ) WHERE t2.b > t4.b); - EXPLAIN EXTENDED SELECT * FROM t3, v4 WHERE v4.c <= (SELECT t2.e FROM t2 LEFT JOIN t1 ON ( t1.a = t2.d ) @@ -4517,11 +4276,6 @@ DROP TABLE t1,t2,t3,t4; # Bug#9801 (Views: imperfect error message) # ---disable_warnings -drop table if exists t_9801; -drop view if exists v_9801; ---enable_warnings - create table t_9801 (s1 int); --error ER_VIEW_NONUPD_CHECK @@ -4542,11 +4296,6 @@ drop table t_9801; --echo # Bug #47335 assert in get_table_share --echo # ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; ---enable_warnings - CREATE TEMPORARY TABLE t1 (id INT); --error ER_NO_SUCH_TABLE ALTER VIEW t1 AS SELECT 1 AS f1; @@ -4559,17 +4308,11 @@ DROP TABLE v1; SELECT * FROM v1; DROP VIEW v1; - --echo # --echo # Bug #47635 assert in start_waiting_global_read_lock --echo # during CREATE VIEW --echo # ---disable_warnings -DROP TABLE IF EXISTS t1, t2; -DROP VIEW IF EXISTS t2; ---enable_warnings - CREATE TABLE t1 (f1 integer); CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); CREATE TEMPORARY TABLE t2 (f1 integer); @@ -4581,17 +4324,11 @@ CREATE VIEW t2 AS SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t1, t2; - --echo # --echo # Bug#48315 Metadata lock is not taken for merged views that --echo # use an INFORMATION_SCHEMA table --echo # ---disable_warnings -DROP VIEW IF EXISTS v1; -DROP PROCEDURE IF EXISTS p1; ---enable_warnings - connect (con2, localhost, root); connect (con3, localhost, root); @@ -4656,19 +4393,14 @@ DROP PROCEDURE p1; disconnect con2; disconnect con3; - --echo # --echo # Bug#12626844: WRONG ERROR MESSAGE WHILE CREATING A VIEW ON A --echo # NON EXISTING DATABASE --echo # ---disable_warnings -DROP DATABASE IF EXISTS nodb; ---enable_warnings --error ER_BAD_DB_ERROR CREATE VIEW nodb.a AS SELECT 1; - --echo # --echo # BUG#14117018 - MYSQL SERVER CREATES INVALID VIEW DEFINITION --echo # BUG#18405221 - SHOW CREATE VIEW OUTPUT INCORRECT @@ -4709,7 +4441,6 @@ SHOW CREATE VIEW v4; DROP VIEW v1, v2, v3, v4, v5; - --echo # --echo # BUG#19886430: VIEW CREATION WITH NAMED COLUMNS, OVER UNION, --echo # IS REJECTED @@ -4752,7 +4483,6 @@ CREATE VIEW v4 AS --echo # Cleanup DROP VIEW v1, v2, v3; - # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc @@ -4952,7 +4682,6 @@ SELECT * FROM ( drop tables t1,t2; - --echo # --echo # MDEV-3876 Wrong result (extra rows) with ALL subquery --echo # from a MERGE view (duplicate of MDEV-3873) @@ -5061,7 +4790,6 @@ insert into t1 values (1),(2); create view v1 (a,r) as select a,rand() from t1; - create table t2 select a, r as r1, r as r2, r as r3 from v1; select a, r1 = r2, r2 = r3 from t2; @@ -5158,7 +4886,6 @@ INSERT INTO t2 VALUES (4),(6); CREATE TABLE t3 (c INT) ENGINE=MyISAM; INSERT INTO t3 VALUES (1),(2); - CREATE ALGORITHM=MERGE VIEW v1 AS SELECT ( SELECT a FROM t1 WHERE ( 1, 1 ) IN ( SELECT b, c FROM t2, t3 HAVING c > 2 ) ) AS field1, @@ -5225,7 +4952,6 @@ DECLARE lResult INTEGER UNSIGNED DEFAULT 0; END| --delimiter ; - SELECT `f1`(1); SELECT `f1`(1); SELECT `f1`(1); @@ -5235,7 +4961,6 @@ DROP FUNCTION f1; DROP VIEW v1; DROP TABLE t1, t2; - create view v1 as select 1; --let SEARCH_FILE= $MYSQLD_DATADIR/test/v1.frm @@ -5346,7 +5071,6 @@ alter table v1 check partition p1; drop view v1; drop table t1; - --echo # --echo # MDEV-10419: crash in mariadb 10.1.16-MariaDB-1~trusty --echo # @@ -5850,7 +5574,6 @@ drop table t1, t2; --error ER_BAD_FIELD_ERROR SELECT 1 FROM (SELECT 1 as a) AS b HAVING (SELECT `SOME_GARBAGE`.b.a)=1; - --echo # --echo # MDEV-10035: DBUG_ASSERT on CREATE VIEW v1 AS SELECT * FROM t1 --echo # FOR UPDATE @@ -6094,7 +5817,6 @@ DROP VIEW v1; DROP TABLE t1; - --echo # --echo # MDEV-9408 CREATE TABLE SELECT MAX(int_column) creates different columns for table vs view --echo # @@ -6337,7 +6059,6 @@ REPLACE INTO v (f1,f2) VALUES (1,1); drop view v; drop table t1,t2,t3; - --echo # --echo # MDEV-12379: Server crashes in TABLE_LIST::is_with_table on --echo # SHOW CREATE VIEW @@ -6537,7 +6258,6 @@ drop view v1; eval CREATE VIEW v1 AS $definition; - drop view v1; --echo # @@ -6603,7 +6323,6 @@ set global table_definition_cache= @tdc, table_open_cache= @tc; --echo # End of 10.2 tests --echo # - --echo # --echo # Start of 10.3 tests --echo # @@ -6672,10 +6391,7 @@ CREATE VIEW v1 as select b''; SELECT * FROM v1; DROP VIEW v1; - ---echo # --echo # End of 10.3 tests ---echo # --echo # --echo # MDEV-25206: view specification contains unknown column reference @@ -6752,7 +6468,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1; - CREATE TABLE t1 (i INT); CREATE VIEW v1 AS SELECT 1 FROM t1 UNION From 50bcc72854a6e5053cc76e070e0224c77b404f4f Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 24 Jul 2026 17:38:59 +0200 Subject: [PATCH 019/150] MDEV-23086 Error codes/messages reveal information about table structure if a user tries to access a table or a database they have no priivleges on, the error is alwaus "access denied", independently from whether the object exists or not. Do the same for columns. --- mysql-test/main/grant5.result | 46 +++++++++++++++++++ mysql-test/main/grant5.test | 41 +++++++++++++++++ .../invisible_field_grant_completely.result | 2 +- .../invisible_field_grant_completely.test | 2 +- sql/sql_base.cc | 23 ++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/grant5.result b/mysql-test/main/grant5.result index 58ebb5935fe..d67e3443fe3 100644 --- a/mysql-test/main/grant5.result +++ b/mysql-test/main/grant5.result @@ -516,4 +516,50 @@ disconnect foo; connection default; drop table t1; drop user foo@localhost; +# +# MDEV-23086 Error codes/messages reveal information about table structure +# +create database db; +create table db.t1 (a int, b int, c int); +create user foo@localhost; +grant select (a,b) on db.t1 to foo@localhost; +alter table db.t1 drop column b; +create table db.t2 (x int, y int); +grant select on db.t2 to foo@localhost; +create table t1 (k int, l int, m int); +grant select on test.t1 to foo@localhost; +connect con1,localhost,foo,,db; +select a from t1; +a +select b from t1; +ERROR 42S22: Unknown column 'b' in 'SELECT' +select c from t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select d from t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select a from t0; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for table `db`.`t0` +use db2; +ERROR 42000: Access denied for user 'foo'@'localhost' to database 'db2' +select a from t2; +ERROR 42S22: Unknown column 'a' in 'SELECT' +select c from t2, t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select c from t1, t2; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select d from t2, t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select d from t1, t2; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select t2.c from t2,t1; +ERROR 42S22: Unknown column 't2.c' in 'SELECT' +select * from test.t1; +k l m +select test.t1.c from t1,test.t1; +ERROR 42S22: Unknown column 'test.t1.c' in 'SELECT' +connection default; +disconnect con1; +drop user foo@localhost; +drop database db; +drop table t1; # End of 10.11 tests diff --git a/mysql-test/main/grant5.test b/mysql-test/main/grant5.test index 79e463abfa0..249bb5790d0 100644 --- a/mysql-test/main/grant5.test +++ b/mysql-test/main/grant5.test @@ -481,4 +481,45 @@ load index into cache test.t2 key (i2); drop table t1; drop user foo@localhost; +--echo # +--echo # MDEV-23086 Error codes/messages reveal information about table structure +--echo # + +create database db; +create table db.t1 (a int, b int, c int); +create user foo@localhost; +grant select (a,b) on db.t1 to foo@localhost; +alter table db.t1 drop column b; + +create table db.t2 (x int, y int); +grant select on db.t2 to foo@localhost; + +create table t1 (k int, l int, m int); +grant select on test.t1 to foo@localhost; + +connect con1,localhost,foo,,db; + +disable_abort_on_error; +select a from t1; # can access, column exist +select b from t1; # can access, column doesn't exist +select c from t1; # no access, column exists +select d from t1; # no access, column doesn't exists +select a from t0; # no access, table doesn't exists +use db2; # no access, db doesn't exists +select a from t2; # table level grant, column doesn't exists +select c from t2, t1; # mix tbl/col grant, no access, column exist +select c from t1, t2; # mix tbl/col grant, no access, column exist +select d from t2, t1; # mix tbl/col grant, no access, column doesn't exists +select d from t1, t2; # mix tbl/col grant, no access, column doesn't exist +select t2.c from t2,t1; # mix tbl/col grant, explicit tbl, column doesn't exist +select * from test.t1; # this should work +select test.t1.c from t1,test.t1; # mix, explicit db name, column doesn't exist +enable_abort_on_error; + +connection default; +disconnect con1; +drop user foo@localhost; +drop database db; +drop table t1; + --echo # End of 10.11 tests diff --git a/mysql-test/main/invisible_field_grant_completely.result b/mysql-test/main/invisible_field_grant_completely.result index ac24aee2fb8..dd5b7fea3be 100644 --- a/mysql-test/main/invisible_field_grant_completely.result +++ b/mysql-test/main/invisible_field_grant_completely.result @@ -39,7 +39,7 @@ a 2 3 select invisible,a from t1; -ERROR 42S22: Unknown column 'invisible' in 'SELECT' +ERROR 42000: SELECT command denied to user 'user_1'@'localhost' for column 'invisible' in table 't1' delete from t1 where a =1; update t1 set a=1 where a=3; select * from t1; diff --git a/mysql-test/main/invisible_field_grant_completely.test b/mysql-test/main/invisible_field_grant_completely.test index b27071ada56..13c6ff4f501 100644 --- a/mysql-test/main/invisible_field_grant_completely.test +++ b/mysql-test/main/invisible_field_grant_completely.test @@ -29,7 +29,7 @@ insert into t1 values(2); select * from t1; insert into t1(a) values(3); select * from t1; ---error ER_BAD_FIELD_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR select invisible,a from t1; delete from t1 where a =1; update t1 set a=1 where a=3; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index bfa787107c5..544df995f2f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7102,7 +7102,30 @@ find_field_in_tables(THD *thd, Item_ident *item, { if (report_error == REPORT_ALL_ERRORS || report_error == REPORT_EXCEPT_NON_UNIQUE) + { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + If the user has no rights on this column in any candidate table, + check_grant_column() will issue a generic "access denied" error + */ + if (check_privileges) + { + for (TABLE_LIST *tl= first_table; tl != last_table; + tl= tl->next_name_resolution_table) + { + if (db && my_strcasecmp(table_alias_charset, tl->db.str, db)) + continue; + if (table_name && + my_strcasecmp(table_alias_charset, tl->alias.str, table_name)) + continue; + if (check_grant_column(thd, &tl->grant, tl->db.str, + tl->table_name.str, name, length, thd->security_ctx)) + return found; + } + } +#endif my_error(ER_BAD_FIELD_ERROR, MYF(0), item->full_name(), thd_where(thd)); + } else found= not_found_field; } From fdb27213bd4cd22c7e422178b5adc2a1c79ebfd3 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 28 Jun 2026 15:16:49 +0200 Subject: [PATCH 020/150] cleanup: only include my_compare.h into heap code as needed to avoid name conflict on `get_key_length` --- include/heap.h | 4 ++-- mysql-test/suite/heap/heap_btree.result | 9 ++++----- mysql-test/suite/heap/heap_btree.test | 12 ++++-------- storage/heap/_check.c | 1 + storage/heap/heapdef.h | 2 +- storage/heap/hp_create.c | 1 + storage/heap/hp_hash.c | 1 + storage/heap/hp_rfirst.c | 3 +-- storage/heap/hp_rkey.c | 2 +- storage/heap/hp_rlast.c | 3 +-- storage/heap/hp_rnext.c | 11 +++++------ storage/heap/hp_rprev.c | 3 +-- storage/heap/hp_test1.c | 1 + storage/heap/hp_test2.c | 1 + 14 files changed, 25 insertions(+), 29 deletions(-) diff --git a/include/heap.h b/include/heap.h index 3fac752abd0..4df384d73d8 100644 --- a/include/heap.h +++ b/include/heap.h @@ -30,7 +30,6 @@ extern "C" { #include #include -#include "my_compare.h" #include "my_tree.h" /* defines used by heap-functions */ @@ -109,6 +108,7 @@ typedef struct st_heap_block } HP_BLOCK; struct st_heap_info; /* For reference */ +struct st_HA_KEYSEG; typedef struct st_hp_keydef /* Key definition with open */ { @@ -116,7 +116,7 @@ typedef struct st_hp_keydef /* Key definition with open */ uint keysegs; /* Number of key-segment */ uint length; /* Length of key (automatic) */ uint8 algorithm; /* HASH / BTREE */ - HA_KEYSEG *seg; + struct st_HA_KEYSEG *seg; HP_BLOCK block; /* Where keys are saved */ /* Number of buckets used in hash table. Used only to provide diff --git a/mysql-test/suite/heap/heap_btree.result b/mysql-test/suite/heap/heap_btree.result index 526c76a52e8..6197e55c3a6 100644 --- a/mysql-test/suite/heap/heap_btree.result +++ b/mysql-test/suite/heap/heap_btree.result @@ -1,4 +1,3 @@ -drop table if exists t1; create table t1 (a int not null,b int not null, primary key using BTREE (a)) engine=heap comment="testing heaps" avg_row_length=100 min_rows=1 max_rows=100; insert into t1 values(1,1),(2,2),(3,3),(4,4); delete from t1 where a=1 or a=0; @@ -317,7 +316,7 @@ SELECT * FROM t1; a 1 DROP TABLE t1; -End of 4.1 tests +# End of 4.1 tests CREATE TABLE t1(val INT, KEY USING BTREE(val)) ENGINE=memory; INSERT INTO t1 VALUES(0); SELECT INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t1'; @@ -346,7 +345,7 @@ CREATE TABLE t1(a INT, KEY USING BTREE (a)) ENGINE=MEMORY; INSERT INTO t1 VALUES(1),(1); DELETE a1 FROM t1 AS a1, t1 AS a2 WHERE a1.a=a2.a; DROP TABLE t1; -End of 5.0 tests +# End of 5.0 tests # bit index in heap tables create table t1 (a bit(63) not null) engine=heap; insert into t1 values (869751),(736494),(226312),(802616),(728912); @@ -382,8 +381,8 @@ explain select 0+a from t1 where a in (869751,736494,226312,802616); id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range uniq_id uniq_id 8 NULL 4 Using where drop table t1; -End of 5.3 tests +# End of 5.3 tests create table t1 (id int, a varchar(300) not null, key using btree(a)) engine=heap; insert t1 values (1, repeat('a', 300)); drop table t1; -End of 5.5 tests +# End of 5.5 tests diff --git a/mysql-test/suite/heap/heap_btree.test b/mysql-test/suite/heap/heap_btree.test index d3fbe4cc0d2..08f94abb57e 100644 --- a/mysql-test/suite/heap/heap_btree.test +++ b/mysql-test/suite/heap/heap_btree.test @@ -4,10 +4,6 @@ # Test of heap tables. # ---disable_warnings -drop table if exists t1; ---enable_warnings - create table t1 (a int not null,b int not null, primary key using BTREE (a)) engine=heap comment="testing heaps" avg_row_length=100 min_rows=1 max_rows=100; insert into t1 values(1,1),(2,2),(3,3),(4,4); delete from t1 where a=1 or a=0; @@ -227,7 +223,7 @@ DELETE FROM t1 WHERE a=2; SELECT * FROM t1; DROP TABLE t1; ---echo End of 4.1 tests +--echo # End of 4.1 tests # # BUG#18160 - Memory-/HEAP Table endless growing indexes @@ -266,7 +262,7 @@ CREATE TABLE t1(a INT, KEY USING BTREE (a)) ENGINE=MEMORY; INSERT INTO t1 VALUES(1),(1); DELETE a1 FROM t1 AS a1, t1 AS a2 WHERE a1.a=a2.a; DROP TABLE t1; ---echo End of 5.0 tests +--echo # End of 5.0 tests -- echo # bit index in heap tables @@ -285,7 +281,7 @@ select 0+a from t1 where a in (869751,736494,226312,802616); explain select 0+a from t1 where a in (869751,736494,226312,802616); drop table t1; ---echo End of 5.3 tests +--echo # End of 5.3 tests # # Bug#27799513: POTENTIAL DOUBLE FREE OR CORRUPTION OF HEAP INFO (HP_INFO) @@ -294,4 +290,4 @@ create table t1 (id int, a varchar(300) not null, key using btree(a)) engine=hea insert t1 values (1, repeat('a', 300)); drop table t1; ---echo End of 5.5 tests +--echo # End of 5.5 tests diff --git a/storage/heap/_check.c b/storage/heap/_check.c index 1a640fa13da..3663eada0b2 100644 --- a/storage/heap/_check.c +++ b/storage/heap/_check.c @@ -17,6 +17,7 @@ /* Check that heap-structure is ok */ #include "heapdef.h" +#include "my_compare.h" static int check_one_key(HP_KEYDEF *, uint, ulong, ulong, my_bool); static int check_one_rb_key(const HP_INFO *, uint, ulong, my_bool); diff --git a/storage/heap/heapdef.h b/storage/heap/heapdef.h index e51fe88d8e2..1880a9839cf 100644 --- a/storage/heap/heapdef.h +++ b/storage/heap/heapdef.h @@ -53,7 +53,7 @@ typedef struct st_hp_hash_info } HASH_INFO; typedef struct { - HA_KEYSEG *keyseg; + struct st_HA_KEYSEG *keyseg; uint key_length; uint search_flag; } heap_rb_param; diff --git a/storage/heap/hp_create.c b/storage/heap/hp_create.c index f35e8e3fac9..84df122829a 100644 --- a/storage/heap/hp_create.c +++ b/storage/heap/hp_create.c @@ -15,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #include "heapdef.h" +#include "my_compare.h" #include static int keys_compare(void *heap_rb, const void *key1, const void *key2); diff --git a/storage/heap/hp_hash.c b/storage/heap/hp_hash.c index a0139151730..45e82a777b8 100644 --- a/storage/heap/hp_hash.c +++ b/storage/heap/hp_hash.c @@ -18,6 +18,7 @@ /* The hash functions used for saveing keys */ #include "heapdef.h" +#include "my_compare.h" #include diff --git a/storage/heap/hp_rfirst.c b/storage/heap/hp_rfirst.c index 60596a2c650..1b1610ac52d 100644 --- a/storage/heap/hp_rfirst.c +++ b/storage/heap/hp_rfirst.c @@ -34,8 +34,7 @@ int heap_rfirst(HP_INFO *info, uchar *record, int inx) if ((pos = tree_search_edge(&keyinfo->rb_tree, info->parents, &info->last_pos, offsetof(TREE_ELEMENT, left)))) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; memcpy(record, pos, (size_t)share->reclength); /* diff --git a/storage/heap/hp_rkey.c b/storage/heap/hp_rkey.c index 2d9fae4c520..aa26bf4e451 100644 --- a/storage/heap/hp_rkey.c +++ b/storage/heap/hp_rkey.c @@ -55,7 +55,7 @@ int heap_rkey(HP_INFO *info, uchar *record, int inx, const uchar *key, info->update= HA_STATE_NO_KEY; DBUG_RETURN(my_errno= HA_ERR_KEY_NOT_FOUND); } - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr= pos; } else diff --git a/storage/heap/hp_rlast.c b/storage/heap/hp_rlast.c index ed9c3499d5e..c95d8af5d47 100644 --- a/storage/heap/hp_rlast.c +++ b/storage/heap/hp_rlast.c @@ -34,8 +34,7 @@ int heap_rlast(HP_INFO *info, uchar *record, int inx) if ((pos = tree_search_edge(&keyinfo->rb_tree, info->parents, &info->last_pos, offsetof(TREE_ELEMENT, right)))) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; memcpy(record, pos, (size_t)share->reclength); info->update = HA_STATE_AKTIV; diff --git a/storage/heap/hp_rnext.c b/storage/heap/hp_rnext.c index ac21ed83da2..a07d8fae511 100644 --- a/storage/heap/hp_rnext.c +++ b/storage/heap/hp_rnext.c @@ -53,9 +53,9 @@ int heap_rnext(HP_INFO *info, uchar *record) or heap_rfirst(). As last key position (info->last_pos) is available, we only need to climb the tree using tree_search_next(). */ - pos = tree_search_next(&keyinfo->rb_tree, &info->last_pos, - offsetof(TREE_ELEMENT, left), - offsetof(TREE_ELEMENT, right)); + pos= tree_search_next(&keyinfo->rb_tree, &info->last_pos, + offsetof(TREE_ELEMENT, left), + offsetof(TREE_ELEMENT, right)); } else if (!info->lastkey_len) { @@ -86,14 +86,13 @@ int heap_rnext(HP_INFO *info, uchar *record) custom_arg.key_length = info->lastkey_len; custom_arg.search_flag = SEARCH_SAME | SEARCH_FIND; info->last_find_flag= HA_READ_KEY_OR_NEXT; - pos = tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, + pos= tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, &info->last_pos, info->last_find_flag, &custom_arg); info->key_version= info->s->key_version; } if (pos) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; } else diff --git a/storage/heap/hp_rprev.c b/storage/heap/hp_rprev.c index cc81d179570..bb9ef06d071 100644 --- a/storage/heap/hp_rprev.c +++ b/storage/heap/hp_rprev.c @@ -62,8 +62,7 @@ int heap_rprev(HP_INFO *info, uchar *record) } if (pos) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; } else diff --git a/storage/heap/hp_test1.c b/storage/heap/hp_test1.c index 9f87ea6b33a..8cc62362b62 100644 --- a/storage/heap/hp_test1.c +++ b/storage/heap/hp_test1.c @@ -23,6 +23,7 @@ #include #include #include "heap.h" +#include "my_compare.h" static int get_options(int argc, char *argv[]); diff --git a/storage/heap/hp_test2.c b/storage/heap/hp_test2.c index 2ca1dd77fbb..a5b03354b9f 100644 --- a/storage/heap/hp_test2.c +++ b/storage/heap/hp_test2.c @@ -17,6 +17,7 @@ /* Test av isam-databas: stor test */ #include "heapdef.h" /* Because of hp_find_block */ +#include "my_compare.h" #include #define MAX_RECORDS 100000 From e1dd1dc000e403db4a3ef9ae50d6737500f81629 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 28 Jun 2026 16:51:30 +0200 Subject: [PATCH 021/150] MDEV-40186 MEMORY tables incorrectly restart index scan on DELETE remember the last found key and restart the search (if needed) from it not from the original one. --- mysql-test/suite/heap/heap_btree.result | 56 +++++++++++++++++++++++++ mysql-test/suite/heap/heap_btree.test | 37 ++++++++++++++++ storage/heap/hp_rkey.c | 5 ++- storage/heap/hp_rnext.c | 9 ++-- storage/heap/hp_rprev.c | 7 +++- 5 files changed, 108 insertions(+), 6 deletions(-) diff --git a/mysql-test/suite/heap/heap_btree.result b/mysql-test/suite/heap/heap_btree.result index 6197e55c3a6..9acaa5f3e3f 100644 --- a/mysql-test/suite/heap/heap_btree.result +++ b/mysql-test/suite/heap/heap_btree.result @@ -386,3 +386,59 @@ create table t1 (id int, a varchar(300) not null, key using btree(a)) engine=hea insert t1 values (1, repeat('a', 300)); drop table t1; # End of 5.5 tests +# +# MDEV-40186 MEMORY tables incorrectly restart index scan on DELETE +# +create table t1 (a int, b int, c int, key (a, b) using btree) engine=heap; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c>60; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_NEXT 8 +select * from t1 where a=3; +a b c +3 3 3 +3 13 13 +3 23 23 +3 33 33 +3 43 43 +3 53 53 +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_NEXT 8 +select * from t1 where a=3; +a b c +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c<20 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_PREV 8 +select * from t1 where a=3; +a b c +3 23 23 +3 33 33 +3 43 43 +3 53 53 +3 63 63 +3 73 73 +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_PREV 8 +select * from t1 where a=3; +a b c +drop table t1; diff --git a/mysql-test/suite/heap/heap_btree.test b/mysql-test/suite/heap/heap_btree.test index 08f94abb57e..a6e2ff0005f 100644 --- a/mysql-test/suite/heap/heap_btree.test +++ b/mysql-test/suite/heap/heap_btree.test @@ -291,3 +291,40 @@ insert t1 values (1, repeat('a', 300)); drop table t1; --echo # End of 5.5 tests + +--echo # +--echo # MDEV-40186 MEMORY tables incorrectly restart index scan on DELETE +--echo # + +--disable_ps2_protocol +create table t1 (a int, b int, c int, key (a, b) using btree) engine=heap; + +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c>60; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c<20 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +drop table t1; +--enable_ps2_protocol diff --git a/storage/heap/hp_rkey.c b/storage/heap/hp_rkey.c index aa26bf4e451..3e4b4dee6ed 100644 --- a/storage/heap/hp_rkey.c +++ b/storage/heap/hp_rkey.c @@ -55,7 +55,10 @@ int heap_rkey(HP_INFO *info, uchar *record, int inx, const uchar *key, info->update= HA_STATE_NO_KEY; DBUG_RETURN(my_errno= HA_ERR_KEY_NOT_FOUND); } - memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); info->current_ptr= pos; } else diff --git a/storage/heap/hp_rnext.c b/storage/heap/hp_rnext.c index a07d8fae511..1f04f94d478 100644 --- a/storage/heap/hp_rnext.c +++ b/storage/heap/hp_rnext.c @@ -84,7 +84,7 @@ int heap_rnext(HP_INFO *info, uchar *record) */ custom_arg.keyseg = keyinfo->seg; custom_arg.key_length = info->lastkey_len; - custom_arg.search_flag = SEARCH_SAME | SEARCH_FIND; + custom_arg.search_flag = SEARCH_BIGGER; info->last_find_flag= HA_READ_KEY_OR_NEXT; pos= tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, &info->last_pos, info->last_find_flag, &custom_arg); @@ -92,8 +92,11 @@ int heap_rnext(HP_INFO *info, uchar *record) } if (pos) { - memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); - info->current_ptr = pos; + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); + info->current_ptr= pos; } else { diff --git a/storage/heap/hp_rprev.c b/storage/heap/hp_rprev.c index bb9ef06d071..eb816ed676a 100644 --- a/storage/heap/hp_rprev.c +++ b/storage/heap/hp_rprev.c @@ -54,7 +54,7 @@ int heap_rprev(HP_INFO *info, uchar *record) { custom_arg.keyseg = keyinfo->seg; custom_arg.key_length = keyinfo->length; - custom_arg.search_flag = SEARCH_SAME; + custom_arg.search_flag = SEARCH_SMALLER; info->last_find_flag= HA_READ_KEY_OR_PREV; pos = tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, &info->last_pos, info->last_find_flag, &custom_arg); @@ -62,7 +62,10 @@ int heap_rprev(HP_INFO *info, uchar *record) } if (pos) { - memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); info->current_ptr = pos; } else From c0055129e5a3fef33a143ca25fb6cab4826f25fe Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 2 Jun 2026 15:50:45 +0200 Subject: [PATCH 022/150] MDEV-39821 heap-use-after-free in heap_rnext with tree indexes heap_update() forgot to update key_changed --- mysql-test/suite/handler/heap.result | 33 ++++++++++++++++++++++++++++ mysql-test/suite/handler/heap.test | 26 ++++++++++++++++++++++ storage/heap/hp_update.c | 1 + 3 files changed, 60 insertions(+) diff --git a/mysql-test/suite/handler/heap.result b/mysql-test/suite/handler/heap.result index 067aeafb4f6..23149f241cb 100644 --- a/mysql-test/suite/handler/heap.result +++ b/mysql-test/suite/handler/heap.result @@ -1704,3 +1704,36 @@ HANDLER m READ `ba`= (30); ERROR HY000: HASH index `ba` does not support this operation HANDLER m CLOSE; DROP TABLE t1; +# +# MDEV-39821 heap-use-after-free in heap_rnext with tree indexes +# +CREATE TABLE t1 ( +k INT, +pad VARCHAR(200), +INDEX idx USING BTREE (k) +) ENGINE=MEMORY; +INSERT t1 VALUES (10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e'), +(60,'f'),(70,'g'),(80,'h'),(90,'i'),(100,'j'), +(110,'k'),(120,'l'),(130,'m'),(140,'n'),(150,'o'), +(160,'p'),(170,'q'),(180,'r'),(190,'s'),(200,'t'); +HANDLER t1 OPEN; +HANDLER t1 READ idx FIRST; +k pad +10 a +HANDLER t1 READ idx NEXT; +k pad +20 b +HANDLER t1 READ idx NEXT; +k pad +30 c +HANDLER t1 READ idx NEXT; +k pad +40 d +HANDLER t1 READ idx NEXT; +k pad +50 e +UPDATE t1 SET k = k + 5000 WHERE k BETWEEN 20 AND 180; +HANDLER t1 READ idx NEXT; +ERROR HY000: Record has changed since last read in table 't1'; try restarting transaction +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/handler/heap.test b/mysql-test/suite/handler/heap.test index d60f92daa76..175044d392e 100644 --- a/mysql-test/suite/handler/heap.test +++ b/mysql-test/suite/handler/heap.test @@ -103,3 +103,29 @@ HANDLER m READ `ba`= (30); HANDLER m CLOSE; DROP TABLE t1; +--echo # +--echo # MDEV-39821 heap-use-after-free in heap_rnext with tree indexes +--echo # +CREATE TABLE t1 ( + k INT, + pad VARCHAR(200), + INDEX idx USING BTREE (k) +) ENGINE=MEMORY; + +INSERT t1 VALUES (10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e'), + (60,'f'),(70,'g'),(80,'h'),(90,'i'),(100,'j'), + (110,'k'),(120,'l'),(130,'m'),(140,'n'),(150,'o'), + (160,'p'),(170,'q'),(180,'r'),(190,'s'),(200,'t'); + +HANDLER t1 OPEN; +HANDLER t1 READ idx FIRST; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +UPDATE t1 SET k = k + 5000 WHERE k BETWEEN 20 AND 180; +--error ER_CHECKREAD +HANDLER t1 READ idx NEXT; +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/storage/heap/hp_update.c b/storage/heap/hp_update.c index ad56ca979de..c99ac370ac8 100644 --- a/storage/heap/hp_update.c +++ b/storage/heap/hp_update.c @@ -47,6 +47,7 @@ int heap_update(HP_INFO *info, const uchar *old, const uchar *heap_new) if ((*keydef->delete_key)(info, keydef, old, pos, keydef == p_lastinx) || (*keydef->write_key)(info, keydef, heap_new, pos)) goto err; + key_changed= 1; if (share->auto_key == (uint) (keydef - share->keydef + 1)) auto_key_changed= 1; } From 57841af1845b1a878a8553552f1d607df634bf5c Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 27 Jul 2026 00:42:46 +0200 Subject: [PATCH 023/150] cleanup: make my_virtual_mem_reserve() portable, unify buf0buf.cc make my_virtual_mem_reserve() work on Windows and on Linux (falling back to my_large_virtual_alloc() - that's what InnoDB did instead). This makes the usage pattern portable without #ifdef: - reserve a memory range - commit (allocate) memory from a reserved range - decommit memory, keeping it reserved - release reserved memory back to OS --- include/my_virtual_mem.h | 2 -- mysys/my_virtual_mem.c | 21 ++++++++++++++------- storage/innobase/buf/buf0buf.cc | 12 +++--------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/include/my_virtual_mem.h b/include/my_virtual_mem.h index b4f26ca979c..56b2f03b329 100644 --- a/include/my_virtual_mem.h +++ b/include/my_virtual_mem.h @@ -24,9 +24,7 @@ extern "C" { #endif -#ifdef _WIN32 char *my_virtual_mem_reserve(size_t *size); -#endif char *my_virtual_mem_commit(char *ptr, size_t size); void my_virtual_mem_decommit(char *ptr, size_t size); void my_virtual_mem_release(char *ptr, size_t size); diff --git a/mysys/my_virtual_mem.c b/mysys/my_virtual_mem.c index 7157dd7ae06..feae605bb09 100644 --- a/mysys/my_virtual_mem.c +++ b/mysys/my_virtual_mem.c @@ -24,19 +24,24 @@ /* Functionality for handling virtual memory - - reserve range, - - commit memory (within reserved range) - - decommit previously commited memory - - release range + - reserve range - memory is reserved, not allocated. + - commit memory (within reserved range) - memory is allocated + - decommit previously commited memory - memory is deallocated, stays reserved + - release range - reserved range is released to OS Not every OS has a "reserve" functionality, i.e it is not always possible to reserve memory larger than swap or RAM for example. - We try to respect use_large_pages setting, on Windows and Linux + The unfortunate terminology comes from WinAPI, see MEM_* flags below. + It's rather confusing in the context of a database (and specifically, + InnoDB, the only user of this functionality), but it's the established + terminology. + + We try to respect use_large_pages setting, both on Windows and Linux */ -#ifdef _WIN32 char *my_virtual_mem_reserve(size_t *size) { +#ifdef _WIN32 DWORD flags= my_use_large_pages ? MEM_LARGE_PAGES | MEM_RESERVE | MEM_COMMIT : MEM_RESERVE; @@ -49,8 +54,10 @@ char *my_virtual_mem_reserve(size_t *size) my_error(EE_OUTOFMEMORY, MYF(ME_BELL + ME_ERROR_LOG), *size); } return ptr; -} +#else + return my_large_virtual_alloc(size); #endif +} #if defined _WIN32 && !defined DBUG_OFF static my_bool is_memory_committed(char *ptr, size_t size) diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index cb7b12ed0d3..0b32a7beca9 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1995, 2018, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2013, 2022, MariaDB Corporation. +Copyright (c) 2013, 2026, MariaDB plc. 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 @@ -1339,14 +1339,9 @@ bool buf_pool_t::create() noexcept retry: { NUMA_MEMPOLICY_INTERLEAVE_IN_SCOPE; -#ifdef _WIN32 memory_unaligned= my_virtual_mem_reserve(&size); - if (!memory_unaligned) - goto oom; -#else - memory_unaligned= my_large_virtual_alloc(&size); if (memory_unaligned); -# if defined __aarch64__ || defined __riscv || defined __mips__ || defined __loongarch64 +#if defined __aarch64__ || defined __riscv || defined __mips__ || defined __loongarch64 else if (size_in_bytes_max_default != 0 && size_in_bytes_max == size_in_bytes_max_default) { @@ -1367,10 +1362,9 @@ bool buf_pool_t::create() noexcept size_in_bytes_max= std::max(size_t(1ULL << 37), size_in_bytes_requested); goto init; } -# endif +#endif else goto oom; -#endif } const size_t alignment_waste= From ee103a5d9e33111915c3e2fc7d3c3ce93ef59f07 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 12 Jul 2026 07:12:34 +0200 Subject: [PATCH 024/150] generalize ROOT_FLAG_MPROTECT to work on Windows, rename accordingly * it's called ROOT_FLAG_VMEM * it does not imply any protection, ROOT_FLAG_MPROTECT was a misnomer * it means that the memory is allocated using my_virtual_mem_commit, not my_malloc, so not on heap * memory allocated this way can be protected with my_virtual_mem_protect() * thus root using ROOT_FLAG_VMEM can be protected with protect_root() Assisted-By: Claude:claude-5-sonnet --- include/my_sys.h | 2 +- include/my_virtual_mem.h | 5 ++- mysys/my_alloc.c | 74 ++++++++++++++++------------------------ mysys/my_virtual_mem.c | 32 +++++++++++++++-- sql/mysqld.cc | 7 ++-- 5 files changed, 67 insertions(+), 53 deletions(-) diff --git a/include/my_sys.h b/include/my_sys.h index 451887d14a5..43f263519be 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -92,7 +92,7 @@ C_MODE_START #define MY_SYNC 4096U /* my_copy(): sync dst file */ #define MY_SYNC_DIR 32768U /* my_create/delete/rename: sync directory */ #define MY_THREAD_SPECIFIC 0x10000U /* my_malloc(): thread specific */ -#define MY_ROOT_USE_MPROTECT 0x20000U /* init_alloc_root: read only segments */ +#define MY_ROOT_USE_VMEM 0x20000U /* init_alloc_root: use my_virtual_mem_commit */ /* Tree that should delete things automatically */ #define MY_TREE_WITH_DELETE 0x40000U diff --git a/include/my_virtual_mem.h b/include/my_virtual_mem.h index 56b2f03b329..689c75d5258 100644 --- a/include/my_virtual_mem.h +++ b/include/my_virtual_mem.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2025, MariaDB +/* Copyright (c) 2025, 2026, MariaDB plc. 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 @@ -24,10 +24,13 @@ extern "C" { #endif +enum my_vmem_prot { MY_VMEM_READONLY= 0, MY_VMEM_READWRITE }; + char *my_virtual_mem_reserve(size_t *size); char *my_virtual_mem_commit(char *ptr, size_t size); void my_virtual_mem_decommit(char *ptr, size_t size); void my_virtual_mem_release(char *ptr, size_t size); +void my_virtual_mem_protect(void *ptr, size_t size, enum my_vmem_prot prot); #ifdef __cplusplus } diff --git a/mysys/my_alloc.c b/mysys/my_alloc.c index 229d5b7d06f..b8cb910b207 100644 --- a/mysys/my_alloc.c +++ b/mysys/my_alloc.c @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2010, Oracle and/or its affiliates - Copyright (c) 2010, 2020, MariaDB + Copyright (c) 2010, 2026, MariaDB plc. 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 @@ -21,15 +21,13 @@ #include #include #include -#ifdef HAVE_SYS_MMAN_H -#include -#endif +#include #undef EXTRA_DEBUG #define EXTRA_DEBUG #define ROOT_FLAG_THREAD_SPECIFIC 1 -#define ROOT_FLAG_MPROTECT 2 +#define ROOT_FLAG_VMEM 2 #define ROOT_FLAG_READ_ONLY 4 /* data packed in MEM_ROOT -> min_malloc */ @@ -43,37 +41,35 @@ /* - Alloc memory through either my_malloc or mmap() + Alloc memory through either my_malloc() or my_virtual_mem_commit() */ static void *root_alloc(MEM_ROOT *root, size_t size, size_t *alloced_size, myf my_flags) { *alloced_size= size; -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) - if (root->flags & ROOT_FLAG_MPROTECT) + if (root->flags & ROOT_FLAG_VMEM) { - void *res; + void *ptr; *alloced_size= MY_ALIGN(size, my_system_page_size); - res= my_mmap(0, *alloced_size, PROT_READ | PROT_WRITE, - MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (res == MAP_FAILED) - res= 0; - return res; + if ((ptr= my_virtual_mem_commit(NULL, *alloced_size))) + update_malloc_size(*alloced_size, + MY_TEST(root->flags & ROOT_FLAG_THREAD_SPECIFIC)); + return ptr; } -#endif /* HAVE_MMAP */ - return my_malloc(root->psi_key, size, - my_flags | MALLOC_FLAG(root)); + return my_malloc(root->psi_key, size, my_flags | MALLOC_FLAG(root)); } static void root_free(MEM_ROOT *root, void *ptr, size_t size) { -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) - if (root->flags & ROOT_FLAG_MPROTECT) - my_munmap(ptr, size); + if (root->flags & ROOT_FLAG_VMEM) + { + update_malloc_size(-(longlong) size, + MY_TEST(root->flags & ROOT_FLAG_THREAD_SPECIFIC)); + my_virtual_mem_release(ptr, size); + } else -#endif my_free(ptr); } @@ -93,7 +89,7 @@ static void calculate_block_sizes(MEM_ROOT *mem_root, size_t block_size, { size_t pre_alloc= *pre_alloc_size; - if (mem_root->flags & ROOT_FLAG_MPROTECT) + if (mem_root->flags & ROOT_FLAG_VMEM) { mem_root->block_size= MY_ALIGN(block_size, my_system_page_size); if (pre_alloc) @@ -132,7 +128,7 @@ static void calculate_block_sizes(MEM_ROOT *mem_root, size_t block_size, pre_alloc_size - if non-0, then size of block that should be pre-allocated during memory root initialization. my_flags MY_THREAD_SPECIFIC flag for my_malloc - MY_RROOT_USE_MPROTECT for read only protected memory + MY_ROOT_USE_VMEM to use anonymos mmap instead of malloc DESCRIPTION This function prepares memory root for further use, sets initial size of @@ -157,11 +153,11 @@ void init_alloc_root(PSI_memory_key key, MEM_ROOT *mem_root, size_t block_size, mem_root->flags= 0; DBUG_ASSERT(!test_all_bits(mem_root->flags, - (MY_THREAD_SPECIFIC | MY_ROOT_USE_MPROTECT))); + (MY_THREAD_SPECIFIC | MY_ROOT_USE_VMEM))); if (my_flags & MY_THREAD_SPECIFIC) mem_root->flags|= ROOT_FLAG_THREAD_SPECIFIC; - if (my_flags & MY_ROOT_USE_MPROTECT) - mem_root->flags|= ROOT_FLAG_MPROTECT; + if (my_flags & MY_ROOT_USE_VMEM) + mem_root->flags|= ROOT_FLAG_VMEM; calculate_block_sizes(mem_root, block_size, &pre_alloc_size); @@ -286,7 +282,7 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) }); #if defined(HAVE_valgrind) && defined(EXTRA_DEBUG) - if (!(mem_root->flags & ROOT_FLAG_MPROTECT)) + if (!(mem_root->flags & ROOT_FLAG_VMEM)) { length+= ALIGN_SIZE(sizeof(USED_MEM)); if (!(next = (USED_MEM*) my_malloc(mem_root->psi_key, length, @@ -621,32 +617,20 @@ void free_all_new_blocks(MEM_ROOT *root, USED_MEM *last_block) Change protection for all blocks in the mem root */ -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) void protect_root(MEM_ROOT *root, int prot) { - reg1 USED_MEM *next,*old; + USED_MEM *next; DBUG_ENTER("protect_root"); DBUG_PRINT("enter",("root: %p prot: %d", root, prot)); - DBUG_ASSERT(root->flags & ROOT_FLAG_MPROTECT); + DBUG_ASSERT(root->flags & ROOT_FLAG_VMEM); - for (next= root->used; next ;) - { - old= next; next= next->next ; - mprotect(old, old->size, prot); - } - for (next= root->free; next ;) - { - old= next; next= next->next ; - mprotect(old, old->size, prot); - } + for (next= root->used; next; next= next->next) + my_virtual_mem_protect(next, next->size, prot); + for (next= root->free; next; next= next->next) + my_virtual_mem_protect(next, next->size, prot); DBUG_VOID_RETURN; } -#else -void protect_root(MEM_ROOT *root, int prot) -{ -} -#endif /* defined(HAVE_MMAP) && ... */ char *strdup_root(MEM_ROOT *root, const char *str) diff --git a/mysys/my_virtual_mem.c b/mysys/my_virtual_mem.c index feae605bb09..acd63a9b0a8 100644 --- a/mysys/my_virtual_mem.c +++ b/mysys/my_virtual_mem.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2025, MariaDB +/* Copyright (c) 2025, 2026, MariaDB plc. 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 @@ -69,10 +69,18 @@ static my_bool is_memory_committed(char *ptr, size_t size) } #endif +/* + "commit" - in other words, allocate - memory. + ptr must lie within a previously reserved range or be NULL. + in the latter case new memory is reserved and committed. + + This is compatible with the mmap / VirtualAlloc semantics. +*/ char *my_virtual_mem_commit(char *ptr, size_t size) { - DBUG_ASSERT(ptr); #ifdef _WIN32 + if (!ptr) + return VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (my_use_large_pages) { DBUG_ASSERT(is_memory_committed(ptr, size)); @@ -88,6 +96,12 @@ char *my_virtual_mem_commit(char *ptr, size_t size) } } #else + if (!ptr) + { + void *p= mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + return p == MAP_FAILED ? NULL : p; + } if (my_use_large_pages) /* my_large_virtual_alloc() already created a read/write mapping. */; else @@ -172,7 +186,6 @@ void my_virtual_mem_decommit(char *ptr, size_t size) void my_virtual_mem_release(char *ptr, size_t size) { #ifdef _WIN32 - DBUG_ASSERT(my_use_large_pages || !is_memory_committed(ptr, size)); if (!VirtualFree(ptr, 0, MEM_RELEASE)) { my_error(EE_BADMEMORYRELEASE, MYF(ME_ERROR_LOG_ONLY), ptr, size, @@ -187,3 +200,16 @@ void my_virtual_mem_release(char *ptr, size_t size) } #endif } + + +void my_virtual_mem_protect(void *ptr, size_t size, enum my_vmem_prot prot) +{ + int res __attribute__((unused)); +#ifdef _WIN32 + DWORD old_prot; + res= !VirtualProtect(ptr, size, prot ? PAGE_READWRITE : PAGE_READONLY, &old_prot); +#else + res= mprotect(ptr, size, prot ? PROT_READ | PROT_WRITE : PROT_READ); +#endif + DBUG_ASSERT(res == 0); // bad ptr or size alignment, neither should be possible +} diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 79b81dd418d..8ca1390edf4 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -54,6 +54,7 @@ #include "ddl_log.h" #include +#include #include #include #include "my_cpu.h" @@ -2047,7 +2048,7 @@ static void clean_up(bool print_message) mysql_library_end(); finish_client_errs(); free_root(&startup_root, MYF(0)); - protect_root(&read_only_root, PROT_READ | PROT_WRITE); + protect_root(&read_only_root, MY_VMEM_READWRITE); free_root(&read_only_root, MYF(0)); cleanup_errmsgs(); free_error_messages(); @@ -3924,7 +3925,7 @@ static int init_early_variables() global_status_var.global_memory_used= 0; init_alloc_root(PSI_NOT_INSTRUMENTED, &startup_root, 1024, 0, MYF(0)); init_alloc_root(PSI_NOT_INSTRUMENTED, &read_only_root, 1024, 0, - MYF(MY_ROOT_USE_MPROTECT)); + MYF(MY_ROOT_USE_VMEM)); return 0; } @@ -6027,7 +6028,7 @@ int mysqld_main(int argc, char **argv) #endif /* WITH_WSREP */ /* Protect read_only_root against writes */ - protect_root(&read_only_root, PROT_READ); + protect_root(&read_only_root, MY_VMEM_READONLY); if (opt_bootstrap) { From 20d2217afa688875d12ac21f6db282089017d150 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 27 Jul 2026 01:21:11 +0200 Subject: [PATCH 025/150] MDEV-40337 store user vars in a dedicated memroot user_var_entry objects and their names have a connection lifetime, they exist until the connection ends (or is reset), and then they're all deleted at once. This is exactly the use case for MEM_ROOT, let's store them there. Additionally, let's set MY_ROOT_USE_VMEM flag to keep this memroot off the general heap where user_var_entry values are stored and where heap buffer overflows can happen. The latter makes memory allocations for the MEM_ROOT about 10x more expensive, so let's always start with an empty memroot (= zero overhead if no user variables are used) and on THD cleanup let's retain one memroot block (= zero overhead if the next connection takes THD from the cache and uses user variables up to one block size). Assisted-By: Claude:claude-4.8-opus --- sql/item_func.cc | 11 ++++------- sql/sql_class.cc | 25 +++++++++++++++++++++++-- sql/sql_class.h | 7 ++++++- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index 95323593e27..09950722e14 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -4735,9 +4735,9 @@ user_var_entry *get_variable(HASH *hash, LEX_CSTRING *name, size_t size=ALIGN_SIZE(sizeof(user_var_entry))+name->length+1+extra_size; if (!my_hash_inited(hash)) return 0; - if (!(entry = (user_var_entry*) my_malloc(key_memory_user_var_entry, size, - MYF(MY_WME | ME_FATAL | - MY_THREAD_SPECIFIC)))) + + if (!(entry= (user_var_entry*) alloc_root(current_thd->user_vars_root(), + size))) return 0; entry->name.str=(char*) entry+ ALIGN_SIZE(sizeof(user_var_entry))+ extra_size; @@ -4760,10 +4760,7 @@ user_var_entry *get_variable(HASH *hash, LEX_CSTRING *name, entry->set_handler(&type_handler_long_blob); memcpy((char*) entry->name.str, name->str, name->length+1); if (my_hash_insert(hash,(uchar*) entry)) - { - my_free(entry); return 0; - } } return entry; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7b54c1ddc2d..30c341eb653 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2022, MariaDB Corporation. + Copyright (c) 2008, 2026, MariaDB plc. 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 @@ -103,7 +103,6 @@ extern "C" void free_user_var(void *entry_) char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry)); if (entry->value && entry->value != pos) my_free(entry->value); - my_free(entry); } /* Functions for last-value-from-sequence hash */ @@ -820,6 +819,13 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) &main_mem_root, DEFAULT_ROOT_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); + /* + Use MY_ROOT_USE_VMEM to keep user vars away from the heap, so that a heap + buffer overflow couldn't corrupt a user_var_entry and its value pointer + */ + init_sql_alloc(key_memory_user_var_entry, &user_vars_memroot, + 256, 0, MYF(MY_THREAD_SPECIFIC | MY_ROOT_USE_VMEM)); + /* Allocation of user variables for binary logging is always done with main mem root @@ -1645,8 +1651,22 @@ void THD::cleanup(void) } wt_thd_destroy(&transaction->wt); + bool user_vars_used= user_vars.records != 0; my_hash_free(&user_vars); my_hash_free(&sequences); + /* + MY_ROOT_USE_VMEM is expensive. If this connection used user vars, + let's keep one page prealloc so that the next connection on this THD + wouldn't need to allocate. + */ + if (user_vars_used) + { + reset_root_defaults(&user_vars_memroot, user_vars_memroot.block_size, + user_vars_memroot.block_size); + free_root(&user_vars_memroot, MYF(MY_KEEP_PREALLOC)); + } + else + free_root(&user_vars_memroot, MYF(0)); sp_caches_clear(); auto_inc_intervals_forced.empty(); auto_inc_intervals_in_cur_stmt_for_binlog.empty(); @@ -1800,6 +1820,7 @@ THD::~THD() #endif main_lex.free_set_stmt_mem_root(); free_root(&main_mem_root, MYF(0)); + free_root(&user_vars_memroot, MYF(0)); my_free(m_token_array); my_free(killed_err); main_da.free_memory(); diff --git a/sql/sql_class.h b/sql/sql_class.h index 506d6cdb2f3..3549e1b753b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -5414,6 +5414,7 @@ public: void mark_transaction_to_rollback(bool all); bool internal_transaction() { return transaction != &default_transaction; } + MEM_ROOT *user_vars_root() { return &user_vars_memroot; } private: /** The current internal error handler for this thread, or NULL. */ @@ -5435,6 +5436,10 @@ private: tree itself is reused between executions and thus is stored elsewhere. */ MEM_ROOT main_mem_root; + /** + Memory root the user_var_entry objects and their names are allocated on. + */ + MEM_ROOT user_vars_memroot; Diagnostics_area main_da; Diagnostics_area *m_stmt_da; From 2233798c11bb769a90de50c1769bfc522d385913 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 12 Jul 2026 09:31:58 +0200 Subject: [PATCH 026/150] MDEV-40341 store read-only sysvars in a read-only segment Protect all READ_ONLY sysvars from run-time changes. Put them into a separate section and use my_virtual_mem_protect() to make this section read-only before the server starts accepting connections. Verify that they're all protected in the sys_var constructor. One exception: opt_noacl (--skip-grant-tables) can be changed from 1 to 0 on FLUSH PRIVILEGES. Let's briefly drop the protection for this 1->0 change. It can be needed only once in a server lifetime and only if it was started with --skip-grant-tables On shutdown the protection is removed, making variables writable again because shutdown resets some of them during the cleanup Assisted-By: Claude:claude-4.8-opus --- config.h.cmake | 4 +- configure.cmake | 21 +- include/my_global.h | 17 +- .../sys_vars/r/sysvars_readonly_debug.result | 11 + .../sys_vars/t/sysvars_readonly_debug.opt | 1 + .../sys_vars/t/sysvars_readonly_debug.test | 17 ++ mysys/charset.c | 4 +- mysys/my_init.c | 3 +- mysys/my_static.c | 6 +- mysys/thr_alarm.c | 4 +- mysys/thr_mutex.c | 3 +- sql-common/client.c | 15 +- sql/CMakeLists.txt | 12 +- sql/log.cc | 8 +- sql/mysqld.cc | 209 +++++++++++++----- sql/mysqld.h | 14 +- sql/mysqld_ro.lds | 31 +++ sql/set_var.cc | 6 + sql/set_var.h | 15 ++ sql/signal_handler.cc | 8 +- sql/slave.cc | 10 +- sql/sql_plugin.cc | 8 +- sql/sql_reload.cc | 4 +- sql/sql_show.cc | 4 +- sql/sys_vars.cc | 35 ++- sql/sys_vars.inl | 18 +- sql/table_cache.cc | 4 +- sql/threadpool_common.cc | 4 +- sql/wsrep_mysqld.cc | 14 +- storage/myisam/ft_static.c | 9 +- storage/perfschema/pfs_server.cc | 3 +- 31 files changed, 371 insertions(+), 151 deletions(-) create mode 100644 mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result create mode 100644 mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt create mode 100644 mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test create mode 100644 sql/mysqld_ro.lds diff --git a/config.h.cmake b/config.h.cmake index a757341ffaa..cd85e93667c 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. - + Copyright (c) 2026, MariaDB plc. + 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. @@ -184,6 +185,7 @@ #cmakedefine HAVE_MMAP 1 #cmakedefine HAVE_MMAP64 1 #cmakedefine HAVE_MPROTECT 1 +#cmakedefine HAVE_RO_AFTER_INIT 1 #cmakedefine HAVE_PERROR 1 #cmakedefine HAVE_POLL 1 #cmakedefine HAVE_POSIX_FALLOCATE 1 diff --git a/configure.cmake b/configure.cmake index 027a810ed0e..3d7af45aec3 100644 --- a/configure.cmake +++ b/configure.cmake @@ -1,5 +1,6 @@ # Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. -# +# Copyright (c) 2026, MariaDB plc. +# # 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. @@ -370,6 +371,24 @@ CHECK_FUNCTION_EXISTS (mmap HAVE_MMAP) CHECK_FUNCTION_EXISTS (mmap64 HAVE_MMAP64) CHECK_FUNCTION_EXISTS (mprotect HAVE_MPROTECT) CHECK_FUNCTION_EXISTS (perror HAVE_PERROR) + +IF(HAVE_SYS_MMAN_H AND HAVE_MPROTECT) + SET(SAVE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + SET(CMAKE_REQUIRED_FLAGS + "${CMAKE_REQUIRED_FLAGS} -Wl,-T,${CMAKE_SOURCE_DIR}/sql/mysqld_ro.lds") + CHECK_C_SOURCE_COMPILES(" + #include + __attribute__((section(\"ro_after_init\"))) int hardened= 1; + extern char __ro_after_init_start[] __attribute__((weak)); + extern char __ro_after_init_end[] __attribute__((weak)); + int main(void) { + size_t size= __ro_after_init_end - __ro_after_init_start; + mprotect(__ro_after_init_start, size, PROT_READ | PROT_WRITE); + return (int)size + hardened; + }" HAVE_RO_AFTER_INIT) + SET(CMAKE_REQUIRED_FLAGS "${SAVE_CMAKE_REQUIRED_FLAGS}") +ENDIF() + CHECK_FUNCTION_EXISTS (poll HAVE_POLL) CHECK_FUNCTION_EXISTS (posix_fallocate HAVE_POSIX_FALLOCATE) CHECK_FUNCTION_EXISTS (pread HAVE_PREAD) diff --git a/include/my_global.h b/include/my_global.h index 16360581f74..52d89a94ea2 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001, 2013, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -819,6 +819,7 @@ typedef long long my_ptrdiff_t; #define MY_ALIGN(A,L) (((A) + (L) - 1) & ~((L) - 1)) #define MY_ALIGN_DOWN(A,L) ((A) & ~((L) - 1)) + #define ALIGN_SIZE(A) MY_ALIGN((A),sizeof(double)) #define ALIGN_MAX_UNIT (sizeof(double)) /* Size to make addressable obj. */ @@ -1020,6 +1021,20 @@ typedef ulong myf; /* Type of MyFlags in my_funcs */ #endif #endif +/* + Tag a global variable as "read-only after init". It'll be made read-only + just before the server starts accepting connection. All READ_ONLY sysvars + must be tagged this way. +*/ +#if defined(HAVE_RO_AFTER_INIT) && !defined(EMBEDDED_LIBRARY) +#define READ_ONLY_SYSVAR __attribute__((section("ro_after_init"))) +#elif defined(_MSC_VER) && !defined(EMBEDDED_LIBRARY) +#pragma section("ro_after_init$m", read, write) +#define READ_ONLY_SYSVAR __declspec(allocate("ro_after_init$m")) +#else +#define READ_ONLY_SYSVAR +#endif + #include /* Some helper macros */ diff --git a/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result new file mode 100644 index 00000000000..b385aecb47d --- /dev/null +++ b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result @@ -0,0 +1,11 @@ +# +# MDEV-40341 store read-only sysvars in a read-only root and a read-only segment +# +call mtr.add_suppression('mariadbd(.exe)? got (exception 0xc0000005|signal 11)'); +select @@global.skip_name_resolve; +@@global.skip_name_resolve +0 +set @@global.skip_name_resolve=0; +ERROR HY000: Variable 'skip_name_resolve' is a read only variable +set @@debug_dbug='d,set_skip_name_resolve'; +Got one of the listed errors diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt new file mode 100644 index 00000000000..590d44a6d12 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt @@ -0,0 +1 @@ +--loose-skip-stack-trace --skip-core-file diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test new file mode 100644 index 00000000000..2add6ae4f54 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -0,0 +1,17 @@ +--source include/have_debug.inc +--source include/not_asan.inc +--source include/not_embedded.inc + +--echo # +--echo # MDEV-40341 store read-only sysvars in a read-only root and a read-only segment +--echo # +call mtr.add_suppression('mariadbd(.exe)? got (exception 0xc0000005|signal 11)'); + +select @@global.skip_name_resolve; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@global.skip_name_resolve=0; +--write_line restart $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--error 2013,2026 +set @@debug_dbug='d,set_skip_name_resolve'; +--enable_reconnect +--source include/wait_until_connected_again.inc diff --git a/mysys/charset.c b/mysys/charset.c index 9e56e0a317f..45acfdcd6e8 100644 --- a/mysys/charset.c +++ b/mysys/charset.c @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates - Copyright (c) 2009, 2020, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -525,7 +525,7 @@ my_charset_loader_init_mysys(MY_CHARSET_LOADER *loader) #define MY_MAX_ALLOWED_BUF 1024*1024 #define MY_CHARSET_INDEX "Index.xml" -const char *charsets_dir= NULL; +READ_ONLY_SYSVAR const char *charsets_dir= NULL; static my_bool diff --git a/mysys/my_init.c b/mysys/my_init.c index 5aa0c780131..0fdae930201 100644 --- a/mysys/my_init.c +++ b/mysys/my_init.c @@ -1,6 +1,7 @@ /* Copyright (c) 2000, 2012, Oracle and/or its affiliates Copyright (c) 2009, 2011, Monty Program Ab + Copyright (c) 2026, MariaDB plc. 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 @@ -52,7 +53,7 @@ my_bool my_init_done= 0; uint mysys_usage_id= 0; /* Incremented for each my_init() */ size_t my_system_page_size= 8192; /* Default if no sysconf() */ -ulonglong my_thread_stack_size= (sizeof(void*) <= 4)? 65536: ((256-16)*1024); +READ_ONLY_SYSVAR ulonglong my_thread_stack_size= (sizeof(void*) <= 4)? 65536: ((256-16)*1024); static mode_t atoi_octal(const char *str) { diff --git a/mysys/my_static.c b/mysys/my_static.c index 8df2deb2786..3d734af8a24 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2009, 2019, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -124,8 +124,8 @@ void (*proc_info_hook)(void *, const PSI_stage_info *, PSI_stage_info *, const char *, const char *, const unsigned int)= proc_info_dummy; void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t)= 0; - /* How to disable options */ -my_bool my_disable_locking=0; +/* How to disable options */ +READ_ONLY_SYSVAR my_bool my_disable_locking=0; my_bool my_disable_sync=0; my_bool my_disable_async_io=0; my_bool my_disable_flush_key_blocks=0; diff --git a/mysys/thr_alarm.c b/mysys/thr_alarm.c index 7ebcfd2dac8..4971de308d7 100644 --- a/mysys/thr_alarm.c +++ b/mysys/thr_alarm.c @@ -1,5 +1,6 @@ /* Copyright (c) 2000, 2013, Oracle and/or its affiliates Copyright (c) 2012, 2014, SkySQL Ab + Copyright (c) 2026, MariaDB plc. 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 @@ -48,7 +49,8 @@ #define one_signal_hand_sigmask(A,B,C) #endif -my_bool thr_alarm_inited= 0, my_disable_thr_alarm= 0; +my_bool thr_alarm_inited= 0; +READ_ONLY_SYSVAR my_bool my_disable_thr_alarm= 0; #if !defined(_WIN32) diff --git a/mysys/thr_mutex.c b/mysys/thr_mutex.c index 550bc83e2ab..28174a26577 100644 --- a/mysys/thr_mutex.c +++ b/mysys/thr_mutex.c @@ -1,6 +1,7 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. Copyright (c) 2010, 2011, Monty Program Ab + Copyright (c) 2026, MariaDB plc. 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 @@ -56,7 +57,7 @@ pthread_mutexattr_t my_errorcheck_mutexattr; static pthread_mutex_t THR_LOCK_mutex; static ulong safe_mutex_count= 0; /* Number of mutexes created */ static ulong safe_mutex_id= 0; -my_bool safe_mutex_deadlock_detector= 1; /* On by default */ +READ_ONLY_SYSVAR my_bool safe_mutex_deadlock_detector= 1; /* On by default */ #ifdef SAFE_MUTEX_DETECT_DESTROY static struct st_safe_mutex_create_info_t *safe_mutex_create_root= NULL; diff --git a/sql-common/client.c b/sql-common/client.c index 81500987397..5f59f2b8356 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -1,5 +1,5 @@ /* Copyright (c) 2003, 2016, Oracle and/or its affiliates. - Copyright (c) 2009, 2020, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -1744,7 +1744,7 @@ mysql_set_character_set_with_default_collation(MYSQL *mysql) { const char *save= charsets_dir; if (mysql->options.charset_dir) - charsets_dir=mysql->options.charset_dir; + charsets_dir= mysql->options.charset_dir; if ((mysql->charset= get_charset_by_csname(mysql->options.charset_name, MY_CS_PRIMARY, MYF(MY_WME| @@ -1768,7 +1768,8 @@ mysql_set_character_set_with_default_collation(MYSQL *mysql) */ } } - charsets_dir= save; + if (mysql->options.charset_dir) + charsets_dir= save; } @@ -3778,10 +3779,12 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) my_free(mysql->options.my_cnf_group); mysql->options.my_cnf_group= opt_strdup(arg,MYF(MY_WME)); break; +#ifdef EMBEDDED_LIBRARY case MYSQL_SET_CHARSET_DIR: my_free(mysql->options.charset_dir); mysql->options.charset_dir= opt_strdup(arg,MYF(MY_WME)); break; +#endif case MYSQL_SET_CHARSET_NAME: my_free(mysql->options.charset_name); mysql->options.charset_name= opt_strdup(arg,MYF(MY_WME)); @@ -4097,7 +4100,8 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) (cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(MY_UTF8_IS_UTF8MB3)))) { char buff[MY_CS_CHARACTER_SET_NAME_SIZE + 10]; - charsets_dir= save_csdir; + if (mysql->options.charset_dir) + charsets_dir= save_csdir; /* Skip execution of "SET NAMES" for pre-4.1 servers */ if (mysql_get_server_version(mysql) < 40100) return 0; @@ -4114,7 +4118,8 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) set_mysql_extended_error(mysql, CR_CANT_READ_CHARSET, unknown_sqlstate, ER(CR_CANT_READ_CHARSET), cs_name, cs_dir_name); } - charsets_dir= save_csdir; + if (mysql->options.charset_dir) + charsets_dir= save_csdir; return mysql->net.last_errno; } diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index e8119f01e4c..80b28200a02 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (c) 2006, 2014, Oracle and/or its affiliates. -# Copyright (c) 2010, 2022, MariaDB Corporation. +# Copyright (c) 2010, 2026, MariaDB plc. # # 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 @@ -338,6 +338,16 @@ ENDIF() DTRACE_INSTRUMENT_STATIC_LIBS(mariadbd "sql;mysys;mysys_ssl;${MYSQLD_STATIC_PLUGIN_LIBS}") +IF(HAVE_RO_AFTER_INIT) + GET_TARGET_PROPERTY(RO_AFTER_INIT_LDFLAGS mariadbd LINK_FLAGS) + IF(NOT RO_AFTER_INIT_LDFLAGS) + SET(RO_AFTER_INIT_LDFLAGS) + ENDIF() + SET_TARGET_PROPERTIES(mariadbd PROPERTIES LINK_FLAGS + "${RO_AFTER_INIT_LDFLAGS} -Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/mysqld_ro.lds") + SET_TARGET_PROPERTIES(mariadbd PROPERTIES + LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mysqld_ro.lds) +ENDIF() SET(WITH_MYSQLD_LDFLAGS "" CACHE STRING "Additional linker flags for mysqld") MARK_AS_ADVANCED(WITH_MYSQLD_LDFLAGS) diff --git a/sql/log.cc b/sql/log.cc index 0d4764cef81..d70fe6b9170 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -84,8 +84,8 @@ handlerton *binlog_hton; LOGGER logger; -const char *log_bin_index= 0; -const char *log_bin_basename= 0; +READ_ONLY_SYSVAR const char *log_bin_index= 0; +READ_ONLY_SYSVAR const char *log_bin_basename= 0; MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period); @@ -10003,7 +10003,7 @@ ulong tc_log_page_waits= 0; static const uchar tc_log_magic[]={(uchar) 254, 0x23, 0x05, 0x74}; -ulong opt_tc_log_size; +READ_ONLY_SYSVAR ulong opt_tc_log_size; ulong tc_log_max_pages_used=0, tc_log_page_size=0, tc_log_cur_pages_used=0; int TC_LOG_MMAP::open(const char *opt_name) diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 8ca1390edf4..76fddf3d560 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2022, MariaDB + Copyright (c) 2008, 2026, MariaDB plc. 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 @@ -307,7 +307,7 @@ const char *my_localhost= "localhost", *delayed_user= "delayed", *slave_user= "", *wsrep_user= ""; -bool opt_large_files= sizeof(my_off_t) > 4; +READ_ONLY_SYSVAR my_bool opt_large_files; static my_bool opt_autocommit; ///< for --autocommit command-line option /* Used with --help for detailed option @@ -344,7 +344,7 @@ static char *default_character_set_name; static char *character_set_filesystem_name; static char *lc_messages; static char *lc_time_names_name; -char *my_bind_addr_str; +READ_ONLY_SYSVAR char *my_bind_addr_str; static char *default_collation_name; char *default_storage_engine, *default_tmp_storage_engine; char *enforced_storage_engine=NULL; @@ -362,7 +362,9 @@ char server_uid[SERVER_UID_SIZE+1]; // server uid will be written here /* Global variables */ -bool opt_bin_log, opt_bin_log_used=0, opt_ignore_builtin_innodb= 0; +READ_ONLY_SYSVAR my_bool opt_bin_log; +bool opt_bin_log_used=0; +READ_ONLY_SYSVAR my_bool opt_ignore_builtin_innodb; bool opt_bin_log_compress; uint opt_bin_log_compress_min_len; my_bool opt_log, debug_assert_if_crashed_table= 0, opt_help= 0; @@ -372,11 +374,12 @@ static my_bool opt_abort; ulonglong log_output_options; my_bool opt_userstat_running; bool opt_error_log= IF_WIN(1,0); -bool opt_disable_networking=0, opt_skip_show_db=0; -bool opt_skip_name_resolve=0; +READ_ONLY_SYSVAR my_bool opt_disable_networking; +READ_ONLY_SYSVAR my_bool opt_skip_show_db; +READ_ONLY_SYSVAR my_bool opt_skip_name_resolve; my_bool opt_character_set_client_handshake= 1; bool opt_endinfo, using_udf_functions; -my_bool locked_in_memory; +READ_ONLY_SYSVAR my_bool locked_in_memory; bool opt_using_transactions; bool volatile abort_loop; uint volatile global_disable_checkpoint; @@ -386,6 +389,64 @@ ulong slow_start_timeout; static MEM_ROOT startup_root; MEM_ROOT read_only_root; +#if defined(HAVE_RO_AFTER_INIT) && !defined(EMBEDDED_LIBRARY) +// start and end of the ro_after_init section, to test if a variable is in it +extern char ro_after_init_start[] __attribute__((weak)); +extern char ro_after_init_end[] __attribute__((weak)); + +#elif defined(_MSC_VER) && !defined(EMBEDDED_LIBRARY) + +/* + $a/$z bracket the ro_after_init$m section (see READ_ONLY_SYSVAR in + my_global.h): MSVC's linker merges and alphabetically sorts sections by + their full "name$suffix" across every object file being linked +*/ +#pragma section("ro_after_init$a", read, write) +#pragma section("ro_after_init$z", read, write) +__declspec(allocate("ro_after_init$a")) __declspec(align(4096)) +static char ro_after_init_start_marker; +__declspec(allocate("ro_after_init$z")) __declspec(align(4096)) +static char ro_after_init_end_marker; +static char * const ro_after_init_start= &ro_after_init_start_marker; +static char * const ro_after_init_end= &ro_after_init_end_marker; + +#else + +static constexpr char *ro_after_init_start= 0; +static constexpr char *ro_after_init_end= 0; + +#endif + +/* set protection of the __ro_after_init section */ +static void set_ro_after_init_prot(enum my_vmem_prot prot) +{ + if (size_t size= ro_after_init_end - ro_after_init_start) + my_virtual_mem_protect(ro_after_init_start, size, prot); +} + +/* used for asserts, so returns TRUE if mprotect is impossible */ +bool var_is_ro_after_init(const char *addr) +{ + return ! ro_after_init_start || + (addr >= ro_after_init_start && addr < ro_after_init_end); +} + +/* + FLUSH PRIVILEGES leaves --skip-grant-tables mode by clearing opt_noacl. + But opt_noacl is READ_ONLY_SYSVAR, so briefly remove the protection for this + single write. It's done at most once and only when the server was started + with --skip-grant-tables. +*/ +void clear_opt_noacl() +{ + if (opt_noacl) + { + set_ro_after_init_prot(MY_VMEM_READWRITE); + opt_noacl= 0; + set_ro_after_init_prot(MY_VMEM_READONLY); + } +} + /** @brief 'grant_option' is used to indicate if privileges needs to be checked, in which case the lock, LOCK_grant, is used @@ -397,15 +458,15 @@ bool volatile grant_option; my_bool opt_skip_slave_start = 0; ///< If set, slave is not autostarted my_bool opt_reckless_slave = 0; -my_bool opt_enable_named_pipe= 0; +READ_ONLY_SYSVAR my_bool opt_enable_named_pipe= 0; my_bool opt_local_infile, opt_slave_compressed_protocol; my_bool opt_safe_user_create = 0; my_bool opt_show_slave_auth_info; -my_bool opt_log_slave_updates= 0; -my_bool opt_replicate_annotate_row_events= 0; +READ_ONLY_SYSVAR my_bool opt_log_slave_updates; +READ_ONLY_SYSVAR my_bool opt_replicate_annotate_row_events; my_bool opt_mysql56_temporal_format=0, strict_password_validation= 1; -char *opt_slave_skip_errors; -char *opt_slave_transaction_retry_errors; +READ_ONLY_SYSVAR char *opt_slave_skip_errors; +READ_ONLY_SYSVAR char *opt_slave_transaction_retry_errors; /* Legacy global handlerton. These will be removed (please do not add more). @@ -417,17 +478,18 @@ handlerton *partition_hton; my_bool read_only= 0, opt_readonly= 0; my_bool use_temp_pool, relay_log_purge; my_bool relay_log_recovery; -my_bool opt_sync_frm, opt_allow_suspicious_udfs; +my_bool opt_sync_frm; +READ_ONLY_SYSVAR my_bool opt_allow_suspicious_udfs; my_bool opt_secure_auth= 0; my_bool opt_require_secure_transport= 0; -char* opt_secure_file_priv; -my_bool lower_case_file_system= 0; -my_bool opt_large_pages= 0; +READ_ONLY_SYSVAR char* opt_secure_file_priv; +READ_ONLY_SYSVAR my_bool lower_case_file_system; +READ_ONLY_SYSVAR my_bool opt_large_pages; #ifdef HAVE_SOLARIS_LARGE_PAGES my_bool opt_super_large_pages= 0; #endif my_bool opt_myisam_use_mmap= 0; -uint opt_large_page_size= 0; +READ_ONLY_SYSVAR uint opt_large_page_size; #if defined(ENABLED_DEBUG_SYNC) MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout= 0; #endif /* defined(ENABLED_DEBUG_SYNC) */ @@ -440,7 +502,7 @@ ulong opt_replicate_events_marked_for_skip; changed). False otherwise. */ volatile bool mqh_used = 0; -my_bool opt_noacl; +READ_ONLY_SYSVAR my_bool opt_noacl; my_bool sp_automatic_privileges= 1; ulong opt_binlog_rows_event_max_size; @@ -449,21 +511,24 @@ my_bool opt_master_verify_checksum= 0; my_bool opt_slave_sql_verify_checksum= 1; const char *binlog_format_names[]= {"MIXED", "STATEMENT", "ROW", NullS}; volatile sig_atomic_t calling_initgroups= 0; /**< Used in SIGSEGV handler. */ -uint mysqld_port, select_errors, dropping_tables, ha_open_options; -uint mysqld_extra_port; +READ_ONLY_SYSVAR uint mysqld_port; +uint select_errors, dropping_tables, ha_open_options; +READ_ONLY_SYSVAR uint mysqld_extra_port; uint mysqld_port_timeout; ulong delay_key_write_options; -uint protocol_version; -uint lower_case_table_names; +READ_ONLY_SYSVAR uint protocol_version; +READ_ONLY_SYSVAR uint lower_case_table_names; ulong tc_heuristic_recover= 0; Atomic_counter THD_count::count, CONNECT::count; bool shutdown_wait_for_slaves; Atomic_counter slave_open_temp_tables; ulong thread_created; -ulong back_log, connect_timeout, server_id; +READ_ONLY_SYSVAR ulong back_log; +ulong connect_timeout, server_id; ulong what_to_log; ulong slow_launch_time; -ulong open_files_limit, max_binlog_size; +READ_ONLY_SYSVAR ulong open_files_limit; +ulong max_binlog_size; ulong slave_trans_retries; ulong slave_trans_retry_interval; uint slave_net_timeout; @@ -480,7 +545,7 @@ double slave_max_statement_time_double; ulonglong slave_max_statement_time; ulonglong binlog_stmt_cache_size=0; ulonglong max_binlog_stmt_cache_size=0; -ulonglong test_flags; +READ_ONLY_SYSVAR ulonglong test_flags; ulonglong query_cache_size=0; ulong query_cache_limit=0; ulong executed_events=0; @@ -496,7 +561,7 @@ ulong binlog_stmt_cache_use= 0, binlog_stmt_cache_disk_use= 0; ulong max_connections, max_connect_errors; uint max_password_errors; ulong extra_max_connections; -uint max_digest_length= 0; +READ_ONLY_SYSVAR uint max_digest_length; ulong slave_retried_transactions; ulong transactions_multi_engine; ulong rpl_transactions_multi_engine; @@ -505,7 +570,7 @@ ulonglong slave_skipped_errors; ulong feature_files_opened_with_delayed_keys= 0, feature_check_constraint= 0; ulonglong denied_connections; my_decimal decimal_zero; -long opt_secure_timestamp; +READ_ONLY_SYSVAR long opt_secure_timestamp; uint default_password_lifetime; my_bool disconnect_on_expired_password; @@ -585,14 +650,20 @@ const double log_10[] = { time_t server_start_time, flush_status_time; -char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30]; +READ_ONLY_SYSVAR char mysql_home[FN_REFLEN]; +READ_ONLY_SYSVAR char pidfile_name[FN_REFLEN]; +READ_ONLY_SYSVAR char system_time_zone[30]; char *default_tz_name; -char log_error_file[FN_REFLEN], glob_hostname[FN_REFLEN], *opt_log_basename; -char mysql_real_data_home[FN_REFLEN], - lc_messages_dir[FN_REFLEN], reg_ext[FN_EXTLEN], - mysql_charsets_dir[FN_REFLEN], - *opt_init_file, *opt_tc_log_file, *opt_ddl_recovery_file; -char *lc_messages_dir_ptr= lc_messages_dir, *log_error_file_ptr; +READ_ONLY_SYSVAR char log_error_file[FN_REFLEN]; +READ_ONLY_SYSVAR char glob_hostname[FN_REFLEN]; +char *opt_log_basename; +READ_ONLY_SYSVAR char mysql_real_data_home[FN_REFLEN]; +READ_ONLY_SYSVAR char lc_messages_dir[FN_REFLEN]; +char reg_ext[FN_EXTLEN], mysql_charsets_dir[FN_REFLEN]; +char *opt_tc_log_file, *opt_ddl_recovery_file; +READ_ONLY_SYSVAR char *opt_init_file; +READ_ONLY_SYSVAR char *lc_messages_dir_ptr= lc_messages_dir; +READ_ONLY_SYSVAR char *log_error_file_ptr; char mysql_unpacked_real_data_home[FN_REFLEN]; size_t mysql_unpacked_real_data_home_len; uint mysql_real_data_home_len, mysql_data_home_len= 1; @@ -600,20 +671,24 @@ uint reg_ext_length; const key_map key_map_empty(0); key_map key_map_full(0); // Will be initialized later -DATE_TIME_FORMAT global_date_format, global_datetime_format, global_time_format; +READ_ONLY_SYSVAR DATE_TIME_FORMAT global_date_format; +READ_ONLY_SYSVAR DATE_TIME_FORMAT global_datetime_format; +READ_ONLY_SYSVAR DATE_TIME_FORMAT global_time_format; Time_zone *default_tz; -const char *mysql_real_data_home_ptr= mysql_real_data_home; +READ_ONLY_SYSVAR const char *mysql_real_data_home_ptr= mysql_real_data_home; extern "C" { -char server_version[SERVER_VERSION_LENGTH]; +READ_ONLY_SYSVAR char server_version[SERVER_VERSION_LENGTH]; } -char *server_version_ptr; +READ_ONLY_SYSVAR char *server_version_ptr; bool using_custom_server_version= false; -char *mysqld_unix_port, *opt_mysql_tmpdir; -ulong thread_handling; +READ_ONLY_SYSVAR char *mysqld_unix_port; +READ_ONLY_SYSVAR char *opt_mysql_tmpdir; +READ_ONLY_SYSVAR ulong thread_handling; -my_bool encrypt_binlog; -my_bool encrypt_tmp_disk_tables, encrypt_tmp_files; +READ_ONLY_SYSVAR my_bool encrypt_binlog; +my_bool encrypt_tmp_disk_tables; +READ_ONLY_SYSVAR my_bool encrypt_tmp_files; /** name of reference on left expression in rewritten IN subquery */ const LEX_CSTRING in_left_expr_name= {STRING_WITH_LEN("") }; @@ -678,19 +753,24 @@ uint temp_pool_set_next() return res; } -CHARSET_INFO *system_charset_info, *files_charset_info ; +READ_ONLY_SYSVAR CHARSET_INFO *system_charset_info; CHARSET_INFO *national_charset_info, *table_alias_charset; -CHARSET_INFO *character_set_filesystem; +CHARSET_INFO *character_set_filesystem, *files_charset_info; CHARSET_INFO *error_message_charset_info; MY_LOCALE *my_default_lc_messages; MY_LOCALE *my_default_lc_time_names; -SHOW_COMP_OPTION have_ssl, have_symlink, have_dlopen, have_query_cache; -SHOW_COMP_OPTION have_geometry, have_rtree_keys; -SHOW_COMP_OPTION have_crypt, have_compress; -SHOW_COMP_OPTION have_profiling; -SHOW_COMP_OPTION have_openssl; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_ssl; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_symlink; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_dlopen; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_query_cache; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_geometry; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_rtree_keys; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_crypt; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_compress; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_profiling; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_openssl; #ifndef EMBEDDED_LIBRARY static std::atomic shutdown_user; @@ -761,11 +841,15 @@ int mysqld_server_started=0, mysqld_server_initialized= 0; File_parser_dummy_hook file_parser_dummy_hook; /* replication parameters, if master_host is not NULL, we are a slave */ -uint report_port= 0; +READ_ONLY_SYSVAR uint report_port= 0; ulong master_retry_count=0; char *master_info_file; -char *relay_log_info_file, *report_user, *report_password, *report_host; -char *opt_relay_logname = 0, *opt_relaylog_index_name=0; +READ_ONLY_SYSVAR char *relay_log_info_file; +READ_ONLY_SYSVAR char *report_user; +READ_ONLY_SYSVAR char *report_password; +READ_ONLY_SYSVAR char *report_host; +READ_ONLY_SYSVAR char *opt_relay_logname= 0; +char *opt_relaylog_index_name=0; char *opt_logname, *opt_slow_logname, *opt_bin_logname; char *opt_binlog_index_name=0; @@ -782,7 +866,8 @@ my_bool opt_expect_abort= 0, opt_bootstrap= 0; static my_bool opt_myisam_log; static int cleanup_done; static ulong opt_specialflag; -char *mysql_home_ptr, *pidfile_name_ptr; +READ_ONLY_SYSVAR char *mysql_home_ptr; +READ_ONLY_SYSVAR char *pidfile_name_ptr; /** Initial command line arguments (count), after load_defaults().*/ static int defaults_argc; /** @@ -1491,10 +1576,15 @@ Query_cache query_cache; my_bool opt_use_ssl = 0; -char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL, - *opt_ssl_cipher= NULL, *opt_ssl_key= NULL, *opt_ssl_crl= NULL, - *opt_ssl_crlpath= NULL, *opt_tls_version= NULL; -ulonglong tls_version= 0; +READ_ONLY_SYSVAR char *opt_ssl_ca= NULL; +READ_ONLY_SYSVAR char *opt_ssl_capath= NULL; +READ_ONLY_SYSVAR char *opt_ssl_cert= NULL; +READ_ONLY_SYSVAR char *opt_ssl_cipher= NULL; +READ_ONLY_SYSVAR char *opt_ssl_key= NULL; +READ_ONLY_SYSVAR char *opt_ssl_crl= NULL; +READ_ONLY_SYSVAR char *opt_ssl_crlpath= NULL; +READ_ONLY_SYSVAR ulonglong tls_version= 0; +char *opt_tls_version= NULL; static scheduler_functions thread_scheduler_struct, extra_thread_scheduler_struct; scheduler_functions *thread_scheduler= &thread_scheduler_struct, @@ -1962,6 +2052,8 @@ static void clean_up(bool print_message) if (cleanup_done++) return; /* purecov: inspected */ + set_ro_after_init_prot(MY_VMEM_READWRITE); // to allow cleanup + #ifdef HAVE_REPLICATION // We must call end_slave() as clean_up may have been called during startup end_slave(); @@ -6030,6 +6122,9 @@ int mysqld_main(int argc, char **argv) /* Protect read_only_root against writes */ protect_root(&read_only_root, MY_VMEM_READONLY); + /* Protect read-only sysvars */ + set_ro_after_init_prot(MY_VMEM_READONLY); + if (opt_bootstrap) { int bootstrap_error= bootstrap(mysql_stdin); diff --git a/sql/mysqld.h b/sql/mysqld.h index 92cb4f639f7..60749dc52b0 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -1,5 +1,5 @@ /* Copyright (c) 2006, 2016, Oracle and/or its affiliates. - Copyright (c) 2010, 2021, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. 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 @@ -80,6 +80,8 @@ void handle_connection_in_main_thread(CONNECT *thd); void create_thread_to_handle_connection(CONNECT *connect); void unlink_thd(THD *thd); void refresh_status(THD *thd); +void clear_opt_noacl(); +bool var_is_ro_after_init(const char *addr); bool is_secure_file_path(char *path); extern void init_net_server_extension(THD *thd); extern void handle_accepted_socket(MYSQL_SOCKET new_sock, MYSQL_SOCKET sock); @@ -103,8 +105,8 @@ extern CHARSET_INFO *character_set_filesystem; void temp_pool_clear_bit(uint bit); uint temp_pool_set_next(); -extern bool opt_large_files; -extern bool opt_update_log, opt_bin_log, opt_error_log, opt_bin_log_compress; +extern my_bool opt_bin_log, opt_large_files; +extern bool opt_update_log, opt_error_log, opt_bin_log_compress; extern uint opt_bin_log_compress_min_len; extern my_bool opt_log, opt_bootstrap; extern my_bool opt_backup_history_log; @@ -112,9 +114,9 @@ extern my_bool opt_backup_progress_log; extern my_bool opt_support_flashback; extern ulonglong log_output_options; extern ulong log_backup_output_options; -extern bool opt_disable_networking, opt_skip_show_db; -extern bool opt_skip_name_resolve; -extern bool opt_ignore_builtin_innodb; +extern my_bool opt_disable_networking, opt_skip_show_db; +extern my_bool opt_skip_name_resolve; +extern my_bool opt_ignore_builtin_innodb; extern my_bool opt_character_set_client_handshake; extern my_bool debug_assert_on_not_freed_memory; extern MYSQL_PLUGIN_IMPORT bool volatile abort_loop; diff --git a/sql/mysqld_ro.lds b/sql/mysqld_ro.lds new file mode 100644 index 00000000000..5777feeeabc --- /dev/null +++ b/sql/mysqld_ro.lds @@ -0,0 +1,31 @@ +/* + Copyright (c) 2026, MariaDB plc. + + 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 */ + +/* + Give the "ro_after_init" section a page-aligned span of its own, so that the + variables placed there (with READ_ONLY_SYSVAR tag) can be mprotect()-ed + once server initialization is complete, without affecting neighbouring data. +*/ +SECTIONS +{ + ro_after_init ALIGN(CONSTANT(COMMONPAGESIZE)) : + { + ro_after_init_start = .; + *(ro_after_init) + . = ALIGN(CONSTANT(COMMONPAGESIZE)); + ro_after_init_end = .; + } +} INSERT AFTER .data; diff --git a/sql/set_var.cc b/sql/set_var.cc index c515aed4066..2f62df1b7c4 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2002, 2013, Oracle and/or its affiliates. Copyright (c) 2008, 2014, SkySQL Ab. + Copyright (c) 2026, MariaDB plc. 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 @@ -183,6 +184,10 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, option.app_type= this; option.var_type= flags & AUTO_SET ? GET_AUTO : 0; + /* only check readonly, global, not-plugin sysvars */ + SYSVAR_ASSERT(!is_readonly() || scope() != GLOBAL || offset == 0 || + var_is_ro_after_init((char*)global_var_ptr())); + if (chain->last) chain->last->next= this; else @@ -749,6 +754,7 @@ int sql_set_variables(THD *thd, List *var_list, bool free) error|= var->update(thd); // Returns 0, -1 or 1 } + DBUG_EXECUTE_IF("set_skip_name_resolve", opt_skip_name_resolve= 0;); err: if (free) free_underlaid_joins(thd, thd->lex->first_select_lex()); diff --git a/sql/set_var.h b/sql/set_var.h index 58b23e14bf7..e1e9948e4d6 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -266,6 +266,21 @@ protected: friend class Session_tracker; }; +/* + special assert for sysvars. Tells the name of the variable, + and fails even in non-debug builds. + + It is supposed to be used *only* in Sys_var* constructors, + and has name_arg hard-coded to prevent incorrect usage. +*/ +#define SYSVAR_ASSERT(X) \ + while(!(X)) \ + { \ + fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ + DBUG_ASSERT(0); \ + exit(255); \ + } + #include "sql_plugin.h" /* SHOW_HA_ROWS, SHOW_MY_BOOL */ diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc index 8f03b7306c6..873675d6ea4 100644 --- a/sql/signal_handler.cc +++ b/sql/signal_handler.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2011, 2012, Oracle and/or its affiliates. - Copyright (c) 2011, 2021, MariaDB Corporation. + Copyright (c) 2011, 2026, MariaDB plc. 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 @@ -203,9 +203,11 @@ extern "C" sig_handler handle_fatal_signal(int sig) "Please include the information from the server start above, to the end of the\n" "information below.\n\n"); - set_server_version(server_version, sizeof(server_version)); + /* print the real version on crash */ + char real_version[SERVER_VERSION_LENGTH]; + set_server_version(real_version, sizeof(real_version)); my_safe_printf_stderr("Server version: %s source revision: %s\n\n", - server_version, SOURCE_REVISION); + real_version, SOURCE_REVISION); #ifdef WITH_WSREP Wsrep_server_state::handle_fatal_signal(); diff --git a/sql/slave.cc b/sql/slave.cc index 881744a7da3..4215a3c1923 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -82,14 +82,14 @@ uint *slave_transaction_retry_errors; uint slave_transaction_retry_error_length= 0; char slave_transaction_retry_error_names[SHOW_VAR_FUNC_BUFF_SIZE]; -char* slave_load_tmpdir = 0; +READ_ONLY_SYSVAR char* slave_load_tmpdir; Master_info *active_mi= 0; my_bool replicate_same_server_id; -ulonglong relay_log_space_limit = 0; +READ_ONLY_SYSVAR ulonglong relay_log_space_limit; ulonglong opt_read_binlog_speed_limit = 0; -const char *relay_log_index= 0; -const char *relay_log_basename= 0; +READ_ONLY_SYSVAR const char *relay_log_index; +READ_ONLY_SYSVAR const char *relay_log_basename; LEX_CSTRING default_master_connection_name= { (char*) "", 0 }; diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index e61f7eae0cc..34a9eec7220 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2005, 2018, Oracle and/or its affiliates. - Copyright (c) 2010, 2020, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. 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 @@ -67,9 +67,9 @@ static TYPELIB global_plugin_typelib= static I_List opt_plugin_load_list; I_List *opt_plugin_load_list_ptr= &opt_plugin_load_list; -char *opt_plugin_dir_ptr; -char opt_plugin_dir[FN_REFLEN]; -ulong plugin_maturity; +READ_ONLY_SYSVAR char *opt_plugin_dir_ptr; +READ_ONLY_SYSVAR char opt_plugin_dir[FN_REFLEN]; +READ_ONLY_SYSVAR ulong plugin_maturity; static LEX_CSTRING MYSQL_PLUGIN_NAME= {STRING_WITH_LEN("plugin") }; diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 74aaa4beba3..cece04c92a3 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2010, 2016, Oracle and/or its affiliates. - Copyright (c) 2011, 2016, MariaDB + Copyright (c) 2011, 2026, MariaDB plc. 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 @@ -116,7 +116,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long long options, }); #endif } - opt_noacl= 0; + clear_opt_noacl(); if (unlikely(tmp_thd)) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index a924e5e74fa..16cdf2fed58 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2023, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -593,7 +593,7 @@ static DYNAMIC_ARRAY ignore_db_dirs_array; A value for the read only system variable to show a list of ignored directories. */ -char *opt_ignore_db_dirs= NULL; +READ_ONLY_SYSVAR char *opt_ignore_db_dirs; /** This flag is ON if: diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 9b2e76b00b8..e40416078e8 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2012, 2022, MariaDB Corporation. + Copyright (c) 2012, 2026, MariaDB plc. 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 @@ -1843,14 +1843,14 @@ static Sys_var_ulonglong Sys_max_heap_table_size( VALID_RANGE(16384, SIZE_T_MAX), DEFAULT(16*1024*1024), BLOCK_SIZE(1024)); -static ulong mdl_locks_cache_size; +READ_ONLY_SYSVAR static ulong mdl_locks_cache_size; static Sys_var_ulong Sys_metadata_locks_cache_size( "metadata_locks_cache_size", "Unused", READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, 1024*1024), DEFAULT(1024), BLOCK_SIZE(1)); -static ulong mdl_locks_hash_partitions; +READ_ONLY_SYSVAR static ulong mdl_locks_hash_partitions; static Sys_var_ulong Sys_metadata_locks_hash_instances( "metadata_locks_hash_instances", "Unused", READ_ONLY GLOBAL_VAR(mdl_locks_hash_partitions), CMD_LINE(REQUIRED_ARG), @@ -1963,7 +1963,7 @@ Sys_gtid_seq_no( #ifdef HAVE_REPLICATION -static unsigned char opt_gtid_binlog_pos_dummy; +READ_ONLY_SYSVAR static unsigned char opt_gtid_binlog_pos_dummy; static Sys_var_gtid_binlog_pos Sys_gtid_binlog_pos( "gtid_binlog_pos", "Last GTID logged to the binary log, per replication" "domain", @@ -1990,7 +1990,7 @@ Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, } -static unsigned char opt_gtid_current_pos_dummy; +READ_ONLY_SYSVAR static unsigned char opt_gtid_current_pos_dummy; static Sys_var_gtid_current_pos Sys_gtid_current_pos( "gtid_current_pos", "Current GTID position of the server. Per " "replication domain, this is either the last GTID replicated by a " @@ -3531,8 +3531,7 @@ Sys_server_id( VALID_RANGE(1, UINT_MAX32), DEFAULT(1), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_server_id), ON_UPDATE(fix_server_id)); -char *server_uid_ptr= &server_uid[0]; - +READ_ONLY_SYSVAR char *server_uid_ptr= &server_uid[0]; static Sys_var_charptr Sys_server_uid( "server_uid", "Automatically calculated server unique id hash", READ_ONLY GLOBAL_VAR(server_uid_ptr), @@ -4083,7 +4082,7 @@ static Sys_var_mybool Sys_sync_frm( GLOBAL_VAR(opt_sync_frm), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); -static char *system_time_zone_ptr; +READ_ONLY_SYSVAR static char *system_time_zone_ptr; static Sys_var_charptr Sys_system_time_zone( "system_time_zone", "The server system time zone", READ_ONLY GLOBAL_VAR(system_time_zone_ptr), @@ -4401,7 +4400,7 @@ static Sys_var_charptr Sys_version( CMD_LINE_HELP_ONLY, DEFAULT(server_version)); -static char *server_version_comment_ptr; +READ_ONLY_SYSVAR static char *server_version_comment_ptr; static Sys_var_charptr Sys_version_comment( "version_comment", "Value of the COMPILATION_COMMENT option " "specified by CMake when building MariaDB, for example " @@ -4410,14 +4409,14 @@ static Sys_var_charptr Sys_version_comment( CMD_LINE_HELP_ONLY, DEFAULT(MYSQL_COMPILATION_COMMENT)); -static char *server_version_compile_machine_ptr; +READ_ONLY_SYSVAR static char *server_version_compile_machine_ptr; static Sys_var_charptr Sys_version_compile_machine( "version_compile_machine", "The machine type or architecture " "MariaDB was built on, for example i686.", READ_ONLY GLOBAL_VAR(server_version_compile_machine_ptr), CMD_LINE_HELP_ONLY, DEFAULT(DEFAULT_MACHINE)); -static char *server_version_compile_os_ptr; +READ_ONLY_SYSVAR static char *server_version_compile_os_ptr; static Sys_var_charptr Sys_version_compile_os( "version_compile_os", "Operating system that MariaDB was built " "on, for example debian-linux-gnu.", @@ -4426,20 +4425,20 @@ static Sys_var_charptr Sys_version_compile_os( DEFAULT(SYSTEM_TYPE)); #include -static char *server_version_source_revision; +READ_ONLY_SYSVAR static char *server_version_source_revision; static Sys_var_charptr Sys_version_source_revision( "version_source_revision", "Source control revision id for MariaDB source code", READ_ONLY GLOBAL_VAR(server_version_source_revision), CMD_LINE_HELP_ONLY, DEFAULT(SOURCE_REVISION)); -static char *malloc_library; +READ_ONLY_SYSVAR static char *malloc_library; static Sys_var_charptr Sys_malloc_library( "version_malloc_library", "Version of the used malloc library", READ_ONLY GLOBAL_VAR(malloc_library), CMD_LINE_HELP_ONLY, DEFAULT(guess_malloc_library())); -static char *ssl_library; +READ_ONLY_SYSVAR static char *ssl_library; static Sys_var_charptr Sys_ssl_library( "version_ssl_library", "Version of the used SSL library", READ_ONLY GLOBAL_VAR(ssl_library), CMD_LINE_HELP_ONLY, @@ -5090,7 +5089,7 @@ static Sys_var_uint Sys_group_concat_max_len( VALID_RANGE(4, MAX_MAX_ALLOWED_PACKET), DEFAULT(1024*1024), BLOCK_SIZE(1)); -static char *glob_hostname_ptr; +READ_ONLY_SYSVAR static char *glob_hostname_ptr; static Sys_var_charptr Sys_hostname( "hostname", "Server host name", READ_ONLY GLOBAL_VAR(glob_hostname_ptr), NO_CMD_LINE, @@ -5138,7 +5137,7 @@ static Sys_var_mybool Sys_keep_files_on_create( "Don't overwrite stale .MYD and .MYI even if no directory is specified", SESSION_VAR(keep_files_on_create), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -static char *license; +READ_ONLY_SYSVAR static char *license; static Sys_var_charptr Sys_license( "license", "The type of license the server has", READ_ONLY GLOBAL_VAR(license), NO_CMD_LINE, @@ -5374,7 +5373,7 @@ static Sys_var_have Sys_have_symlink( # define SANITIZER_MODE "MSAN" # endif -static char *have_sanitizer; +READ_ONLY_SYSVAR static char *have_sanitizer; static Sys_var_charptr Sys_have_santitizer( "have_sanitizer", "If the server is compiled with sanitize (compiler option), this " @@ -6436,7 +6435,7 @@ static Sys_var_mybool Sys_wsrep_gtid_mode( "ignored (backward compatibility).", GLOBAL_VAR(wsrep_gtid_mode), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -static char *wsrep_patch_version_ptr; +READ_ONLY_SYSVAR static char *wsrep_patch_version_ptr; static Sys_var_charptr Sys_wsrep_patch_version( "wsrep_patch_version", "Wsrep patch version, for example wsrep_25.10.", READ_ONLY GLOBAL_VAR(wsrep_patch_version_ptr), CMD_LINE_HELP_ONLY, diff --git a/sql/sys_vars.inl b/sql/sys_vars.inl index 20155acba8a..dee62b414bc 100644 --- a/sql/sys_vars.inl +++ b/sql/sys_vars.inl @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2011, Oracle and/or its affiliates. - Copyright (c) 2010, 2020, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. 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 @@ -83,22 +83,6 @@ #pragma clang diagnostic ignored "-Winvalid-offsetof" #endif -/* - special assert for sysvars. Tells the name of the variable, - and fails even in non-debug builds. - - It is supposed to be used *only* in Sys_var* constructors, - and has name_arg hard-coded to prevent incorrect usage. -*/ -#define SYSVAR_ASSERT(X) \ - while(!(X)) \ - { \ - fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ - DBUG_ASSERT(0); \ - exit(255); \ - } - - static const char *bool_values[3]= {"OFF", "ON", 0}; TYPELIB bool_typelib={ array_elements(bool_values)-1, "", bool_values, 0 }; diff --git a/sql/table_cache.cc b/sql/table_cache.cc index 48cd63401e6..c2ebdcb8d3f 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2012, Oracle and/or its affiliates. - Copyright (c) 2010, 2022, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation This program is free software; you can redistribute it and/or modify @@ -56,7 +56,7 @@ /** Configuration. */ ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ ulong tc_size; /**< Table cache threshold for LRU eviction. */ -uint32 tc_instances; +READ_ONLY_SYSVAR uint32 tc_instances; static size_t tc_allocated_size; static std::atomic tc_active_instances(1); static std::atomic tc_contention_warning_reported; diff --git a/sql/threadpool_common.cc b/sql/threadpool_common.cc index 1dd30958883..0dd7db08d1f 100644 --- a/sql/threadpool_common.cc +++ b/sql/threadpool_common.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2012, 2020, MariaDB +/* Copyright (C) 2012, 2026, MariaDB plc. 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 @@ -43,7 +43,7 @@ uint threadpool_max_size; uint threadpool_stall_limit; uint threadpool_max_threads; uint threadpool_oversubscribe; -uint threadpool_mode; +READ_ONLY_SYSVAR uint threadpool_mode; uint threadpool_prio_kickup_timer; my_bool threadpool_exact_stats; my_bool threadpool_dedicated_listener; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 2dd9911908b..95b8031c2dc 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -74,7 +74,7 @@ const char *wsrep_SR_store_types[]= { "none", "table", NullS }; extern my_bool plugins_are_initialized; /* System variables. */ -const char *wsrep_provider; +READ_ONLY_SYSVAR const char *wsrep_provider; const char *wsrep_provider_options; const char *wsrep_cluster_address; const char *wsrep_cluster_name; @@ -82,11 +82,11 @@ const char *wsrep_node_name; const char *wsrep_node_address; const char *wsrep_node_incoming_address; const char *wsrep_start_position; -const char *wsrep_data_home_dir; +READ_ONLY_SYSVAR const char *wsrep_data_home_dir; const char *wsrep_dbug_option; -const char *wsrep_notify_cmd; -const char *wsrep_status_file; -const char *wsrep_allowlist; +READ_ONLY_SYSVAR const char *wsrep_notify_cmd; +READ_ONLY_SYSVAR const char *wsrep_status_file; +READ_ONLY_SYSVAR const char *wsrep_allowlist; ulong wsrep_debug; // Debug level logging my_bool wsrep_convert_LOCK_to_trx; // Convert locking sessions to trx @@ -94,7 +94,7 @@ my_bool wsrep_auto_increment_control; // Control auto increment variab my_bool wsrep_drupal_282555_workaround; // Retry autoinc insert after dupkey my_bool wsrep_certify_nonPK; // Certify, even when no primary key ulong wsrep_certification_rules = WSREP_CERTIFICATION_RULES_STRICT; -my_bool wsrep_recovery; // Recovery +READ_ONLY_SYSVAR my_bool wsrep_recovery; // Recovery my_bool wsrep_log_conflicts; my_bool wsrep_load_data_splitting= 0; // Commit load data every 10K intervals my_bool wsrep_slave_UK_checks; // Slave thread does UK checks @@ -125,7 +125,7 @@ long wsrep_max_protocol_version= 4; // Maximum protocol version to u long int wsrep_protocol_version= wsrep_max_protocol_version; ulong wsrep_trx_fragment_unit= WSREP_FRAG_BYTES; // unit for fragment size -ulong wsrep_SR_store_type= WSREP_SR_STORE_TABLE; +READ_ONLY_SYSVAR ulong wsrep_SR_store_type= WSREP_SR_STORE_TABLE; uint wsrep_ignore_apply_errors= 0; std::atomic wsrep_thread_create_failed; diff --git a/storage/myisam/ft_static.c b/storage/myisam/ft_static.c index 78123cdf261..2b84efb7673 100644 --- a/storage/myisam/ft_static.c +++ b/storage/myisam/ft_static.c @@ -1,4 +1,5 @@ /* Copyright (c) 2000-2008 MySQL AB, 2009 Sun Microsystems, Inc. + Copyright (c) 2026, MariaDB plc. Use is subject to license terms. This program is free software; you can redistribute it and/or modify @@ -18,9 +19,9 @@ #include "ftdefs.h" -ulong ft_min_word_len= 4; -ulong ft_max_word_len= HA_FT_MAXCHARLEN; -ulong ft_query_expansion_limit= 5; +READ_ONLY_SYSVAR ulong ft_min_word_len= 4; +READ_ONLY_SYSVAR ulong ft_max_word_len= HA_FT_MAXCHARLEN; +READ_ONLY_SYSVAR ulong ft_query_expansion_limit= 5; const char *ft_boolean_syntax= DEFAULT_FTB_SYNTAX; const HA_KEYSEG ft_keysegs[FT_SEGS]= { @@ -55,7 +56,7 @@ const struct _ft_vft _ft_vft_boolean= { ft_boolean_get_relevance, ft_boolean_reinit_search }; -const char *ft_stopword_file= 0; +READ_ONLY_SYSVAR const char *ft_stopword_file= 0; const char *ft_precompiled_stopwords[]= { #ifdef COMPILE_STOPWORDS_IN diff --git a/storage/perfschema/pfs_server.cc b/storage/perfschema/pfs_server.cc index 258c2153d24..57adf9211cb 100644 --- a/storage/perfschema/pfs_server.cc +++ b/storage/perfschema/pfs_server.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2008, 2023, Oracle and/or its affiliates. + Copyright (c) 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -50,7 +51,7 @@ //#include "template_utils.h" #include "pfs_prepared_stmt.h" -PFS_global_param pfs_param; +READ_ONLY_SYSVAR PFS_global_param pfs_param; PFS_table_stat PFS_table_stat::g_reset_template; From e379b7d35320be1452bc181aa38c9bcf5eb8e345 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 14 Jul 2026 14:26:09 +0200 Subject: [PATCH 027/150] MDEV-40341 store read-only sysvars in a read-only root Store all READ_ONLY sysvar values in the read_only_root Even though READ_ONLY sysvars are protected, for string variables it usually means that the pointer cannot be changed. The value it points to - the string itself - still can be. Let's store all values of string READ_ONLY sysvars in the read_only_root. sysvars that point directly into argv are copied to read_only_root. sysvars that have their values calculated and allocated now must be explicitly marked with PREALLOCATED to let it know they have to be free()-d. Assisted-By: Claude:claude-4.8-opus --- .../sys_vars/r/sysvars_readonly_debug.result | 7 +++++ .../sys_vars/t/sysvars_readonly_debug.opt | 1 + .../sys_vars/t/sysvars_readonly_debug.test | 9 ++++++ sql/mysqld.cc | 9 +----- sql/set_var.cc | 31 +++++++++++++++++++ sql/set_var.h | 4 ++- sql/sql_show.cc | 6 +--- sql/sys_vars.cc | 10 +++--- 8 files changed, 58 insertions(+), 19 deletions(-) diff --git a/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result index b385aecb47d..7badd1cb5f0 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result +++ b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result @@ -9,3 +9,10 @@ set @@global.skip_name_resolve=0; ERROR HY000: Variable 'skip_name_resolve' is a read only variable set @@debug_dbug='d,set_skip_name_resolve'; Got one of the listed errors +select @@global.version; +@@global.version +12.11.10-fake-version-for-the-test +set @@global.version=0; +ERROR HY000: Variable 'version' is a read only variable +set @@debug_dbug='d,set_version_buf'; +Got one of the listed errors diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt index 590d44a6d12..a5812bb7aa0 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt @@ -1 +1,2 @@ --loose-skip-stack-trace --skip-core-file +--version=12.11.10-fake-version-for-the-test diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test index 2add6ae4f54..39f5dc95189 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -15,3 +15,12 @@ set @@global.skip_name_resolve=0; set @@debug_dbug='d,set_skip_name_resolve'; --enable_reconnect --source include/wait_until_connected_again.inc + +select @@global.version; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@global.version=0; +--write_line restart $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--error 2013,2026 +set @@debug_dbug='d,set_version_buf'; +--enable_reconnect +--source include/wait_until_connected_again.inc diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 76fddf3d560..289613a63e9 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2148,13 +2148,6 @@ static void clean_up(bool print_message) logger.cleanup_end(); sys_var_end(); free_charsets(); - - my_free(const_cast(log_bin_basename)); - my_free(const_cast(log_bin_index)); -#ifndef EMBEDDED_LIBRARY - my_free(const_cast(relay_log_basename)); - my_free(const_cast(relay_log_index)); -#endif free_list(opt_plugin_load_list_ptr); destroy_proxy_protocol_networks(); @@ -6120,6 +6113,7 @@ int mysqld_main(int argc, char **argv) #endif /* WITH_WSREP */ /* Protect read_only_root against writes */ + move_allocated_sysvars_to_root(&read_only_root); protect_root(&read_only_root, MY_VMEM_READONLY); /* Protect read-only sysvars */ @@ -8557,7 +8551,6 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, } break; case OPT_IGNORE_DB_DIRECTORY: - opt_ignore_db_dirs= NULL; // will be set in ignore_db_dirs_process_additions if (*argument == 0) ignore_db_dirs_reset(); else diff --git a/sql/set_var.cc b/sql/set_var.cc index 2f62df1b7c4..aaec395910a 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -197,6 +197,35 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, test_load= &static_test_load; } +/* + move a read-only global char* value to a memroot and reset ALLOCATED flag. + used before mprotect()-ing the memroot from changes. +*/ +void sys_var::move_charptr_to_root(MEM_ROOT *root) +{ + char **ptr= (char**)global_var_ptr(); + if (!is_readonly() || scope() != GLOBAL || cast_pluginvar() || is_struct() + || show_type() != SHOW_CHAR_PTR || !*ptr || var_is_ro_after_init(*ptr)) + return; + + char *old_val= *ptr; + *ptr= strdup_root(root, old_val); + if (flags & ALLOCATED) + { + my_free(old_val); + flags&= ~ALLOCATED; + } +} + + +void move_allocated_sysvars_to_root(MEM_ROOT *root) +{ + for (uint i= 0; i < system_variable_hash.records; i++) + ((sys_var*) my_hash_element(&system_variable_hash, i)) + ->move_charptr_to_root(root); +} + + bool sys_var::update(THD *thd, set_var *var) { enum_var_type type= var->type; @@ -755,6 +784,8 @@ int sql_set_variables(THD *thd, List *var_list, bool free) } DBUG_EXECUTE_IF("set_skip_name_resolve", opt_skip_name_resolve= 0;); + DBUG_EXECUTE_IF("set_version_buf", server_version_ptr[0]= '2';); + err: if (free) free_underlaid_joins(thd, thd->lex->first_select_lex()); diff --git a/sql/set_var.h b/sql/set_var.h index e1e9948e4d6..f7f497f7730 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -1,7 +1,7 @@ #ifndef SET_VAR_INCLUDED #define SET_VAR_INCLUDED /* Copyright (c) 2002, 2013, Oracle and/or its affiliates. - Copyright (c) 2009, 2020, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -113,6 +113,7 @@ public: virtual sys_var_pluginvar *cast_pluginvar() { return 0; } bool check(THD *thd, set_var *var); + void move_charptr_to_root(MEM_ROOT *root); const uchar *value_ptr(THD *thd, enum_var_type type, const LEX_CSTRING *base) const; /** @@ -446,6 +447,7 @@ ulong get_system_variable_hash_records(void); ulonglong get_system_variable_hash_version(void); SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type); +void move_allocated_sysvars_to_root(MEM_ROOT *root); int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond); sys_var *find_sys_var(THD *thd, const char *str, size_t length= 0, diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 16cdf2fed58..eda8f77d260 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -708,11 +708,6 @@ ignore_db_dirs_reset() void ignore_db_dirs_free() { - if (opt_ignore_db_dirs) - { - my_free(opt_ignore_db_dirs); - opt_ignore_db_dirs= NULL; - } ignore_db_dirs_reset(); delete_dynamic(&ignore_db_dirs_array); my_hash_free(&ignore_db_dirs_hash); @@ -823,6 +818,7 @@ ignore_db_dirs_process_additions() len--; /* +1 the terminating zero */ + my_free(opt_ignore_db_dirs); ptr= opt_ignore_db_dirs= (char *) my_malloc(key_memory_ignored_db, len + 1, MYF(0)); if (!ptr) diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index e40416078e8..db759c37017 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -5497,7 +5497,7 @@ static Sys_var_charptr_fscs Sys_relay_log( static Sys_var_charptr_fscs Sys_relay_log_index( "relay_log_index", "The location and name to use for the file " "that keeps a list of the last relay logs.", - READ_ONLY GLOBAL_VAR(relay_log_index), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(relay_log_index), NO_CMD_LINE, DEFAULT(0)); /* @@ -5507,19 +5507,19 @@ static Sys_var_charptr_fscs Sys_relay_log_index( */ static Sys_var_charptr_fscs Sys_binlog_index( "log_bin_index", "File that holds the names for last binary log files.", - READ_ONLY GLOBAL_VAR(log_bin_index), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(log_bin_index), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_relay_log_basename( "relay_log_basename", "The full path of the relay log file names, excluding the extension.", - READ_ONLY GLOBAL_VAR(relay_log_basename), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(relay_log_basename), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_log_bin_basename( "log_bin_basename", "The full path of the binary log file names, excluding the extension.", - READ_ONLY GLOBAL_VAR(log_bin_basename), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(log_bin_basename), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_relay_log_info_file( @@ -6516,7 +6516,7 @@ static Sys_var_charptr_fscs Sys_ignore_db_dirs( "Specifies a directory to add to the ignore list when collecting " "database names from the datadir. Put a blank argument to reset " "the list accumulated so far.", - READ_ONLY GLOBAL_VAR(opt_ignore_db_dirs), + PREALLOCATED READ_ONLY GLOBAL_VAR(opt_ignore_db_dirs), CMD_LINE(REQUIRED_ARG, OPT_IGNORE_DB_DIRECTORY), DEFAULT(0)); From 97967e78821f9181866497d2253c7bb76de9ba61 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 30 Jul 2026 11:48:44 +1000 Subject: [PATCH 028/150] MDEV-40552 UBSAN: not a valid value for wkbByteOrder Gis_multi_point The byteorder field could contain an invalid value (> 1) on a GIS multi_point object where there byteorder was on any of the inner points. This can occur with any of the GIS "*FromWKB" functions that contained a multipoint object. This result in a invalid object being accepted and also potentially trigger undefined behaviour in the processing of the object. --- mysql-test/main/gis.result | 9 +++++++++ mysql-test/main/gis.test | 7 +++++++ sql/spatial.cc | 2 ++ 3 files changed, 18 insertions(+) diff --git a/mysql-test/main/gis.result b/mysql-test/main/gis.result index 0fe92b2f50c..d60f8da2d5d 100644 --- a/mysql-test/main/gis.result +++ b/mysql-test/main/gis.result @@ -5644,4 +5644,13 @@ NULL SELECT ST_GEOMFROMWKB (0x01050000000100000082040000000100000000000000000000000000000000000000) as g; g NULL +# +# MDEV-40552 UBSAN : load of value X, which is not a valid value for type 'wkbByteOrder' Gis_multi_point::init_from_wkb +# +SELECT ST_GEOMFROMWKB (0x0104000000010000000201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +multipoint_bad_inner_bo +NULL +SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +multipoint_bad_inner_bo +NULL # End of 10.11 tests diff --git a/mysql-test/main/gis.test b/mysql-test/main/gis.test index 609c1509396..1efed8d0769 100644 --- a/mysql-test/main/gis.test +++ b/mysql-test/main/gis.test @@ -3605,4 +3605,11 @@ SELECT ST_GEOMFROMWKB (0x01070000000100000082010000000000000000000000) as g; SELECT ST_GEOMFROMWKB (0x01050000000100000002040000000100000000000000000000000000000000000000) as g; SELECT ST_GEOMFROMWKB (0x01050000000100000082040000000100000000000000000000000000000000000000) as g; +--echo # +--echo # MDEV-40552 UBSAN : load of value X, which is not a valid value for type 'wkbByteOrder' Gis_multi_point::init_from_wkb +--echo # + +SELECT ST_GEOMFROMWKB (0x0104000000010000000201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; + --echo # End of 10.11 tests diff --git a/sql/spatial.cc b/sql/spatial.cc index 2de3c57ef1a..979bac09545 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -2180,6 +2180,8 @@ uint Gis_multi_point::init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, { res->q_append((char)wkb_ndr); res->q_append((uint32)wkb_point); + if ((uchar) wkb[0] > wkb_ndr) /* invalid */ + return 0; if (!p.init_from_wkb(wkb + WKB_HEADER_SIZE, POINT_DATA_SIZE, (wkbByteOrder) wkb[0], res)) return 0; From 4cc88f7e1927817d442ce61c4cb1a9fbd12cc5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Wed, 29 Jul 2026 14:29:35 +0300 Subject: [PATCH 029/150] MDEV-40538 : Galera MDL-conflict logging does not work correctly Problem was that wsrep_log_state allocated buffer for String object, but did not set current length. In wsrep_get_state wsrep transaction state values was appended to this object but it current length was same as maximum length allocated leading to fact that nothing happened. Fixed by setting String object current length to 0 as it does not yet contain anything and fixed actual WSREP_DEBUG format. Removed debug only test case from galera_bf_kill because same test is on galera_bf_kill_debug test case. This allows following (1) galera_bf_kill test case can be run on both release and debug builds with same result file (2) galera_bf_kill_debug is run on debug only because it requires debug sync --- .../suite/galera/r/galera_bf_kill,debug.rdiff | 37 --------- .../suite/galera/r/galera_bf_kill.result | 5 +- mysql-test/suite/galera/t/galera_bf_kill.test | 83 +------------------ sql/wsrep_mysqld.cc | 21 ++--- 4 files changed, 15 insertions(+), 131 deletions(-) delete mode 100644 mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff diff --git a/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff b/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff deleted file mode 100644 index 098ce2b28a1..00000000000 --- a/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff +++ /dev/null @@ -1,37 +0,0 @@ ---- r/galera_bf_kill.result -+++ r/galera_bf_kill,debug.reject -@@ -77,4 +77,34 @@ a b - 5 2 - disconnect node_2a; - connection node_1; -+connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2; -+connection node_2a; -+truncate t1; -+insert into t1 values (7,0); -+connection node_2; -+set wsrep_sync_wait=0; -+begin; -+update t1 set b=2 where a=7; -+connect node_2b, 127.0.0.1, root, , test, $NODE_MYPORT_2; -+set wsrep_sync_wait=0; -+SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb"; -+connection node_1; -+update t1 set b=1 where a=7; -+connection node_2b; -+SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; -+connection node_2; -+connection node_2b; -+SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; -+connection node_2; -+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -+commit; -+select * from t1; -+a b -+7 1 -+connection node_2a; -+SET DEBUG_SYNC= 'RESET'; -+SET GLOBAL debug_dbug = ""; -+disconnect node_2a; -+disconnect node_2b; -+connection node_1; - drop table t1; diff --git a/mysql-test/suite/galera/r/galera_bf_kill.result b/mysql-test/suite/galera/r/galera_bf_kill.result index e6ddf3fc3c5..537a6d48835 100644 --- a/mysql-test/suite/galera/r/galera_bf_kill.result +++ b/mysql-test/suite/galera/r/galera_bf_kill.result @@ -61,9 +61,6 @@ ALTER TABLE t1 ADD UNIQUE KEY b3(b); connection node_2b; SET SESSION wsrep_sync_wait=0; connection node_2a; -select * from t1; -a b -5 2 commit; connection node_2; disconnect node_2a; @@ -77,6 +74,6 @@ connection node_2; select * from t1; a b 5 2 -disconnect node_2a; connection node_1; +disconnect node_2a; drop table t1; diff --git a/mysql-test/suite/galera/t/galera_bf_kill.test b/mysql-test/suite/galera/t/galera_bf_kill.test index b59eef11cd3..5cf98e9fb75 100644 --- a/mysql-test/suite/galera/t/galera_bf_kill.test +++ b/mysql-test/suite/galera/t/galera_bf_kill.test @@ -97,8 +97,8 @@ select * from t1; # # Test case 5: Start a transaction on node_2a with wsrep disabled. -# A conflicting DDL on other transaction can't BF abort -# transaction from node_2a (wsrep disabled). +# A conflicting DDL on other transaction can BF abort +# transaction from node_2a. # --connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 @@ -117,10 +117,6 @@ SET SESSION wsrep_sync_wait=0; --source include/wait_condition.inc --connection node_2a -select * from t1; - -# We expect that ALTER should not be able to BF abort -# this transaction, it must wait for it to finish. # Expect commit to succeed. commit; @@ -153,80 +149,7 @@ update t1 set a =5, b=2; select * from t1; +--connection node_1 --disconnect node_2a ---connection node_1 - -source include/maybe_debug.inc; -if ($have_debug) { -# -# Test case 7: Start a transaction on node_2 and use KILL to abort -# a query in connection node_2a -# During the KILL execution replicate conflicting transaction from node_1 -# to BF abort the transaction executing the KILL -# - ---connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 ---connection node_2a -truncate t1; -insert into t1 values (7,0); - ---connection node_2 -set wsrep_sync_wait=0; - -# get the ID of connection to be later killed ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1 ---source include/wait_condition.inc ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# start a transaction -begin; -update t1 set b=2 where a=7; - -# set sync point for incoming applying ---connect node_2b, 127.0.0.1, root, , test, $NODE_MYPORT_2 -set wsrep_sync_wait=0; - -SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb"; - -# replicate conflicting transaction, should stopp in the sync point ---connection node_1 -update t1 set b=1 where a=7; - -# wait for the applier to reach the sync point ---connection node_2b -SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; - -# issue KILL inside the transacion, implicit commit is expected ---connection node_2 ---disable_query_log ---send_eval KILL QUERY $k_thread ---enable_query_log - -# wait for the KILL processing to be seen in processlist ---connection node_2b ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND INFO LIKE 'KILL QUERY%' ---source include/wait_condition.inc - -# resume applying, BF abort should follow -SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; - ---connection node_2 ---error ER_LOCK_DEADLOCK ---reap - -commit; - -select * from t1; - ---connection node_2a -SET DEBUG_SYNC= 'RESET'; -SET GLOBAL debug_dbug = ""; - ---disconnect node_2a ---disconnect node_2b - ---connection node_1 -} - drop table t1; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 95b8031c2dc..bd25aef8696 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -3385,10 +3385,11 @@ static void wsrep_mdl_log(wsrep_mdl_log_t level, */ static void wsrep_log_state(const char *msg, const THD *thd, bool granted) { - char buff[2048]; + char buff[2048]={'\0'}; String buffer(buff, sizeof(buff), system_charset_info); + buffer.length(0); wsrep_get_state(thd, granted, &buffer); - WSREP_DEBUG(msg, buffer.c_ptr()); + WSREP_DEBUG("%s %s", msg, buffer.c_ptr()); } /** This function handles MDL-conflict when thread holding MDL-lock @@ -3417,8 +3418,8 @@ static void wsrep_handle_granted_bf( if (wsrep_debug) { - wsrep_log_state("wsrep_handle_granted_bf() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_granted_bf() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_granted_bf(): ", request_thd, false); + wsrep_log_state("wsrep_handle_granted_bf(): ", granted_thd, true); } if (wsrep_thd_is_aborting(granted_thd)) @@ -3469,8 +3470,8 @@ static void wsrep_handle_locked(THD* request_thd, if (wsrep_debug) { - wsrep_log_state("wsrep_handle_locked() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_locked() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_locked(): ", request_thd, false); + wsrep_log_state("wsrep_handle_locked(): ", granted_thd, true); } if (granted_thd->current_backup_stage != BACKUP_FINISHED && @@ -3509,8 +3510,8 @@ static void wsrep_abort_granted(THD* request_thd, if (wsrep_debug) { - wsrep_log_state("wsrep_abort_granted() : (%s)", request_thd, false); - wsrep_log_state("wsrep_abort_granted() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_abort_granted(): ", request_thd, false); + wsrep_log_state("wsrep_abort_granted(): ", granted_thd, true); } wsrep_mdl_log(WSREP_MDL_DEBUG, "MDL conflict-> BF abort", @@ -3583,8 +3584,8 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx, if (wsrep_debug) { - wsrep_log_state("wsrep_handle_mdl_conflict() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_mdl_conflict() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_mdl_conflict(): ", request_thd, false); + wsrep_log_state("wsrep_handle_mdl_conflict(): ", granted_thd, true); } if (granted_thd->wsrep_aborter != 0) From 1bbe91facc0517a08e5af179e35d8eeaf532389e Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Mon, 13 Jul 2026 15:32:58 +1200 Subject: [PATCH 030/150] MDEV-36610 Subquery wrongly eliminated by table elimination When equality propagation (build_equal_items()) merges an equality that contains a subquery, such as "t1.a = (SELECT ...)", with an outer join's ON equality, it can inject a reference to that subquery into the ON expression. If the join columns have compatible types the subquery ends up as the constant of a multiple equality (which Item::walk() skips); if they differ (e.g. BIGINT vs INT) the field cannot be merged and the subquery is substituted in as a plain "tbl.col = (SELECT ...)" argument. In the latter case, if that outer join is removed by table elimination, mark_as_eliminated() walks the ON expression and flags the shared Item_subselect as eliminated. The subquery, however, still lives in another part of the query and has to be executed, tripping DBUG_ASSERT(!eliminated) in Item_subselect::exec() (and, in release builds, disabling the subquery cache and hiding it from EXPLAIN). The surviving reference can be: - a WHERE/HAVING/select-list/ORDER/GROUP expression (subquery written there and pushed down into the eliminated ON), or - the ON expression of an outer join that was not eliminated (subquery written in a surviving outer ON and pushed down into an eliminated inner one). Fix: after table elimination, walk the expressions that survive into execution (WHERE, HAVING, select list, ORDER/GROUP BY and the ON expressions of outer joins that were not eliminated) and clear the "eliminated" flag on any subquery still reachable from them. Because a subquery can also be the constant of a multiple equality, and Item::walk() does not visit an Item_equal's constant, Item_equal gets an unmark_as_eliminated_processor() override that descends into its constant explicitly. Assisted by Claude Opus --- mysql-test/main/table_elim.result | 76 +++++++++++++++++++++++++++++++ mysql-test/main/table_elim.test | 57 +++++++++++++++++++++++ sql/item.h | 1 + sql/item_cmpfunc.cc | 15 ++++++ sql/item_cmpfunc.h | 1 + sql/item_subselect.cc | 29 ++++++++++++ sql/item_subselect.h | 1 + sql/opt_table_elimination.cc | 70 ++++++++++++++++++++++++++++ 8 files changed, 250 insertions(+) diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index 4aac1c4fa32..5d431be599c 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1086,5 +1086,81 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 2 DROP TABLE t1, t2; # +# MDEV-36610: Subquery wrongly marked as eliminated by table elimination +# when equality propagation shares a WHERE-clause subquery +# into an eliminated outer join's ON expression +# +CREATE TABLE t0 (k INT); +INSERT INTO t0 VALUES (20),(9); +CREATE TABLE t1 (a BIGINT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT PRIMARY KEY); +INSERT INTO t2 VALUES (3),(4); +INSERT INTO t2 VALUES (5),(6); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (2); +CREATE TABLE t4 (a BIGINT, x INT); +INSERT INTO t4 VALUES (2,20),(3,30); +# t2 is eliminated, but the subquery must still be shown and executed: +explain SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) +WHERE t1.a = (SELECT c FROM t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) +WHERE t1.a = (SELECT c FROM t3); +a +2 +# t2 is eliminated; the subquery must still be shown and executed: +explain SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 +1 PRIMARY t4 ALL NULL NULL NULL NULL 2 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +k x +20 20 +9 NULL +truncate t2; +DROP TABLE t4; +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2),(7); +# The subquery is shared with the WHERE clause, so it must still be +# shown and executed: +explain SELECT t1.* FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a) +LEFT JOIN t4 ON (t4.d = t1.a) +WHERE t1.a = (SELECT c FROM t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 system PRIMARY NULL NULL NULL 0 Const row not found +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +1 PRIMARY t4 ALL NULL NULL NULL NULL 3 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.* FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a) +LEFT JOIN t4 ON (t4.d = t1.a) +WHERE t1.a = (SELECT c FROM t3); +a +2 +explain SELECT t1.a FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) +LEFT JOIN t4 ON (t4.d = t1.a); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 system PRIMARY NULL NULL NULL 0 Const row not found +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 +1 PRIMARY t4 ALL NULL NULL NULL NULL 3 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.a FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) +LEFT JOIN t4 ON (t4.d = t1.a); +a +1 +2 +DROP TABLE t0, t1, t2, t3, t4; +# # End of 10.11 tests # diff --git a/mysql-test/main/table_elim.test b/mysql-test/main/table_elim.test index 376a7389348..6d61432fbc8 100644 --- a/mysql-test/main/table_elim.test +++ b/mysql-test/main/table_elim.test @@ -837,6 +837,63 @@ select t1.null_col from t1 left join t2 on (t2.unique_col<=>t1.notnull_col); DROP TABLE t1, t2; +--echo # +--echo # MDEV-36610: Subquery wrongly marked as eliminated by table elimination +--echo # when equality propagation shares a WHERE-clause subquery +--echo # into an eliminated outer join's ON expression +--echo # +CREATE TABLE t0 (k INT); +INSERT INTO t0 VALUES (20),(9); +CREATE TABLE t1 (a BIGINT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT PRIMARY KEY); +INSERT INTO t2 VALUES (3),(4); +INSERT INTO t2 VALUES (5),(6); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (2); +CREATE TABLE t4 (a BIGINT, x INT); +INSERT INTO t4 VALUES (2,20),(3,30); + +--echo # t2 is eliminated, but the subquery must still be shown and executed: + +let $q= +SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) + WHERE t1.a = (SELECT c FROM t3); +eval explain $q; +eval $q; + +--echo # t2 is eliminated; the subquery must still be shown and executed: +let $q= +SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +eval explain $q; +eval $q; + +truncate t2; +DROP TABLE t4; +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2),(7); +--echo # The subquery is shared with the WHERE clause, so it must still be +--echo # shown and executed: +let $q= +SELECT t1.* FROM t1 + LEFT JOIN t2 ON (t2.b = t1.a) + LEFT JOIN t4 ON (t4.d = t1.a) + WHERE t1.a = (SELECT c FROM t3); +eval explain $q; +eval $q; + +let $q= +SELECT t1.a FROM t1 + LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) + LEFT JOIN t4 ON (t4.d = t1.a); +eval explain $q; +eval $q; + + +DROP TABLE t0, t1, t2, t3, t4; + --echo # --echo # End of 10.11 tests --echo # diff --git a/sql/item.h b/sql/item.h index bc676a760c7..e8ea7d205ae 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2300,6 +2300,7 @@ public: virtual bool enumerate_field_refs_processor(void *arg) { return 0; } virtual bool mark_as_eliminated_processor(void *arg) { return 0; } + virtual bool unmark_as_eliminated_processor(void *arg) { return 0; } virtual bool eliminate_subselect_processor(void *arg) { return 0; } virtual bool view_used_tables_processor(void *arg) { return 0; } virtual bool eval_not_null_tables(void *arg) { return 0; } diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 0055da2f7f8..cfae1bae1da 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -7459,6 +7459,21 @@ bool Item_equal::walk(Item_processor processor, bool walk_subquery, void *arg) } +/** + @brief + Subqueries that equality propagation converted to constants must not + be marked as eliminated, so unmark them. +*/ + +bool Item_equal::unmark_as_eliminated_processor(void *arg) +{ + Item *c= get_const(); + if (c) + c->walk(&Item::unmark_as_eliminated_processor, FALSE, arg); + return FALSE; +} + + Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 66db4361d72..a75b1eef510 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -3519,6 +3519,7 @@ public: SARGABLE_PARAM **sargables) override; SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) override; bool walk(Item_processor processor, bool walk_subquery, void *arg) override; + bool unmark_as_eliminated_processor(void *arg) override; Item *transform(THD *thd, Item_transformer transformer, uchar *arg) override; void print(String *str, enum_query_type query_type) override; const Type_handler *compare_type_handler() const { return m_compare_handler; } diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 213a3d22fa7..0d4c2e5361a 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -374,12 +374,41 @@ bool Item_subselect::enumerate_field_refs_processor(void *arg) return FALSE; } + +/** + @brief + Set the "eliminated" flag set by mark_as_eliminated_processor(). + + @details + Table elimination marks every subquery reachable from an eliminated outer + join's ON expression as eliminated. +*/ + bool Item_subselect::mark_as_eliminated_processor(void *arg) { eliminated= TRUE; return FALSE; } +/** + @brief + Clear the "eliminated" flag set by mark_as_eliminated_processor(). + + @details + Equality propagation (build_equal_items()) can inject a reference to a + subquery that lives in another part of the query (e.g. the WHERE clause) + into that ON expression. Such a subquery still has to be executed, + so after elimination we walk the surviving expressions and clear the flag + on any subquery still referenced from them. +*/ + + +bool Item_subselect::unmark_as_eliminated_processor(void *arg) +{ + eliminated= FALSE; + return FALSE; +} + /** Remove a subselect item from its unit so that the unit no longer diff --git a/sql/item_subselect.h b/sql/item_subselect.h index b3b5e0ff3cc..ef2fa8b1fe1 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -242,6 +242,7 @@ public: bool walk(Item_processor processor, bool walk_subquery, void *arg) override; bool unknown_splocal_processor(void *arg) override; bool mark_as_eliminated_processor(void *arg) override; + bool unmark_as_eliminated_processor(void *arg) override; bool eliminate_subselect_processor(void *arg) override; bool enumerate_field_refs_processor(void *arg) override; bool check_vcol_func_processor(void *arg) override diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index c93e5421c6f..53e8bdf328a 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -629,6 +629,75 @@ void add_module_expr(Dep_analysis_context *dac, Dep_module_expr **eq_mod, /*****************************************************************************/ +/** + @brief + Clear the "eliminated" flag on every subquery reachable from the given + expression (see Item_subselect::unmark_as_eliminated_processor). +*/ + +static inline void unmark_eliminated_subqueries(Item *expr) +{ + if (expr) + expr->walk(&Item::unmark_as_eliminated_processor, FALSE, NULL); +} + + +/** + @brief + Clear the "eliminated" flag on subqueries reachable from ON expressions of + outer joins that were NOT eliminated. Such an ON expression is still + evaluated at runtime. +*/ + +static void unmark_live_on_expr_subqueries(JOIN *join, + List *join_list) +{ + TABLE_LIST *tbl; + List_iterator it(*join_list); + while ((tbl= it++)) + { + if (tbl->nested_join) + { + unmark_live_on_expr_subqueries(join, &tbl->nested_join->join_list); + /* Only sweep this nest's ON expr if some of its tables survived. */ + if (tbl->nested_join->used_tables & ~join->eliminated_tables) + unmark_eliminated_subqueries(tbl->on_expr); + } + else if (tbl->table && !(tbl->table->map & join->eliminated_tables)) + unmark_eliminated_subqueries(tbl->on_expr); + } +} + + +/** + @brief + Equality propagation (build_equal_items()) can inject into the ON + expression of an eliminated outer join, a reference to a subquery that + actually lives in another (surviving) part of the query. + mark_as_eliminated() then flags that subquery as eliminated even though + it still has to be executed. + Clear the flag on every subquery that is still reachable. +*/ + +static void unmark_still_reachable(JOIN *join) +{ + unmark_eliminated_subqueries(join->conds); + unmark_eliminated_subqueries(join->having); + + List_iterator live_it(join->fields_list); + Item *item; + while ((item= live_it++)) + unmark_eliminated_subqueries(item); + + for (ORDER *ord= join->order; ord; ord= ord->next) + unmark_eliminated_subqueries(*(ord->item)); + + for (ORDER *ord= join->group_list; ord; ord= ord->next) + unmark_eliminated_subqueries(*(ord->item)); + + unmark_live_on_expr_subqueries(join, join->join_list); +} + /* Perform table elimination @@ -768,6 +837,7 @@ void eliminate_tables(JOIN *join) /* There are some tables that we probably could eliminate. Try it. */ eliminate_tables_for_list(join, join->join_list, all_tables, NULL, used_tables, &trace_eliminated_tables); + unmark_still_reachable(join); } DBUG_VOID_RETURN; } From e4bdf5596c9b2835a600730627478cd66360d6db Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 3 Jun 2026 13:11:35 +1000 Subject: [PATCH 031/150] MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth Geometry::create_from_json used strings as things that reinitialize the je (json_engine). By reinitializing with json_start the killed pointer gets reset, and also the concept of depth is reset. There are two states in create_from_json: 1. The search if for the type, once found ci is set 2. Search for the argument, arg becomes != T_NONE If the type is found first, ci become the class, and once the arg is found, continued execution on the same je occurs. If the argument comes before the "type", we've save a copy of the json_engine_t to process this argument. In both cases je after the execution is the most progressed pointer. Debug assertions on je state aren't enforced by code, if some invalid GeoJSON is passed, these are actual errors. Because Geometry::create_from_json is recursive, from geometrymcollections, a json_read_value is pushed from inside this function up to Item_func_geometry_from_json::val_str. Other Gis*::init_from_json start from their actual object and don't need a json_read_value to start. Test case an bug report thanks to byteoverride. --- mysql-test/main/gis-json.result | 80 +++++++++++- mysql-test/main/gis-json.test | 77 ++++++++++++ sql/item_geofunc.cc | 3 +- sql/spatial.cc | 215 ++++++++++++++++++++------------ 4 files changed, 291 insertions(+), 84 deletions(-) diff --git a/mysql-test/main/gis-json.result b/mysql-test/main/gis-json.result index 6e55b6d88e2..e636291b5db 100644 --- a/mysql-test/main/gis-json.result +++ b/mysql-test/main/gis-json.result @@ -60,7 +60,9 @@ exp POINT(102 0.5) SELECT st_astext(st_geomfromgeojson('{ "geometry": { "type": "Point", "coordinates": [102.0, 0.5] }, "type": "Feature" }')) as exp; exp -POINT(102 0.5) +NULL +Warnings: +Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. SELECT st_astext(st_geomfromgeojson('{ "type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [102.0, 0.5] }, "properties": { "prop0": "value0" } }]}')) as exp; exp GEOMETRYCOLLECTION(POINT(102 0.5)) @@ -110,6 +112,8 @@ Warning 4076 Incorrect GeoJSON format - empty 'coordinates' array. SELECT ST_GEOMFROMGEOJSON("{ \"type\": \"Feature\", \"geometry\": [10, 20] }") as exp; exp NULL +Warnings: +Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. SELECT ST_ASTEXT (ST_GEOMFROMGEOJSON ('{ "type": "GEOMETRYCOLLECTION", "coordinates": [102.0, 0.0]}')) as exp; exp NULL @@ -135,5 +139,77 @@ repeat(']}', 2000) exp NULL Warnings: -Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 473 +Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 688 +# Boundary condition: foreign-member nesting at depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; +SET @doc := CONCAT( +REPEAT('{"x":', 29), +@geom, +REPEAT('}', 29) +); +SELECT ST_ASTEXT( ST_GeomFromGeoJSON(@doc) ) AS expr; +expr +NULL +Warnings: +Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. +# Foreign-member nesting exceeds depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; +SET @doc := CONCAT( +REPEAT('{"x":', 30), +@geom, +REPEAT('}', 30) +); +SELECT ST_GeomFromGeoJSON(@doc) AS expr; +expr +NULL +Warnings: +Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 181 +# Deep foreign member attached to otherwise valid GeoJSON +SET @foreign := CONCAT( +REPEAT('{"x":', 28), +'{"leaf":1}', +REPEAT('}', 28) +); +SET @doc := CONCAT( +'{"type":"Point","coordinates":[10,20],"foreign":', +@foreign, +'}' +); +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +POINT(10 20) +# Foreign member exceeds depth limit +SET @foreign := CONCAT( +REPEAT('{"x":', 29), +'{"leaf":1}', +REPEAT('}', 29) +); +SET @doc := CONCAT( +'{"type":"Point","coordinates":[10,20],"foreign":', +@foreign, +'}' +); +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +POINT(10 20) +# GeometryCollection containing 33 valid GeoJSON Point objects +SET @point := '{"type":"Point","coordinates":[1,2]}'; +SET @geoms := CONCAT( +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point +); +SET @doc := CONCAT( +'{"type":"GeometryCollection","geometries":[', +@geoms, +']}' +); +SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +GEOMETRYCOLLECTION(POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2)) # End of 10.6 tests diff --git a/mysql-test/main/gis-json.test b/mysql-test/main/gis-json.test index f635e2c6ec7..33f00ee4144 100644 --- a/mysql-test/main/gis-json.test +++ b/mysql-test/main/gis-json.test @@ -70,4 +70,81 @@ SELECT ST_GeomFromGeoJSON( repeat(']}', 2000) )) as exp; +--echo # Boundary condition: foreign-member nesting at depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; + +SET @doc := CONCAT( + REPEAT('{"x":', 29), + @geom, + REPEAT('}', 29) +); + +SELECT ST_ASTEXT( ST_GeomFromGeoJSON(@doc) ) AS expr; + + +--echo # Foreign-member nesting exceeds depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; + +SET @doc := CONCAT( + REPEAT('{"x":', 30), + @geom, + REPEAT('}', 30) +); + +SELECT ST_GeomFromGeoJSON(@doc) AS expr; + + +--echo # Deep foreign member attached to otherwise valid GeoJSON +SET @foreign := CONCAT( + REPEAT('{"x":', 28), + '{"leaf":1}', + REPEAT('}', 28) +); + +SET @doc := CONCAT( + '{"type":"Point","coordinates":[10,20],"foreign":', + @foreign, + '}' +); + +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; + + +--echo # Foreign member exceeds depth limit +SET @foreign := CONCAT( + REPEAT('{"x":', 29), + '{"leaf":1}', + REPEAT('}', 29) +); + +SET @doc := CONCAT( + '{"type":"Point","coordinates":[10,20],"foreign":', + @foreign, + '}' +); + +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; + +--echo # GeometryCollection containing 33 valid GeoJSON Point objects +SET @point := '{"type":"Point","coordinates":[1,2]}'; + +SET @geoms := CONCAT( + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point +); + +SET @doc := CONCAT( + '{"type":"GeometryCollection","geometries":[', + @geoms, + ']}' +); + +SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; + --echo # End of 10.6 tests diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index cc54d06ec7b..deb2b1018f9 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -163,7 +163,8 @@ String *Item_func_geometry_from_json::val_str(String *str) (const uchar *) js->end()); je.killed_ptr= (uint32_t *) ¤t_thd->killed; - if ((null_value= !Geometry::create_from_json(&buffer, &je, options==1, str))) + if (!json_read_value(&je) && + (null_value= !Geometry::create_from_json(&buffer, &je, options==1, str))) { int code= 0; diff --git a/sql/spatial.cc b/sql/spatial.cc index 79e36bf6215..ee808a00042 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -493,19 +493,14 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, json_engine_t *je, bool er_on_3D, String *res) { Class_info *ci= NULL; - const uchar *coord_start= NULL, *geom_start= NULL, - *features_start= NULL, *geometry_start= NULL; + enum t_enum { T_NONE, T_GEOMETRY, T_GEOMETRIES, T_COORD, T_FEATURE }; + enum t_enum arg= T_NONE; + json_engine_t argje, *je_arg; Geometry *result; uchar key_buf[max_keyname_len]; uint key_len; - int fcoll_type_found= 0, feature_type_found= 0; + bool feature_type_found= false; - const uint32_t *killed_ptr= (uint32_t *) je->killed_ptr; - int stack_p; - - if (json_read_value(je)) - goto err_return; - if (je->value_type != JSON_VALUE_OBJECT) { je->s.error= GEOJ_INCORRECT_GEOJSON; @@ -514,7 +509,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, while (json_scan_next(je) == 0 && je->state != JST_OBJ_END) { - DBUG_ASSERT(je->state == JST_KEY); + if (je->state != JST_KEY) + break; key_len=0; while (json_read_keyname_chr(je) == 0) @@ -543,31 +539,36 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_STRING) { - if ((ci= find_class((const char *) je->value, je->value_len))) + if ((ci= find_class(reinterpret_cast(je->value), je->value_len))) { - if ((coord_start= - (ci == &geometrycollection_class) ? geom_start : coord_start)) + if ((ci == &geometrycollection_class && arg == T_GEOMETRIES) || arg == T_COORD) goto create_geom; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else if (je->value_len == feature_coll_type_len && my_charset_latin1.strnncoll(je->value, je->value_len, - feature_coll_type, feature_coll_type_len) == 0) + feature_coll_type, feature_coll_type_len) == 0) { /* - 'FeatureCollection' type found. Handle the 'Featurecollection'/'features' - GeoJSON construction. + 'FeatureCollection' type found. Handle the 'Featurecollection' + /'features' GeoJSON construction. */ - if (features_start) - goto handle_feature_collection; - fcoll_type_found= 1; + ci= &geometrycollection_class; + if (arg == T_FEATURE) + goto create_geom; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else if (je->value_len == feature_type_len && my_charset_latin1.strnncoll(je->value, je->value_len, - feature_type, feature_type_len) == 0) + feature_type, feature_type_len) == 0) { - if (geometry_start) + if (arg == T_GEOMETRY) goto handle_geometry_key; - feature_type_found= 1; + feature_type_found= true; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else /* can't understand the type. */ break; @@ -578,6 +579,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, else if (key_len == coord_keyname_len && memcmp(key_buf, coord_keyname, coord_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* Found the "coordinates" key. Let's check it's an array and remember where it starts. @@ -587,16 +590,25 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_ARRAY) { - coord_start= je->value_begin; + arg= T_COORD; if (ci && ci != &geometrycollection_class) - goto create_geom; + { + je_arg= je; + goto create_geom; + } + argje= *je; + je_arg= &argje; if (json_skip_level(je)) goto err_return; } + else + break; /* coordinates needs to be an array */ } else if (key_len == geometries_keyname_len && memcmp(key_buf, geometries_keyname, geometries_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* Found the "geometries" key. Let's check it's an array and remember where it starts. @@ -606,17 +618,25 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_ARRAY) { - geom_start= je->value_begin; if (ci == &geometrycollection_class) { - coord_start= geom_start; + je_arg= je; goto create_geom; } + if (ci != nullptr) + break; /* geometries only valid inside geometrycollation */ + arg= T_GEOMETRIES; + argje= *je; + je_arg= &argje; } + else + break; /* geometries needs to be an array */ } else if (key_len == features_keyname_len && memcmp(key_buf, features_keyname, features_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* 'features' key found. Handle the 'Featurecollection'/'features' GeoJSON construction. @@ -625,10 +645,20 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, goto err_return; if (je->value_type == JSON_VALUE_ARRAY) { - features_start= je->value_begin; - if (fcoll_type_found) - goto handle_feature_collection; + if (ci == &geometrycollection_class) + { + je_arg= je; + goto create_geom; + } + if (ci != nullptr) + break; /* features only valid inside featurecollation */ + + arg= T_FEATURE; + argje= *je; + je_arg= &argje; } + else + break; /* feature collections needs to be an array */ } else if (key_len == geometry_keyname_len && memcmp(key_buf, geometry_keyname, geometry_keyname_len) == 0) @@ -637,12 +667,19 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, goto err_return; if (je->value_type == JSON_VALUE_OBJECT) { - geometry_start= je->value_begin; if (feature_type_found) + { + je_arg= je; goto handle_geometry_key; + } + if (ci != nullptr) + break; /* geometry only valid inside feature */ + arg= T_GEOMETRY; + argje= *je; + je_arg= &argje; } else - goto err_return; + break; /* geometry needs to be an object */ } else { @@ -661,35 +698,35 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, } goto err_return; -handle_feature_collection: - ci= &geometrycollection_class; - coord_start= features_start; - create_geom: - stack_p= je->stack_p; - json_scan_start(je, je->s.cs, coord_start, je->s.str_end); - je->killed_ptr= killed_ptr; - je->stack_p= stack_p; - if (res->reserve(1 + 4, 512)) goto err_return; result= (*ci->m_create_func)(buffer->data); res->q_append((char) wkb_ndr); res->q_append((uint32) result->get_class_info()->m_type_id); - if (result->init_from_json(je, er_on_3D, res)) + if (result->init_from_json(je_arg, er_on_3D, res)) + { + if (je_arg != je) /* copy error out of copied engine */ + *je= *je_arg; + goto err_return; + } + /* finish of the object scan for validation/geomcollection */ + if (json_skip_level(je)) goto err_return; return result; -handle_geometry_key: - stack_p= je->stack_p; - json_scan_start(je, je->s.cs, geometry_start, je->s.str_end); - je->killed_ptr= killed_ptr; - je->stack_p= stack_p; - return create_from_json(buffer, je, er_on_3D, res); +handle_geometry_key: /* feature */ + result= create_from_json(buffer, je_arg, er_on_3D, res); + + /* skip rest of feature - can be arbitrary fields */ + if (json_skip_level(je)) + goto err_return; + + return result; err_return: return NULL; } @@ -961,7 +998,6 @@ static int read_point_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); if (json_read_value(je)) return 1; @@ -975,6 +1011,8 @@ static int read_point_from_json(json_engine_t *je, bool er_on_3D, n_coord++; } + if (je->s.error != 0) + return 1; if (n_coord <= 2 || !er_on_3D) return 0; je->s.error= Geometry::GEOJ_DIMENSION_NOT_SUPPORTED; @@ -988,8 +1026,6 @@ bad_coordinates: bool Gis_point::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) { double x, y; - if (json_read_value(je)) - return TRUE; if (je->value_type != JSON_VALUE_ARRAY) { @@ -1003,6 +1039,7 @@ bool Gis_point::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) wkb->q_append(x); wkb->q_append(y); + /* Note GEOJSON RFC7946 3.3.1 - 3D possible, but not WKB? */ return FALSE; } @@ -1279,11 +1316,9 @@ bool Gis_line_string::init_from_json(json_engine_t *je, bool er_on_3D, uint32 np_pos= wkb->length(); Gis_point p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -1294,7 +1329,13 @@ bool Gis_line_string::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (p.init_from_json(je, er_on_3D, wkb)) return TRUE; @@ -1673,11 +1714,9 @@ bool Gis_polygon::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) uint32 lr_pos= wkb->length(); int closed; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -1689,11 +1728,18 @@ bool Gis_polygon::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { Gis_line_string ls; - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; uint32 ls_pos=wkb->length(); if (ls.init_from_json(je, er_on_3D, wkb)) return TRUE; + ls.set_data_ptr(wkb->ptr() + ls_pos, wkb->length() - ls_pos); if (ls.is_closed(&closed) || !closed) { @@ -2189,11 +2235,9 @@ bool Gis_multi_point::init_from_json(json_engine_t *je, bool er_on_3D, uint32 np_pos= wkb->length(); Gis_point p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2204,7 +2248,12 @@ bool Gis_multi_point::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (wkb->reserve(1 + 4, 512)) return TRUE; @@ -2551,11 +2600,9 @@ bool Gis_multi_line_string::init_from_json(json_engine_t *je, bool er_on_3D, uint32 n_line_strings= 0; uint32 ls_pos= wkb->length(); - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2566,9 +2613,14 @@ bool Gis_multi_line_string::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - Gis_line_string ls; - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; + Gis_line_string ls; if (wkb->reserve(1 + 4, 512)) return TRUE; wkb->q_append((char) wkb_ndr); @@ -2953,11 +3005,9 @@ bool Gis_multi_polygon::init_from_json(json_engine_t *je, bool er_on_3D, int np_pos= wkb->length(); Gis_polygon p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2968,7 +3018,12 @@ bool Gis_multi_polygon::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (wkb->reserve(1 + 4, 512)) return TRUE; @@ -3448,11 +3503,9 @@ bool Gis_geometry_collection::init_from_json(json_engine_t *je, bool er_on_3D, Geometry_buffer buffer; Geometry *g; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -3463,17 +3516,17 @@ bool Gis_geometry_collection::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - json_engine_t sav_je= *je; - - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state == JST_VALUE) + { + if (json_read_value(je)) + return TRUE; + } + if (je->state != JST_OBJ_START) + goto err_geoj_incorrect; if (!(g= create_from_json(&buffer, je, er_on_3D, wkb))) return TRUE; - *je= sav_je; - if (json_skip_array_item(je)) - return TRUE; - n_objects++; } From d0eb9e167af4024190d65afc0fafd6f33005af3a Mon Sep 17 00:00:00 2001 From: Akshat Nehra Date: Wed, 29 Jul 2026 12:37:29 +1000 Subject: [PATCH 032/150] MDEV-39981 Fix ST_GEOMFROMGEOJSON returning wrong result with reversed key order ST_GEOMFROMGEOJSON requires JSON keys to appear in a specific order (type before geometries/features/geometry/coordinates). When keys appear in a different order, the JSON scanner is left in an inconsistent state because json_skip_level() is not called to advance past the value when the type has not yet been determined. Fix: add json_skip_level() calls in the geometries, features, and geometry key handlers when the type key has not yet been encountered. This mirrors the existing pattern in the coordinates handler. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/gis-json.result | 51 +++++++++++++++++++++++++++++++-- mysql-test/main/gis-json.test | 37 ++++++++++++++++++++++++ sql/spatial.cc | 8 ++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/mysql-test/main/gis-json.result b/mysql-test/main/gis-json.result index e636291b5db..78432766981 100644 --- a/mysql-test/main/gis-json.result +++ b/mysql-test/main/gis-json.result @@ -60,9 +60,7 @@ exp POINT(102 0.5) SELECT st_astext(st_geomfromgeojson('{ "geometry": { "type": "Point", "coordinates": [102.0, 0.5] }, "type": "Feature" }')) as exp; exp -NULL -Warnings: -Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. +POINT(102 0.5) SELECT st_astext(st_geomfromgeojson('{ "type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [102.0, 0.5] }, "properties": { "prop0": "value0" } }]}')) as exp; exp GEOMETRYCOLLECTION(POINT(102 0.5)) @@ -212,4 +210,51 @@ SET @doc := CONCAT( SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; expr GEOMETRYCOLLECTION(POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2)) +# +# MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +# +# GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(0 1)) +# GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4)) +# Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')) as expr; +expr +POINT(0 1) +# LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')) as expr; +expr +LINESTRING(0 0,1 1,2 2) +# Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')) as expr; +expr +POLYGON((0 0,1 0,1 1,0 1,0 0)) +# FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"geometry":{"type":"Point","coordinates":[5,6]},"type":"Feature","properties":{}}],"type":"FeatureCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(5 6)) +# Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"coordinates":[7,8],"type":"Point"},"type":"Feature","properties":{}}')) as expr; +expr +POINT(7 8) +# Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"geometries":[{"type":"Point","coordinates":[9,10]}],"type":"GeometryCollection"}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(9 10))) # End of 10.6 tests diff --git a/mysql-test/main/gis-json.test b/mysql-test/main/gis-json.test index 33f00ee4144..e1b14c06e5b 100644 --- a/mysql-test/main/gis-json.test +++ b/mysql-test/main/gis-json.test @@ -147,4 +147,41 @@ SET @doc := CONCAT( SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; +--echo # +--echo # MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +--echo # + +--echo # GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')) as expr; + +--echo # GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')) as expr; + +--echo # GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')) as expr; + +--echo # GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')) as expr; + +--echo # Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')) as expr; + +--echo # LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')) as expr; + +--echo # Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')) as expr; + +--echo # FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"geometry":{"type":"Point","coordinates":[5,6]},"type":"Feature","properties":{}}],"type":"FeatureCollection"}')) as expr; + +--echo # Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"coordinates":[7,8],"type":"Point"},"type":"Feature","properties":{}}')) as expr; + +--echo # Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')) as expr; + +--echo # Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"geometries":[{"type":"Point","coordinates":[9,10]}],"type":"GeometryCollection"}],"type":"GeometryCollection"}')) as expr; + --echo # End of 10.6 tests diff --git a/sql/spatial.cc b/sql/spatial.cc index ee808a00042..74c470bb98c 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -628,6 +628,9 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, arg= T_GEOMETRIES; argje= *je; je_arg= &argje; + /* skip geometries for now and search for type */ + if (json_skip_level(je)) + goto err_return; } else break; /* geometries needs to be an array */ @@ -656,6 +659,9 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, arg= T_FEATURE; argje= *je; je_arg= &argje; + /* skip features for now and search for type */ + if (json_skip_level(je)) + goto err_return; } else break; /* feature collections needs to be an array */ @@ -677,6 +683,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, arg= T_GEOMETRY; argje= *je; je_arg= &argje; + if (json_skip_level(je)) + goto err_return; } else break; /* geometry needs to be an object */ From 6b7cfb4d9ed6f287236898950f3124076614ad91 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Fri, 31 Jul 2026 08:13:47 +0200 Subject: [PATCH 033/150] new CC 3.3 --- libmariadb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmariadb b/libmariadb index 85c322f70e4..cc3a686a7eb 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit 85c322f70e477c5a7bd42c8344754d9335e56f58 +Subproject commit cc3a686a7eb966d216bc92e352b818ce60a5809c From c57069561c4fe8f72191d4a2a4a829e5b57537dc Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Wed, 29 Jul 2026 09:28:21 +0200 Subject: [PATCH 034/150] New CC 3.4 --- libmariadb | 2 +- mysql-test/main/ssl_autoverify.result | 2 +- plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libmariadb b/libmariadb index 64e9fe09fea..df953d6b76d 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit 64e9fe09fea6a017bab92e19a08d2301e3da5b56 +Subproject commit df953d6b76d2f68265422cd929889a36405f7716 diff --git a/mysql-test/main/ssl_autoverify.result b/mysql-test/main/ssl_autoverify.result index 8c78a21d6f9..71e27bbf773 100644 --- a/mysql-test/main/ssl_autoverify.result +++ b/mysql-test/main/ssl_autoverify.result @@ -38,7 +38,7 @@ yes test.have_ssl() yes # mysql -unohash -ponetwothree --ssl-verify-server-cert -e "select test.have_ssl()" -ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate +ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure # mysql -umulti -ppw1 --ssl-verify-server-cert -e "select test.have_ssl()" test.have_ssl() yes diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result index 4d45bc89c50..b46d470c349 100644 --- a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result @@ -23,7 +23,7 @@ test2@localhost test2@% 1 # mysql -utest2 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()" ERROR 1045 (28000): Access denied for user 'test2'@'localhost' (using password: YES) # mysql -utest1 -ppwd --ssl-verify-server-cert -e "call test.checkme()" -ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure: The certificate is NOT trusted. +ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure drop procedure checkme; drop user test1@'%'; drop user test2@'%'; From 5e8ab3a4f59109ab283b41474c0df1cac803b30e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 31 Jul 2026 16:11:51 +1000 Subject: [PATCH 035/150] MDEV-37224 Remove UBSAN limitation from MTR tests There's no good reason why undefined behaviour is acceptable in our codebase let alone having a test that triggers this. The thread_stack_basic test because of compulation has a different stack size under UBSAN. With replace_results we can include all values of the default stack size in this test. plugins.multiauth was added in 031f11717d9f before CONC-730 and MDEV-31379 corrected the ref10 implementaiton. --- mysql-test/suite/plugins/t/multiauth.test | 2 -- .../suite/sys_vars/t/thread_stack_basic.test | 14 ++++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/plugins/t/multiauth.test b/mysql-test/suite/plugins/t/multiauth.test index 6ae50b2b3ba..fa902ab3b2c 100644 --- a/mysql-test/suite/plugins/t/multiauth.test +++ b/mysql-test/suite/plugins/t/multiauth.test @@ -1,5 +1,3 @@ ---source include/not_ubsan.inc - let $REGEX_VERSION_ID=/$mysql_get_server_version/VERSION_ID/; let $REGEX_PASSWORD_LAST_CHANGED=/password_last_changed": [0-9]*/password_last_changed": #/; let $REGEX_GLOBAL_PRIV=$REGEX_PASSWORD_LAST_CHANGED $REGEX_VERSION_ID; diff --git a/mysql-test/suite/sys_vars/t/thread_stack_basic.test b/mysql-test/suite/sys_vars/t/thread_stack_basic.test index a1f52576d15..5efc42d0a83 100644 --- a/mysql-test/suite/sys_vars/t/thread_stack_basic.test +++ b/mysql-test/suite/sys_vars/t/thread_stack_basic.test @@ -1,20 +1,18 @@ # # only global # ---source include/not_asan.inc ---source include/not_ubsan.inc ---source include/not_msan.inc ---replace_result 392192 299008 +# sizes per DEFAULT_THREAD_STACK in include/my_pthread.h +--replace_result 11534336 299008 2097152 299008 select @@global.thread_stack; --error ER_INCORRECT_GLOBAL_LOCAL_VAR select @@session.thread_stack; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 show global variables like 'thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 show session variables like 'thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 select * from information_schema.global_variables where variable_name='thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 select * from information_schema.session_variables where variable_name='thread_stack'; # From 1dab253482dd562457c89deb68082fb647572959 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 21 Jul 2026 18:32:41 +1000 Subject: [PATCH 036/150] MDEV-32331: JSON path functions with no charset on path crash server Across a range of JSON functions taking a path argument there are SQL expressions that dont' have a character set. If these expressions don't have a character set fall back to the character set of the argument of the json function that represent the document being operated on. If this doesn't have a character set fall back to my_charset_utf8mb4_bin. This covers the 11.4 JSON_KEY_VALUE function also as it reuses the Json_path_extractor::extract method. Add nonnull and warn_unused_result to the json path functions to facilitate compiler and UBSAN catching of the problem early. As null values of s_p are incompatible with report_path_error, jump directly to a null return which is consistent with the defination of the JSON sql funciton. --- include/json_lib.h | 1 + mysql-test/main/func_json.result | 63 +++++++++++++++++++++++++++ mysql-test/main/func_json.test | 20 +++++++++ sql/item_jsonfunc.cc | 74 +++++++++++++++++++++++--------- 4 files changed, 137 insertions(+), 21 deletions(-) diff --git a/include/json_lib.h b/include/json_lib.h index e12ab10b4d1..d71fcd243be 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -114,6 +114,7 @@ typedef struct st_json_path_t } json_path_t; +__attribute__((nonnull, warn_unused_result)) int json_path_setup(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end); diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index da78b027409..15da9211752 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -2959,4 +2959,67 @@ NULL SELECT JSON_OVERLAPS(JSON_ARRAY_INSERT(NULL,'$[0]',1), '[]') AS x; x NULL +# +# MDEV-32331 JSON path functions with no charset on path crash server +# +SELECT 1 FROM +(SELECT CASE WHEN x * x THEN x END as a +FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 +) dt +WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +x +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +x +NULL +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); +1 # End of 10.11 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index c9a65929a16..58b806c78bd 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -2130,4 +2130,24 @@ SELECT JSON_ARRAY_INSERT (0,NULL,1) as j; SELECT JSON_EQUALS('[]', JSON_ARRAY_INSERT(NULL,'$[0]',1)) AS x; SELECT JSON_OVERLAPS(JSON_ARRAY_INSERT(NULL,'$[0]',1), '[]') AS x; +--echo # +--echo # MDEV-32331 JSON path functions with no charset on path crash server +--echo # + +SELECT 1 FROM + (SELECT CASE WHEN x * x THEN x END as a + FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 + ) dt + WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; + +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); + --echo # End of 10.11 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index cfe244dcd8e..a976effbd91 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -572,6 +572,7 @@ void report_path_error_ex(const char *ps, json_path_t *p, Checks if the path has '.*' '[*]' or '**' constructions and sets the NO_WILDCARD_ALLOWED error if the case. */ +__attribute__((nonnull, warn_unused_result)) static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end) { @@ -586,6 +587,13 @@ static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, return 1; } +static inline +CHARSET_INFO *def_path_charset(CHARSET_INFO *cs, CHARSET_INFO *alt) +{ + if (cs) return cs; + if (alt) return alt; + return &my_charset_utf8mb4_bin; +} bool Item_func_json_valid::val_bool() { @@ -772,18 +780,20 @@ bool Json_path_extractor::extract(String *str, Item *item_js, Item *item_jp, { String *s_p= item_jp->val_str(&tmp_path); + if (!s_p) + return true; if (allow_wildcard) { - if (s_p && + if (!s_p->charset() || json_path_setup(&p, s_p->charset(), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) error= true; } else { - if (s_p && - path_setup_nwc(&p, s_p->charset(), (const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length())) + if (path_setup_nwc(&p, def_path_charset(s_p->charset(), cs), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) error= true; } @@ -1603,8 +1613,11 @@ bool Item_func_json_contains::val_bool() if (!path.parsed) { String *s_p= args[2]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->end())) { report_path_error(s_p, &path.p, 2); @@ -2164,8 +2177,11 @@ String *Item_func_json_array_append::val_str(String *str) if (!c_path->parsed) { String *s_p= args[n_arg]->val_str(tmp_paths+n_path); - if (s_p && - path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &c_path->p, n_arg); @@ -2301,15 +2317,19 @@ String *Item_func_json_array_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (!s_p) goto return_null; + if (!s_p->charset()) + goto path_err; - if (path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length()) || + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length()) || c_path->p.last_step - 1 < c_path->p.steps || c_path->p.last_step->type != JSON_PATH_ARRAY) { if (c_path->p.s.error == 0) c_path->p.s.error= SHOULD_END_WITH_ARRAY; - +path_err: report_path_error(s_p, &c_path->p, n_arg); goto return_null; @@ -3165,8 +3185,11 @@ longlong Item_func_json_length::val_int() if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &path.p, 1); @@ -3410,7 +3433,8 @@ String *Item_func_json_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3421,6 +3445,8 @@ String *Item_func_json_insert::val_str(String *str) /* We search to the last step. */ c_path->p.last_step--; } + else + goto return_null; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3675,7 +3701,8 @@ String *Item_func_json_remove::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3692,6 +3719,8 @@ String *Item_func_json_remove::val_str(String *str) goto null_return; } } + else + goto null_return; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3905,13 +3934,16 @@ String *Item_func_json_keys::val_str(String *str) if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) - { - report_path_error(s_p, &path.p, 1); - goto null_return; - } + { + report_path_error(s_p, &path.p, 1); + goto null_return; + } path.parsed= path.constant; } From 51263b851df7093758e98c7029f091332c1c576f Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 13 Jul 2026 14:16:19 +1000 Subject: [PATCH 037/150] MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth Using stack_p wasn't a portable concept in 12.3 when JSON parsing got unlimited depth. To let ST_GeomFromGeoJSON was already a recursive function, needed because object order of "type" may be after the "geometries", but with json_engine_t no longer enforcing the depth, some stack checking was required. Use the check_stack_depth function to allow excessively deep GeoJSON objects to error. As this is cleaned up the gis-json test can be enabled. The exceeding stack depth is moved to lotofstack.test. --- mysql-test/main/disabled.def | 1 - mysql-test/main/gis-json.result | 13 ------------- mysql-test/main/gis-json.test | 10 ---------- mysql-test/main/lotofstack.result | 11 +++++++++++ mysql-test/main/lotofstack.test | 18 ++++++++++++++++++ sql/spatial.cc | 8 +++----- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/mysql-test/main/disabled.def b/mysql-test/main/disabled.def index ff0f3ab1781..a38906361c5 100644 --- a/mysql-test/main/disabled.def +++ b/mysql-test/main/disabled.def @@ -16,4 +16,3 @@ mysql_embedded : Bug#12561297 2011-05-14 Anitha Dependent on PB2 chang file_contents : MDEV-6526 these files are not installed anymore max_statement_time : cannot possibly work, depends on timing partition_open_files_limit : open_files_limit check broken by MDEV-18360 -gis-json : MDEV-39813 infinite recursion diff --git a/mysql-test/main/gis-json.result b/mysql-test/main/gis-json.result index 49ec7739458..9cb7cb774a2 100644 --- a/mysql-test/main/gis-json.result +++ b/mysql-test/main/gis-json.result @@ -123,19 +123,6 @@ Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. # # End of 10.2 tests # -# -# MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth -# -SELECT ST_GeomFromGeoJSON( -concat( -repeat('{"type":"GeometryCollection","geometries":[', 2000), -'{"type":"Point","coordinates":[0,0]}', -repeat(']}', 2000) -)) as exp; -exp -NULL -Warnings: -Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 473 # End of 10.6 tests # # MDEV-34079: ST_AsGeoJSON returns incorrect value for empty geometry diff --git a/mysql-test/main/gis-json.test b/mysql-test/main/gis-json.test index ca402d1c2ef..022672ab9b6 100644 --- a/mysql-test/main/gis-json.test +++ b/mysql-test/main/gis-json.test @@ -59,16 +59,6 @@ SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type": ["POINT"], "coINates": [0,0] }')) --echo # End of 10.2 tests --echo # ---echo # ---echo # MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth ---echo # - -SELECT ST_GeomFromGeoJSON( - concat( - repeat('{"type":"GeometryCollection","geometries":[', 2000), - '{"type":"Point","coordinates":[0,0]}', - repeat(']}', 2000) - )) as exp; --echo # End of 10.6 tests diff --git a/mysql-test/main/lotofstack.result b/mysql-test/main/lotofstack.result index 6f776eb6733..1e17487cde8 100644 --- a/mysql-test/main/lotofstack.result +++ b/mysql-test/main/lotofstack.result @@ -104,3 +104,14 @@ drop table t3| # SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',REPEAT(')',5000))); # End of 10.6 tests +# +# MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth +# +set @len = 3000; +SELECT ST_GeomFromGeoJSON( +concat( +repeat('{"type":"GeometryCollection", "geometries":[', @len), +'{"type":"Point","coordinates":[0,0]}', +repeat(']}', @len) +)) as exp; +# End of 12.3 tests diff --git a/mysql-test/main/lotofstack.test b/mysql-test/main/lotofstack.test index 20d9789b99c..2c5d082168a 100644 --- a/mysql-test/main/lotofstack.test +++ b/mysql-test/main/lotofstack.test @@ -148,3 +148,21 @@ SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',RE --enable_result_log --echo # End of 10.6 tests + + +--echo # +--echo # MDEV-39813 ST_GeomFromGeoJSON does not control recursion depth +--echo # + +set @len = 3000; +--disable_result_log +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ST_GeomFromGeoJSON( + concat( + repeat('{"type":"GeometryCollection", "geometries":[', @len), + '{"type":"Point","coordinates":[0,0]}', + repeat(']}', @len) + )) as exp; +--enable_result_log + +--echo # End of 12.3 tests diff --git a/sql/spatial.cc b/sql/spatial.cc index 15e43cbbe5e..d30cd58fbf2 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -587,8 +587,10 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, uint key_len; int fcoll_type_found= 0, feature_type_found= 0; + if (check_stack_overrun(current_thd, STACK_MIN_SIZE , NULL)) + return NULL; + const uint32_t *killed_ptr= (uint32_t *) je->killed_ptr; - int stack_p; if (json_read_value(je)) goto err_return; @@ -754,10 +756,8 @@ handle_feature_collection: create_geom: - stack_p= je->stack_p; json_scan_start(je, je->s.cs, coord_start, je->s.str_end); je->killed_ptr= killed_ptr; - je->stack_p= stack_p; if (res->reserve(1 + 4, 512)) goto err_return; @@ -771,10 +771,8 @@ create_geom: return result; handle_geometry_key: - stack_p= je->stack_p; json_scan_start(je, je->s.cs, geometry_start, je->s.str_end); je->killed_ptr= killed_ptr; - je->stack_p= stack_p; return create_from_json(buffer, je, er_on_3D, res); err_return: From 4f66ca1b69daf1b7e6a562ca8e86d77a6a70abfe Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Thu, 9 Jul 2026 17:45:24 +0200 Subject: [PATCH 038/150] MDEV-40323 CONNECT engine - add file access checks for JSON UDFs Check FILE_ACL and secure_file_priv for the current user inside CONNECT file UDFs (json_file, jfile_make, jbin_file, bson_file, bfile_make, bbin_file). Fix NULL filename: guard GetFileLength in jbin_file_init and check for NULL fn in UDF bodies before the access check, so NULL arguments report "Missing file name" instead of "Access denied". --- storage/connect/CMakeLists.txt | 4 +- storage/connect/bsonudf.cpp | 26 ++- storage/connect/filechk.cpp | 41 +++++ storage/connect/filechk.h | 20 +++ storage/connect/jsonudf.cpp | 35 +++- .../connect/r/json_bson_file_priv.result | 156 ++++++++++++++++++ .../connect/t/json_bson_file_priv.opt | 1 + .../connect/t/json_bson_file_priv.test | 100 +++++++++++ 8 files changed, 376 insertions(+), 7 deletions(-) create mode 100644 storage/connect/filechk.cpp create mode 100644 storage/connect/filechk.h create mode 100644 storage/connect/mysql-test/connect/r/json_bson_file_priv.result create mode 100644 storage/connect/mysql-test/connect/t/json_bson_file_priv.opt create mode 100644 storage/connect/mysql-test/connect/t/json_bson_file_priv.test diff --git a/storage/connect/CMakeLists.txt b/storage/connect/CMakeLists.txt index d4a0c80a525..330b61a6458 100644 --- a/storage/connect/CMakeLists.txt +++ b/storage/connect/CMakeLists.txt @@ -29,14 +29,14 @@ SET(CONNECT_SOURCES ha_connect.cc connect.cc user_connect.cc mycat.cc fmdlex.c osutil.c rcmsg.c rcmsg.h array.cpp blkfil.cpp colblk.cpp csort.cpp -filamap.cpp filamdbf.cpp filamfix.cpp filamgz.cpp filamtxt.cpp filter.cpp +filamap.cpp filamdbf.cpp filamfix.cpp filamgz.cpp filamtxt.cpp filechk.cpp filter.cpp json.cpp jsonudf.cpp maputil.cpp myconn.cpp myutil.cpp plgdbutl.cpp plugutil.cpp reldef.cpp tabcol.cpp tabdos.cpp tabext.cpp tabfix.cpp tabfmt.cpp tabjson.cpp table.cpp tabmul.cpp tabmysql.cpp taboccur.cpp tabpivot.cpp tabsys.cpp tabtbl.cpp tabutil.cpp tabvir.cpp tabxcl.cpp valblk.cpp value.cpp xindex.cpp xobject.cpp array.h blkfil.h block.h catalog.h checklvl.h colblk.h connect.h csort.h -engmsg.h filamap.h filamdbf.h filamfix.h filamgz.h filamtxt.h +engmsg.h filamap.h filamdbf.h filamfix.h filamgz.h filamtxt.h filechk.h filter.h global.h ha_connect.h inihandl.h json.h jsonudf.h maputil.h msgid.h mycat.h myconn.h myutil.h os.h osutil.h plgcnx.h plgdbsem.h preparse.h reldef.h resource.h tabcol.h tabdos.h tabext.h tabfix.h tabfmt.h tabjson.h tabmul.h diff --git a/storage/connect/bsonudf.cpp b/storage/connect/bsonudf.cpp index bed40a05249..a621db1a40c 100644 --- a/storage/connect/bsonudf.cpp +++ b/storage/connect/bsonudf.cpp @@ -15,6 +15,7 @@ #include #include "bsonudf.h" +#include "filechk.h" #if defined(UNIX) || defined(UNIV_LINUX) #define _O_RDONLY O_RDONLY @@ -4474,6 +4475,16 @@ char *bson_file(UDF_INIT *initid, UDF_ARGS *args, char *result, PlugSubSet(g->Sarea, g->Sarea_Size); fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *is_null = 1; + return NULL; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *is_null = 1; + return NULL; + } + if (args->arg_count > 1) { int pretty = 3, pty = 3; size_t len; @@ -4625,7 +4636,10 @@ char *bfile_make(UDF_INIT *initid, UDF_ARGS *args, char *result, } // endswitch arg_type if (fn) { - if (!bnx.Serialize(g, jvp, fn, pretty)) + if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + fn = NULL; + } else if (!bnx.Serialize(g, jvp, fn, pretty)) PUSH_WARNING(g->Message); } else PUSH_WARNING("Missing file name"); @@ -6061,6 +6075,16 @@ char *bbin_file(UDF_INIT *initid, UDF_ARGS *args, char *result, fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *error = 1; + goto fin; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *error = 1; + goto fin; + } + for (unsigned int i = 1; i < args->arg_count; i++) if (args->arg_type[i] == INT_RESULT && *(longlong*)args->args[i] < 4) { pretty = (int) * (longlong*)args->args[i]; diff --git a/storage/connect/filechk.cpp b/storage/connect/filechk.cpp new file mode 100644 index 00000000000..03e88633e99 --- /dev/null +++ b/storage/connect/filechk.cpp @@ -0,0 +1,41 @@ +/* Copyright (C) MariaDB Corporation Ab + + 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 Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +#include +#include +#include +#include "filechk.h" + +/* + @return true if the current user may access path. +*/ +bool connect_can_access_file(const char *path) +{ + char real_path[FN_REFLEN]; + + if (!path) + return false; /* callers must check for NULL before calling */ + + if (check_global_access(current_thd, FILE_ACL, true)) + return false; + + /* MY_SAFE_PATH returns NULL on overflow; fail closed rather than validate a truncated path */ + if (!fn_format(real_path, path, mysql_real_data_home, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME | MY_RETURN_REAL_PATH | MY_SAFE_PATH)) + return false; + + return is_secure_file_path(real_path); +} diff --git a/storage/connect/filechk.h b/storage/connect/filechk.h new file mode 100644 index 00000000000..75ab7eda09e --- /dev/null +++ b/storage/connect/filechk.h @@ -0,0 +1,20 @@ +/* Copyright (C) MariaDB Corporation Ab + + 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 Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +/* File access checks for CONNECT file UDFs */ +#pragma once +bool connect_can_access_file(const char *path); + diff --git a/storage/connect/jsonudf.cpp b/storage/connect/jsonudf.cpp index 9ce1ff5e48b..5a6eacdd01a 100644 --- a/storage/connect/jsonudf.cpp +++ b/storage/connect/jsonudf.cpp @@ -14,6 +14,7 @@ #include #include "jsonudf.h" +#include "filechk.h" #if defined(UNIX) || defined(UNIV_LINUX) #define _O_RDONLY O_RDONLY @@ -4555,6 +4556,16 @@ char *json_file(UDF_INIT *initid, UDF_ARGS *args, char *result, PlugSubSet(g->Sarea, g->Sarea_Size); fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *is_null = 1; + return NULL; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *is_null = 1; + return NULL; + } + if (args->arg_count > 1) { int pretty = 3, pty = 3; size_t len; @@ -4708,7 +4719,10 @@ char *jfile_make(UDF_INIT *initid, UDF_ARGS *args, char *result, } // endswitch arg_type if (fn) { - if (!Serialize(g, jvp->GetJson(), fn, pretty)) + if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + fn = NULL; + } else if (!Serialize(g, jvp->GetJson(), fn, pretty)) PUSH_WARNING(g->Message); } else PUSH_WARNING("Missing file name"); @@ -5760,8 +5774,8 @@ my_bool jbin_file_init(UDF_INIT *initid, UDF_ARGS *args, char *message) if (args->arg_count < 1 || args->arg_count > 4) { strcpy(message, "This function only accepts 1 to 4 arguments"); return true; - } else if (args->arg_type[0] != STRING_RESULT || !args->args[0]) { - strcpy(message, "First argument must be a constant string (file name)"); + } else if (args->arg_type[0] != STRING_RESULT) { + strcpy(message, "First argument must be a string (file name)"); return true; } // endifs @@ -5779,7 +5793,10 @@ my_bool jbin_file_init(UDF_INIT *initid, UDF_ARGS *args, char *message) initid->maybe_null = 1; CalcLen(args, false, reslen, memlen); - fl = GetFileLength(args->args[0]); + if (args->args[0]) + fl = GetFileLength(args->args[0]); + else + fl = 100; // What can be done here? reslen += fl; more += fl * M; //memlen += more; @@ -5804,6 +5821,16 @@ char *jbin_file(UDF_INIT *initid, UDF_ARGS *args, char *result, g->Xchk = NULL; fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *error = 1; + goto fin; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *error = 1; + goto fin; + } + for (unsigned int i = 1; i < args->arg_count; i++) if (args->arg_type[i] == INT_RESULT && *(longlong*)args->args[i] < 4) { pretty = (int) * (longlong*)args->args[i]; diff --git a/storage/connect/mysql-test/connect/r/json_bson_file_priv.result b/storage/connect/mysql-test/connect/r/json_bson_file_priv.result new file mode 100644 index 00000000000..34cbf8c972b --- /dev/null +++ b/storage/connect/mysql-test/connect/r/json_bson_file_priv.result @@ -0,0 +1,156 @@ +# +# Test FILE privilege and secure_file_priv enforcement for JSON/BSON file UDFs +# +# Privileged user: path inside secure_file_priv MYSQL_TMP_DIR +select json_file('MYSQL_TMP_DIR/bib0.json', 0) IS NOT NULL AS ok; +ok +1 +select bson_file('MYSQL_TMP_DIR/biblio.json', 0) IS NOT NULL AS ok; +ok +1 +Warnings: +Warning 1105 File pretty format doesn't match the specified pretty value +select jfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok; +ok +1 +select bfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok; +ok +1 +select jbin_file('MYSQL_TMP_DIR/bib0.json') IS NOT NULL AS ok; +ok +1 +select bbin_file('MYSQL_TMP_DIR/biblio.json') IS NOT NULL AS ok; +ok +1 +# Privileged user: path outside secure_file_priv +select json_file('DATADIR/bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('DATADIR/biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jfile_make('{"test":1}', 'DATADIR/out.json', 0) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bfile_make('{"test":1}', 'DATADIR/out.json', 0) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jbin_file('DATADIR/bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bbin_file('DATADIR/biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# NULL argument: no "access denied", UDF reports its own error +select json_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bson_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select jfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select jbin_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bbin_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +# Privileged user: relative path resolving outside secure_file_priv +select json_file('../../bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('../../biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# Overlong filename: MY_SAFE_PATH rejects it, fail closed +select json_file(concat('MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file(concat('MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# Non-constant filename expression (args->args[0] is NULL at init time) +set @fname = 'MYSQL_TMP_DIR/bib0.json'; +select jbin_file(@fname) IS NOT NULL AS ok; +ok +1 +set @fname = 'MYSQL_TMP_DIR/biblio.json'; +select bbin_file(@fname) IS NOT NULL AS ok; +ok +1 +# User without FILE privilege +create user unprivileged@localhost; +grant select ON *.* TO unprivileged@localhost; +connect unprivileged,localhost,unprivileged,,; +connection unprivileged; +# inside secure_file_priv: denied without FILE privilege +select json_file('MYSQL_TMP_DIR/bib0.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('MYSQL_TMP_DIR/biblio.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jbin_file('MYSQL_TMP_DIR/bib0.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bbin_file('MYSQL_TMP_DIR/biblio.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +disconnect unprivileged; +connection default; +drop user unprivileged@localhost; diff --git a/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt b/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt new file mode 100644 index 00000000000..e9a43a5584d --- /dev/null +++ b/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt @@ -0,0 +1 @@ +--secure_file_priv=$MYSQL_TMP_DIR diff --git a/storage/connect/mysql-test/connect/t/json_bson_file_priv.test b/storage/connect/mysql-test/connect/t/json_bson_file_priv.test new file mode 100644 index 00000000000..04c91184a94 --- /dev/null +++ b/storage/connect/mysql-test/connect/t/json_bson_file_priv.test @@ -0,0 +1,100 @@ +--source include/not_embedded.inc +--source json_udf.inc +--source bson_udf.inc + +let $MYSQLD_DATADIR = `select @@datadir`; + +--copy_file $MTR_SUITE_DIR/std_data/bib0.json $MYSQL_TMP_DIR/bib0.json +--copy_file $MTR_SUITE_DIR/std_data/biblio.json $MYSQL_TMP_DIR/biblio.json + +--echo # +--echo # Test FILE privilege and secure_file_priv enforcement for JSON/BSON file UDFs +--echo # + +--echo # Privileged user: path inside secure_file_priv MYSQL_TMP_DIR +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file('$MYSQL_TMP_DIR/bib0.json', 0) IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file('$MYSQL_TMP_DIR/biblio.json', 0) IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok +--remove_file $MYSQL_TMP_DIR/out.json +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok +--remove_file $MYSQL_TMP_DIR/out.json +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jbin_file('$MYSQL_TMP_DIR/bib0.json') IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bbin_file('$MYSQL_TMP_DIR/biblio.json') IS NOT NULL AS ok + +--echo # Privileged user: path outside secure_file_priv +--replace_result $MYSQLD_DATADIR DATADIR +--eval select json_file('$MYSQLD_DATADIR/bib0.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bson_file('$MYSQLD_DATADIR/biblio.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select jfile_make('{"test":1}', '$MYSQLD_DATADIR/out.json', 0) IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bfile_make('{"test":1}', '$MYSQLD_DATADIR/out.json', 0) IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select jbin_file('$MYSQLD_DATADIR/bib0.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bbin_file('$MYSQLD_DATADIR/biblio.json') IS NULL AS denied + +--echo # NULL argument: no "access denied", UDF reports its own error +select json_file(NULL) IS NULL AS ok; +select bson_file(NULL) IS NULL AS ok; +select jfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +select bfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +select jbin_file(NULL) IS NULL AS ok; +select bbin_file(NULL) IS NULL AS ok; + +--echo # Privileged user: relative path resolving outside secure_file_priv +select json_file('../../bib0.json') IS NULL AS denied; +select bson_file('../../biblio.json') IS NULL AS denied; + +--echo # Overlong filename: MY_SAFE_PATH rejects it, fail closed +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file(concat('$MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file(concat('$MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied + +--echo # Non-constant filename expression (args->args[0] is NULL at init time) +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval set @fname = '$MYSQL_TMP_DIR/bib0.json' +select jbin_file(@fname) IS NOT NULL AS ok; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval set @fname = '$MYSQL_TMP_DIR/biblio.json' +select bbin_file(@fname) IS NOT NULL AS ok; + +--echo # User without FILE privilege +create user unprivileged@localhost; +grant select ON *.* TO unprivileged@localhost; + +--connect(unprivileged,localhost,unprivileged,,) +--connection unprivileged + +--echo # inside secure_file_priv: denied without FILE privilege +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file('$MYSQL_TMP_DIR/bib0.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file('$MYSQL_TMP_DIR/biblio.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jbin_file('$MYSQL_TMP_DIR/bib0.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bbin_file('$MYSQL_TMP_DIR/biblio.json') IS NULL AS denied; + +--disconnect unprivileged +--connection default + +--remove_file $MYSQL_TMP_DIR/bib0.json +--remove_file $MYSQL_TMP_DIR/biblio.json + +drop user unprivileged@localhost; + +--source json_udf2.inc +--source bson_udf2.inc From 9c9282a5feb7f8026ec867669b0698c796270711 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 1 Jun 2026 14:22:03 +1000 Subject: [PATCH 039/150] MDEV-39803 RPM dependencies missing from MariaDB-server-galera package RPM dependencies where not included in cpack build due to incorrect component name. Corrects a103be381b38 Becase the wsrep_info plugin installs as a plugin, it overrites the cpack_rpm server-galera PACKAGE_DEPENDS. As such make the cmake/plugin.cmake only sets the PACKAGE_DEPENDS if not already set. This allows plugin to override automatic REQUIRES, fine-tuning their own dependencies --- cmake/cpack_rpm.cmake | 6 +++--- cmake/plugin.cmake | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/cpack_rpm.cmake b/cmake/cpack_rpm.cmake index 67b67d1fa76..2a94a4af4fb 100644 --- a/cmake/cpack_rpm.cmake +++ b/cmake/cpack_rpm.cmake @@ -270,12 +270,12 @@ SETA(CPACK_RPM_server_PACKAGE_REQUIRES "MariaDB-client >= 11.0.0") IF(WITH_WSREP) - SETA(CPACK_RPM_server_galera_PACKAGE_REQUIRES + SETA(CPACK_RPM_server-galera_PACKAGE_REQUIRES "MariaDB-server >= ${SERVER_VERSION}" "galera-4" "rsync" "grep" "gawk" "iproute" "coreutils" "findutils" "tar") - SETA(CPACK_RPM_server_galera_PACKAGE_RECOMMENDS "lsof" "socat" "pv") - SETA(CPACK_RPM_server_galera_PACKAGE_CONFLICTS + SETA(CPACK_RPM_server-galera_PACKAGE_RECOMMENDS "lsof" "socat" "pv") + SETA(CPACK_RPM_server-galera_PACKAGE_CONFLICTS "MariaDB-server <= 12.3.2") ENDIF() diff --git a/cmake/plugin.cmake b/cmake/plugin.cmake index 07c92205255..d5da7649aa5 100644 --- a/cmake/plugin.cmake +++ b/cmake/plugin.cmake @@ -275,7 +275,7 @@ MACRO(MYSQL_ADD_PLUGIN) SET(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} ${ARG_COMPONENT} ${ARG_COMPONENT}Symlinks) SET(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} PARENT_SCOPE) - IF (NOT ARG_CLIENT) + IF (NOT ARG_CLIENT AND NOT CPACK_RPM_${ARG_COMPONENT}_PACKAGE_REQUIRES) SET(CPACK_RPM_${ARG_COMPONENT}_PACKAGE_REQUIRES "MariaDB-server${ver}" PARENT_SCOPE) ENDIF() SET(CPACK_RPM_${ARG_COMPONENT}_USER_FILELIST ${ignored} PARENT_SCOPE) From 5e9bb382649c5ee8d0f7ee57da432e8841933de0 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Thu, 23 Jul 2026 10:11:20 +1000 Subject: [PATCH 040/150] MDEV-40173 RPM conflicts on /usr/lib64/security The move of the install location of pam files in MDEV-37197 (34aac090f2acc1a4b5850810fe41370c19659d55) resulted in different install locations on different RPM distros. Correct the RPM packaging to ignore the path of the pam files (but not the pam files themselves). --- cmake/cpack_rpm.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/cpack_rpm.cmake b/cmake/cpack_rpm.cmake index 2a94a4af4fb..c23c48d6e8c 100644 --- a/cmake/cpack_rpm.cmake +++ b/cmake/cpack_rpm.cmake @@ -155,9 +155,8 @@ SET(ignored "%ignore /etc/systemd" "%ignore /etc/systemd/system" "%ignore /lib" - "%ignore /lib/security" "%ignore /lib64" - "%ignore /lib64/security" + "%ignore ${INSTALL_PAMDIR_RPM}" "%ignore ${CMAKE_INSTALL_PREFIX}" "%ignore ${CMAKE_INSTALL_PREFIX}/bin" "%ignore ${CMAKE_INSTALL_PREFIX}/include" From 2894e90b6545d3c7fa13ff00fb50c9e8631003e7 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 30 Jul 2026 12:20:06 +0200 Subject: [PATCH 041/150] MDEV-40165 post-fix never use item->null_value before this item is evaluated followup for 14c16e02b26 --- sql/item_jsonfunc.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index a976effbd91..1681b555ca5 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -633,14 +633,11 @@ bool Item_func_json_equals::val_bool() THD *thd; json_engine_t je; - if ((null_value= args[0]->null_value || args[1]->null_value)) - return 1; - String *a= args[0]->val_json(&a_tmp); - if ((null_value= a == nullptr)) + if ((null_value= a == nullptr || args[0]->null_value)) return 1; String *b= args[1]->val_json(&b_tmp); - if ((null_value= b == nullptr)) + if ((null_value= b == nullptr || args[1]->null_value)) return 1; DYNAMIC_STRING a_res; From 9d1466c8be263256c45e02d9b484b25438971fd8 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 1 Aug 2026 12:21:48 +0200 Subject: [PATCH 042/150] MDEV-40337 disable the test in valgrind builds valgrind doesn't like it when a process writes to r/o memory and complains "Bad permissions for mapped region". It's not memcheck, but valgrind core error, cannot be suppressed. --- mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test index 39f5dc95189..192c481b4ce 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -1,6 +1,7 @@ --source include/have_debug.inc --source include/not_asan.inc --source include/not_embedded.inc +--source include/not_valgrind.inc --echo # --echo # MDEV-40341 store read-only sysvars in a read-only root and a read-only segment From 1a572838e246d8f156b286b8220428b2f0824c8c Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 30 Jul 2026 09:20:12 +0200 Subject: [PATCH 043/150] MDEV-40554 KILL checks user (not priv_user) and doesn't verify hostname The "is it my own thread" check compared the login user name (Security_context::user) and ignored the host, so u1@localhost could see and kill threads of u1@'127.0.0.1'. Compare the authenticated account instead: * all comparisons are done in sctx->is_priv_user() now * change user_matches() to priv_user_matches(), which uses is_priv_user() * use it in KILL, KILL USER, SHOW PROCESSLIST, I_S.PROCESSLIST, COM_PROCESS_INFO and SHOW EXPLAIN/ANALYZE FOR. * all the remaining places use is_priv_user() directly instead of doing strcmp: SHOW GRANTS, SHOW CREATE PROCEDURE, I_S.VIEWS, the DEFINER clause, optimizer trace and change_security_context(). Assisted-by: Claude:claude-5-opus --- mysql-test/main/kill-2-master.opt | 2 +- mysql-test/main/kill-2.result | 81 +++++++++++++++++++++++++++- mysql-test/main/kill-2.test | 89 ++++++++++++++++++++++++++++++- sql/opt_trace.cc | 7 +-- sql/privilege.h | 5 +- sql/sp_head.cc | 8 ++- sql/sql_acl.cc | 5 +- sql/sql_class.cc | 19 ++++--- sql/sql_class.h | 4 +- sql/sql_parse.cc | 65 ++++------------------ sql/sql_show.cc | 60 +++++++++------------ sql/sql_show.h | 4 +- 12 files changed, 228 insertions(+), 121 deletions(-) diff --git a/mysql-test/main/kill-2-master.opt b/mysql-test/main/kill-2-master.opt index ab6ca1731f5..94ed5e3778b 100644 --- a/mysql-test/main/kill-2-master.opt +++ b/mysql-test/main/kill-2-master.opt @@ -1 +1 @@ ---skip-name-resolve +--skip-name-resolve --bind-address=::1,127.0.0.1 diff --git a/mysql-test/main/kill-2.result b/mysql-test/main/kill-2.result index 95164fda164..174b8a8b6b5 100644 --- a/mysql-test/main/kill-2.result +++ b/mysql-test/main/kill-2.result @@ -35,8 +35,87 @@ root kill user b; ERROR HY000: Operation KILL USER failed for b@% connection default; +disconnect a; +disconnect b; drop user a@'127.0.0.1'; drop user b@'127.0.0.1'; -# # End of 10.3 tests # +# MDEV-40554 KILL checks user (not priv_user) and doesn't verify hostname +# +create user u1@'::1' identified by 'pw1'; +create user ''@'127.0.0.1'; +connect u1_ipv6,::1,u1,pw1; +select current_user(); +current_user() +u1@::1 +# the reported case: same login name, but an anonymous account +connect u1_anon,127.0.0.1,u1,; +select user(), current_user(); +user() current_user() +u1@127.0.0.1 @127.0.0.1 +# anonymous users don't see the process list at all +show processlist; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +select count(*) as `threads visible` from information_schema.processlist; +threads visible +1 +select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +u1@'::1' visible +0 +kill $cid_sock; +ERROR HY000: You are not owner of thread CID +show explain for $cid_sock; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +user() current_user() +u1@::1 u1@::1 +connection u1_anon; +connection default; +disconnect u1_anon; +drop user ''@'127.0.0.1'; +# same login name, a real account, but a different host +create user u1@'127.0.0.1' identified by 'pw2'; +connect u1_ipv4,127.0.0.1,u1,pw2; +select current_user(); +current_user() +u1@127.0.0.1 +# u1@127.0.0.1 must not see u1@'::1' - same user name, other account +show processlist; +Id User Host db Command Time State Info Progress +# u1 # # Query # # # # +select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +u1@'::1' visible +0 +kill $cid_sock; +ERROR HY000: You are not owner of thread CID +show explain for $cid_sock; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +user() current_user() +u1@::1 u1@::1 +connection u1_ipv4; +# but another u1@127.0.0.1 connection is the same account, so it may +connect u1_tcp2,127.0.0.1,u1,pw2; +select count(*) as `u1@127.0.0.1 visible` + from information_schema.processlist where id=$cid_tcp; +u1@127.0.0.1 visible +1 +kill $cid_tcp; +# u1@'::1' was not killed +connection u1_ipv6; +select 1; +1 +1 +connection default; +disconnect u1_ipv6; +disconnect u1_tcp2; +drop user u1@'::1'; +drop user u1@'127.0.0.1'; +# End of 10.11 tests diff --git a/mysql-test/main/kill-2.test b/mysql-test/main/kill-2.test index 1fdf693c414..b5fd8fec189 100644 --- a/mysql-test/main/kill-2.test +++ b/mysql-test/main/kill-2.test @@ -6,7 +6,9 @@ # connection does not read() from a socket, but returns control to the # application. 'mysqltest' does not handle the kill request. # --- source include/not_embedded.inc +--source include/not_embedded.inc +--source include/count_sessions.inc +--source include/check_ipv6.inc --echo # --echo # MDEV-6896 kill user command cause MariaDB crash! @@ -50,9 +52,92 @@ evalp select user from information_schema.processlist where id in (connection_id --error ER_KILL_DENIED_ERROR kill user b; --connection default +--disconnect a +--disconnect b drop user a@'127.0.0.1'; drop user b@'127.0.0.1'; ---echo # --echo # End of 10.3 tests + --echo # +--echo # MDEV-40554 KILL checks user (not priv_user) and doesn't verify hostname +--echo # + +create user u1@'::1' identified by 'pw1'; +create user ''@'127.0.0.1'; + +connect u1_ipv6,::1,u1,pw1; +select current_user(); +let $cid_sock=`select connection_id()`; + +--echo # the reported case: same login name, but an anonymous account +connect u1_anon,127.0.0.1,u1,; +select user(), current_user(); + +--echo # anonymous users don't see the process list at all +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +show processlist; +select count(*) as `threads visible` from information_schema.processlist; +evalp select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +--replace_regex /thread [0-9]+/thread CID/ +--error ER_KILL_DENIED_ERROR +evalp kill $cid_sock; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +evalp show explain for $cid_sock; + +# killing a non-matching user doesn't report an error to not disclose +# that the user exists. but doesn't kill either +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +connection u1_anon; + +connection default; +disconnect u1_anon; +drop user ''@'127.0.0.1'; + +--echo # same login name, a real account, but a different host +create user u1@'127.0.0.1' identified by 'pw2'; +connect u1_ipv4,127.0.0.1,u1,pw2; +select current_user(); +let $cid_tcp=`select connection_id()`; + +--echo # u1@127.0.0.1 must not see u1@'::1' - same user name, other account +--replace_result Execute Query +--replace_column 1 # 3 # 4 # 6 # 7 # 8 # 9 # +show processlist; +evalp select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +--replace_regex /thread [0-9]+/thread CID/ +--error ER_KILL_DENIED_ERROR +evalp kill $cid_sock; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +evalp show explain for $cid_sock; + +# doesn't report an error, doesn't kill +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +connection u1_ipv4; + +--echo # but another u1@127.0.0.1 connection is the same account, so it may +connect u1_tcp2,127.0.0.1,u1,pw2; +evalp select count(*) as `u1@127.0.0.1 visible` + from information_schema.processlist where id=$cid_tcp; +evalp kill $cid_tcp; + +--echo # u1@'::1' was not killed +connection u1_ipv6; +select 1; + +connection default; +disconnect u1_ipv6; +#u1_ipv4 was killed, must not be disconnected +disconnect u1_tcp2; +--source include/wait_until_count_sessions.inc + +drop user u1@'::1'; +drop user u1@'127.0.0.1'; + +--echo # End of 10.11 tests diff --git a/sql/opt_trace.cc b/sql/opt_trace.cc index fc3eba2bddd..db20b364be6 100644 --- a/sql/opt_trace.cc +++ b/sql/opt_trace.cc @@ -201,11 +201,8 @@ void opt_trace_disable_if_no_security_context_access(THD *thd) existing connection, per the manual. */ if (!(thd->main_security_ctx.check_access(GLOBAL_ACLS & ~GRANT_ACL)) && - (0 != strcmp(thd->main_security_ctx.priv_user, - thd->security_context()->priv_user) || - 0 != my_strcasecmp(system_charset_info, - thd->main_security_ctx.priv_host, - thd->security_context()->priv_host))) + !thd->main_security_ctx.is_priv_user(thd->security_context()->priv_user, + thd->security_context()->priv_host)) trace->missing_privilege(); } diff --git a/sql/privilege.h b/sql/privilege.h index 8e9b9a3748e..7e7343e9d4f 100644 --- a/sql/privilege.h +++ b/sql/privilege.h @@ -637,11 +637,10 @@ constexpr privilege_t PRIV_STMT_DROP_SERVER= FEDERATED_ADMIN_ACL | SUPER_ACL; /* Privileges related to processes */ -constexpr privilege_t PRIV_COM_PROCESS_INFO= PROCESS_ACL; -// This privilege applies both for SHOW EXPLAIN and SHOW ANALYZE -constexpr privilege_t PRIV_STMT_SHOW_EXPLAIN= PROCESS_ACL; constexpr privilege_t PRIV_STMT_SHOW_ENGINE_STATUS= PROCESS_ACL; constexpr privilege_t PRIV_STMT_SHOW_ENGINE_MUTEX= PROCESS_ACL; +// This privilege is used in thd_visible_in_processlist() and thus applies +// to SHOW PROCESSLIST, I_S.PROCESSLIST, SHOW EXPLAIN and SHOW ANALYZE constexpr privilege_t PRIV_STMT_SHOW_PROCESSLIST= PROCESS_ACL; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index c7f1fcaedac..f237e9b3cdc 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2002, 2016, Oracle and/or its affiliates. - Copyright (c) 2011, 2020, MariaDB + Copyright (c) 2011, 2026, MariaDB plc. 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 @@ -3064,10 +3064,8 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) 1, TRUE) && (tables.grant.privilege & SELECT_ACL) != NO_ACL) || /* Check if user owns the routine. */ - (!strcmp(sp->m_definer.user.str, - thd->security_ctx->priv_user) && - !strcmp(sp->m_definer.host.str, - thd->security_ctx->priv_host)) || + thd->security_ctx->is_priv_user(sp->m_definer.user.str, + sp->m_definer.host.str) || /* Check if current role or any of the sub-granted roles own the routine. */ (sp->m_definer.host.length == 0 && diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 7e3a7e9d486..d7782dea96e 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. - Copyright (c) 2009, 2023, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. 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 @@ -9613,8 +9613,7 @@ bool get_show_user(THD *thd, LEX_USER *lex_user, const char **username, { *username= lex_user->user.str; *hostname= lex_user->host.str; - do_check_access= strcmp(*username, sctx->priv_user) || - strcmp(*hostname, sctx->priv_host); + do_check_access= !sctx->is_priv_user(*username, *hostname); } if (do_check_access && check_access(thd, SELECT_ACL, "mysql", 0, 0, 1, 0)) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 30c341eb653..a7545acacfb 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -4700,9 +4700,8 @@ change_security_context(THD *thd, DBUG_ASSERT(definer_user->str && definer_host->str); *backup= NULL; - needs_change= (strcmp(definer_user->str, thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, definer_host->str, - thd->security_ctx->priv_host)); + needs_change= !thd->security_ctx->is_priv_user(definer_user->str, + definer_host->str); if (needs_change) { if (acl_getroot(this, definer_user->str, definer_host->str, @@ -4730,13 +4729,19 @@ Security_context::restore_security_context(THD *thd, #endif -bool Security_context::user_matches(Security_context *them) +/** + check that `this` is not system or pre-auth thread and + is owned by the same account as in `them` + + Note that it's not symmetric, it's used in KILL/SHOW commands + to see whether "them" can kill/see "us", never the other way around! +*/ +bool Security_context::priv_user_matches(const Security_context *them) const { - return ((user != NULL) && (them->user != NULL) && - !strcmp(user, them->user)); + return user && is_priv_user(them->priv_user, them->priv_host); } -bool Security_context::is_priv_user(const char *user, const char *host) +bool Security_context::is_priv_user(const char *user, const char *host) const { return ((user != NULL) && (host != NULL) && !strcmp(user, priv_user) && diff --git a/sql/sql_class.h b/sql/sql_class.h index 3549e1b753b..20b3385b058 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1574,7 +1574,7 @@ public: void restore_security_context(THD *thd, Security_context *backup); #endif - bool user_matches(Security_context *); + bool priv_user_matches(const Security_context *) const; /** Check global access @param want_access The required privileges @@ -1583,7 +1583,7 @@ public: @return True if the security context fulfills the access requirements. */ bool check_access(const privilege_t want_access, bool match_any = false); - bool is_priv_user(const char *user, const char *host); + bool is_priv_user(const char *user, const char *host) const; bool is_user_defined() const { return user && user != delayed_user && user != slave_user && user != wsrep_user; }; }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9188bdd762e..8be73d4ba48 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2023, MariaDB + Copyright (c) 2008, 2026, MariaDB plc. 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 @@ -2333,13 +2333,8 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD * break; case COM_PROCESS_INFO: status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_PROCESSLIST]); - if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_COM_PROCESS_INFO)) - break; general_log_print(thd, command, NullS); - mysqld_list_processes(thd, - thd->security_ctx->master_access & PRIV_COM_PROCESS_INFO ? - NullS : thd->security_ctx->priv_user, 0); + mysqld_list_processes(thd, 0); break; case COM_PROCESS_KILL: { @@ -2884,12 +2879,9 @@ bool sp_process_definer(THD *thd) to create a stored routine under another user one must have SUPER privilege). */ - bool curuser= !strcmp(d->user.str, thd->security_ctx->priv_user); + bool curuser= thd->security_ctx->is_priv_user(d->user.str, d->host.str); bool currole= !curuser && !strcmp(d->user.str, thd->security_ctx->priv_role); - bool curuserhost= curuser && d->host.str && - !my_strcasecmp(system_charset_info, d->host.str, - thd->security_ctx->priv_host); - if (!curuserhost && !currole && + if (!curuser && !currole && check_global_access(thd, PRIV_DEFINER_CLAUSE, false)) DBUG_RETURN(TRUE); } @@ -3964,7 +3956,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) case SQLCOM_SHOW_ANALYZE: { if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_STMT_SHOW_EXPLAIN)) + check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) break; /* @@ -5133,14 +5125,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) break; } case SQLCOM_SHOW_PROCESSLIST: - if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) - break; - mysqld_list_processes(thd, - (thd->security_ctx->master_access & PRIV_STMT_SHOW_PROCESSLIST ? - NullS : - thd->security_ctx->priv_user), - lex->verbose); + mysqld_list_processes(thd, lex->verbose); break; case SQLCOM_SHOW_AUTHORS: res= mysqld_show_authors(thd); @@ -9399,31 +9384,10 @@ kill_one_thread(THD *thd, my_thread_id id, killed_state kill_signal, killed_type DEBUG_SYNC(thd, "found_killee"); if (tmp->get_command() != COM_DAEMON) { - /* - If we're SUPER, we can KILL anything, including system-threads. - No further checks. - - KILLer: thd->security_ctx->user could in theory be NULL while - we're still in "unauthenticated" state. This is a theoretical - case (the code suggests this could happen, so we play it safe). - - KILLee: tmp->security_ctx->user will be NULL for system threads. - We need to check so Jane Random User doesn't crash the server - when trying to kill a) system threads or b) unauthenticated users' - threads (Bug#43748). - - If user of both killer and killee are non-NULL, proceed with - slayage if both are string-equal. - - It's ok to also kill DELAYED threads with KILL_CONNECTION instead of - KILL_SYSTEM_THREAD; The difference is that KILL_CONNECTION may be - faster and do a harder kill than KILL_SYSTEM_THREAD; - */ - mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from concurrent usage if ((thd->security_ctx->master_access & PRIV_KILL_OTHER_USER_PROCESS) || - thd->security_ctx->user_matches(tmp->security_ctx)) + tmp->security_ctx->priv_user_matches(thd->security_ctx)) { #ifdef WITH_WSREP if (wsrep_thd_is_BF(tmp, false) || tmp->wsrep_applier) @@ -9436,21 +9400,14 @@ kill_one_thread(THD *thd, my_thread_id id, killed_state kill_signal, killed_type tmp->thread_id, (tmp->wsrep_applier ? "wsrep applier" : "high priority")); } + else if (WSREP(tmp)) + error = wsrep_kill_thd(thd, tmp, kill_signal); else - { - if (WSREP(tmp)) - { - error = wsrep_kill_thd(thd, tmp, kill_signal); - } - else - { #endif /* WITH_WSREP */ + { tmp->awake_no_mutex(kill_signal); error= 0; -#ifdef WITH_WSREP - } } -#endif /* WITH_WSREP */ } else error= (type == KILL_TYPE_QUERY ? ER_KILL_QUERY_DENIED_ERROR : @@ -9503,7 +9460,7 @@ static my_bool kill_threads_callback(THD *thd, kill_threads_callback_arg *arg) { if (!(arg->thd->security_ctx->master_access & PRIV_KILL_OTHER_USER_PROCESS) && - !arg->thd->security_ctx->user_matches(thd->security_ctx)) + !thd->security_ctx->priv_user_matches(arg->thd->security_ctx)) { return MY_TEST(arg->thd->security_ctx->master_access & PROCESS_ACL); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index eda8f77d260..7c7a023500f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2797,37 +2797,31 @@ static const char *thread_state_info(THD *tmp) Privileged users can see all THDs. - @param user - user name or NULL for privileged users. - @param thd - THD + @param caller - security context of the acting thread + @param thd - THD to check if visible to the caller @retval true - THD visible in processlist @retval false - THD not visible in processlist */ -static bool thd_visible_in_processlist(const char *user, THD *thd) +static bool thd_visible_in_processlist(const Security_context *caller, THD *thd) { if (!thd->vio_ok() && !thd->system_thread) return false; // "something bad happened" thread, don't show it - if (!user) + if (caller->master_access & PRIV_STMT_SHOW_PROCESSLIST) return true; // privileged user can see all threads - - const char *thd_user= thd->security_ctx->user; - if (!thd_user) - return false; // dunno if this ever happens, safety first - bool user_or_event_worker_thread= - !thd->system_thread || thd->system_thread & SYSTEM_THREAD_EVENT_WORKER; + !thd->system_thread || thd->system_thread & SYSTEM_THREAD_EVENT_WORKER; - return user_or_event_worker_thread && !strcmp(thd_user, user); + return user_or_event_worker_thread && + thd->security_ctx->priv_user_matches(caller); } struct list_callback_arg { - list_callback_arg(const char *u, THD *t, ulong m): - user(u), thd(t), max_query_length(m) {} + list_callback_arg(THD *t, ulong m): thd(t), max_query_length(m) {} I_List thread_infos; - const char *user; THD *thd; ulong max_query_length; }; @@ -2838,7 +2832,7 @@ static my_bool list_callback(THD *tmp, list_callback_arg *arg) Security_context *tmp_sctx= tmp->security_ctx; bool got_thd_data; - if (thd_visible_in_processlist(arg->user, tmp)) + if (thd_visible_in_processlist(arg->thd->security_ctx, tmp)) { thread_info *thd_info= new (arg->thd->mem_root) thread_info; @@ -2921,17 +2915,21 @@ static my_bool list_callback(THD *tmp, list_callback_arg *arg) } -void mysqld_list_processes(THD *thd,const char *user, bool verbose) +void mysqld_list_processes(THD *thd, bool verbose) { Item *field; List field_list; - list_callback_arg arg(user, thd, - verbose ? thd->variables.max_allowed_packet : - PROCESS_LIST_WIDTH); + list_callback_arg arg(thd, verbose ? thd->variables.max_allowed_packet + : PROCESS_LIST_WIDTH); Protocol *protocol= thd->protocol; MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_list_processes"); + /* anonymous users cannot see anything */ + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) + DBUG_VOID_RETURN; + field_list.push_back(new (mem_root) Item_int(thd, "Id", 0, MY_INT32_NUM_DECIMAL_DIGITS), mem_root); @@ -3172,30 +3170,24 @@ void select_result_text_buffer::save_to(String *res) int fill_show_explain_or_analyze(THD *thd, TABLE_LIST *table, COND *cond, bool json_format, bool is_analyze) { - const char *calling_user; THD *tmp; my_thread_id thread_id; DBUG_ENTER("fill_show_explain_or_analyze"); DBUG_ASSERT(cond==NULL); thread_id= thd->lex->value_list.head()->val_int(); - calling_user= (thd->security_ctx->master_access & PRIV_STMT_SHOW_EXPLAIN) ? - NullS : thd->security_ctx->priv_user; if ((tmp= find_thread_by_id(thread_id))) { - Security_context *tmp_sctx= tmp->security_ctx; MEM_ROOT explain_mem_root, *save_mem_root; /* - If calling_user==NULL, calling thread has SUPER or PROCESS - privilege, and so can do SHOW EXPLAIN/SHOW ANALYZE on any user. - - if calling_user!=NULL, he's only allowed to view + Same rule as for SHOW PROCESSLIST: + A thread with PROCESS privilege can do SHOW EXPLAIN/SHOW + ANALYZE on any user, everybody else is only allowed to view SHOW EXPLAIN/SHOW ANALYZE on his own threads. */ - if (calling_user && (!tmp_sctx->user || strcmp(calling_user, - tmp_sctx->user))) + if (!thd_visible_in_processlist(thd->security_ctx, tmp)) { my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS"); mysql_mutex_unlock(&tmp->LOCK_thd_kill); @@ -3341,11 +3333,8 @@ static my_bool processlist_callback(THD *tmp, processlist_callback_arg *arg) const char *val; ulonglong max_counter; bool got_thd_data; - char *user= - arg->thd->security_ctx->master_access & PRIV_STMT_SHOW_PROCESSLIST ? - NullS : arg->thd->security_ctx->priv_user; - if (!thd_visible_in_processlist(user, tmp)) + if (!thd_visible_in_processlist(arg->thd->security_ctx, tmp)) return 0; restore_record(arg->table, s->default_values); @@ -7145,9 +7134,8 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, Security_context *sctx= thd->security_ctx; if (!tables->allowed_show) { - if (!strcmp(tables->definer.user.str, sctx->priv_user) && - !my_strcasecmp(system_charset_info, tables->definer.host.str, - sctx->priv_host)) + if (sctx->is_priv_user(tables->definer.user.str, + tables->definer.host.str)) tables->allowed_show= TRUE; #ifndef NO_EMBEDDED_ACCESS_CHECKS else diff --git a/sql/sql_show.h b/sql/sql_show.h index 9a269b49f11..a41fe9892a9 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -1,5 +1,5 @@ /* Copyright (c) 2005, 2010, Oracle and/or its affiliates. - Copyright (c) 2012, 2016, MariaDB + Copyright (c) 2012, 2026, MariaDB plc. 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 @@ -106,7 +106,7 @@ bool mysqld_show_create_db(THD *thd, LEX_CSTRING *db_name, LEX_CSTRING *orig_db_name, const DDL_options_st &options); -void mysqld_list_processes(THD *thd,const char *user,bool verbose); +void mysqld_list_processes(THD *thd, bool verbose); int mysqld_show_status(THD *thd); int mysqld_show_variables(THD *thd,const char *wild); bool mysqld_show_storage_engines(THD *thd); From 09bc25339b6dc70cb2a457053c94f474b87e8f4c Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 8 Jul 2026 20:55:00 +0200 Subject: [PATCH 044/150] CREATE SERVER: fix option parsing to support backticks properly CREATE SERVER used to: * support arbitrary options in backticks and not in backticks * hard-coded historical options worked *only* without backticks * PORT range was different when given as a number or a string all that is fixed, unused keywords are removed --- mysql-test/main/servers.result | 39 ++++++--- mysql-test/main/servers.test | 27 ++++--- .../suite/federated/federated_server.result | 14 ++-- .../suite/federated/federated_server.test | 14 ++-- sql/lex.h | 3 - sql/sql_servers.cc | 79 +++++++++++++++---- sql/sql_show.cc | 15 ++-- sql/sql_yacc.yy | 78 ------------------ 8 files changed, 135 insertions(+), 134 deletions(-) diff --git a/mysql-test/main/servers.result b/mysql-test/main/servers.result index 10ca5f953ed..9c53216a89b 100644 --- a/mysql-test/main/servers.result +++ b/mysql-test/main/servers.result @@ -77,10 +77,10 @@ create or replace server srv foreign data wrapper foo options (host "localhost", port "12345"); create or replace server srv foreign data wrapper mysql options (host "localhost", port "bar321"); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '"bar321")' at line 2 +ERROR HY000: Incorrect value 'bar321' for option 'PORT' create or replace server srv foreign data wrapper mysql options (host "localhost", port "123bar"); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '"123bar")' at line 2 +ERROR HY000: Incorrect value '123bar' for option 'PORT' create or replace server srv foreign data wrapper mysql options (host "localhost", port "0"); show create server nonexist; @@ -153,21 +153,42 @@ rename table mysql.plugin_save to mysql.plugin; # ## Error code depends on length of long CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 4294967295); -ERROR 22003: port value is out of range in 'INT' +ERROR HY000: Incorrect value '4294967295' for option 'PORT' +show warnings; +Level Code Message +Error 1912 Incorrect value '4294967295' for option 'PORT' CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 2147483647); select * from mysql.servers; Server_name Host Db Username Password Port Socket Wrapper Owner Options s 2147483647 mysql {"HOST": "", "PORT": "2147483647"} -ALTER SERVER s OPTIONS (PORT 4294967295); -ERROR 22003: port value is out of range in 'INT' +ALTER SERVER s OPTIONS (PORT '4294967295'); +ERROR HY000: Incorrect value '4294967295' for option 'PORT' select * from mysql.servers; Server_name Host Db Username Password Port Socket Wrapper Owner Options s 2147483647 mysql {"HOST": "", "PORT": "2147483647"} -drop server s; +CREATE OR REPLACE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT '4294967295'); +ERROR HY000: Incorrect value '4294967295' for option 'PORT' +select * from mysql.servers; +Server_name Host Db Username Password Port Socket Wrapper Owner Options +s 2147483647 mysql {"HOST": "", "PORT": "2147483647"} +DROP SERVER s; CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 2147483648); -ERROR 22003: port value is out of range in 'INT' -CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 18446744073709551615); -ERROR 22003: port value is out of range in 'INT' +ERROR HY000: Incorrect value '2147483648' for option 'PORT' CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT -5); ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '-5)' at line 1 +CREATE SERVER s FOREIGN DATA WRAPPER mysqL OPTIONS (HOST '',PORT '1', PORT '2'); +SHOW CREATE SERVER s; +Server Create Server +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (HOST '', PORT '2'); +select * from mysql.servers; +Server_name Host Db Username Password Port Socket Wrapper Owner Options +s 2 mysqL {"HOST": "", "PORT": "2"} +ALTER SERVER s options (PORT '7', PORT '8'); +SHOW CREATE SERVER s; +Server Create Server +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (HOST '', PORT '8'); +select * from mysql.servers; +Server_name Host Db Username Password Port Socket Wrapper Owner Options +s 8 mysqL {"HOST": "", "PORT": "8"} +DROP SERVER s; # End of 10.11 tests diff --git a/mysql-test/main/servers.test b/mysql-test/main/servers.test index 13c75e8e7bc..560283f0eed 100644 --- a/mysql-test/main/servers.test +++ b/mysql-test/main/servers.test @@ -71,11 +71,11 @@ show create server srv; create or replace server srv foreign data wrapper foo options (host "localhost", port "12345"); ---error ER_PARSE_ERROR +--error ER_BAD_OPTION_VALUE create or replace server srv foreign data wrapper mysql options (host "localhost", port "bar321"); ---error ER_PARSE_ERROR +--error ER_BAD_OPTION_VALUE create or replace server srv foreign data wrapper mysql options (host "localhost", port "123bar"); @@ -166,19 +166,28 @@ rename table mysql.plugin_save to mysql.plugin; --echo # MDEV-36230 SIGSEGV in store_server_fields on CREATE SERVER --echo # --echo ## Error code depends on length of long ---error ER_DATA_OUT_OF_RANGE +--error ER_BAD_OPTION_VALUE CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 4294967295); +show warnings; CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 2147483647); select * from mysql.servers; ---error ER_DATA_OUT_OF_RANGE -ALTER SERVER s OPTIONS (PORT 4294967295); +--error ER_BAD_OPTION_VALUE +ALTER SERVER s OPTIONS (PORT '4294967295'); select * from mysql.servers; -drop server s; ---error ER_DATA_OUT_OF_RANGE +--error ER_BAD_OPTION_VALUE +CREATE OR REPLACE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT '4294967295'); +select * from mysql.servers; +DROP SERVER s; +--error ER_BAD_OPTION_VALUE CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 2147483648); ---error ER_DATA_OUT_OF_RANGE -CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT 18446744073709551615); --error ER_PARSE_ERROR CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST'',PORT -5); +CREATE SERVER s FOREIGN DATA WRAPPER mysqL OPTIONS (HOST '',PORT '1', PORT '2'); +SHOW CREATE SERVER s; +select * from mysql.servers; +ALTER SERVER s options (PORT '7', PORT '8'); +SHOW CREATE SERVER s; +select * from mysql.servers; +DROP SERVER s; --echo # End of 10.11 tests diff --git a/mysql-test/suite/federated/federated_server.result b/mysql-test/suite/federated/federated_server.result index 946050b23e0..88bf126493b 100644 --- a/mysql-test/suite/federated/federated_server.result +++ b/mysql-test/suite/federated/federated_server.result @@ -44,13 +44,13 @@ DEFAULT CHARSET=latin1; connection master; drop server if exists 'server_one'; create server 'server_one' foreign data wrapper 'mysql' options -(HOST '127.0.0.1', -DATABASE 'first_db', -USER 'root', -PASSWORD '', -PORT SLAVE_PORT, -SOCKET '', -OWNER 'root'); +(`HOST` '127.0.0.1', +`DATABASE` 'first_db', +`USER` 'root', +`PASSWORD` '', +`PORT` 'SLAVE_PORT', +`SOCKET` '', +`OWNER` 'root'); drop server if exists 'server_two'; create server 'server_two' foreign data wrapper 'mysql' options (HOST '127.0.0.1', diff --git a/mysql-test/suite/federated/federated_server.test b/mysql-test/suite/federated/federated_server.test index 10f383d4efa..17283a09e84 100644 --- a/mysql-test/suite/federated/federated_server.test +++ b/mysql-test/suite/federated/federated_server.test @@ -46,13 +46,13 @@ connection master; drop server if exists 'server_one'; --replace_result $SLAVE_MYPORT SLAVE_PORT eval create server 'server_one' foreign data wrapper 'mysql' options - (HOST '127.0.0.1', - DATABASE 'first_db', - USER 'root', - PASSWORD '', - PORT $SLAVE_MYPORT, - SOCKET '', - OWNER 'root'); + (`HOST` '127.0.0.1', + `DATABASE` 'first_db', + `USER` 'root', + `PASSWORD` '', + `PORT` '$SLAVE_MYPORT', + `SOCKET` '', + `OWNER` 'root'); drop server if exists 'server_two'; --replace_result $SLAVE_MYPORT SLAVE_PORT diff --git a/sql/lex.h b/sql/lex.h index 2a35d057d63..003a5612e0d 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -279,7 +279,6 @@ SYMBOL symbols[] = { { "HELP", SYM(HELP_SYM)}, { "HIGH_PRIORITY", SYM(HIGH_PRIORITY)}, { "HISTORY", SYM(HISTORY_SYM)}, - { "HOST", SYM(HOST_SYM)}, { "HOSTS", SYM(HOSTS_SYM)}, { "HOUR", SYM(HOUR_SYM)}, { "HOUR_MICROSECOND", SYM(HOUR_MICROSECOND_SYM)}, @@ -468,7 +467,6 @@ SYMBOL symbols[] = { { "OUTFILE", SYM(OUTFILE)}, { "OVER", SYM(OVER_SYM)}, { "OVERLAPS", SYM(OVERLAPS_SYM)}, - { "OWNER", SYM(OWNER_SYM)}, { "PACKAGE", SYM(PACKAGE_MARIADB_SYM)}, { "PACK_KEYS", SYM(PACK_KEYS_SYM)}, { "PAGE", SYM(PAGE_SYM)}, @@ -604,7 +602,6 @@ SYMBOL symbols[] = { { "SLOW", SYM(SLOW)}, { "SNAPSHOT", SYM(SNAPSHOT_SYM)}, { "SMALLINT", SYM(SMALLINT)}, - { "SOCKET", SYM(SOCKET_SYM)}, { "SOFT", SYM(SOFT_SYM)}, { "SOME", SYM(ANY_SYM)}, { "SONAME", SYM(SONAME_SYM)}, diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 45b4362f347..bc76ea1c370 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -46,6 +46,7 @@ #include "transaction.h" #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #include "create_options.h" +#include "strfunc.h" /* We only use 1 mutex to guard the data structures - THR_LOCK_servers. @@ -76,7 +77,7 @@ static int delete_server_record(TABLE *table, LEX_CSTRING *name); static int delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options); /* update functions */ -static void prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, +static int prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered); static int update_server(THD *thd, FOREIGN_SERVER *existing, @@ -651,7 +652,7 @@ store_server_fields(TABLE *table, FOREIGN_SERVER *server) (uint) strlen(server->password), system_charset_info)) goto err; if (server->port > -1 && - table->field[PORT_FIELD]->store(server->port)) + table->field[PORT_FIELD]->store(server->port, true)) goto err; if (server->socket && table->field[SOCKET_FIELD]->store(server->socket, @@ -1176,6 +1177,47 @@ delete_server_record(TABLE *table, LEX_CSTRING *name) DBUG_RETURN(error); } + +static bool set_known_options(LEX_SERVER_OPTIONS *options) +{ + static const char *known_option_names[]= { + "user", "host", "database", "password", "owner", "socket", "port", 0 + }; + static const TYPELIB known_options= CREATE_TYPELIB_FOR(known_option_names); +#define CASE_KNOWN_OPTION(ID, NAME) case ID: options->NAME=opt->value; break; + + for (engine_option_value *opt= options->option_list; opt; opt= opt->next) + { + if (!opt->value.str) + continue; + switch (find_type(&known_options, opt->name.str, opt->name.length, 0)) { + CASE_KNOWN_OPTION(1, username); + CASE_KNOWN_OPTION(2, host); + CASE_KNOWN_OPTION(3, db); + CASE_KNOWN_OPTION(4, password); + CASE_KNOWN_OPTION(5, owner); + CASE_KNOWN_OPTION(6, socket); + case 7: // PORT + { + int error; + char *end= (char *) opt->value.str + opt->value.length; + char *old_end= end; + longlong p= my_strtoll10(opt->value.str, &end, &error); + if (error > 0 || end != old_end || p > INT32_MAX || p < 0) + { + my_error(ER_BAD_OPTION_VALUE, MYF(0), opt->value.str, "PORT"); + return 1; + } + options->port= (int32)p; + break; + } + default: break; + } + } + return 0; +} + + /* SYNOPSIS @@ -1199,6 +1241,9 @@ int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options) DBUG_PRINT("info", ("server_options->server_name %s", server_options->server_name.str)); + if (set_known_options(server_options)) + DBUG_RETURN(ER_BAD_OPTION_VALUE); + mysql_rwlock_wrlock(&THR_LOCK_servers); /* hit the memory first */ @@ -1289,9 +1334,10 @@ int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options) server_options->server_name.length))) goto end; - prepare_server_struct_for_update(server_options, existing, &altered); + error= prepare_server_struct_for_update(server_options, existing, &altered); - error= update_server(thd, existing, &altered); + if (!error) + error= update_server(thd, existing, &altered); /* close the servers table before we call closed_cached_connection_tables */ close_mysql_tables(thd); @@ -1351,7 +1397,7 @@ prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options) if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER)))) DBUG_RETURN(NULL); /* purecov: inspected */ -#define SET_SERVER_OR_RETURN(X, DEFAULT) \ +#define SET_SERVER_OR_RETURN(X) \ do { \ if (!(server->X= server_options->X.str ? \ strmake_root(&mem, server_options->X.str, \ @@ -1360,8 +1406,8 @@ prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options) } while(0) /* name and scheme are always set (the parser guarantees it) */ - SET_SERVER_OR_RETURN(server_name, NULL); - SET_SERVER_OR_RETURN(scheme, NULL); + SET_SERVER_OR_RETURN(server_name); + SET_SERVER_OR_RETURN(scheme); /* scheme-specific checks */ if (!strcasecmp(server->scheme, "mysql")) @@ -1375,12 +1421,12 @@ prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options) } } - SET_SERVER_OR_RETURN(host, ""); - SET_SERVER_OR_RETURN(db, ""); - SET_SERVER_OR_RETURN(username, ""); - SET_SERVER_OR_RETURN(password, ""); - SET_SERVER_OR_RETURN(socket, ""); - SET_SERVER_OR_RETURN(owner, ""); + SET_SERVER_OR_RETURN(host); + SET_SERVER_OR_RETURN(db); + SET_SERVER_OR_RETURN(username); + SET_SERVER_OR_RETURN(password); + SET_SERVER_OR_RETURN(socket); + SET_SERVER_OR_RETURN(owner); copy_option_list(&mem, server, server_options->option_list); server->server_name_length= server_options->server_name.length; @@ -1405,7 +1451,7 @@ prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options) */ -static void +static int prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) @@ -1417,6 +1463,9 @@ prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, DBUG_PRINT("info", ("existing name %s altered name %s", existing->server_name, altered->server_name)); + if (set_known_options(server_options)) + DBUG_RETURN(1); + /* The logic here is this: is this value set AND is it different than the existing value? @@ -1446,7 +1495,7 @@ prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, server_options->port != existing->port) ? server_options->port : -1; - DBUG_VOID_RETURN; + DBUG_RETURN(0); } /* diff --git a/sql/sql_show.cc b/sql/sql_show.cc index c6c872293cf..08bef31c75e 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1571,12 +1571,15 @@ bool mysql_show_create_server(THD *thd, LEX_CSTRING *name) bool first= true; while (option) { - if (!first) - buffer.append(STRING_WITH_LEN(", ")); - buffer.append(option->name); - buffer.append(STRING_WITH_LEN(" ")); - append_unescaped(&buffer, option->value.str, option->value.length); - first= false; + if (option->value.str) + { + if (!first) + buffer.append(STRING_WITH_LEN(", ")); + buffer.append(option->name); + buffer.append(STRING_WITH_LEN(" ")); + append_unescaped(&buffer, option->value.str, option->value.length); + first= false; + } option= option->next; } buffer.append(STRING_WITH_LEN(");")); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 21eb9e9c9bf..60815a0c660 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -898,7 +898,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token HELP_SYM %token HIGH_PRIORITY %token HISTORY_SYM /* MYSQL */ -%token HOST_SYM %token HOSTS_SYM %token HOUR_SYM /* SQL-2003-R */ %token ID_SYM /* MYSQL */ @@ -1009,7 +1008,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token OPTIONS_SYM %token OPTION /* SQL-2003-N */ %token OVERLAPS_SYM -%token OWNER_SYM %token PACK_KEYS_SYM %token PAGE_SYM %token PARSER_SYM @@ -1098,7 +1096,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token SLAVE_POS_SYM %token SLOW %token SNAPSHOT_SYM -%token SOCKET_SYM %token SOFT_SYM %token SONAME_SYM %token SOUNDS_SYM @@ -2938,21 +2935,6 @@ server_options_list: server_option: USER_SYM TEXT_STRING_sys { - MYSQL_YYABORT_UNLESS(Lex->server_options.username.str == 0); - Lex->server_options.username= $2; - engine_option_value *new_option= - new (thd->mem_root) engine_option_value( - engine_option_value::Name( - safe_lexcstrdup_root(thd->mem_root, $1)), - engine_option_value::Value( - safe_lexcstrdup_root(thd->mem_root, $2)), true); - new_option->link(&Lex->server_options.option_list, - &Lex->option_list_last); - } - | HOST_SYM TEXT_STRING_sys - { - MYSQL_YYABORT_UNLESS(Lex->server_options.host.str == 0); - Lex->server_options.host= $2; engine_option_value *new_option= new (thd->mem_root) engine_option_value( engine_option_value::Name( @@ -2964,21 +2946,6 @@ server_option: } | DATABASE TEXT_STRING_sys { - MYSQL_YYABORT_UNLESS(Lex->server_options.db.str == 0); - Lex->server_options.db= $2; - engine_option_value *new_option= - new (thd->mem_root) engine_option_value( - engine_option_value::Name( - safe_lexcstrdup_root(thd->mem_root, $1)), - engine_option_value::Value( - safe_lexcstrdup_root(thd->mem_root, $2)), true); - new_option->link(&Lex->server_options.option_list, - &Lex->option_list_last); - } - | OWNER_SYM TEXT_STRING_sys - { - MYSQL_YYABORT_UNLESS(Lex->server_options.owner.str == 0); - Lex->server_options.owner= $2; engine_option_value *new_option= new (thd->mem_root) engine_option_value( engine_option_value::Name( @@ -2990,21 +2957,6 @@ server_option: } | PASSWORD_SYM TEXT_STRING_sys { - MYSQL_YYABORT_UNLESS(Lex->server_options.password.str == 0); - Lex->server_options.password= $2; - engine_option_value *new_option= - new (thd->mem_root) engine_option_value( - engine_option_value::Name( - safe_lexcstrdup_root(thd->mem_root, $1)), - engine_option_value::Value( - safe_lexcstrdup_root(thd->mem_root, $2)), true); - new_option->link(&Lex->server_options.option_list, - &Lex->option_list_last); - } - | SOCKET_SYM TEXT_STRING_sys - { - MYSQL_YYABORT_UNLESS(Lex->server_options.socket.str == 0); - Lex->server_options.socket= $2; engine_option_value *new_option= new (thd->mem_root) engine_option_value( engine_option_value::Name( @@ -3016,23 +2968,6 @@ server_option: } | PORT_SYM ulong_num { - /* - We especially don't want this to happen: - - The value of $2 is ULONG_MAX, causing - server_options.port to be -1, which means "default - port". - - Because we are doing a check here, we may as well check - against the SQL data type in one go rather than just the - C++ type here and SQL type later in sql_servers.cc. - */ - if ($2 > INT32_MAX) - { - my_error(ER_DATA_OUT_OF_RANGE, myf(0), "port", "INT"); - MYSQL_YYABORT; - } - Lex->server_options.port= $2; engine_option_value *new_option= new (thd->mem_root) engine_option_value( engine_option_value::Name( @@ -3044,16 +2979,6 @@ server_option: /* port can be a quoted number */ | PORT_SYM TEXT_STRING_sys { - int error; - char *end= (char *) $2.str + $2.length; - longlong p= my_strtoll10($2.str, &end, &error); - if (error > 0 || end != (char *) $2.str + $2.length || - p > LONG_MAX || p < LONG_MIN) - { - thd->parse_error(); - MYSQL_YYABORT; - } - Lex->server_options.port= (long) p; engine_option_value *new_option= new (thd->mem_root) engine_option_value( engine_option_value::Name( @@ -16292,12 +16217,10 @@ keyword_sp_var_not_label: | FOLLOWING_SYM | GET_SYM | HELP_SYM - | HOST_SYM | INSTALL_SYM | OPTION | OPTIONS_SYM | OTHERS_MARIADB_SYM - | OWNER_SYM | PARSER_SYM | PERIOD_SYM | PORT_SYM @@ -16308,7 +16231,6 @@ keyword_sp_var_not_label: | RESTORE_SYM | SECURITY_SYM | SERVER_SYM - | SOCKET_SYM | SLAVE | SLAVES | SONAME_SYM From 0803e80ed4fcf8da1f8bcccefcb44f448d5d6be9 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 1 Aug 2026 23:14:40 +0200 Subject: [PATCH 045/150] MDEV-26910 mysqld_multi starts same instance multiple times with the risk to crash database Revert the fix 6ce0682b269e and implement deduplication differently - we want an array here to return groups in the order of appearence. --- scripts/mysqld_multi.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/mysqld_multi.sh b/scripts/mysqld_multi.sh index 221403f1ce1..ea97b7bc881 100644 --- a/scripts/mysqld_multi.sh +++ b/scripts/mysqld_multi.sh @@ -620,7 +620,8 @@ sub find_groups my ($raw_gids) = @_; my %gids; - my %groups; + my %seen_group; + my @groups; if (defined($raw_gids)) { @@ -659,7 +660,8 @@ sub find_groups # Use $2 + 0 to normalize numbers (002 + 0 -> 2) if (not defined($raw_gids) or $gids{$2 + 0}) { - $groups{"$1$2"} = 1; + push @groups, "$1$2" unless $seen_group{"$1$2"}; + $seen_group{"$1$2"}=1; } } elsif (/^\s*!include\s+(\S.*?)\s*$/) @@ -674,7 +676,7 @@ sub find_groups close CONF; } - return keys %groups; + return @groups; } #### From a9309230b3cc176ba102f210163cff345b939ab2 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 1 Aug 2026 23:42:59 +0200 Subject: [PATCH 046/150] MDEV-40411 fix the comparison don't convert ulonglong max_mem_used to uint32 before the comparison --- sql/item_strfunc.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 9b31b501cad..b4388f12e7f 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1409,8 +1409,8 @@ bool Item_func_sformat::fix_length_and_dec(THD *thd) return TRUE; } - max_length= MY_MIN((uint32)thd->variables.max_allowed_packet, - (uint32)thd->variables.max_mem_used); + max_length= (uint32)MY_MIN((ulonglong)thd->variables.max_allowed_packet, + thd->variables.max_mem_used); return FALSE; } From 9d4ffe23b25808f7858b2dc48106fbd60cf0e513 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 8 Jul 2026 19:13:39 +0200 Subject: [PATCH 047/150] MDEV-40312 SHOW CREATE SERVER incorrect quoting quote protocol name and option names --- mysql-test/main/grant_server.result | 2 +- mysql-test/main/mysqldump-system,win.rdiff | 4 ++-- mysql-test/main/mysqldump-system.result | 22 ++++++++++++++-------- mysql-test/main/mysqldump-system.test | 3 +++ mysql-test/main/servers.result | 21 +++++++++++++++------ mysql-test/main/servers.test | 10 ++++++++++ mysql-test/main/servers_upgrade.result | 4 ++-- sql/sql_show.cc | 2 +- 8 files changed, 48 insertions(+), 20 deletions(-) diff --git a/mysql-test/main/grant_server.result b/mysql-test/main/grant_server.result index c4e6cd19f53..71c47d7de8e 100644 --- a/mysql-test/main/grant_server.result +++ b/mysql-test/main/grant_server.result @@ -86,7 +86,7 @@ GRANT FEDERATED ADMIN ON *.* TO user1@localhost; connect con1,localhost,user1,,; SHOW CREATE SERVER srv; Server Create Server -srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `mysql` OPTIONS (USER 'remote_user', HOST 'localhost', PASSWORD 'secret', DATABASE 'test2'); +srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `mysql` OPTIONS (`USER` 'remote_user', `HOST` 'localhost', `PASSWORD` 'secret', `DATABASE` 'test2'); disconnect con1; connection default; DROP SERVER srv; diff --git a/mysql-test/main/mysqldump-system,win.rdiff b/mysql-test/main/mysqldump-system,win.rdiff index d9435bd1a63..d75c16dca9c 100644 --- a/mysql-test/main/mysqldump-system,win.rdiff +++ b/mysql-test/main/mysqldump-system,win.rdiff @@ -6,7 +6,7 @@ mysql.time_zone_transition 3719776009 -mysql.plugin 1587119305 +mysql.plugin 2184891911 - mysql.servers 1109359608 + mysql.servers 3351805004 -mysql.func 2140302938 +mysql.func 2154697260 mysql.innodb_table_stats 1972297402 @@ -18,7 +18,7 @@ mysql.time_zone_transition 3719776009 -mysql.plugin 1587119305 +mysql.plugin 2184891911 - mysql.servers 1109359608 + mysql.servers 3351805004 -mysql.func 2140302938 +mysql.func 2154697260 mysql.innodb_table_stats 1972297402 diff --git a/mysql-test/main/mysqldump-system.result b/mysql-test/main/mysqldump-system.result index 23078fae512..3a160b38d4f 100644 --- a/mysql-test/main/mysqldump-system.result +++ b/mysql-test/main/mysqldump-system.result @@ -40,6 +40,7 @@ CREATE SERVER s1 FOREIGN DATA WRAPPER mysql OPTIONS (HOST 'localhost', DATABASE 'd''b', USER 'o''brien', PASSWORD 'pw''d'); CREATE SERVER `s'2` FOREIGN DATA WRAPPER 'my''sql' OPTIONS (HOST 'h2', DATABASE 'db2', USER 'u2'); +create server `s``3` foreign data wrapper mysql options (host 'value'); CREATE FUNCTION metaphon RETURNS STRING SONAME "UDF_EXAMPLE_LIB"; CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME "UDF_EXAMPLE_LIB"; # @@ -95,8 +96,9 @@ DROP ROLE mariadb_dump_import_role; /*M!100203 EXECUTE IMMEDIATE CONCAT('SET ROLE ', @current_role) */; CREATE FUNCTION `metaphon` RETURNS STRING SONAME 'UDF_EXAMPLE_LIB'; CREATE AGGREGATE FUNCTION `avgcost` RETURNS REAL SONAME 'UDF_EXAMPLE_LIB'; -CREATE SERVER `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (HOST 'h2', DATABASE 'db2', USER 'u2'); -CREATE SERVER `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (HOST 'localhost', DATABASE 'd''b', USER 'o''brien', PASSWORD 'pw''d'); +CREATE SERVER `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (`HOST` 'h2', `DATABASE` 'db2', `USER` 'u2'); +CREATE SERVER `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (`HOST` 'localhost', `DATABASE` 'd''b', `USER` 'o''brien', `PASSWORD` 'pw''d'); +CREATE SERVER `s``3` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` 'value'); USE mysql; @@ -718,8 +720,9 @@ DROP ROLE mariadb_dump_import_role; CREATE /*M!100103 OR REPLACE */ FUNCTION `metaphon` RETURNS STRING SONAME 'UDF_EXAMPLE_LIB'; /*!50701 DROP FUNCTION IF EXISTS `avgcost` */; CREATE /*M!100103 OR REPLACE */ AGGREGATE FUNCTION `avgcost` RETURNS REAL SONAME 'UDF_EXAMPLE_LIB'; -CREATE /*M!100103 OR REPLACE */ SERVER `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (HOST 'h2', DATABASE 'db2', USER 'u2'); -CREATE /*M!100103 OR REPLACE */ SERVER `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (HOST 'localhost', DATABASE 'd''b', USER 'o''brien', PASSWORD 'pw''d'); +CREATE /*M!100103 OR REPLACE */ SERVER `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (`HOST` 'h2', `DATABASE` 'db2', `USER` 'u2'); +CREATE /*M!100103 OR REPLACE */ SERVER `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (`HOST` 'localhost', `DATABASE` 'd''b', `USER` 'o''brien', `PASSWORD` 'pw''d'); +CREATE /*M!100103 OR REPLACE */ SERVER `s``3` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` 'value'); USE mysql; @@ -1317,8 +1320,9 @@ DROP ROLE mariadb_dump_import_role; /*M!100203 EXECUTE IMMEDIATE CONCAT('SET ROLE ', @current_role) */; CREATE FUNCTION IF NOT EXISTS `metaphon` RETURNS STRING SONAME 'UDF_EXAMPLE_LIB'; CREATE AGGREGATE FUNCTION IF NOT EXISTS `avgcost` RETURNS REAL SONAME 'UDF_EXAMPLE_LIB'; -CREATE SERVER /*M!100103 IF NOT EXISTS */ `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (HOST 'h2', DATABASE 'db2', USER 'u2'); -CREATE SERVER /*M!100103 IF NOT EXISTS */ `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (HOST 'localhost', DATABASE 'd''b', USER 'o''brien', PASSWORD 'pw''d'); +CREATE SERVER /*M!100103 IF NOT EXISTS */ `s'2` FOREIGN DATA WRAPPER `my'sql` OPTIONS (`HOST` 'h2', `DATABASE` 'db2', `USER` 'u2'); +CREATE SERVER /*M!100103 IF NOT EXISTS */ `s1` FOREIGN DATA WRAPPER `mysql` OPTIONS (`HOST` 'localhost', `DATABASE` 'd''b', `USER` 'o''brien', `PASSWORD` 'pw''d'); +CREATE SERVER /*M!100103 IF NOT EXISTS */ `s``3` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` 'value'); USE mysql; @@ -1876,7 +1880,7 @@ Table Checksum mysql.roles_mapping 2510045525 mysql.time_zone_transition 3719776009 mysql.plugin 1587119305 -mysql.servers 1109359608 +mysql.servers 3351805004 mysql.func 2140302938 mysql.innodb_table_stats 1972297402 mysql.table_stats 1911089388 @@ -1897,6 +1901,7 @@ DROP FUNCTION IF EXISTS metaphon; DROP FUNCTION IF EXISTS avgcost; DROP SERVER s1; DROP SERVER `s'2`; +DROP SERVER `s``3`; set time_zone= @@global.time_zone; # Restore from mysqldump DROP USER mariadb_test_restore; @@ -1913,7 +1918,7 @@ Table Checksum mysql.roles_mapping 2510045525 mysql.time_zone_transition 3719776009 mysql.plugin 1587119305 -mysql.servers 1109359608 +mysql.servers 3351805004 mysql.func 2140302938 mysql.innodb_table_stats 1972297402 mysql.table_stats 1911089388 @@ -1921,6 +1926,7 @@ DROP FUNCTION IF EXISTS metaphon; DROP FUNCTION IF EXISTS avgcost; DROP SERVER s1; DROP SERVER `s'2`; +DROP SERVER `s``3`; DELETE FROM mysql.column_stats WHERE db_name='mysql'; DELETE FROM mysql.index_stats WHERE db_name='mysql'; DELETE FROM mysql.table_stats WHERE db_name='mysql'; diff --git a/mysql-test/main/mysqldump-system.test b/mysql-test/main/mysqldump-system.test index 0866661c4ae..ef89c6b9e65 100644 --- a/mysql-test/main/mysqldump-system.test +++ b/mysql-test/main/mysqldump-system.test @@ -62,6 +62,7 @@ CREATE SERVER s1 FOREIGN DATA WRAPPER mysql OPTIONS (HOST 'localhost', DATABASE 'd''b', USER 'o''brien', PASSWORD 'pw''d'); CREATE SERVER `s'2` FOREIGN DATA WRAPPER 'my''sql' OPTIONS (HOST 'h2', DATABASE 'db2', USER 'u2'); +create server `s``3` foreign data wrapper mysql options (host 'value'); --replace_result $UDF_EXAMPLE_SO UDF_EXAMPLE_LIB eval CREATE FUNCTION metaphon RETURNS STRING SONAME "$UDF_EXAMPLE_SO"; @@ -131,6 +132,7 @@ DROP FUNCTION IF EXISTS metaphon; DROP FUNCTION IF EXISTS avgcost; DROP SERVER s1; DROP SERVER `s'2`; +DROP SERVER `s``3`; set time_zone= @@global.time_zone; --echo # Restore from mysqldump @@ -155,6 +157,7 @@ DROP FUNCTION IF EXISTS avgcost; DROP SERVER s1; DROP SERVER `s'2`; +DROP SERVER `s``3`; # EITS && innodb stats DELETE FROM mysql.column_stats WHERE db_name='mysql'; diff --git a/mysql-test/main/servers.result b/mysql-test/main/servers.result index 9c53216a89b..ea7567ecfef 100644 --- a/mysql-test/main/servers.result +++ b/mysql-test/main/servers.result @@ -59,20 +59,20 @@ create server srv foreign data wrapper mysql options (host "localhost", port 12345, wait_what "it's all good"); show create server srv; Server Create Server -srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `mysql` OPTIONS (host 'localhost', port '12345', wait_what 'it''s all good'); +srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` 'localhost', `port` '12345', `wait_what` 'it''s all good'); create or replace server srv foreign data wrapper Foo options (host "somewhere.else", port 54321, wait_what "it's all good", foo 'bar'); show create server srv; Server Create Server -srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (host 'somewhere.else', port '54321', wait_what 'it''s all good', foo 'bar'); +srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (`host` 'somewhere.else', `port` '54321', `wait_what` 'it''s all good', `foo` 'bar'); alter server srv options (socket "sock", port 123, foo "", bar ')"{'); show create server srv; Server Create Server -srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (host 'somewhere.else', wait_what 'it''s all good', socket 'sock', port '123', foo '', bar ')"{'); +srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (`host` 'somewhere.else', `wait_what` 'it''s all good', `socket` 'sock', `port` '123', `foo` '', `bar` ')"{'); alter server srv options (socket "sock", port 123, bar "quux"); show create server srv; Server Create Server -srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (host 'somewhere.else', wait_what 'it''s all good', foo '', socket 'sock', port '123', bar 'quux'); +srv CREATE SERVER `srv` FOREIGN DATA WRAPPER `Foo` OPTIONS (`host` 'somewhere.else', `wait_what` 'it''s all good', `foo` '', `socket` 'sock', `port` '123', `bar` 'quux'); create or replace server srv foreign data wrapper foo options (host "localhost", port "12345"); create or replace server srv foreign data wrapper mysql options @@ -179,16 +179,25 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp CREATE SERVER s FOREIGN DATA WRAPPER mysqL OPTIONS (HOST '',PORT '1', PORT '2'); SHOW CREATE SERVER s; Server Create Server -s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (HOST '', PORT '2'); +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (`HOST` '', `PORT` '2'); select * from mysql.servers; Server_name Host Db Username Password Port Socket Wrapper Owner Options s 2 mysqL {"HOST": "", "PORT": "2"} ALTER SERVER s options (PORT '7', PORT '8'); SHOW CREATE SERVER s; Server Create Server -s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (HOST '', PORT '8'); +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysqL` OPTIONS (`HOST` '', `PORT` '8'); select * from mysql.servers; Server_name Host Db Username Password Port Socket Wrapper Owner Options s 8 mysqL {"HOST": "", "PORT": "8"} DROP SERVER s; # End of 10.11 tests +# +# MDEV-40312 SHOW CREATE SERVER incorrect quoting +# +create server s1 foreign data wrapper 'my''sql' options (`ho``st` 'va''lue'); +show create server s1; +Server Create Server +s1 CREATE SERVER `s1` FOREIGN DATA WRAPPER `my'sql` OPTIONS (`ho``st` 'va''lue'); +drop server s1; +# End of 11.8 tests diff --git a/mysql-test/main/servers.test b/mysql-test/main/servers.test index 560283f0eed..2e58fc2a4e9 100644 --- a/mysql-test/main/servers.test +++ b/mysql-test/main/servers.test @@ -191,3 +191,13 @@ select * from mysql.servers; DROP SERVER s; --echo # End of 10.11 tests + +--echo # +--echo # MDEV-40312 SHOW CREATE SERVER incorrect quoting +--echo # + +create server s1 foreign data wrapper 'my''sql' options (`ho``st` 'va''lue'); +show create server s1; +drop server s1; + +--echo # End of 11.8 tests diff --git a/mysql-test/main/servers_upgrade.result b/mysql-test/main/servers_upgrade.result index 5fe4cc73556..7290af58a63 100644 --- a/mysql-test/main/servers_upgrade.result +++ b/mysql-test/main/servers_upgrade.result @@ -5,11 +5,11 @@ call mtr.add_suppression('$SEARCH_PATTERN'); create server s foreign data wrapper mysql options (host "127.0.0.1", database "test", user "root", port 3306); show create server s; Server Create Server -s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (host '127.0.0.1', database 'test', user 'root', port '3306'); +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` '127.0.0.1', `database` 'test', `user` 'root', `port` '3306'); alter table mysql.servers drop column Options; show create server s; Server Create Server -s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (host '127.0.0.1', database 'test', user 'root', port '3306'); +s CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (`host` '127.0.0.1', `database` 'test', `user` 'root', `port` '3306'); # restart show create server s; Server Create Server diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 08bef31c75e..ed108adff32 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1575,7 +1575,7 @@ bool mysql_show_create_server(THD *thd, LEX_CSTRING *name) { if (!first) buffer.append(STRING_WITH_LEN(", ")); - buffer.append(option->name); + append_identifier(thd, &buffer, &option->name); buffer.append(STRING_WITH_LEN(" ")); append_unescaped(&buffer, option->value.str, option->value.length); first= false; From 2a4cfdde2bcc1eddf87c36c19a25381f278c3956 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 11 Jul 2026 12:00:17 +0200 Subject: [PATCH 048/150] mysqldump: remove dead and broken code since 2006 (3840774309bc) mysqldump tried to be smart when dumping events - it tried to automatically detected a delimiter per event that was not present in the event body, using ";;" by default. This never worked, was broken since the first commit. It either used the default ";;" or failed after trying the same ";;" delimiter 2147483646 times. All other objects (routines, triggers, etc) used a hard-coded ";;" for 20 years, which apparently worked fine. Let's remove the old broken logic and dump events like all other objects, --- client/mysqldump.cc | 189 ++++++++------------------ mysql-test/main/ddl_i18n_koi8r.result | 138 ++++++++++--------- mysql-test/main/ddl_i18n_utf8.result | 138 ++++++++++--------- mysql-test/main/mysqldump.result | 30 ++-- mysql-test/suite/roles/definer.result | 92 +++++++------ 5 files changed, 255 insertions(+), 332 deletions(-) diff --git a/client/mysqldump.cc b/client/mysqldump.cc index e26fd833cac..362e5c30d4e 100644 --- a/client/mysqldump.cc +++ b/client/mysqldump.cc @@ -1744,7 +1744,6 @@ static char *my_case_str(const char *str, static int switch_db_collation(FILE *sql_file, const char *db_name, - const char *delimiter, const char *current_db_cl_name, const char *required_db_cl_name, int *db_cl_altered) @@ -1760,11 +1759,10 @@ static int switch_db_collation(FILE *sql_file, return 1; fprintf(sql_file, - "ALTER DATABASE %s CHARACTER SET %s COLLATE %s %s\n", + "ALTER DATABASE %s CHARACTER SET %s COLLATE %s ;\n", (const char *) quoted_db_name, (const char *) db_cl->cs_name.str, - (const char *) db_cl->coll_name.str, - (const char *) delimiter); + (const char *) db_cl->coll_name.str); *db_cl_altered= 1; @@ -1779,7 +1777,6 @@ static int switch_db_collation(FILE *sql_file, static int restore_db_collation(FILE *sql_file, const char *db_name, - const char *delimiter, const char *db_cl_name) { char quoted_db_buf[NAME_LEN * 2 + 3]; @@ -1791,100 +1788,71 @@ static int restore_db_collation(FILE *sql_file, return 1; fprintf(sql_file, - "ALTER DATABASE %s CHARACTER SET %s COLLATE %s %s\n", + "ALTER DATABASE %s CHARACTER SET %s COLLATE %s ;\n", (const char *) quoted_db_name, (const char *) db_cl->cs_name.str, - (const char *) db_cl->coll_name.str, - (const char *) delimiter); + (const char *) db_cl->coll_name.str); return 0; } static void switch_cs_variables(FILE *sql_file, - const char *delimiter, const char *character_set_client, const char *character_set_results, const char *collation_connection) { fprintf(sql_file, - "/*!50003 SET @saved_cs_client = @@character_set_client */ %s\n" - "/*!50003 SET @saved_cs_results = @@character_set_results */ %s\n" - "/*!50003 SET @saved_col_connection = @@collation_connection */ %s\n" - "/*!50003 SET character_set_client = %s */ %s\n" - "/*!50003 SET character_set_results = %s */ %s\n" - "/*!50003 SET collation_connection = %s */ %s\n", - (const char *) delimiter, - (const char *) delimiter, - (const char *) delimiter, - + "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" + "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" + "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" + "/*!50003 SET character_set_client = %s */ ;\n" + "/*!50003 SET character_set_results = %s */ ;\n" + "/*!50003 SET collation_connection = %s */ ;\n", (const char *) character_set_client, - (const char *) delimiter, - (const char *) character_set_results, - (const char *) delimiter, - - (const char *) collation_connection, - (const char *) delimiter); + (const char *) collation_connection); } -static void restore_cs_variables(FILE *sql_file, - const char *delimiter) +static void restore_cs_variables(FILE *sql_file) { fprintf(sql_file, - "/*!50003 SET character_set_client = @saved_cs_client */ %s\n" - "/*!50003 SET character_set_results = @saved_cs_results */ %s\n" - "/*!50003 SET collation_connection = @saved_col_connection */ %s\n", - (const char *) delimiter, - (const char *) delimiter, - (const char *) delimiter); + "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" + "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" + "/*!50003 SET collation_connection = @saved_col_connection */ ;\n"); } -static void switch_sql_mode(FILE *sql_file, - const char *delimiter, - const char *sql_mode) +static void switch_sql_mode(FILE *sql_file, const char *sql_mode) { fprintf(sql_file, - "/*!50003 SET @saved_sql_mode = @@sql_mode */ %s\n" - "/*!50003 SET sql_mode = '%s' */ %s\n", - (const char *) delimiter, - - (const char *) sql_mode, - (const char *) delimiter); + "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" + "/*!50003 SET sql_mode = '%s' */ ;\n", + (const char *) sql_mode); } -static void restore_sql_mode(FILE *sql_file, - const char *delimiter) +static void restore_sql_mode(FILE *sql_file) { fprintf(sql_file, - "/*!50003 SET sql_mode = @saved_sql_mode */ %s\n", - (const char *) delimiter); + "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n"); } -static void switch_time_zone(FILE *sql_file, - const char *delimiter, - const char *time_zone) +static void switch_time_zone(FILE *sql_file, const char *time_zone) { fprintf(sql_file, - "/*!50003 SET @saved_time_zone = @@time_zone */ %s\n" - "/*!50003 SET time_zone = '%s' */ %s\n", - (const char *) delimiter, - - (const char *) time_zone, - (const char *) delimiter); + "/*!50003 SET @saved_time_zone = @@time_zone */ ;\n" + "/*!50003 SET time_zone = '%s' */ ;\n", + (const char *) time_zone); } -static void restore_time_zone(FILE *sql_file, - const char *delimiter) +static void restore_time_zone(FILE *sql_file) { fprintf(sql_file, - "/*!50003 SET time_zone = @saved_time_zone */ %s\n", - (const char *) delimiter); + "/*!50003 SET time_zone = @saved_time_zone */ ;\n"); } @@ -2679,44 +2647,6 @@ static void print_comment(FILE *sql_file, my_bool is_error, const char *format, print_xml_comment(sql_file, strlen(comment_buff), comment_buff); } -/* - create_delimiter - Generate a new (null-terminated) string that does not exist in query - and is therefore suitable for use as a query delimiter. Store this - delimiter in delimiter_buff . - - This is quite simple in that it doesn't even try to parse statements as an - interpreter would. It merely returns a string that is not in the query, which - is much more than adequate for constructing a delimiter. - - RETURN - ptr to the delimiter on Success - NULL on Failure -*/ -static char *create_delimiter(char *query, char *delimiter_buff, - int delimiter_max_size) -{ - int proposed_length; - char *presence; - - delimiter_buff[0]= ';'; /* start with one semicolon, and */ - - for (proposed_length= 2; proposed_length < delimiter_max_size; - delimiter_max_size++) { - - delimiter_buff[proposed_length-1]= ';'; /* add semicolons, until */ - delimiter_buff[proposed_length]= '\0'; - - presence = strstr(query, delimiter_buff); - if (presence == NULL) { /* the proposed delimiter is not in the query. */ - return delimiter_buff; - } - - } - return NULL; /* but if we run out of space, return nothing at all. */ -} - - /* dump_events_for_db -- retrieves list of events for a given db, and prints out @@ -2731,7 +2661,6 @@ static uint dump_events_for_db(char *db) char query_buff[QUERY_LENGTH]; char db_name_buff[NAME_LEN*2+3], name_buff[NAME_LEN*2+3]; char *event_name; - char delimiter[QUERY_LENGTH]; FILE *sql_file= md_result_file; MYSQL_RES *event_res= NULL, *event_list_res= NULL; MYSQL_ROW row, event_list_row; @@ -2759,7 +2688,6 @@ static uint dump_events_for_db(char *db) if (mysql_query_with_error_report(mysql, &event_list_res, "show events")) DBUG_RETURN(0); - safe_strcpy(delimiter, sizeof(delimiter), ";"); if (mysql_num_rows(event_list_res) > 0) { if (opt_xml) @@ -2805,25 +2733,16 @@ static uint dump_events_for_db(char *db) char *query_str; if (opt_drop) - fprintf(sql_file, "/*!50106 DROP EVENT IF EXISTS %s */%s\n", - event_name, delimiter); - - if (create_delimiter(row[3], delimiter, sizeof(delimiter)) == NULL) - { - fprintf(stderr, "%s: Warning: Can't create delimiter for event '%s'\n", - my_progname_short, event_name); - goto err; - } - - fprintf(sql_file, "DELIMITER %s\n", delimiter); + fprintf(sql_file, "/*!50106 DROP EVENT IF EXISTS %s */;\n", + event_name); if (mysql_num_fields(event_res) >= 7) { - if (switch_db_collation(sql_file, db_name_buff, delimiter, + if (switch_db_collation(sql_file, db_name_buff, db_cl_name, row[6], &db_cl_altered)) goto err; - switch_cs_variables(sql_file, delimiter, + switch_cs_variables(sql_file, row[4], /* character_set_client */ row[4], /* character_set_results */ row[5]); /* collation_connection */ @@ -2846,32 +2765,31 @@ static uint dump_events_for_db(char *db) "--\n"); } - switch_sql_mode(sql_file, delimiter, row[1]); + switch_sql_mode(sql_file, row[1]); - switch_time_zone(sql_file, delimiter, row[2]); + switch_time_zone(sql_file, row[2]); query_str= cover_definer_clause(row[3], strlen(row[3]), C_STRING_WITH_LEN("50117"), C_STRING_WITH_LEN("50106"), C_STRING_WITH_LEN(" EVENT")); - fprintf(sql_file, - "/*!50106 %s \n*/ %s\n", - (const char *) (query_str != NULL ? query_str : row[3]), - (const char *) delimiter); + fprintf(sql_file, "DELIMITER ;;\n" + "/*!50106 %s \n*/ ;;\n" + "DELIMITER ;\n", + (const char *) (query_str != NULL ? query_str : row[3])); my_free(query_str); - restore_time_zone(sql_file, delimiter); - restore_sql_mode(sql_file, delimiter); + restore_time_zone(sql_file); + restore_sql_mode(sql_file); if (mysql_num_fields(event_res) >= 7) { - restore_cs_variables(sql_file, delimiter); + restore_cs_variables(sql_file); if (db_cl_altered) { - if (restore_db_collation(sql_file, db_name_buff, delimiter, - db_cl_name)) + if (restore_db_collation(sql_file, db_name_buff, db_cl_name)) goto err; } } @@ -2888,7 +2806,6 @@ static uint dump_events_for_db(char *db) } else { - fprintf(sql_file, "DELIMITER ;\n"); fprintf(sql_file, "/*!50106 SET TIME_ZONE= @save_time_zone */ ;\n"); } @@ -3048,7 +2965,7 @@ static uint dump_routines_for_db(char *db) continue; } - switch_sql_mode(sql_file, ";", row[1]); + switch_sql_mode(sql_file, row[1]); if (opt_drop) fprintf(sql_file, "/*!50003 DROP %s IF EXISTS %s */;\n", @@ -3056,7 +2973,7 @@ static uint dump_routines_for_db(char *db) if (mysql_num_fields(routine_res) >= 6) { - if (switch_db_collation(sql_file, db, ";", + if (switch_db_collation(sql_file, db, db_cl_name, row[5], &db_cl_altered)) { mysql_free_result(routine_res); @@ -3065,7 +2982,7 @@ static uint dump_routines_for_db(char *db) DBUG_RETURN(1); } - switch_cs_variables(sql_file, ";", + switch_cs_variables(sql_file, row[3], /* character_set_client */ row[3], /* character_set_results */ row[4]); /* collation_connection */ @@ -3096,15 +3013,15 @@ static uint dump_routines_for_db(char *db) "DELIMITER ;\n", (const char *) row[2]); - restore_sql_mode(sql_file, ";"); + restore_sql_mode(sql_file); if (mysql_num_fields(routine_res) >= 6) { - restore_cs_variables(sql_file, ";"); + restore_cs_variables(sql_file); if (db_cl_altered) { - if (restore_db_collation(sql_file, db, ";", db_cl_name)) + if (restore_db_collation(sql_file, db, db_cl_name)) { mysql_free_result(routine_res); mysql_free_result(routine_list_res); @@ -4010,16 +3927,16 @@ static int dump_trigger(FILE *sql_file, MYSQL_RES *show_create_trigger_rs, continue; } - if (switch_db_collation(sql_file, db_name, ";", + if (switch_db_collation(sql_file, db_name, db_cl_name, row[5], &db_cl_altered)) DBUG_RETURN(TRUE); - switch_cs_variables(sql_file, ";", + switch_cs_variables(sql_file, row[3], /* character_set_client */ row[3], /* character_set_results */ row[4]); /* collation_connection */ - switch_sql_mode(sql_file, ";", row[1]); + switch_sql_mode(sql_file, row[1]); if (opt_drop_trigger) fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", @@ -4037,12 +3954,12 @@ static int dump_trigger(FILE *sql_file, MYSQL_RES *show_create_trigger_rs, my_free(query_str); - restore_sql_mode(sql_file, ";"); - restore_cs_variables(sql_file, ";"); + restore_sql_mode(sql_file); + restore_cs_variables(sql_file); if (db_cl_altered) { - if (restore_db_collation(sql_file, db_name, ";", db_cl_name)) + if (restore_db_collation(sql_file, db_name, db_cl_name)) DBUG_RETURN(TRUE); } } diff --git a/mysql-test/main/ddl_i18n_koi8r.result b/mysql-test/main/ddl_i18n_koi8r.result index f5aa94d1410..29d686fc540 100644 --- a/mysql-test/main/ddl_i18n_koi8r.result +++ b/mysql-test/main/ddl_i18n_koi8r.result @@ -2509,18 +2509,18 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysqltest1` /*!40100 DEFAULT CHARACTER USE `mysqltest1`; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = koi8r */ ; +/*!50003 SET character_set_results = koi8r */ ; +/*!50003 SET collation_connection = koi8r_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = koi8r */ ;; -/*!50003 SET character_set_results = koi8r */ ;; -/*!50003 SET collation_connection = koi8r_general_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev1` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE ÐÅÒÅÍ1 CHAR(10); SELECT @@ -2532,24 +2532,25 @@ COLLATION(_utf8 'текст') AS c4, @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ; +ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = koi8r */ ; +/*!50003 SET character_set_results = koi8r */ ; +/*!50003 SET collation_connection = koi8r_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = koi8r */ ;; -/*!50003 SET character_set_results = koi8r */ ;; -/*!50003 SET collation_connection = koi8r_general_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev2` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE ÐÅÒÅÍ1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2561,13 +2562,13 @@ COLLATION(_utf8 'текст') AS c4, @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; ---> Dumping mysqltest1 to ddl_i18n_koi8r.events.mysqltest1.sql @@ -2579,18 +2580,18 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysqltest2` /*!40100 DEFAULT CHARACTER USE `mysqltest2`; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = koi8r */ ; +/*!50003 SET character_set_results = koi8r */ ; +/*!50003 SET collation_connection = koi8r_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = koi8r */ ;; -/*!50003 SET character_set_results = koi8r */ ;; -/*!50003 SET collation_connection = koi8r_general_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev3` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE ÐÅÒÅÍ1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2602,24 +2603,25 @@ COLLATION(_utf8 'текст') AS c4, @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ; +ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = koi8r */ ; +/*!50003 SET character_set_results = koi8r */ ; +/*!50003 SET collation_connection = koi8r_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = koi8r */ ;; -/*!50003 SET character_set_results = koi8r */ ;; -/*!50003 SET collation_connection = koi8r_general_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev4` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE ÐÅÒÅÍ1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2631,13 +2633,13 @@ COLLATION(_utf8 'текст') AS c4, @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; ---> Dumping mysqltest2 to ddl_i18n_koi8r.events.mysqltest2.sql diff --git a/mysql-test/main/ddl_i18n_utf8.result b/mysql-test/main/ddl_i18n_utf8.result index 8fea6c7ade4..906200f72b7 100644 --- a/mysql-test/main/ddl_i18n_utf8.result +++ b/mysql-test/main/ddl_i18n_utf8.result @@ -2509,18 +2509,18 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysqltest1` /*!40100 DEFAULT CHARACTER USE `mysqltest1`; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb3 */ ; +/*!50003 SET character_set_results = utf8mb3 */ ; +/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = utf8mb3 */ ;; -/*!50003 SET character_set_results = utf8mb3 */ ;; -/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev1` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE перем1 CHAR(10); SELECT @@ -2532,24 +2532,25 @@ COLLATION(_koi8r ' @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ; +ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb3 */ ; +/*!50003 SET character_set_results = utf8mb3 */ ; +/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest1` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = utf8mb3 */ ;; -/*!50003 SET character_set_results = utf8mb3 */ ;; -/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev2` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE перем1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2561,13 +2562,13 @@ COLLATION(_koi8r ' @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest1` CHARACTER SET cp866 COLLATE cp866_general_ci ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; ---> Dumping mysqltest1 to ddl_i18n_utf8events.mysqltest1.sql @@ -2579,18 +2580,18 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysqltest2` /*!40100 DEFAULT CHARACTER USE `mysqltest2`; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb3 */ ; +/*!50003 SET character_set_results = utf8mb3 */ ; +/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = utf8mb3 */ ;; -/*!50003 SET character_set_results = utf8mb3 */ ;; -/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev3` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE перем1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2602,24 +2603,25 @@ COLLATION(_koi8r ' @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ; +ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb3 */ ; +/*!50003 SET character_set_results = utf8mb3 */ ; +/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -ALTER DATABASE `mysqltest2` CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = utf8mb3 */ ;; -/*!50003 SET character_set_results = utf8mb3 */ ;; -/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `ev4` ON SCHEDULE AT '1970-01-02 00:00:00' ON COMPLETION PRESERVE DISABLE DO BEGIN DECLARE перем1 CHAR(10) CHARACTER SET utf8; SELECT @@ -2631,13 +2633,13 @@ COLLATION(_koi8r ' @@character_set_client AS c6; END */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; -ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +ALTER DATABASE `mysqltest2` CHARACTER SET cp866 COLLATE cp866_general_ci ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; ---> Dumping mysqltest2 to ddl_i18n_utf8events.mysqltest2.sql diff --git a/mysql-test/main/mysqldump.result b/mysql-test/main/mysqldump.result index 2e08597a081..4600ef81c1f 100644 --- a/mysql-test/main/mysqldump.result +++ b/mysql-test/main/mysqldump.result @@ -6973,25 +6973,25 @@ mariadb-dump: Couldn't execute 'SHOW CREATE FUNCTION `f1`': Undeclared variable: COMMIT; SET AUTOCOMMIT=@OLD_AUTOCOMMIT; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb3 */ ; +/*!50003 SET character_set_results = utf8mb3 */ ; +/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = '' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = utf8mb3 */ ;; -/*!50003 SET character_set_results = utf8mb3 */ ;; -/*!50003 SET collation_connection = utf8mb3_uca1400_ai_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = '' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`root`@`localhost`*/ /*!50106 EVENT `e1` ON SCHEDULE EVERY 1 YEAR STARTS '2030-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO select not_a_value */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = '' */ ; diff --git a/mysql-test/suite/roles/definer.result b/mysql-test/suite/roles/definer.result index a38952147ce..1906d54cb6e 100644 --- a/mysql-test/suite/roles/definer.result +++ b/mysql-test/suite/roles/definer.result @@ -393,61 +393,63 @@ DELIMITER ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = latin1 */ ; +/*!50003 SET character_set_results = latin1 */ ; +/*!50003 SET collation_connection = latin1_swedish_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = latin1 */ ;; -/*!50003 SET character_set_results = latin1 */ ;; -/*!50003 SET collation_connection = latin1_swedish_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`role1`*/ /*!50106 EVENT `e1` ON SCHEDULE EVERY 1 SECOND STARTS '2000-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO insert t1 values (111, 2, 0) */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = latin1 */ ; +/*!50003 SET character_set_results = latin1 */ ; +/*!50003 SET collation_connection = latin1_swedish_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = latin1 */ ;; -/*!50003 SET character_set_results = latin1 */ ;; -/*!50003 SET collation_connection = latin1_swedish_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`role2`*/ /*!50106 EVENT `e2` ON SCHEDULE EVERY 1 SECOND STARTS '2000-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO insert t1 values (111, 4, 0) */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; +DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = latin1 */ ; +/*!50003 SET character_set_results = latin1 */ ; +/*!50003 SET collation_connection = latin1_swedish_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +/*!50003 SET @saved_time_zone = @@time_zone */ ; +/*!50003 SET time_zone = 'SYSTEM' */ ; DELIMITER ;; -/*!50003 SET @saved_cs_client = @@character_set_client */ ;; -/*!50003 SET @saved_cs_results = @@character_set_results */ ;; -/*!50003 SET @saved_col_connection = @@collation_connection */ ;; -/*!50003 SET character_set_client = latin1 */ ;; -/*!50003 SET character_set_results = latin1 */ ;; -/*!50003 SET collation_connection = latin1_swedish_ci */ ;; -/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; -/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;; -/*!50003 SET @saved_time_zone = @@time_zone */ ;; -/*!50003 SET time_zone = 'SYSTEM' */ ;; /*!50106 CREATE*/ /*!50117 DEFINER=`role3`@`%`*/ /*!50106 EVENT `e3` ON SCHEDULE EVERY 1 SECOND STARTS '2000-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO insert t1 values (111, 3, 0) */ ;; -/*!50003 SET time_zone = @saved_time_zone */ ;; -/*!50003 SET sql_mode = @saved_sql_mode */ ;; -/*!50003 SET character_set_client = @saved_cs_client */ ;; -/*!50003 SET character_set_results = @saved_cs_results */ ;; -/*!50003 SET collation_connection = @saved_col_connection */ ;; DELIMITER ; +/*!50003 SET time_zone = @saved_time_zone */ ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; /*!50106 SET TIME_ZONE= @save_time_zone */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; From 6552e450613fc4b8bc67974cf7ac6b4c88eeb157 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 11 Jul 2026 13:14:21 +0200 Subject: [PATCH 049/150] MDEV-40340 mariadb-import --lock-tables crashes don't change `argv` pointer, it's needed later for --lock-tables --- client/mysqlimport.cc | 4 ++-- mysql-test/main/mysqldump.test | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/mysqlimport.cc b/client/mysqlimport.cc index 06f44b5be23..c96a90c50bc 100644 --- a/client/mysqlimport.cc +++ b/client/mysqlimport.cc @@ -1314,9 +1314,9 @@ int main(int argc, char **argv) } else { - for (; *argv != NULL; argv++) + for (char **t=argv; *t != NULL; t++) { - table_load_params p(*argv, "", current_db, 0); + table_load_params p(*t, "", current_db, 0); files_to_load.push_back(p); } } diff --git a/mysql-test/main/mysqldump.test b/mysql-test/main/mysqldump.test index 060106bad15..3338c5e3807 100644 --- a/mysql-test/main/mysqldump.test +++ b/mysql-test/main/mysqldump.test @@ -3227,7 +3227,7 @@ drop table db2.t1; --exec $MYSQL db1 < $MYSQLTEST_VARDIR/tmp/dump/db1/t1.sql --exec $MYSQL db2 < $MYSQLTEST_VARDIR/tmp/dump/db2/t1.sql --exec $MYSQL_IMPORT --silent db1 $MYSQLTEST_VARDIR/tmp/dump/db1/t1.txt ---exec $MYSQL_IMPORT --silent db2 $MYSQLTEST_VARDIR/tmp/dump/db2/t1.txt +--exec $MYSQL_IMPORT --lock-tables --silent db2 $MYSQLTEST_VARDIR/tmp/dump/db2/t1.txt select * from db1.t1; select * from db2.t1; drop database db1; From dee8add860cdca409ab3313209f182b9acf7073b Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 1 Aug 2026 20:58:55 +0200 Subject: [PATCH 050/150] memory leak in mariadb-import --lock-tables --- client/mysqlimport.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/client/mysqlimport.cc b/client/mysqlimport.cc index c96a90c50bc..eddab6a7aa6 100644 --- a/client/mysqlimport.cc +++ b/client/mysqlimport.cc @@ -853,6 +853,7 @@ static void lock_table(MYSQL *mysql, int tablecount, char **raw_tablename) } if (mysql_real_query(mysql, query.str, (ulong)query.length-1)) db_error(mysql); /* We shall continue here, if --force was given */ + dynstr_free(&query); } From f6d0bd374c399af8b2a0161a11c685ed48d04a5f Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Mon, 3 Aug 2026 14:06:13 +0200 Subject: [PATCH 051/150] new CC 3.3 --- libmariadb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmariadb b/libmariadb index cc3a686a7eb..35c41cddb3c 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit cc3a686a7eb966d216bc92e352b818ce60a5809c +Subproject commit 35c41cddb3c16e9d80dee55158a59118a8fd831d From 46ee2066f06e45b3cb701feb35a8816b27ac31b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 3 Aug 2026 12:58:58 +0300 Subject: [PATCH 052/150] MDEV-40596 clang-23 reports unused global variables Let us remove a number of unused variables to suppress -Wunused-but-set-global and other warnings. test_thread(): Instead of incrementing a global counter in a race condition prone fashion, invoke MY_RELAX_CPU() in order to spend some time. --- mysys/thr_lock.c | 4 ++-- sql/mysqld.cc | 6 ++++-- storage/innobase/dict/dict0stats.cc | 2 -- storage/maria/ma_checkpoint.c | 5 ----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 214ff01bfbf..f7ffbf5c3aa 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -1651,7 +1651,6 @@ int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test), static mysql_cond_t COND_thread_count; static mysql_mutex_t LOCK_thread_count; static uint thread_count; -static ulong sum=0; #define MAX_LOCK_COUNT 8 #define TEST_TIMEOUT 100000 @@ -1678,6 +1677,7 @@ static my_bool test_check_status(void* param __attribute__((unused))) return 0; } +#include "my_cpu.h" static void *test_thread(void *arg) { @@ -1711,7 +1711,7 @@ static void *test_thread(void *arg) { ulong k; for (k=0 ; k < (ulong) (tmp-2)*100000L ; k++) - sum+=k; + MY_RELAX_CPU(); } } mysql_mutex_unlock(&LOCK_thread_count); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 289613a63e9..e980b79f5c5 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -868,8 +868,10 @@ static int cleanup_done; static ulong opt_specialflag; READ_ONLY_SYSVAR char *mysql_home_ptr; READ_ONLY_SYSVAR char *pidfile_name_ptr; +#ifdef EMBEDDED_LIBRARY /** Initial command line arguments (count), after load_defaults().*/ static int defaults_argc; +#endif /** Initial command line arguments (arguments), after load_defaults(). This memory is allocated by @c load_defaults() and should be freed @@ -1538,7 +1540,6 @@ static void charset_error_reporter(enum loglevel level, C_MODE_END struct passwd *user_info; -static pthread_t select_thread; #endif /* OS specific variables */ @@ -5781,7 +5782,6 @@ static void test_lc_time_sz() static void run_main_loop() { - select_thread=pthread_self(); mysql_mutex_lock(&LOCK_start_thread); select_thread_in_use=1; mysql_mutex_unlock(&LOCK_start_thread); @@ -5831,7 +5831,9 @@ int mysqld_main(int argc, char **argv) orig_argv= argv; my_defaults_mark_files= TRUE; load_defaults_or_exit(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv); +#ifdef EMBEDDED_LIBRARY defaults_argc= argc; +#endif defaults_argv= argv; remaining_argc= argc; remaining_argv= argv; diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc index ec52352f614..88255e17249 100644 --- a/storage/innobase/dict/dict0stats.cc +++ b/storage/innobase/dict/dict0stats.cc @@ -332,7 +332,6 @@ static int dtype_sql_name(unsigned mtype, unsigned prtype, unsigned len, return snprintf(name, name_sz, "%s%s%s", Main, Unsigned, Not_null); } -static bool innodb_table_stats_not_found; static bool innodb_index_stats_not_found; static bool innodb_table_stats_not_found_reported; static bool innodb_index_stats_not_found_reported; @@ -363,7 +362,6 @@ dict_table_schema_check( if (innodb_table_stats_not_found_reported) { return DB_STATS_DO_NOT_EXIST; } - innodb_table_stats_not_found = true; innodb_table_stats_not_found_reported = true; } else { ut_ad(req_schema == &index_stats_schema); diff --git a/storage/maria/ma_checkpoint.c b/storage/maria/ma_checkpoint.c index d910a5fbd0f..1c301ac76a8 100644 --- a/storage/maria/ma_checkpoint.c +++ b/storage/maria/ma_checkpoint.c @@ -53,9 +53,6 @@ static PAGECACHE_FILE *dfiles, /**< data files to flush in background */ *dfiles_end; /**< list of data files ends here */ static PAGECACHE_FILE *kfiles, /**< index files to flush in background */ *kfiles_end; /**< list of index files ends here */ -/* those two statistics below could serve in SHOW GLOBAL STATUS */ -static uint checkpoints_total= 0, /**< all checkpoint requests made */ - checkpoints_ok_total= 0; /**< all checkpoints which succeeded */ struct st_filter_param { @@ -314,8 +311,6 @@ end: my_free(record_pieces[i].str); mysql_mutex_lock(&LOCK_checkpoint); checkpoint_in_progress= CHECKPOINT_NONE; - checkpoints_total++; - checkpoints_ok_total+= !error; mysql_mutex_unlock(&LOCK_checkpoint); DBUG_RETURN(error); } From b68e29a9c64e96971edcc74525af571fc6299bc7 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Tue, 4 Aug 2026 12:39:49 +0300 Subject: [PATCH 053/150] Galera MTR: disable SSL by default for SST, guard tests missing stunnel Set ssl-mode=DISABLED in [sst] across all top-level galera/galera_3nodes topology .cnf files, so tests no longer implicitly require stunnel via inherited ssl-ca/ssl-cert/ssl-key. Also add the missing have_stunnel.inc skip guard to galera_ist_rsync_verify_ca and galera_sst_cn_injection's rsync combination, which intentionally test SSL SST but were not skipped when stunnel is absent. --- mysql-test/suite/galera/galera_2nodes.cnf | 2 ++ mysql-test/suite/galera/galera_2nodes_as_master.cnf | 2 ++ mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf | 2 ++ mysql-test/suite/galera/galera_2nodes_as_slave.cnf | 2 ++ mysql-test/suite/galera/galera_2x2nodes.cnf | 2 ++ mysql-test/suite/galera/galera_3nodes_as_slave.cnf | 2 ++ mysql-test/suite/galera/galera_4nodes.cnf | 2 ++ mysql-test/suite/galera/suite.pm | 4 ++++ mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test | 1 + mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf | 2 ++ mysql-test/suite/galera_3nodes/galera_3nodes.cnf | 2 ++ 11 files changed, 23 insertions(+) diff --git a/mysql-test/suite/galera/galera_2nodes.cnf b/mysql-test/suite/galera/galera_2nodes.cnf index f0996fa3a97..391cdbf6019 100644 --- a/mysql-test/suite/galera/galera_2nodes.cnf +++ b/mysql-test/suite/galera/galera_2nodes.cnf @@ -34,6 +34,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.2.port wsrep_sst_receive_address='127.0.0.1:@mysqld.2.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_master.cnf b/mysql-test/suite/galera/galera_2nodes_as_master.cnf index 43bd091c8ee..64f10b086e1 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_master.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_master.cnf @@ -48,6 +48,8 @@ wsrep-on=OFF server-id=3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf b/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf index 570eeda0bc6..32f02ad0876 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf @@ -54,6 +54,8 @@ gtid_domain_id=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_slave.cnf b/mysql-test/suite/galera/galera_2nodes_as_slave.cnf index c00e6c6a937..75c247d05b3 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_slave.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_slave.cnf @@ -47,6 +47,8 @@ wsrep-on=OFF server-id=3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2x2nodes.cnf b/mysql-test/suite/galera/galera_2x2nodes.cnf index a35baed0c32..68d81983528 100644 --- a/mysql-test/suite/galera/galera_2x2nodes.cnf +++ b/mysql-test/suite/galera/galera_2x2nodes.cnf @@ -61,6 +61,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.4.port wsrep_sst_receive_address='127.0.0.1:@mysqld.4.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_3nodes_as_slave.cnf b/mysql-test/suite/galera/galera_3nodes_as_slave.cnf index 6231da6c246..b1091cfd645 100644 --- a/mysql-test/suite/galera/galera_3nodes_as_slave.cnf +++ b/mysql-test/suite/galera/galera_3nodes_as_slave.cnf @@ -60,6 +60,8 @@ wsrep-on=OFF server-id=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_4nodes.cnf b/mysql-test/suite/galera/galera_4nodes.cnf index 3006882eda3..c54ff58e5e2 100644 --- a/mysql-test/suite/galera/galera_4nodes.cnf +++ b/mysql-test/suite/galera/galera_4nodes.cnf @@ -61,6 +61,8 @@ wsrep_sst_receive_address='127.0.0.1:@mysqld.4.#sst_port' auto-increment-offset=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index bb0f230eb7a..e85e5c32518 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -79,6 +79,10 @@ sub skip_combinations { unless which("lsof") || which("sockstat") || which("ss"); $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" unless which("stunnel"); + # 'mariabackup' combination doesn't need stunnel: only 'rsync' SST goes + # through it. + $skip{'t/galera_sst_cn_injection.combinations'} = [ 'rsync' ] + unless which("stunnel"); $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" unless which("qpress"); $skip{'../encryption/include/have_file_key_management_plugin.combinations'} = [ 'ctr' ] diff --git a/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test b/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test index d9f7bb152fc..fb16ba7f8d3 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test +++ b/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_innodb.inc +--source include/have_stunnel.inc --let $node_1=node_1 --let $node_2=node_2 diff --git a/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf b/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf index 5cd16f029e0..b385d5c160f 100644 --- a/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf +++ b/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf @@ -85,6 +85,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.6.port wsrep_sst_receive_address='127.0.0.1:@mysqld.6.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera_3nodes/galera_3nodes.cnf b/mysql-test/suite/galera_3nodes/galera_3nodes.cnf index b0229971c57..35d51c98a0f 100644 --- a/mysql-test/suite/galera_3nodes/galera_3nodes.cnf +++ b/mysql-test/suite/galera_3nodes/galera_3nodes.cnf @@ -50,6 +50,8 @@ wsrep_sst_receive_address='127.0.0.1:@mysqld.3.#sst_port' wsrep_node_name=node3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log transferfmt=@ENV.MTR_GALERA_TFMT From af9e20aac1e950471cb4cbc142224819440afbe5 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Tue, 4 Aug 2026 15:09:08 +0300 Subject: [PATCH 054/150] wsrep_info MTR: disable SSL by default for SST --- plugin/wsrep_info/mysql-test/wsrep_info/my.cnf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf b/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf index 8f62cfd0cc9..5d24c7c00f9 100644 --- a/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf +++ b/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf @@ -26,6 +26,10 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port' wsrep_sst_receive_address='127.0.0.1:@mysqld.2.#sst_port' wsrep_node_name=test-node-2 +[sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED + [ENV] NODE_MYPORT_1= @mysqld.1.port NODE_MYSOCK_1= @mysqld.1.socket From 16cf3d80ff90fa4385d872629b41fa9594fa297f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Tue, 4 Aug 2026 14:34:57 +0300 Subject: [PATCH 055/150] Fix test failure on galera_sst_cn_injection test case. Test requires pkill so skip it is not found from system. Additionally pkill may fail if socat is not anymore open when pkill executed (or when socat is not actually used). --- mysql-test/suite/galera/include/have_pkill.inc | 4 ++++ mysql-test/suite/galera/suite.pm | 3 +++ mysql-test/suite/galera/t/galera_sst_cn_injection.test | 4 ++++ 3 files changed, 11 insertions(+) create mode 100644 mysql-test/suite/galera/include/have_pkill.inc diff --git a/mysql-test/suite/galera/include/have_pkill.inc b/mysql-test/suite/galera/include/have_pkill.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_pkill.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index e85e5c32518..158fb597c5d 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -96,6 +96,9 @@ sub skip_combinations { $skip{'t/galera_sst_cn_injection.test'} = 'does not work with OpenSSL <= 1.1.1' unless $openssl_ver ge "3.0.0"; + $skip{'include/have_pkill.inc'} = "Need 'pkill' utility" + unless which("pkill"); + %skip; } diff --git a/mysql-test/suite/galera/t/galera_sst_cn_injection.test b/mysql-test/suite/galera/t/galera_sst_cn_injection.test index c2235013dc2..0c1b48912c8 100644 --- a/mysql-test/suite/galera/t/galera_sst_cn_injection.test +++ b/mysql-test/suite/galera/t/galera_sst_cn_injection.test @@ -10,6 +10,7 @@ --source include/have_mariabackup.inc --source include/have_ssl_communication.inc --source include/have_openssl.inc +--source include/have_pkill.inc SELECT 1; @@ -51,6 +52,9 @@ call mtr.add_suppression("WSREP: Illegal character in variable:"); --echo # cleanup # we have to kill joiner's socat here, because the donor has aborted SST # and joiner's socat will timeout in 5 minutes +# pkill below might return 0 = success or 1 in error when it does not find socat +# anymore +--error 0,1 --exec pkill -f 'socat.*[s]erver-new-cert' --exec echo ssl-cert=$MYSQL_TEST_DIR/std_data/server-cert.pem >> $MYSQLTEST_VARDIR/my.cnf --exec echo ssl-key=$MYSQL_TEST_DIR/std_data/server-key.pem >> $MYSQLTEST_VARDIR/my.cnf From 2c9d60d7132fca38f2dfc1a5aaf4acd7bc24e547 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 29 Jul 2026 21:26:30 +0300 Subject: [PATCH 056/150] MDEV-40480 LOAD DATA leaves a stale STORED generated column after a BEFORE INSERT trigger changes its base column On the LOAD DATA path the base columns are filled directly from the input file and fill_record_n_invoke_before_triggers() is then called with an empty field list (there is no SET clause). After the BEFORE INSERT trigger changed a base column, the stored generated columns were not recomputed, so they kept the value derived from the pre-trigger input (e.g. g=24 instead of 40 for g=v*2 with v set to 20 by the trigger). A regular INSERT was unaffected. The recompute was guarded by "fields.elements". That condition is a leftover from the original computed-columns implementation (f7a75b999b4), where fill_record_n_invoke_before_triggers() had no TABLE* argument and had to reverse-derive the table from the first item of the field list: if (fields.elements) { fld= (Item_field*)f++; item_field= fld->field_for_view_update(); table= item_field->field->table; ... } With an empty field list there was no way to obtain the table, so the recompute was silently skipped. Since bc4a456758c (MDEV-452) the function receives TABLE* explicitly, which made the whole derivation dead code (as the in-place DBUG_ASSERT(table == item_field->field->table) confirmed). Recompute the virtual fields unconditionally on table->vfield, the same way the Field** overload of fill_record_n_invoke_before_triggers() already does. --- mysql-test/suite/gcol/r/gcol_bugfixes.result | 19 ++++++++++++++ mysql-test/suite/gcol/t/gcol_bugfixes.test | 27 ++++++++++++++++++++ sql/sql_base.cc | 14 +++------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/mysql-test/suite/gcol/r/gcol_bugfixes.result b/mysql-test/suite/gcol/r/gcol_bugfixes.result index 5dfa416a540..aded2ec3cb7 100644 --- a/mysql-test/suite/gcol/r/gcol_bugfixes.result +++ b/mysql-test/suite/gcol/r/gcol_bugfixes.result @@ -782,3 +782,22 @@ a c1 Warnings: Warning 1292 Incorrect datetime value: '0' DROP TABLE t1; +# +# MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +# BEFORE INSERT trigger changes its base column +# +create table t ( +id int primary key, +v int not null, +g int generated always as (v * 2) stored, +note varchar(20) +) engine=innodb; +create trigger t_bi before insert on t for each row set new.v = 20; +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); +# The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; +id v g note +2 20 40 new +drop table t; +# End of 10.11 tests diff --git a/mysql-test/suite/gcol/t/gcol_bugfixes.test b/mysql-test/suite/gcol/t/gcol_bugfixes.test index c4beb69058a..0ff5c1a1aad 100644 --- a/mysql-test/suite/gcol/t/gcol_bugfixes.test +++ b/mysql-test/suite/gcol/t/gcol_bugfixes.test @@ -753,3 +753,30 @@ UPDATE t1 SET a=2; UPDATE t1 SET c1=1; SELECT * FROM t1; DROP TABLE t1; + + +--echo # +--echo # MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +--echo # BEFORE INSERT trigger changes its base column +--echo # + +create table t ( + id int primary key, + v int not null, + g int generated always as (v * 2) stored, + note varchar(20) +) engine=innodb; + +create trigger t_bi before insert on t for each row set new.v = 20; + +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); + +--echo # The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; + +drop table t; +--let $datadir= `select @@datadir` +--remove_file $datadir/test/load_40480 + +--echo # End of 10.11 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 544df995f2f..160d7d4776d 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -9372,17 +9372,9 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Re-calculate virtual fields to cater for cases when base columns are updated by the triggers. */ - if (table->vfield && fields.elements) - { - Item *fld= (Item_field*) fields.head(); - Item_field *item_field= fld->field_for_view_update(); - if (item_field) - { - DBUG_ASSERT(table == item_field->field->table); - result|= table->update_virtual_fields(table->file, - VCOL_UPDATE_FOR_WRITE); - } - } + if (table->vfield) + result|= table->update_virtual_fields(table->file, + VCOL_UPDATE_FOR_WRITE); } return result; } From 76ca478ca32d323c12a998e36fd792dfe1b0d0f7 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Tue, 28 Jul 2026 21:04:12 +0100 Subject: [PATCH 057/150] chore: use extended stack with up to 11 MB for ASAN/UBSAN builds. --- storage/duckdb/runtime/fiber_scan.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/storage/duckdb/runtime/fiber_scan.cc b/storage/duckdb/runtime/fiber_scan.cc index 5826cf5b8e4..588786f2fee 100644 --- a/storage/duckdb/runtime/fiber_scan.cc +++ b/storage/duckdb/runtime/fiber_scan.cc @@ -165,7 +165,13 @@ int FiberScanState::init(TABLE *tbl, buffer.Initialize(duckdb::Allocator::DefaultAllocator(), types); - if (fiber_context_init(&ctx, FIBER_STACK_SIZE)) + size_t fiber_stack_size= FIBER_STACK_SIZE; +#if defined(FIBER_ASAN_ENABLED) || defined(WITH_UBSAN) + size_t configured_stack_size= static_cast(my_thread_stack_size); + if (configured_stack_size > fiber_stack_size) + fiber_stack_size= configured_stack_size; +#endif + if (fiber_context_init(&ctx, fiber_stack_size)) return 1; fiber_thd= create_background_thd(); From 81516d56dac3d6fc18fbf1f0cc31fecebb14b6e9 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Wed, 29 Jul 2026 19:27:02 +0100 Subject: [PATCH 058/150] chore: add more RYOW tests for DuckDB. --- .../duckdb/r/cross_engine_ryow_dup.result | 83 +++++++++++++++++ .../duckdb/r/cross_engine_ryow_limit.result | 74 ++++++++++++++++ .../duckdb/t/cross_engine_ryow_dup.test | 88 +++++++++++++++++++ .../duckdb/t/cross_engine_ryow_limit.test | 80 +++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_dup.result create mode 100644 storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_limit.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test create mode 100644 storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_dup.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_dup.result new file mode 100644 index 00000000000..6a8e42c778a --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_dup.result @@ -0,0 +1,83 @@ +# +# Cross-engine RYOW: the same external table referenced more than once. +# +# The external table registry is keyed by table name, so two references +# to one table would collapse onto a single TABLE/handler and produce two +# concurrent scans over one cursor. Such queries must not be pushed +# down; the server executes them itself and the results must still show +# the uncommitted writes of the current transaction. +# +DROP DATABASE IF EXISTS cross_engine_ryow_dup; +CREATE DATABASE cross_engine_ryow_dup CHARACTER SET utf8mb4; +USE cross_engine_ryow_dup; +SET @old_max_threads= @@global.duckdb_max_threads; +SET GLOBAL duckdb_max_threads= 4; +CREATE TABLE t_duck (id INT PRIMARY KEY, val VARCHAR(50)) ENGINE=DuckDB; +CREATE TABLE t_inno (id INT PRIMARY KEY, score INT) ENGINE=InnoDB; +INSERT INTO t_duck VALUES (1,'alpha'),(2,'beta'),(3,'gamma'); +INSERT INTO t_inno VALUES (1,10),(2,20),(3,30); +SET SESSION duckdb_cross_engine_ryow=1; + +# (1) Self-join of the external table under two aliases + +BEGIN; +UPDATE t_inno SET score=999 WHERE id=2; +SELECT d.id, a.score AS a_score, b.score AS b_score +FROM t_duck d +JOIN t_inno a ON d.id = a.id +JOIN t_inno b ON a.id = b.id; +id a_score b_score +1 10 10 +2 999 999 +3 30 30 +ROLLBACK; + +# (2) The external table used both in the join and in a subquery + +BEGIN; +UPDATE t_inno SET score=999 WHERE id=3; +SELECT d.id, i.score +FROM t_duck d JOIN t_inno i ON d.id = i.id +WHERE i.score = (SELECT MAX(score) FROM t_inno); +id score +3 999 +ROLLBACK; + +# (3) The external table twice in a UNION + +BEGIN; +UPDATE t_inno SET score=999 WHERE id=1; +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.id = 1 +UNION ALL +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.id = 2; +id score +1 999 +2 20 +ROLLBACK; + +# (4) A single reference is still pushed down and still sees own writes + +BEGIN; +UPDATE t_inno SET score=555 WHERE id=2; +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id; +id score +1 10 +2 555 +3 30 +ROLLBACK; + +# (5) Committed state is intact + +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id; +id score +1 10 +2 20 +3 30 + +# Cleanup + +SET SESSION duckdb_cross_engine_ryow=DEFAULT; +SET GLOBAL duckdb_max_threads= @old_max_threads; +DROP TABLE t_duck; +DROP TABLE t_inno; +DROP DATABASE cross_engine_ryow_dup; diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_limit.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_limit.result new file mode 100644 index 00000000000..a869558da8a --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow_limit.result @@ -0,0 +1,74 @@ +# +# Cross-engine RYOW: early query termination. +# +# With LIMIT (or any other early stop) DuckDB stops pulling chunks from +# the external table scan, so the scan is never driven to end-of-file. +# The handler scan started behind the server's back must still be +# finished, otherwise the table cannot be closed cleanly. +# +# FLUSH TABLES below forces the table to be closed inside the test +# instead of at shutdown, so that a leaked scan is detected here. +# +# NOTE: the leak itself is caught by DBUG_ASSERT(inited == NONE) in +# handler::~handler(), which is compiled out when DBUG_OFF is set. This +# test therefore only proves the absence of a leak in a debug build; in a +# release build it merely checks the query results. +# +DROP DATABASE IF EXISTS cross_engine_ryow_limit; +CREATE DATABASE cross_engine_ryow_limit CHARACTER SET utf8mb4; +USE cross_engine_ryow_limit; +SET @old_max_threads= @@global.duckdb_max_threads; +SET GLOBAL duckdb_max_threads= 4; +CREATE TABLE t_duck (id INT PRIMARY KEY, val VARCHAR(50)) ENGINE=DuckDB; +CREATE TABLE t_inno (id INT PRIMARY KEY, score INT) ENGINE=InnoDB; +INSERT INTO t_duck SELECT seq, CONCAT('v', seq) FROM seq_1_to_3000; +INSERT INTO t_inno SELECT seq, seq FROM seq_1_to_3000; +SET SESSION duckdb_cross_engine_ryow=1; + +# (1) LIMIT stops the scan long before end-of-file + +BEGIN; +UPDATE t_inno SET score=999 WHERE id=1; +SELECT d.id, i.score +FROM t_duck d JOIN t_inno i ON d.id = i.id +ORDER BY d.id LIMIT 1; +id score +1 999 + +# (2) The same external table is usable again in the same transaction + +SELECT d.id, i.score +FROM t_duck d JOIN t_inno i ON d.id = i.id +ORDER BY d.id LIMIT 2; +id score +1 999 +2 2 +SELECT score FROM t_inno WHERE id = 1; +score +999 +ROLLBACK; + +# (3) Closing the table must not hit a leaked scan + +FLUSH TABLES; +SELECT COUNT(*) FROM t_inno; +COUNT(*) +3000 + +# (4) Early termination combined with an aggregate that stops early + +BEGIN; +UPDATE t_inno SET score=999 WHERE id=2; +SELECT EXISTS (SELECT 1 FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.score = 999) AS found; +found +1 +ROLLBACK; +FLUSH TABLES; + +# Cleanup + +SET SESSION duckdb_cross_engine_ryow=DEFAULT; +SET GLOBAL duckdb_max_threads= @old_max_threads; +DROP TABLE t_duck; +DROP TABLE t_inno; +DROP DATABASE cross_engine_ryow_limit; diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test new file mode 100644 index 00000000000..b19b13d33d1 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test @@ -0,0 +1,88 @@ +--source ../include/have_duckdb.inc + +--echo # +--echo # Cross-engine RYOW: the same external table referenced more than once. +--echo # +--echo # The external table registry is keyed by table name, so two references +--echo # to one table would collapse onto a single TABLE/handler and produce two +--echo # concurrent scans over one cursor. Such queries must not be pushed +--echo # down; the server executes them itself and the results must still show +--echo # the uncommitted writes of the current transaction. +--echo # + +--disable_warnings +DROP DATABASE IF EXISTS cross_engine_ryow_dup; +--enable_warnings +CREATE DATABASE cross_engine_ryow_dup CHARACTER SET utf8mb4; +USE cross_engine_ryow_dup; + +SET @old_max_threads= @@global.duckdb_max_threads; +SET GLOBAL duckdb_max_threads= 4; + +CREATE TABLE t_duck (id INT PRIMARY KEY, val VARCHAR(50)) ENGINE=DuckDB; +CREATE TABLE t_inno (id INT PRIMARY KEY, score INT) ENGINE=InnoDB; + +INSERT INTO t_duck VALUES (1,'alpha'),(2,'beta'),(3,'gamma'); +INSERT INTO t_inno VALUES (1,10),(2,20),(3,30); + +SET SESSION duckdb_cross_engine_ryow=1; + +--echo +--echo # (1) Self-join of the external table under two aliases +--echo +BEGIN; +UPDATE t_inno SET score=999 WHERE id=2; +--sorted_result +SELECT d.id, a.score AS a_score, b.score AS b_score + FROM t_duck d + JOIN t_inno a ON d.id = a.id + JOIN t_inno b ON a.id = b.id; +ROLLBACK; + +--echo +--echo # (2) The external table used both in the join and in a subquery +--echo +BEGIN; +UPDATE t_inno SET score=999 WHERE id=3; +--sorted_result +SELECT d.id, i.score + FROM t_duck d JOIN t_inno i ON d.id = i.id + WHERE i.score = (SELECT MAX(score) FROM t_inno); +ROLLBACK; + +--echo +--echo # (3) The external table twice in a UNION +--echo +BEGIN; +UPDATE t_inno SET score=999 WHERE id=1; +--sorted_result +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.id = 1 +UNION ALL +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.id = 2; +ROLLBACK; + +--echo +--echo # (4) A single reference is still pushed down and still sees own writes +--echo +BEGIN; +UPDATE t_inno SET score=555 WHERE id=2; +--sorted_result +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id; +ROLLBACK; + +--echo +--echo # (5) Committed state is intact +--echo +--sorted_result +SELECT d.id, i.score FROM t_duck d JOIN t_inno i ON d.id = i.id; + +--echo +--echo # Cleanup +--echo +SET SESSION duckdb_cross_engine_ryow=DEFAULT; +SET GLOBAL duckdb_max_threads= @old_max_threads; +DROP TABLE t_duck; +DROP TABLE t_inno; +DROP DATABASE cross_engine_ryow_dup; + +--source ../include/cleanup_duckdb.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test new file mode 100644 index 00000000000..c7daf2b5e3c --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test @@ -0,0 +1,80 @@ +--source ../include/have_duckdb.inc +--source include/have_sequence.inc + +--echo # +--echo # Cross-engine RYOW: early query termination. +--echo # +--echo # With LIMIT (or any other early stop) DuckDB stops pulling chunks from +--echo # the external table scan, so the scan is never driven to end-of-file. +--echo # The handler scan started behind the server's back must still be +--echo # finished, otherwise the table cannot be closed cleanly. +--echo # +--echo # FLUSH TABLES below forces the table to be closed inside the test +--echo # instead of at shutdown, so that a leaked scan is detected here. +--echo # +--echo # NOTE: the leak itself is caught by DBUG_ASSERT(inited == NONE) in +--echo # handler::~handler(), which is compiled out when DBUG_OFF is set. This +--echo # test therefore only proves the absence of a leak in a debug build; in a +--echo # release build it merely checks the query results. +--echo # + +--disable_warnings +DROP DATABASE IF EXISTS cross_engine_ryow_limit; +--enable_warnings +CREATE DATABASE cross_engine_ryow_limit CHARACTER SET utf8mb4; +USE cross_engine_ryow_limit; + +SET @old_max_threads= @@global.duckdb_max_threads; +SET GLOBAL duckdb_max_threads= 4; + +CREATE TABLE t_duck (id INT PRIMARY KEY, val VARCHAR(50)) ENGINE=DuckDB; +CREATE TABLE t_inno (id INT PRIMARY KEY, score INT) ENGINE=InnoDB; + +INSERT INTO t_duck SELECT seq, CONCAT('v', seq) FROM seq_1_to_3000; +INSERT INTO t_inno SELECT seq, seq FROM seq_1_to_3000; + +SET SESSION duckdb_cross_engine_ryow=1; + +--echo +--echo # (1) LIMIT stops the scan long before end-of-file +--echo +BEGIN; +UPDATE t_inno SET score=999 WHERE id=1; +SELECT d.id, i.score + FROM t_duck d JOIN t_inno i ON d.id = i.id + ORDER BY d.id LIMIT 1; + +--echo +--echo # (2) The same external table is usable again in the same transaction +--echo +SELECT d.id, i.score + FROM t_duck d JOIN t_inno i ON d.id = i.id + ORDER BY d.id LIMIT 2; +SELECT score FROM t_inno WHERE id = 1; +ROLLBACK; + +--echo +--echo # (3) Closing the table must not hit a leaked scan +--echo +FLUSH TABLES; +SELECT COUNT(*) FROM t_inno; + +--echo +--echo # (4) Early termination combined with an aggregate that stops early +--echo +BEGIN; +UPDATE t_inno SET score=999 WHERE id=2; +SELECT EXISTS (SELECT 1 FROM t_duck d JOIN t_inno i ON d.id = i.id WHERE i.score = 999) AS found; +ROLLBACK; +FLUSH TABLES; + +--echo +--echo # Cleanup +--echo +SET SESSION duckdb_cross_engine_ryow=DEFAULT; +SET GLOBAL duckdb_max_threads= @old_max_threads; +DROP TABLE t_duck; +DROP TABLE t_inno; +DROP DATABASE cross_engine_ryow_limit; + +--source ../include/cleanup_duckdb.inc From 25636531e5a8762707b7bd961dd99b608818550e Mon Sep 17 00:00:00 2001 From: drrtuy Date: Sat, 1 Aug 2026 21:42:20 +0100 Subject: [PATCH 059/150] feat: implement SEQUENCE support in DEFAULT value clause for DuckDB tables. --- storage/duckdb/convertor/ddl_convertor.cc | 175 ++++++++++++++++-- .../duckdb/r/alter_duckdb_column.result | 16 +- .../duckdb/r/duckdb_default_expr.result | 153 +++++++++++++++ .../duckdb/t/duckdb_default_expr.test | 78 ++++++++ 4 files changed, 402 insertions(+), 20 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/duckdb_default_expr.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index 9d9433c638d..3a77991d4c6 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -152,6 +152,70 @@ static std::string get_default_expr_from_vcol_defs(const TABLE_SHARE *share, return ""; } +/** + Does a default expression reference a MariaDB sequence? + + NEXT VALUE FOR s, PREVIOUS VALUE FOR s and SETVAL() are printed by + Item_func_nextval::print() as nextval(`db`.`seq`), lastval(...) and + setval(...) (sql/item_func.cc). DuckDB binds the quoted argument as a + column reference and rejects the whole DDL with "DEFAULT value cannot + contain column names" (MDEV-40266). + + The sequence lives in MariaDB and has no DuckDB counterpart, so such a + default can never be forwarded. It is omitted instead: MariaDB evaluates + column defaults into record[0] before write_row(), and both write paths + always send the value, so only writes that bypass MariaDB entirely (i.e. + run_in_duckdb) would ever consult the DuckDB-side default. + + This matches on the printed function name because the Item tree in + field->default_value->expr is unusable at ha_duckdb::create() time. +*/ +static bool has_mariadb_sequence_func(const std::string &expr) +{ + static const char *const funcs[]= {"nextval(", "lastval(", "setval("}; + + for (const char *func : funcs) + { + size_t len= strlen(func); + for (size_t pos= 0; pos + len <= expr.length(); pos++) + { + if (strncasecmp(expr.c_str() + pos, func, len) != 0) + continue; + /* Ignore a match that is only the tail of a longer identifier. */ + char prev= pos ? expr[pos - 1] : ' '; + if (!isalnum((uchar) prev) && prev != '_') + return true; + } + } + return false; +} + +/** + Expression default text of a field, or "" if it has none or the text is + not available in this share. +*/ +static std::string get_expr_default_text(const Field *field) +{ + if (!field->default_value) + return ""; + return get_default_expr_from_vcol_defs(field->table->s, field->field_index); +} + +/** Does this field carry a default expression referencing a sequence? */ +static bool is_sequence_default(const Field *field) +{ + return has_mariadb_sequence_func(get_expr_default_text(field)); +} + +static void note_sequence_default_omitted(const char *column) +{ + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_NOTE, + ER_UNKNOWN_ERROR, + "DuckDB: sequence default of column '%s' is not " + "forwarded to DuckDB; MariaDB supplies the value", + column); +} + /** Read the literal default value of a field from the default record and return it as a string suitable for DuckDB SQL. @@ -419,7 +483,9 @@ std::string FieldConvertor::translate() */ std::string expr_str= get_default_expr_from_vcol_defs(field->table->s, field->field_index); - if (!expr_str.empty()) + if (has_mariadb_sequence_func(expr_str)) + note_sequence_default_omitted(field->field_name.str); + else if (!expr_str.empty()) result << " DEFAULT (" << expr_str << ")"; } else if (field->table->s->default_values && field->table->record[0]) @@ -712,10 +778,20 @@ std::string AddColumnConvertor::translate() } else if (!(field->flags & NO_DEFAULT_VALUE_FLAG)) { - my_ptrdiff_t offset= - field->table->s->default_values - field->table->record[0]; - has_default= true; - default_value= get_field_default_for_duckdb(field, offset); + /* + A sequence default must not be forwarded. Every other default, + including a constant-foldable expression, keeps using the value + MariaDB already materialised in default_values. + */ + if (is_sequence_default(field)) + note_sequence_default_omitted(field->field_name.str); + else + { + my_ptrdiff_t offset= + field->table->s->default_values - field->table->record[0]; + has_default= true; + default_value= get_field_default_for_duckdb(field, offset); + } } append_stmt_column_add(result, m_schema_name, m_table_name, @@ -884,10 +960,39 @@ void ChangeColumnDefaultConvertor::prepare_columns() else if (new_has_default && !old_has_default) { /* Default was added */ - m_columns_to_set_default.emplace_back(nullptr, new_field); + if (is_sequence_default(new_field)) + { + note_sequence_default_omitted(new_field->field_name.str); + m_columns_to_drop_default.emplace_back(nullptr, new_field); + } + else + m_columns_to_set_default.emplace_back(nullptr, new_field); } else if (old_has_default && new_has_default) { + std::string old_expr= get_expr_default_text(old_field); + std::string new_expr= get_expr_default_text(new_field); + + if (!old_expr.empty() || !new_expr.empty()) + { + /* + Expression default. It is not materialised in default_values, so + the byte comparison below would be meaningless — compare the + expression text instead. + */ + if (old_expr != new_expr) + { + if (has_mariadb_sequence_func(new_expr)) + { + note_sequence_default_omitted(new_field->field_name.str); + m_columns_to_drop_default.emplace_back(nullptr, new_field); + } + else + m_columns_to_set_default.emplace_back(nullptr, new_field); + } + continue; + } + /* Both have default — check if value changed */ my_ptrdiff_t old_off= old_field->table->s->default_values - old_field->table->record[0]; @@ -951,9 +1056,24 @@ std::string ChangeColumnDefaultConvertor::translate() { Field *field= pair.second; - my_ptrdiff_t offset= - field->table->s->default_values - field->table->record[0]; - std::string default_value= get_field_default_for_duckdb(field, offset); + std::string default_value; + std::string expr= get_expr_default_text(field); + if (!expr.empty()) + { + /* + Expression default: forward the expression text. It is not + materialised in default_values, so the byte path below would + produce a meaningless literal. Sequence defaults never reach + here — prepare_columns() diverts them to drop-default. + */ + default_value= "(" + expr + ")"; + } + else + { + my_ptrdiff_t offset= + field->table->s->default_values - field->table->record[0]; + default_value= get_field_default_for_duckdb(field, offset); + } append_stmt_column_set_default(result, m_schema_name, m_table_name, field->field_name.str, default_value); @@ -1060,9 +1180,40 @@ std::string ChangeColumnConvertor::translate() if (!field || (field->flags & NO_DEFAULT_VALUE_FLAG)) continue; - my_ptrdiff_t offset= - field->table->s->default_values - field->table->record[0]; - std::string default_value= get_field_default_for_duckdb(field, offset); + std::string new_expr= get_expr_default_text(field); + if (has_mariadb_sequence_func(new_expr)) + { + /* + A sequence default is never forwarded to DuckDB. Act only when the + sequence default is newly introduced by this ALTER: drop whatever + default DuckDB had for the column. An unchanged sequence default + needs no statement (DuckDB never had one) and no repeated note. + */ + if (new_expr != get_expr_default_text(new_field->field)) + { + note_sequence_default_omitted(new_field->field_name.str); + append_stmt_column_drop_default(result, m_schema_name, m_table_name, + new_field->field_name.str); + } + continue; + } + + std::string default_value; + if (!new_expr.empty()) + { + /* + Expression default: forward the expression text. It is not + materialised in default_values, so the byte path below would + produce a meaningless literal. + */ + default_value= "(" + new_expr + ")"; + } + else + { + my_ptrdiff_t offset= + field->table->s->default_values - field->table->record[0]; + default_value= get_field_default_for_duckdb(field, offset); + } append_stmt_column_set_default(result, m_schema_name, m_table_name, new_field->field_name.str, default_value); diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result index 2c8cf374fd8..67b63d9d662 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result @@ -568,12 +568,12 @@ table_schema table_name column_name column_default is_nullable data_type COLUMN_ VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR [ Rows: 7] db_alter_col t id NULL NO INTEGER NULL -db_alter_col t a '6' YES INTEGER NULL +db_alter_col t a (3 + 3) YES INTEGER NULL db_alter_col t b '3+3' YES VARCHAR NULL -db_alter_col t c '6' YES VARCHAR NULL -db_alter_col t d '4' YES INTEGER NULL +db_alter_col t c (3 + 3) YES VARCHAR NULL +db_alter_col t d (2 + 2) YES INTEGER NULL db_alter_col t e '2+2' YES VARCHAR NULL -db_alter_col t f '4' YES VARCHAR NULL +db_alter_col t f (2 + 2) YES VARCHAR NULL SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, COLUMN_COMMENT FROM information_schema.columns WHERE TABLE_NAME = 't'; @@ -1446,12 +1446,12 @@ table_schema table_name column_name column_default is_nullable data_type COLUMN_ VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR [ Rows: 7] db_alter_col t id NULL NO INTEGER NULL -db_alter_col t a '6' YES INTEGER NULL +db_alter_col t a (3 + 3) YES INTEGER NULL db_alter_col t b '3+3' YES VARCHAR NULL -db_alter_col t c '6' YES VARCHAR NULL -db_alter_col t d '4' YES INTEGER NULL +db_alter_col t c (3 + 3) YES VARCHAR NULL +db_alter_col t d (2 + 2) YES INTEGER NULL db_alter_col t e '2+2' YES VARCHAR NULL -db_alter_col t f '4' YES VARCHAR NULL +db_alter_col t f (2 + 2) YES VARCHAR NULL SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, COLUMN_COMMENT FROM information_schema.columns WHERE TABLE_NAME = 't'; diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_default_expr.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_default_expr.result new file mode 100644 index 00000000000..694f69aa031 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_default_expr.result @@ -0,0 +1,153 @@ +SET NAMES utf8mb4; +# +# MDEV-40266: DEFAULT(NEXT VALUE FOR seq) must not break the DuckDB DDL +# +CREATE SEQUENCE s START WITH 1 INCREMENT BY 1; +# CREATE TABLE +CREATE TABLE t (id BIGINT NOT NULL DEFAULT(NEXT VALUE FOR s) PRIMARY KEY, a INT) ENGINE=DuckDB; +Warnings: +Note 1105 DuckDB: sequence default of column 'id' is not forwarded to DuckDB; MariaDB supplies the value +SHOW WARNINGS; +Level Code Message +Note 1105 DuckDB: sequence default of column 'id' is not forwarded to DuckDB; MariaDB supplies the value +SHOW CREATE TABLE t; +Table Create Table +t CREATE TABLE `t` ( + `id` bigint(20) NOT NULL DEFAULT nextval(`db_default_expr`.`s`), + `a` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +# The DuckDB column must carry no default: MariaDB always supplies the value. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 2] +a NULL +id NULL + + +INSERT INTO t (a) VALUES (10); +INSERT INTO t (a) VALUES (20); +SELECT id, a FROM t ORDER BY id; +id a +1 10 +2 20 +# ADD COLUMN +CREATE SEQUENCE s2 START WITH 100 INCREMENT BY 1; +ALTER TABLE t ADD COLUMN b BIGINT DEFAULT(NEXT VALUE FOR s2); +Warnings: +Note 1105 DuckDB: sequence default of column 'b' is not forwarded to DuckDB; MariaDB supplies the value +SHOW WARNINGS; +Level Code Message +Note 1105 DuckDB: sequence default of column 'b' is not forwarded to DuckDB; MariaDB supplies the value +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 3] +a NULL +b NULL +id NULL + + +INSERT INTO t (a) VALUES (30); +SELECT id, a, b FROM t ORDER BY id; +id a b +1 10 NULL +2 20 NULL +3 30 100 +# MODIFY COLUMN, adding a sequence default to a plain column +ALTER TABLE t MODIFY COLUMN a INT DEFAULT(NEXT VALUE FOR s2); +Warnings: +Note 1105 DuckDB: sequence default of column 'a' is not forwarded to DuckDB; MariaDB supplies the value +SHOW WARNINGS; +Level Code Message +Note 1105 DuckDB: sequence default of column 'a' is not forwarded to DuckDB; MariaDB supplies the value +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 3] +a NULL +b NULL +id NULL + + +INSERT INTO t () VALUES (); +SELECT id, a, b FROM t ORDER BY id; +id a b +1 10 NULL +2 20 NULL +3 30 100 +4 101 102 +# MODIFY COLUMN, replacing a sequence default with a literal +ALTER TABLE t MODIFY COLUMN a INT DEFAULT 7; +SHOW WARNINGS; +Level Code Message +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 3] +a '7' +b NULL +id NULL + + +# MODIFY COLUMN, replacing a literal default with an expression +ALTER TABLE t MODIFY COLUMN a INT DEFAULT (1+1); +SHOW WARNINGS; +Level Code Message +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' AND column_name = 'a'"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' AND column_name = 'a'") +column_name column_default +VARCHAR VARCHAR +[ Rows: 1] +a (1 + 1) + + +# Insert bypassing MariaDB: relies on the DuckDB-side expression default. +SELECT run_in_duckdb("INSERT INTO db_default_expr.t(id, b) VALUES(1000, 1000)"); +run_in_duckdb("INSERT INTO db_default_expr.t(id, b) VALUES(1000, 1000)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT a FROM t WHERE id = 1000; +a +2 +DROP TABLE t; +DROP SEQUENCE s; +DROP SEQUENCE s2; +# +# Non-sequence expression defaults keep reaching DuckDB +# +CREATE TABLE t2 (id INT PRIMARY KEY, a INT DEFAULT (1+1), b VARCHAR(10) DEFAULT ('1+1')) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't2' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't2' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 3] +a (1 + 1) +b '1+1' +id NULL + + +INSERT INTO t2 (id) VALUES (1); +# Insert bypassing MariaDB: relies on the DuckDB-side default. +SELECT run_in_duckdb("INSERT INTO db_default_expr.t2(id) VALUES(2)"); +run_in_duckdb("INSERT INTO db_default_expr.t2(id) VALUES(2)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT * FROM t2 ORDER BY id; +id a b +1 2 1+1 +2 2 1+1 +DROP TABLE t2; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test new file mode 100644 index 00000000000..15cc00024f8 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test @@ -0,0 +1,78 @@ +--source ../include/have_duckdb.inc + +--disable_query_log +CREATE DATABASE IF NOT EXISTS db_default_expr CHARACTER SET utf8mb4; +USE db_default_expr; +--enable_query_log +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-40266: DEFAULT(NEXT VALUE FOR seq) must not break the DuckDB DDL +--echo # + +CREATE SEQUENCE s START WITH 1 INCREMENT BY 1; + +--echo # CREATE TABLE +CREATE TABLE t (id BIGINT NOT NULL DEFAULT(NEXT VALUE FOR s) PRIMARY KEY, a INT) ENGINE=DuckDB; +SHOW WARNINGS; +SHOW CREATE TABLE t; + +--echo # The DuckDB column must carry no default: MariaDB always supplies the value. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); + +INSERT INTO t (a) VALUES (10); +INSERT INTO t (a) VALUES (20); +SELECT id, a FROM t ORDER BY id; + +--echo # ADD COLUMN +CREATE SEQUENCE s2 START WITH 100 INCREMENT BY 1; +ALTER TABLE t ADD COLUMN b BIGINT DEFAULT(NEXT VALUE FOR s2); +SHOW WARNINGS; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); + +INSERT INTO t (a) VALUES (30); +SELECT id, a, b FROM t ORDER BY id; + +--echo # MODIFY COLUMN, adding a sequence default to a plain column +ALTER TABLE t MODIFY COLUMN a INT DEFAULT(NEXT VALUE FOR s2); +SHOW WARNINGS; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); + +INSERT INTO t () VALUES (); +SELECT id, a, b FROM t ORDER BY id; + +--echo # MODIFY COLUMN, replacing a sequence default with a literal +ALTER TABLE t MODIFY COLUMN a INT DEFAULT 7; +SHOW WARNINGS; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' ORDER BY column_name"); + +--echo # MODIFY COLUMN, replacing a literal default with an expression +ALTER TABLE t MODIFY COLUMN a INT DEFAULT (1+1); +SHOW WARNINGS; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't' AND column_name = 'a'"); + +--echo # Insert bypassing MariaDB: relies on the DuckDB-side expression default. +SELECT run_in_duckdb("INSERT INTO db_default_expr.t(id, b) VALUES(1000, 1000)"); +SELECT a FROM t WHERE id = 1000; + +DROP TABLE t; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +--echo # +--echo # Non-sequence expression defaults keep reaching DuckDB +--echo # + +CREATE TABLE t2 (id INT PRIMARY KEY, a INT DEFAULT (1+1), b VARCHAR(10) DEFAULT ('1+1')) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_default_expr' AND table_name = 't2' ORDER BY column_name"); + +INSERT INTO t2 (id) VALUES (1); +--echo # Insert bypassing MariaDB: relies on the DuckDB-side default. +SELECT run_in_duckdb("INSERT INTO db_default_expr.t2(id) VALUES(2)"); +SELECT * FROM t2 ORDER BY id; + +DROP TABLE t2; + +--disable_query_log +DROP DATABASE db_default_expr; +--enable_query_log From 2295f22baffcef8888e87ccce27c55656d55bc72 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Tue, 4 Aug 2026 20:54:28 +0100 Subject: [PATCH 060/150] feat: DuckDB supports AUTO INCREMENT stanza. --- storage/duckdb/CLAUDE.md | 2 +- storage/duckdb/CMakeLists.txt | 4 +- storage/duckdb/cmake/duckdb.cmake | 12 ++ storage/duckdb/convertor/ddl_convertor.cc | 88 +++++++- storage/duckdb/convertor/ddl_convertor.h | 10 + storage/duckdb/ha_duckdb.cc | 156 +++++++++++++- storage/duckdb/ha_duckdb.h | 30 ++- .../duckdb/include/alter_duckdb_column.inc | 9 +- .../duckdb/r/alter_duckdb_column.result | 8 +- .../duckdb/r/alter_duckdb_column_copy.result | 4 +- .../duckdb/r/duckdb_auto_increment.result | 204 ++++++++++++++++++ .../duckdb/t/duckdb_auto_increment.test | 84 ++++++++ .../duckdb/patches/duckdb-pr24061-setval.diff | 179 +++++++++++++++ 13 files changed, 767 insertions(+), 23 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test create mode 100644 storage/duckdb/patches/duckdb-pr24061-setval.diff diff --git a/storage/duckdb/CLAUDE.md b/storage/duckdb/CLAUDE.md index bccdb4bf834..b6cea1a72ef 100644 --- a/storage/duckdb/CLAUDE.md +++ b/storage/duckdb/CLAUDE.md @@ -70,7 +70,7 @@ All generated SQL must use **double quotes** for identifiers (DuckDB follows SQL ### DuckDB source and patches -DuckDB source is at `third_parties/duckdb/` (git submodule). No patches are applied — all compatibility is handled at runtime via `duckdb_mysql_compat.cc`. The build produces a static library; `_GLIBCXX_DEBUG` is explicitly undefined in CMakeLists.txt to avoid ABI mismatch with MariaDB's debug build. +DuckDB source is at `third_parties/duckdb/` (git submodule). One local patch is applied on top of the pinned submodule commit — `patches/duckdb-pr24061-setval.diff` (backport of upstream PR #24061 adding `setval()`, needed for AUTO_INCREMENT counter repositioning), applied via `PATCH_COMMAND` in `cmake/duckdb.cmake` and to be dropped when the submodule is bumped to a release containing the PR. Other compatibility is handled at runtime via `duckdb_mysql_compat.cc`. The build produces a static library; `_GLIBCXX_DEBUG` is explicitly undefined in CMakeLists.txt to avoid ABI mismatch with MariaDB's debug build. ### Configuration diff --git a/storage/duckdb/CMakeLists.txt b/storage/duckdb/CMakeLists.txt index f3a2242f96e..cf4e06a7f8e 100644 --- a/storage/duckdb/CMakeLists.txt +++ b/storage/duckdb/CMakeLists.txt @@ -63,8 +63,10 @@ IF(TARGET duckdb) ADD_SUBDIRECTORY(convertor) ADD_SUBDIRECTORY(runtime) + # atomic: GCC lowers the 16-byte atomic ops of the AUTO_INCREMENT block + # cache (Duckdb_share::autoinc_range) to libatomic calls. target_link_libraries(duckdb duckdb_runtime duckdb_convertor duckdb_common - ${DUCKDB_LIBRARY} + ${DUCKDB_LIBRARY} atomic ) duckdb_setup_target(duckdb) diff --git a/storage/duckdb/cmake/duckdb.cmake b/storage/duckdb/cmake/duckdb.cmake index 9da9909940b..1eb9924924d 100644 --- a/storage/duckdb/cmake/duckdb.cmake +++ b/storage/duckdb/cmake/duckdb.cmake @@ -51,6 +51,16 @@ SET(_DUCKDB_STATIC_LIBS MESSAGE(STATUS "=== Building DuckDB from submodule (${DUCKDB_SUBMODULE_DIR}) ===") ADD_SUBMODULE(third_parties/duckdb) +# Local patches applied on top of the pinned submodule commit. +# +# duckdb-pr24061-setval.diff: backport of upstream PR #24061 ("Add SetVal", +# merged 2026-07-23, first release after v1.5.2) rewritten against the v1.5.2 +# API. Provides setval('seq', value[, is_called]) needed for AUTO_INCREMENT +# counter repositioning (TRUNCATE, ALTER ... AUTO_INCREMENT=N, explicit-id +# reconciliation). Drop when the submodule is bumped to a release containing +# the PR: the apply below will fail loudly on an already-present setval. +SET(_DUCKDB_PATCH "${CMAKE_CURRENT_SOURCE_DIR}/patches/duckdb-pr24061-setval.diff") + # Upstream sets DUCKDB_EXTENSION_JEMALLOC_LINKED via add_extension_definitions(), # which runs in extension/ but NOT in src/, so allocator.cpp (in duckdb_static) # compiles the glibc malloc() path even though libjemalloc_extension.a is linked. @@ -79,6 +89,8 @@ ExternalProject_Add(duckdb_build PREFIX "${_DUCKDB_BUILD_DIR}" SOURCE_DIR "${DUCKDB_SUBMODULE_DIR}" BINARY_DIR "${_DUCKDB_BUILD_DIR}" + # Idempotent: skip when the working tree already carries the patch. + PATCH_COMMAND bash -c "git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}'" CMAKE_ARGS -DCMAKE_BUILD_TYPE=${_DUCKDB_BUILD_TYPE} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index 3a77991d4c6..f349d1fd700 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -216,6 +216,29 @@ static void note_sequence_default_omitted(const char *column) column); } +std::string autoinc_sequence_name(const std::string &table_name) +{ + return "mdb_autoinc_" + table_name; +} + +Field *find_autoinc_field(const TABLE *table) +{ + for (Field **ptr= table->field; *ptr; ptr++) + { + if ((*ptr)->flags & AUTO_INCREMENT_FLAG) + return *ptr; + } + return nullptr; +} + +/** Reference to the auto-increment sequence usable as a nextval() argument. */ +static std::string autoinc_nextval_expr(const std::string &schema_name, + const std::string &table_name) +{ + return "nextval('\"" + schema_name + "\".\"" + + autoinc_sequence_name(table_name) + "\"')"; +} + /** Read the literal default value of a field from the default record and return it as a string suitable for DuckDB SQL. @@ -419,11 +442,21 @@ static void append_stmt_table_rename(std::ostringstream &output, bool FieldConvertor::check() { - /* not support auto_increment */ - if (m_field->flags & AUTO_INCREMENT_FLAG) - return report_duckdb_table_struct_error( - "AUTO_INCREMENT", "removing AUTO_INCREMENT from column", - m_field->field_name.str, m_ctx); + /* + AUTO_INCREMENT is supported by CREATE TABLE only. Adding it to an + existing table would require seeding the sequence from the current + column maximum, which is not implemented yet. + */ + if ((m_field->flags & AUTO_INCREMENT_FLAG) && + m_ctx != ddl_error_context::CREATE) + { + /* + Same error the server raised while HA_NO_AUTO_INCREMENT was set, so + that the rejection stays indistinguishable from before. + */ + my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), "DUCKDB"); + return true; + } /* No support for INVISIBLE columns. */ if (m_field->invisible >= INVISIBLE_USER) @@ -503,8 +536,6 @@ std::string FieldConvertor::translate() } } - assert(!(field->flags & AUTO_INCREMENT_FLAG)); - return result.str(); } @@ -633,6 +664,20 @@ bool CreateTableConvertor::check() return true; } + /* + An ALTER using ALGORITHM=COPY builds a shadow table through create(), so + it arrives here rather than through the ALTER convertors. Reject + AUTO_INCREMENT on that path: the new sequence would start at 1 while the + copied rows already hold larger ids, silently handing out duplicates. + Seeding from the current column maximum is not implemented. + */ + if (m_thd->lex->sql_command != SQLCOM_CREATE_TABLE && + find_autoinc_field(m_table)) + { + my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), "DUCKDB"); + return true; + } + /* Check PK. */ TABLE_SHARE *share= m_table->s; @@ -679,6 +724,24 @@ std::string CreateTableConvertor::translate() result << "USE " << '"' << m_schema_name << '"' << ";"; + /* + The sequence must exist before the table, because the AUTO_INCREMENT + column default resolves it at CREATE TABLE bind time. + */ + if (find_autoinc_field(m_table)) + { + ulonglong start= m_create_info->auto_increment_value + ? m_create_info->auto_increment_value + : 1; + /* A DuckDB sequence counter is int64; never emit an unrepresentable start. */ + if (start > (ulonglong) INT64_MAX) + start= (ulonglong) INT64_MAX; + + result << "CREATE SEQUENCE IF NOT EXISTS " << '"' << m_schema_name << '"' + << "." << '"' << autoinc_sequence_name(m_table_name) << '"' + << " START WITH " << start << ";"; + } + result << CREATE_TABLE_STR; /* MariaDB: IF NOT EXISTS is handled at the SQL layer, not in HA_CREATE_INFO. Always use IF NOT EXISTS for safety in DuckDB. */ @@ -701,7 +764,16 @@ void CreateTableConvertor::append_column_definition(std::ostringstream &output) { if (ptr != first_field) output << ","; - output << FieldConvertor(field).translate(); + output << FieldConvertor(field, ddl_error_context::CREATE).translate(); + + /* + The AUTO_INCREMENT column draws from a DuckDB sequence. Keeping the + default in DuckDB lets writes that bypass MariaDB still obtain a + non-colliding id from the same counter. + */ + if (field->flags & AUTO_INCREMENT_FLAG) + output << " DEFAULT " + << autoinc_nextval_expr(m_schema_name, m_table_name); } } diff --git a/storage/duckdb/convertor/ddl_convertor.h b/storage/duckdb/convertor/ddl_convertor.h index a3a084ade88..75871fc066a 100644 --- a/storage/duckdb/convertor/ddl_convertor.h +++ b/storage/duckdb/convertor/ddl_convertor.h @@ -49,6 +49,16 @@ static inline bool is_uuid_field(const Field *field) field->type_handler()->type_collection() == uuid_coll; } +/** + Name of the DuckDB sequence backing the AUTO_INCREMENT column of a table. + MariaDB allows at most one AUTO_INCREMENT column per table, so the table + name alone identifies the sequence. +*/ +std::string autoinc_sequence_name(const std::string &table_name); + +/** The AUTO_INCREMENT field of a table, or nullptr if there is none. */ +Field *find_autoinc_field(const TABLE *table); + class BaseConvertor { public: diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 79f5b8c3222..34400e647de 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -61,6 +61,7 @@ static duckdb_status_t srv_duckdb_status; static my_bool copy_ddl_in_batch= TRUE; static my_bool dml_in_batch= TRUE; static my_bool update_modified_column_only= TRUE; +static ulonglong autoinc_cache_size= 1000; static handler *duckdb_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root); @@ -262,7 +263,12 @@ static int duckdb_deinit_func(void *p) /* ----- Share management ----- */ -Duckdb_share::Duckdb_share() { thr_lock_init(&lock); } +Duckdb_share::Duckdb_share() +{ + thr_lock_init(&lock); + mysql_mutex_init(PSI_INSTRUMENT_ME, &autoinc_refill_mutex, + MY_MUTEX_INIT_FAST); +} Duckdb_share *ha_duckdb::get_share() { @@ -458,7 +464,7 @@ static void build_duckdb_blob_map(Field **field_list, MY_BITMAP *map) /* ----- DML operations ----- */ -int ha_duckdb::write_row(const uchar *) +int ha_duckdb::write_row(const uchar *buf) { DBUG_ENTER("ha_duckdb::write_row"); int ret= 0; @@ -474,6 +480,19 @@ int ha_duckdb::write_row(const uchar *) DBUG_RETURN(ret); } + /* + Fill the AUTO_INCREMENT field. Every row reaches DuckDB with all columns + materialized (both the Appender and the SQL insert paths), so the value + must be assigned here; the sequence-backed column default in DuckDB is + never consulted on this path. + */ + if (table->next_number_field && buf == table->record[0] && + (ret= update_auto_increment())) + { + dbug_tmp_restore_column_map(&table->read_set, org_bitmap); + DBUG_RETURN(ret); + } + myduck::BatchState batch_state= get_batch_state(thd); if (batch_state == myduck::BatchState::NOT_IN_BATCH) @@ -510,6 +529,119 @@ int ha_duckdb::write_row(const uchar *) DBUG_RETURN(ret); } +/* + Consume `want` ids from the table's AUTO_INCREMENT sequence in one query + and return the first id of the block, or 0 on failure (a sequence value is + never 0: CREATE SEQUENCE is emitted with START WITH >= 1). + + nextval() calls of a concurrent DuckDB-side writer (batch ingestion through + run_in_duckdb relying on the column default) may interleave with ours + inside this query. The ids missing from our span belong to that writer, so + such a block must not be handed out; detect it by comparing the span width + with the block size and retry. MariaDB-side callers cannot cause this: they + are serialized on the share's autoinc_refill_mutex. +*/ +static ulonglong reserve_autoinc_block(THD *thd, const char *db_name, + const char *table_name, ulonglong want) +{ + std::string query= + "SELECT min(x), max(x) FROM (SELECT nextval('\"" + + std::string(db_name) + "\".\"" + autoinc_sequence_name(table_name) + + "\"') AS x FROM range(" + std::to_string(want) + ")) t"; + + auto *ctx= get_duckdb_context(thd); + for (int attempt= 0; attempt < 3; attempt++) + { + auto result= myduck::duckdb_query(ctx->get_connection(), query); + if (result == nullptr || result->HasError()) + return 0; + auto chunk= result->Fetch(); + if (chunk == nullptr || chunk->size() == 0) + return 0; + int64_t lo= chunk->GetValue(0, 0).GetValue(); + int64_t hi= chunk->GetValue(1, 0).GetValue(); + if ((ulonglong) (hi - lo) + 1 == want) + return (ulonglong) lo; + } + return 0; +} + +void ha_duckdb::get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, + ulonglong *first_value, + ulonglong *nb_reserved_values) +{ + DBUG_ENTER("ha_duckdb::get_auto_increment"); + + /* + The server rounds the first value up to the offset/increment grid + (handler.cc:4389: at most first + increment - 1) and then steps + `increment` at a time, so it uses ids <= first + nb_desired * increment + - 1. Consume exactly that many from the cache to keep the server inside + our block while leaving no unused ids behind. + */ + ulonglong need= nb_desired_values * increment; + if (need / increment != nb_desired_values) /* multiplication overflow */ + { + *first_value= ULONGLONG_MAX; + *nb_reserved_values= 0; + DBUG_VOID_RETURN; + } + + Duckdb_share::Autoinc_range cur= share->autoinc_range.load(); + for (;;) + { + /* Fast path: claim [cur.next, cur.next + need) with one CAS. */ + while (cur.end - cur.next >= need) + { + Duckdb_share::Autoinc_range claimed= {cur.next + need, cur.end}; + if (share->autoinc_range.compare_exchange_weak(cur, claimed)) + { + *first_value= cur.next; + *nb_reserved_values= nb_desired_values; + DBUG_VOID_RETURN; + } + /* CAS failure reloaded cur. */ + } + + mysql_mutex_lock(&share->autoinc_refill_mutex); + cur= share->autoinc_range.load(); + if (cur.end - cur.next >= need) + { + /* Someone refilled while we waited; back to the fast path. */ + mysql_mutex_unlock(&share->autoinc_refill_mutex); + continue; + } + + ulonglong want= std::max(need, autoinc_cache_size); + DatabaseTableNames dt(table->s->normalized_path.str); + ulonglong first= reserve_autoinc_block(ha_thd(), dt.db_name.c_str(), + dt.table_name.c_str(), want); + if (first == 0) + { + mysql_mutex_unlock(&share->autoinc_refill_mutex); + /* The only failure signal the server understands. */ + *first_value= ULONGLONG_MAX; + *nb_reserved_values= 0; + DBUG_VOID_RETURN; + } + /* + Publish the new block minus our own share. A plain store cannot clash + with concurrent fast-path claims: those still come from the old range, + which lies entirely below `first` because the sequence is monotonic. + The remainder of the old block is dropped, not merged: ids between the + two blocks may already belong to a DuckDB-side writer. The unused ids + become gaps, which AUTO_INCREMENT permits. + */ + share->autoinc_range.store({first + need, first + want}); + mysql_mutex_unlock(&share->autoinc_refill_mutex); + + *first_value= first; + *nb_reserved_values= nb_desired_values; + DBUG_VOID_RETURN; + } +} + int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) { DBUG_ENTER("ha_duckdb::update_row"); @@ -1012,6 +1144,19 @@ int ha_duckdb::delete_table(const char *name) if (query_result == nullptr || query_result->HasError()) DBUG_RETURN(HA_DUCKDB_DROP_TABLE_ERROR); + /* + Drop the AUTO_INCREMENT sequence, if any. It must go after the table: + while the column default exists the catalog dependency blocks the drop. + The name is derived from the table alone, so no lookup is needed and + the statement is harmless for tables without AUTO_INCREMENT. + */ + std::string seq_query= "DROP SEQUENCE IF EXISTS \"" + dt.db_name + "\".\"" + + autoinc_sequence_name(dt.table_name) + "\""; + auto seq_result= myduck::duckdb_query(ctx->get_connection(), seq_query); + + if (seq_result == nullptr || seq_result->HasError()) + DBUG_RETURN(HA_DUCKDB_DROP_TABLE_ERROR); + DBUG_RETURN(0); } @@ -1229,6 +1374,12 @@ static MYSQL_SYSVAR_BOOL(update_modified_column_only, "Whether to only update modified columns", NULL, NULL, TRUE); +static MYSQL_SYSVAR_ULONGLONG(autoinc_cache_size, autoinc_cache_size, + PLUGIN_VAR_RQCMDARG, + "Number of AUTO_INCREMENT ids reserved from the " + "backing DuckDB sequence per query", + NULL, NULL, 1000, 1, 1048576, 0); + /* ---- Global proxy variables (pushed into DuckDB) ---- */ static MYSQL_SYSVAR_ULONGLONG(memory_limit, myduck::global_memory_limit, @@ -1355,6 +1506,7 @@ static struct st_mysql_sys_var *duckdb_system_variables[]= { MYSQL_SYSVAR(allow_run_in_duckdb), MYSQL_SYSVAR(copy_ddl_in_batch), MYSQL_SYSVAR(dml_in_batch), MYSQL_SYSVAR(update_modified_column_only), + MYSQL_SYSVAR(autoinc_cache_size), /* Global proxy */ MYSQL_SYSVAR(memory_limit), MYSQL_SYSVAR(temp_directory), MYSQL_SYSVAR(max_temp_directory_size), MYSQL_SYSVAR(max_threads), diff --git a/storage/duckdb/ha_duckdb.h b/storage/duckdb/ha_duckdb.h index b0ff93d2471..4f32bbebe10 100644 --- a/storage/duckdb/ha_duckdb.h +++ b/storage/duckdb/ha_duckdb.h @@ -21,6 +21,7 @@ #define HA_DUCKDB_H #include +#include #include #include "my_global.h" @@ -47,8 +48,30 @@ class Duckdb_share : public Handler_share { public: THR_LOCK lock; + /* + AUTO_INCREMENT block cache. Blocks are drawn from the table's DuckDB + sequence in get_auto_increment(); the cache spreads the cost of that + query over duckdb_autoinc_cache_size ids. + + The fast path claims ids with one CAS on the {next, end} pair. Both + fields must move together: with independent atomics a reader could pair + a stale bound with a fresh one and claim ids of a dropped block gap, + which may belong to a DuckDB-side writer. The mutex only serializes + block refills. + */ + struct Autoinc_range + { + ulonglong next; ///< next unused id of the cached block + ulonglong end; ///< one past the last id of the cached block + }; + alignas(16) std::atomic autoinc_range{{0, 0}}; + mysql_mutex_t autoinc_refill_mutex; Duckdb_share(); - ~Duckdb_share() { thr_lock_delete(&lock); } + ~Duckdb_share() + { + thr_lock_delete(&lock); + mysql_mutex_destroy(&autoinc_refill_mutex); + } }; /** @brief @@ -69,7 +92,7 @@ public: ulonglong table_flags() const override { return (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE | - HA_NO_AUTO_INCREMENT | HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | + HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | HA_CAN_DIRECT_UPDATE_AND_DELETE); } @@ -111,6 +134,9 @@ public: int close(void) override; int write_row(const uchar *buf) override; + void get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, ulonglong *first_value, + ulonglong *nb_reserved_values) override; int update_row(const uchar *old_data, const uchar *new_data) override; int delete_row(const uchar *buf) override; diff --git a/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc b/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc index 0883ef37454..4e28aa30087 100644 --- a/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc +++ b/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc @@ -292,11 +292,14 @@ eval ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = $algorithm; --error ER_PARSE_ERROR eval ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = $algorithm; -# AUTO_INCREMENT +# AUTO_INCREMENT is supported by CREATE TABLE only. +# 'c INT AUTO_INCREMENT KEY' would be rejected by the parser instead (bare KEY +# means PRIMARY KEY and t already has one), and AUTO_INCREMENT without any key +# is rejected by the server, so neither would reach the engine. --error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT -eval ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = $algorithm; +eval ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = $algorithm; --error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT -eval ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = $algorithm; +eval ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = $algorithm; # ENGINE_ATTRIBUTE --error ER_UNKNOWN_OPTION diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result index 67b63d9d662..0e3040ddcc7 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result @@ -718,9 +718,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = INSTANT; ERROR HY000: Got error 168 'INVISIBLE column is not supported. Try removing INVISIBLE from column 'd'' from DuckDB ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = INSTANT; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'INVISIBLE, ALGORITHM = INSTANT' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = INSTANT; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = INSTANT; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = INSTANT; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = INSTANT; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' @@ -1596,9 +1596,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = INPLACE; ERROR HY000: Got error 168 'INVISIBLE column is not supported. Try removing INVISIBLE from column 'd'' from DuckDB ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = INPLACE; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'INVISIBLE, ALGORITHM = INPLACE' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = INPLACE; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = INPLACE; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = INPLACE; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = INPLACE; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result index a8158a2afb0..3dc59f19a2d 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result @@ -718,9 +718,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = COPY; ERROR HY000: Table storage engine 'DuckDB' does not support the create option 'INVISIBLE column' ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = COPY; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'INVISIBLE, ALGORITHM = COPY' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = COPY; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = COPY; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = COPY; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = COPY; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result new file mode 100644 index 00000000000..20faf8a00c1 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result @@ -0,0 +1,204 @@ +SET NAMES utf8mb4; +# +# MDEV-40266: AUTO_INCREMENT backed by a DuckDB sequence +# +# --- CREATE TABLE creates the sequence ------------------------------- +CREATE TABLE t (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB; +SHOW CREATE TABLE t; +Table Create Table +t CREATE TABLE `t` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `a` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +# The sequence must exist in DuckDB, named after the table. +SELECT run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name"); +run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name") +schema_name sequence_name start_value +VARCHAR VARCHAR BIGINT +[ Rows: 1] +db_autoinc mdb_autoinc_t 1 + + +# The column default must reference that sequence, so that writes which +# bypass MariaDB still draw a non-colliding id from the same counter. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 2] +a NULL +id nextval('"db_autoinc"."mdb_autoinc_t"') + + +# --- an insert bypassing MariaDB uses the sequence -------------------- +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT id, a FROM t ORDER BY id; +id a +1 10 +2 20 +# --- MariaDB-side inserts draw from the same sequence ------------------ +INSERT INTO t(a) VALUES(30); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +3 +INSERT INTO t(a) VALUES(40); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +4 +# A multi-row insert reserves its ids in one go. +INSERT INTO t(a) VALUES(50),(60),(70); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +5 +SELECT id, a FROM t ORDER BY id; +id a +1 10 +2 20 +3 30 +4 40 +5 50 +6 60 +7 70 +# A DuckDB-side insert after MariaDB ones must not collide: the block +# reservation advanced the shared counter past the cached ids. +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT count(*), count(DISTINCT id) FROM t; +count(*) count(DISTINCT id) +8 8 +# --- START WITH honours CREATE TABLE ... AUTO_INCREMENT=N ------------- +CREATE TABLE t2 (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB AUTO_INCREMENT=100; +SELECT run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'"); +run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'") +sequence_name start_value +VARCHAR BIGINT +[ Rows: 1] +mdb_autoinc_t2 100 + + +SELECT run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)"); +run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)") +Count +BIGINT +[ Rows: 1] +1 + + +INSERT INTO t2(a) VALUES(2); +SELECT id, a FROM t2 ORDER BY id; +id a +100 1 +101 2 +# --- a table without AUTO_INCREMENT gets no sequence ------------------ +CREATE TABLE t3 (id INT PRIMARY KEY, a INT) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'") +count_star() +BIGINT +[ Rows: 1] +0 + + +# AUTO_INCREMENT is accepted by CREATE TABLE only; seeding an existing +# column from its current maximum is not implemented. +ALTER TABLE t3 MODIFY COLUMN id INT AUTO_INCREMENT; +ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns +ALTER TABLE t3 ADD COLUMN b INT AUTO_INCREMENT UNIQUE; +ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns +# The rejected ALTER must leave the table untouched. +SHOW CREATE TABLE t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `id` int(11) NOT NULL, + `a` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +# --- setval() is available (local backport of DuckDB PR #24061) -------- +SELECT run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval"); +run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval") +Count +BIGINT +[ Rows: 0] + + +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)"); +run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)") +setval('db_autoinc.check_setval', 42) +BIGINT +[ Rows: 1] +42 + + +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +run_in_duckdb("SELECT nextval('db_autoinc.check_setval')") +nextval('db_autoinc.check_setval') +BIGINT +[ Rows: 1] +43 + + +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)"); +run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)") +setval('db_autoinc.check_setval', 100, CAST('f' AS BOOLEAN)) +BIGINT +[ Rows: 1] +100 + + +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +run_in_duckdb("SELECT nextval('db_autoinc.check_setval')") +nextval('db_autoinc.check_setval') +BIGINT +[ Rows: 1] +100 + + +SELECT run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval"); +run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval") +Success +BOOLEAN +[ Rows: 0] + + +# --- DROP TABLE removes the sequence ---------------------------------- +DROP TABLE t; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'") +count_star() +BIGINT +[ Rows: 1] +0 + + +DROP TABLE t2; +DROP TABLE t3; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'") +count_star() +BIGINT +[ Rows: 1] +0 + + diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test new file mode 100644 index 00000000000..b0614b1992e --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test @@ -0,0 +1,84 @@ +--source ../include/have_duckdb.inc + +--disable_query_log +CREATE DATABASE IF NOT EXISTS db_autoinc CHARACTER SET utf8mb4; +USE db_autoinc; +--enable_query_log +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-40266: AUTO_INCREMENT backed by a DuckDB sequence +--echo # + +--echo # --- CREATE TABLE creates the sequence ------------------------------- +CREATE TABLE t (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB; + +SHOW CREATE TABLE t; + +--echo # The sequence must exist in DuckDB, named after the table. +SELECT run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name"); + +--echo # The column default must reference that sequence, so that writes which +--echo # bypass MariaDB still draw a non-colliding id from the same counter. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name"); + +--echo # --- an insert bypassing MariaDB uses the sequence -------------------- +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)"); +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)"); +SELECT id, a FROM t ORDER BY id; + +--echo # --- MariaDB-side inserts draw from the same sequence ------------------ +INSERT INTO t(a) VALUES(30); +SELECT LAST_INSERT_ID(); +INSERT INTO t(a) VALUES(40); +SELECT LAST_INSERT_ID(); + +--echo # A multi-row insert reserves its ids in one go. +INSERT INTO t(a) VALUES(50),(60),(70); +SELECT LAST_INSERT_ID(); +SELECT id, a FROM t ORDER BY id; + +--echo # A DuckDB-side insert after MariaDB ones must not collide: the block +--echo # reservation advanced the shared counter past the cached ids. +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)"); +SELECT count(*), count(DISTINCT id) FROM t; + +--echo # --- START WITH honours CREATE TABLE ... AUTO_INCREMENT=N ------------- +CREATE TABLE t2 (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB AUTO_INCREMENT=100; +SELECT run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'"); +SELECT run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)"); +INSERT INTO t2(a) VALUES(2); +SELECT id, a FROM t2 ORDER BY id; + +--echo # --- a table without AUTO_INCREMENT gets no sequence ------------------ +CREATE TABLE t3 (id INT PRIMARY KEY, a INT) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'"); + +--echo # AUTO_INCREMENT is accepted by CREATE TABLE only; seeding an existing +--echo # column from its current maximum is not implemented. +--error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT +ALTER TABLE t3 MODIFY COLUMN id INT AUTO_INCREMENT; +--error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT +ALTER TABLE t3 ADD COLUMN b INT AUTO_INCREMENT UNIQUE; +--echo # The rejected ALTER must leave the table untouched. +SHOW CREATE TABLE t3; + +--echo # --- setval() is available (local backport of DuckDB PR #24061) -------- +SELECT run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval"); +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)"); +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)"); +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +SELECT run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval"); + +--echo # --- DROP TABLE removes the sequence ---------------------------------- +DROP TABLE t; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'"); + +DROP TABLE t2; +DROP TABLE t3; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'"); + +--disable_query_log +DROP DATABASE db_autoinc; +--enable_query_log diff --git a/storage/duckdb/patches/duckdb-pr24061-setval.diff b/storage/duckdb/patches/duckdb-pr24061-setval.diff new file mode 100644 index 00000000000..68965fa3504 --- /dev/null +++ b/storage/duckdb/patches/duckdb-pr24061-setval.diff @@ -0,0 +1,179 @@ +diff --git a/src/catalog/catalog_entry/sequence_catalog_entry.cpp b/src/catalog/catalog_entry/sequence_catalog_entry.cpp +index d6a548a267..dbec7a91ac 100644 +--- a/src/catalog/catalog_entry/sequence_catalog_entry.cpp ++++ b/src/catalog/catalog_entry/sequence_catalog_entry.cpp +@@ -81,6 +81,28 @@ int64_t SequenceCatalogEntry::NextValue(DuckTransaction &transaction) { + return result; + } + ++int64_t SequenceCatalogEntry::SetValue(DuckTransaction &transaction, int64_t value, bool is_called) { ++ { ++ lock_guard seqlock(lock); ++ if (value < data.min_value || value > data.max_value) { ++ throw SequenceException("setval: value %lld is out of bounds for sequence \"%s\" (%lld..%lld)", value, name, ++ data.min_value, data.max_value); ++ } ++ ++ data.counter = value; ++ if (!is_called) { ++ data.usage_count++; ++ if (!temporary) { ++ transaction.PushSequenceUsage(*this, data); ++ } ++ return value; ++ } ++ } ++ ++ // is_called: behave as if nextval() was just invoked and returned `value`. ++ return NextValue(transaction); ++} ++ + void SequenceCatalogEntry::ReplayValue(uint64_t v_usage_count, int64_t v_counter) { + if (v_usage_count > data.usage_count) { + data.usage_count = v_usage_count; +diff --git a/src/function/function_list.cpp b/src/function/function_list.cpp +index 4e269f0604..58bf369e01 100644 +--- a/src/function/function_list.cpp ++++ b/src/function/function_list.cpp +@@ -153,6 +153,7 @@ static const StaticFunctionDefinition function[] = { + DUCKDB_SCALAR_FUNCTION_SET_ALIAS(RegexpSplitToArrayFun), + DUCKDB_SCALAR_FUNCTION(RemapStructFun), + DUCKDB_SCALAR_FUNCTION(RowFun), ++ DUCKDB_SCALAR_FUNCTION_SET(SetvalFun), + DUCKDB_SCALAR_FUNCTION_SET(SHA1Fun), + DUCKDB_SCALAR_FUNCTION_SET(SHA256Fun), + DUCKDB_SCALAR_FUNCTION_ALIAS(SplitFun), +diff --git a/src/function/scalar/sequence/nextval.cpp b/src/function/scalar/sequence/nextval.cpp +index 8a53af7bbb..366afcbc3d 100644 +--- a/src/function/scalar/sequence/nextval.cpp ++++ b/src/function/scalar/sequence/nextval.cpp +@@ -20,17 +20,24 @@ namespace duckdb { + namespace { + + struct CurrentSequenceValueOperator { +- static int64_t Operation(DuckTransaction &, SequenceCatalogEntry &seq) { ++ static int64_t Operation(DuckTransaction &, SequenceCatalogEntry &seq, const int64_t, const bool) { + return seq.CurrentValue(); + } + }; + + struct NextSequenceValueOperator { +- static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq) { ++ static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq, const int64_t, const bool) { + return seq.NextValue(transaction); + } + }; + ++struct SetValValueOperator { ++ static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq, const int64_t value, ++ const bool is_called) { ++ return seq.SetValue(transaction, value, is_called); ++ } ++}; ++ + SequenceCatalogEntry &BindSequence(Binder &binder, string &catalog, string &schema, const string &name) { + // fetch the sequence from the catalog + Binder::BindSchemaOrCatalog(binder.context, catalog, schema); +@@ -83,10 +90,44 @@ void NextValFunction(DataChunk &args, ExpressionState &state, Vector &result) { + // sequence to use is hard coded + // increment the sequence + result.SetVectorType(VectorType::FLAT_VECTOR); ++ ++ // setval takes the new value (and optionally is_called) as extra arguments ++ UnifiedVectorFormat new_val_data; ++ UnifiedVectorFormat is_called_data; ++ bool has_new_val = false; ++ bool has_is_called = false; ++ if (std::is_same::value) { ++ args.data[1].ToUnifiedFormat(args.size(), new_val_data); ++ has_new_val = true; ++ if (args.ColumnCount() == 3) { ++ args.data[2].ToUnifiedFormat(args.size(), is_called_data); ++ has_is_called = true; ++ } ++ } ++ + auto result_data = FlatVector::GetData(result); ++ auto &result_validity = FlatVector::Validity(result); + for (idx_t i = 0; i < args.size(); i++) { ++ int64_t value = 0; ++ bool is_called = true; ++ if (has_new_val) { ++ auto idx = new_val_data.sel->get_index(i); ++ if (!new_val_data.validity.RowIsValid(idx)) { ++ result_validity.SetInvalid(i); ++ continue; ++ } ++ value = UnifiedVectorFormat::GetData(new_val_data)[idx]; ++ } ++ if (has_is_called) { ++ auto idx = is_called_data.sel->get_index(i); ++ if (!is_called_data.validity.RowIsValid(idx)) { ++ result_validity.SetInvalid(i); ++ continue; ++ } ++ is_called = UnifiedVectorFormat::GetData(is_called_data)[idx]; ++ } + // get the next value from the sequence +- result_data[i] = OP::Operation(lstate.transaction, lstate.sequence); ++ result_data[i] = OP::Operation(lstate.transaction, lstate.sequence, value, is_called); + } + } + +@@ -162,4 +203,25 @@ ScalarFunction CurrvalFun::GetFunction() { + return curr_val; + } + ++ScalarFunctionSet SetvalFun::GetFunctions() { ++ ScalarFunction set_val("setval", {LogicalType::VARCHAR, LogicalType::BIGINT}, LogicalType::BIGINT, ++ NextValFunction, nullptr, nullptr); ++ set_val.SetBindExtendedCallback(NextValBind); ++ set_val.SetSerializeCallback(Serialize); ++ set_val.SetDeserializeCallback(Deserialize); ++ set_val.SetModifiedDatabasesCallback(NextValModifiedDatabases); ++ set_val.SetInitStateCallback(NextValLocalFunction); ++ set_val.SetVolatile(); ++ set_val.SetFallible(); ++ ++ ScalarFunctionSet set_val_set; ++ set_val_set.AddFunction(set_val); ++ ++ // Add an overload that takes an additional boolean parameter ++ set_val.arguments.push_back(LogicalType::BOOLEAN); ++ set_val_set.AddFunction(set_val); ++ ++ return set_val_set; ++} ++ + } // namespace duckdb +diff --git a/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp b/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +index ed12b77a1c..19d3737fb8 100644 +--- a/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp ++++ b/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +@@ -61,6 +61,7 @@ public: + SequenceData GetData() const; + int64_t CurrentValue(); + int64_t NextValue(DuckTransaction &transaction); ++ int64_t SetValue(DuckTransaction &transaction, int64_t value, bool is_called); + void ReplayValue(uint64_t usage_count, int64_t counter); + + string ToSQL() const override; +diff --git a/src/include/duckdb/function/scalar/sequence_functions.hpp b/src/include/duckdb/function/scalar/sequence_functions.hpp +index 99766a3326..44b8224006 100644 +--- a/src/include/duckdb/function/scalar/sequence_functions.hpp ++++ b/src/include/duckdb/function/scalar/sequence_functions.hpp +@@ -35,4 +35,14 @@ struct NextvalFun { + static ScalarFunction GetFunction(); + }; + ++struct SetvalFun { ++ static constexpr const char *Name = "setval"; ++ static constexpr const char *Parameters = "sequence_name,value[,is_called]"; ++ static constexpr const char *Description = "Set the value of the sequence to the specified value. If is_called is true, the sequence is called. If is_called is false, the sequence is not called."; ++ static constexpr const char *Example = "setval('my_sequence_name', 100, true)"; ++ static constexpr const char *Categories = ""; ++ ++ static ScalarFunctionSet GetFunctions(); ++}; ++ + } // namespace duckdb From d29da23e136713716b37912e5fc1722ccc6f3bc9 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Wed, 5 Aug 2026 08:27:42 +0300 Subject: [PATCH 061/150] mysqltest: fix do_exec() status decoding for signal-killed processes WEXITSTATUS() only produces a meaningful value when the process exited normally. If the command was killed by a signal, it silently returned status 0, hiding the failure. Check WIFEXITED/WIFSIGNALED and map signal deaths to the shell's 128+signal convention. --- client/mysqltest.cc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 24a0298d28c..4eddd590aef 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -3807,9 +3807,24 @@ void do_exec(struct st_command *command) if (error) { - uint status= WEXITSTATUS(error); + uint status; int i; +#ifdef _WIN32 + status= WEXITSTATUS(error); +#else + /* WEXITSTATUS() is only valid for a normal exit; a process killed by an + uncaught signal must be translated using the shell's 128+signal + convention, or the real error is silently lost as status 0. */ + if (WIFEXITED(error)) + status= WEXITSTATUS(error); + else if (WIFSIGNALED(error)) + status= 128 + WTERMSIG(error); + else + status= error; + +#endif + if (command->abort_on_error) { report_or_die("exec of '%s' failed, error: %d, status: %d, errno: %d\n" From 1624c2fe796725bbc7b49cbec166866a352a99c3 Mon Sep 17 00:00:00 2001 From: Alexey Botchkov Date: Mon, 20 Jul 2026 21:08:26 +0400 Subject: [PATCH 062/150] MDEV-39750 ExtractValue does not control recursion depth. Stack exhaustive test shouldn't be ran with the ASAN/UBSAN. --- mysql-test/main/lotofstack.result | 7 +++++++ mysql-test/main/lotofstack.test | 11 +++++++++++ mysql-test/main/xml.result | 7 ------- mysql-test/main/xml.test | 12 ------------ 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/mysql-test/main/lotofstack.result b/mysql-test/main/lotofstack.result index dfe12c3f796..eb51ccd8e04 100644 --- a/mysql-test/main/lotofstack.result +++ b/mysql-test/main/lotofstack.result @@ -108,4 +108,11 @@ SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',RE # set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); SELECT ST_GeomFromWKB(@a); +# +# MDEV-39750 ExtractValue does not control recursion depth. +# +SELECT ExtractValue('', REPEAT('(', 100000)); +ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. +SELECT ExtractValue('', REPEAT('/n', 100000)); +ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. # End of 10.6 tests diff --git a/mysql-test/main/lotofstack.test b/mysql-test/main/lotofstack.test index 768c87160b6..7c8dfa21271 100644 --- a/mysql-test/main/lotofstack.test +++ b/mysql-test/main/lotofstack.test @@ -149,4 +149,15 @@ set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); SELECT ST_GeomFromWKB(@a); --enable_result_log +--echo # +--echo # MDEV-39750 ExtractValue does not control recursion depth. +--echo # +--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ExtractValue('', REPEAT('(', 100000)); + +--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ExtractValue('', REPEAT('/n', 100000)); + --echo # End of 10.6 tests diff --git a/mysql-test/main/xml.result b/mysql-test/main/xml.result index 1cf232e5475..e699a8fb108 100644 --- a/mysql-test/main/xml.result +++ b/mysql-test/main/xml.result @@ -1414,11 +1414,4 @@ ExtractValue(@xml, '/employee/dt3') NULL Warnings: Warning 1525 Incorrect XML value: 'parse error at line 10 pos 27: unexpected END-OF-INPUT' -# -# MDEV-39750 ExtractValue does not control recursion depth. -# -SELECT ExtractValue('', REPEAT('(', 100000)); -ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. -SELECT ExtractValue('', REPEAT('/n', 100000)); -ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. # End of 10.6 tests diff --git a/mysql-test/main/xml.test b/mysql-test/main/xml.test index f849e81d974..bc1e36a0eb8 100644 --- a/mysql-test/main/xml.test +++ b/mysql-test/main/xml.test @@ -899,17 +899,5 @@ set @xml= ' SELECT ExtractValue(@xml, '/employee/dt3'); ---echo # ---echo # MDEV-39750 ExtractValue does not control recursion depth. ---echo # - ---replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ ---error ER_STACK_OVERRUN_NEED_MORE -SELECT ExtractValue('', REPEAT('(', 100000)); - ---replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ ---error ER_STACK_OVERRUN_NEED_MORE -SELECT ExtractValue('', REPEAT('/n', 100000)); - --echo # End of 10.6 tests From 3b1c2e58abab5571bec4ff52773ddc75b1738da9 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 30 Jul 2026 18:36:14 +0200 Subject: [PATCH 063/150] MDEV-40571 insufficient validation of frm data when opening a table numerous checks that the frm is valid, no OOB reads, values make sense (number of keyparts not less than number of keys, no keys means no keyparts, number of long unique fields is not larger than number of fields, fields values in the record don't overlap and don't go over record ends, and so on). most asserts were changed to if()'s --- sql/table.cc | 285 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 197 insertions(+), 88 deletions(-) diff --git a/sql/table.cc b/sql/table.cc index a14580c6fad..659c7d069b4 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -113,10 +113,8 @@ static std::atomic last_table_id; /* Functions defined in this file */ -static bool fix_type_pointers(const char ***typelib_value_names, - uint **typelib_value_lengths, - TYPELIB *point_to_type, uint types, - char *names, size_t names_length); +static bool fix_type_pointers(const char ***, const char **, uint **, + TYPELIB *, uint, char *, size_t); static field_index_t find_field(Field **fields, uchar *record, uint start, uint length); @@ -767,7 +765,7 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, uint i, j, n_length; KEY_PART_INFO *key_part= NULL; ulong *rec_per_key= NULL; - KEY_PART_INFO *first_key_part= NULL; + KEY_PART_INFO *first_key_part= NULL, *key_part_end; uint first_key_parts= 0; if (!keys) @@ -775,7 +773,7 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, if (!(keyinfo = (KEY*) alloc_root(&share->mem_root, len))) return 1; bzero((char*) keyinfo, len); - key_part= reinterpret_cast (keyinfo); + key_part_end= key_part= reinterpret_cast (keyinfo); } /* @@ -831,6 +829,7 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, sizeof(ulong) * *ext_key_parts))) return 1; first_key_part= key_part; + key_part_end= key_part + *ext_key_parts; first_key_parts= first_keyinfo->user_defined_key_parts; keyinfo->flags= first_keyinfo->flags; keyinfo->key_length= first_keyinfo->key_length; @@ -844,6 +843,8 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, keyinfo->rec_per_key= rec_per_key; for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++) { + if (key_part >= key_part_end) + return 1; if (strpos + (new_frm_ver >= 1 ? 9 : 7) >= frm_image_end) return 1; if (!(keyinfo->algorithm == HA_KEY_ALG_LONG_HASH)) @@ -901,6 +902,8 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, } if (curr_key_part == curr_key_part_end) { + if (key_part >= key_part_end) + return 1; *key_part++= first_key_part[j]; *rec_per_key++= 0; keyinfo->ext_key_parts++; @@ -914,13 +917,17 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, share->ext_key_parts++; share->ext_key_parts+= keyinfo->ext_key_parts; } - keynames->str= (char*) key_part; - keynames->length= strnmov(keynames->str, (char *) strpos, - frm_image_end - strpos) - keynames->str; - strpos+= keynames->length; - if (*strpos++) // key names are \0-terminated + if (key_part > key_part_end) return 1; - keynames->length++; // Include '\0', to make fix_type_pointers() happy. + size_t max_keyname_len= MY_MIN(len, (uint)(frm_image_end - strpos)); + keynames->str= (char*) key_part; + keynames->length= strnmov(keynames->str, (char *)strpos, + max_keyname_len) - keynames->str; + if (keynames->length >= max_keyname_len) + return 1; // meaning key name was not \0-terminated + keynames->length++; // include '\0', to make fix_type_pointers() happy. + strpos+= keynames->length; + len-= (uint)keynames->length; //reading index comments for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++) @@ -932,11 +939,13 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, keyinfo->comment.length= uint2korr(strpos); strpos+= 2; - if (strpos + keyinfo->comment.length >= frm_image_end) + if (strpos + keyinfo->comment.length >= frm_image_end || + keyinfo->comment.length > len) return 1; keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos, keyinfo->comment.length); strpos+= keyinfo->comment.length; + len-= (uint)keyinfo->comment.length; } DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) == (keyinfo->comment.length > 0)); @@ -1055,7 +1064,8 @@ static void mysql57_calculate_null_position(TABLE_SHARE *share, uchar **null_pos, uint *null_bit_pos, const uchar *strpos, - const uchar *vcol_screen_pos) + const uchar *vcol_screen_pos, + const uchar *vcol_screen_end) { uint field_pack_length= 17; @@ -1066,6 +1076,8 @@ static void mysql57_calculate_null_position(TABLE_SHARE *share, if ((strpos[10] & MYSQL57_GENERATED_FIELD)) { + if (vcol_screen_pos + MYSQL57_GCOL_HEADER_SIZE >= vcol_screen_end) + return; /* Skip virtual (not stored) generated field */ bool stored_in_db= vcol_screen_pos[3]; vcol_screen_pos+= (uint2korr(vcol_screen_pos + 1) + @@ -1786,6 +1798,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, const uchar *forminfo; const uchar *frm_image_end = frm_image + frm_length; uchar *record, *null_flags, *null_pos, *UNINIT_VAR(mysql57_vcol_null_pos); + uchar *data_start, *data_end; + uchar *first_stored= 0, *first_virtual= 0, *next_stored= 0, *next_virtual= 0; const uchar *disk_buff, *strpos; ulong pos, record_offset; ulong rec_buff_length; @@ -1793,13 +1807,13 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, KEY *keyinfo; KEY_PART_INFO *key_part= NULL; Field **field_ptr, *reg_field; - const char **interval_array; + const char **interval_array, **interval_array_end; uint *typelib_value_lengths= NULL; enum legacy_db_type legacy_db_type; my_bitmap_map *bitmaps; bool null_bits_are_used; uint vcol_screen_length; - uchar *vcol_screen_pos; + uchar *vcol_screen_pos, *vcol_screen_end; LEX_CUSTRING options; LEX_CSTRING se_name= empty_clex_str; KEY first_keyinfo; @@ -1809,7 +1823,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, bool vers_can_native= false, frm_created= 0; Field_data_type_info_array field_data_type_info_array; MEM_ROOT *old_root= thd->mem_root; - Virtual_column_info **table_check_constraints; + Virtual_column_info **table_check_constr; bool *interval_unescaped= NULL; extra2_fields extra2; bool extra_index_flags_present= FALSE; @@ -1850,6 +1864,14 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* Length of the MariaDB extra2 segment in the form file. */ len = uint2korr(frm_image+4); + if (frm_length < FRM_HEADER_SIZE + len || + !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len))) + goto err; + + forminfo= frm_image + pos; + if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end) + goto err; + if (read_extra2(frm_image, len, &extra2)) goto err; @@ -1869,17 +1891,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (!share->default_part_plugin) goto err; } -#endif - if (frm_length < FRM_HEADER_SIZE + len || - !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len))) - goto err; - - forminfo= frm_image + pos; - if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end) - goto err; - -#ifdef WITH_PARTITION_STORAGE_ENGINE if (frm_image[61] && !share->default_part_plugin) { enum legacy_db_type db_type= (enum legacy_db_type) (uint) frm_image[61]; @@ -1971,6 +1983,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->keys_in_use.init(keys); ext_key_parts= key_parts; + if ((key_parts && !keys) || key_parts < keys) + goto err; + if (extra2.index_flags.str && extra2.index_flags.length != keys) goto err; @@ -2127,12 +2142,10 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, { //reading long table comment if (next_chunk + 2 > buff_end) - { - DBUG_PRINT("error", - ("long table comment is not defined in .frm")); goto err; - } share->comment.length = uint2korr(next_chunk); + if (next_chunk + 2 + share->comment.length > buff_end) + goto err; if (! (share->comment.str= strmake_root(&share->mem_root, (char*)next_chunk + 2, share->comment.length))) { @@ -2141,17 +2154,18 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, next_chunk+= 2 + share->comment.length; } - DBUG_ASSERT(next_chunk <= buff_end); - if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS_legacy) { if (options.str) goto err; + if (next_chunk + 4 > buff_end) + goto err; options.length= uint4korr(next_chunk); options.str= next_chunk + 4; next_chunk+= options.length + 4; + if (next_chunk > buff_end) + goto err; } - DBUG_ASSERT(next_chunk <= buff_end); } else { @@ -2163,7 +2177,6 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->key_block_size= uint2korr(frm_image+62); keyinfo= share->key_info; - if (extra2.index_flags.str) extra_index_flags_present= TRUE; @@ -2208,6 +2221,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, disk_buff= frm_image + pos + FRM_FORMINFO_SIZE; share->fields= uint2korr(forminfo+258); + if (share->fields <= 0 || share->fields > MAX_FIELDS) + goto err; if (extra2.field_flags.str && extra2.field_flags.length != share->fields) goto err; pos= uint2korr(forminfo+260); /* Length of all screens */ @@ -2229,6 +2244,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->comment.length); } + if (hash_fields > share->fields || share->null_fields > share->fields) + goto err; + DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d vcol_screen_length: %d", interval_count,interval_parts, keys,n_length,int_length, com_length, vcol_screen_length)); /* @@ -2253,12 +2271,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, &share->field, (uint)(share->fields+1)*sizeof(Field*), &share->intervals, (uint)interval_count*sizeof(TYPELIB), &share->check_constraints, (uint) share->table_check_constraints * sizeof(Virtual_column_info*), - /* - This looks wrong: shouldn't it be (+2+interval_count) - instread of (+3) ? - */ - &interval_array, (uint) (share->fields+interval_parts+ keys+3)*sizeof(char *), - &typelib_value_lengths, total_typelib_value_count * sizeof(uint *), + &interval_array, (uint)total_typelib_value_count * sizeof(char *), + &typelib_value_lengths, (uint)total_typelib_value_count * sizeof(uint), &names, (uint) (n_length+int_length), &comment_pos, (uint) com_length, &vcol_screen_pos, vcol_screen_length, @@ -2274,10 +2288,13 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } field_ptr= share->field; - table_check_constraints= share->check_constraints; + interval_array_end= interval_array + total_typelib_value_count; + table_check_constr= share->check_constraints; read_length=(uint) (share->fields * field_pack_length + pos+ (uint) (n_length+int_length+com_length+ vcol_screen_length)); + if (disk_buff + read_length > frm_image_end) + goto err; strpos= disk_buff+pos; if (!interval_count) @@ -2291,21 +2308,24 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, com_length); memcpy(vcol_screen_pos, disk_buff+read_length-vcol_screen_length, vcol_screen_length); + vcol_screen_end= vcol_screen_pos + vcol_screen_length; - if (fix_type_pointers(&interval_array, &typelib_value_lengths, - &share->fieldnames, 1, names, n_length) || + if (fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, &share->fieldnames, 1, names, + n_length) || share->fieldnames.count != share->fields) goto err; - if (fix_type_pointers(&interval_array, &typelib_value_lengths, - share->intervals, interval_count, - names + n_length, int_length)) + if (fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, share->intervals, + interval_count, names + n_length, int_length)) goto err; - if (keynames.length && - (fix_type_pointers(&interval_array, &typelib_value_lengths, - &share->keynames, 1, keynames.str, keynames.length) || - share->keynames.count != keys)) + if ((keynames.length && + fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, &share->keynames, 1, + keynames.str, keynames.length)) || + share->keynames.count != keys) goto err; /* Allocate handler */ @@ -2329,14 +2349,12 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, */ share->null_bytes= (share->null_fields + null_bit_pos + 7) / 8; } -#ifndef WE_WANT_TO_SUPPORT_VERY_OLD_FRM_FILES else { share->null_bytes= (share->null_fields+7)/8; null_flags= null_pos= record + 1 + share->reclength - share->null_bytes; null_bit_pos= 0; } -#endif use_hash= share->fields >= MAX_FIELDS_BEFORE_HASH; if (use_hash) @@ -2357,7 +2375,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, mysql57_vcol_null_bit_pos= null_bit_pos; mysql57_calculate_null_position(share, &mysql57_vcol_null_pos, &mysql57_vcol_null_bit_pos, - strpos, vcol_screen_pos); + strpos, vcol_screen_pos, vcol_screen_end); } /* Set system versioning information. */ @@ -2406,20 +2424,22 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (extra2.without_overlaps.str) { - if (extra2.application_period.str == NULL) + if (extra2.application_period.str == NULL || + extra2.without_overlaps.length < frm_keyno_size) goto err; const uchar *key_pos= extra2.without_overlaps.str; period.unique_keys= read_frm_keyno(key_pos); + if (period.unique_keys > keys || + extra2.without_overlaps.length != (period.unique_keys+1)*frm_keyno_size) + goto err; for (uint k= 0; k < period.unique_keys; k++) { key_pos+= frm_keyno_size; uint key_nr= read_frm_keyno(key_pos); + if (key_nr >= keys) + goto err; key_info[key_nr].without_overlaps= true; } - - if ((period.unique_keys + 1) * frm_keyno_size - != extra2.without_overlaps.length) - goto err; } if (extra2.field_data_type_info.length && @@ -2453,6 +2473,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, comment.str= (char*) comment_pos; comment.length= comment_length; comment_pos+= comment_length; + if (com_length < comment_length) + goto err; + com_length-= comment_length; } if (strpos[13] == MYSQL_TYPE_VIRTUAL && @@ -2465,7 +2488,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, */ uint vcol_info_length= (uint) strpos[12]; - if (!vcol_info_length) // Expect non-null expression + if (vcol_info_length < 4 || + vcol_screen_pos + vcol_info_length > vcol_screen_end) goto err; attr.frm_unpack_basic(strpos); @@ -2567,15 +2591,17 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, byte 4 = stored_in_db byte 5.. = expr */ - if ((uint)(vcol_screen_pos)[0] != 1) + if (vcol_screen_pos + MYSQL57_GCOL_HEADER_SIZE > vcol_screen_end || + vcol_screen_pos[0] != 1) goto err; vcol_info= new (&share->mem_root) Virtual_column_info(); uint vcol_info_length= uint2korr(vcol_screen_pos + 1); - if (!vcol_info_length) // Expect non-empty expression - goto err; vcol_info->stored_in_db= vcol_screen_pos[3]; vcol_info->utf8= 0; vcol_screen_pos+= vcol_info_length + MYSQL57_GCOL_HEADER_SIZE;; + if (!vcol_info_length || // Expect non-empty expression + vcol_screen_pos > vcol_screen_end) + goto err; share->virtual_fields++; } } @@ -2632,6 +2658,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, attr.pack_flag&= ~FIELDFLAG_LONG_DECIMAL; } + if (interval_nr > interval_count) + goto err; + if (interval_nr && attr.charset->mbminlen > 1 && !interval_unescaped[interval_nr - 1]) { @@ -2727,6 +2756,52 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (!reg_field) // Not supported field type goto err; + /* + Verify that fields follow one another in the record without overlaps. + That is the next field starts where the previous field ended. + Virtual fields complicate the check. Stored fields follow each other in + the record order, virtual fields also follow each in the record order + but they're always at the end of the record, and can be interleaved + with normal fields in the table. + + We detect this dynamically - as long as the next field starts where the + previous ended, assume they're normal fields. When the next field starts + with a gap - it's the first virtual (and the future virtual fields must + follow that field). If the next field starts before the previous - it + means the table started from virtual fields and this is the first stored + field. Either ("gap" or "before") can happen only once. + */ + if (!next_stored) // first field, i == 0 + { + first_stored= reg_field->ptr; + next_stored= first_stored + reg_field->pack_length_in_rec(); + } + else if (reg_field->ptr == next_stored) // next stored field + next_stored+= reg_field->pack_length_in_rec(); + else if (reg_field->ptr > next_stored) // virtual + { + if (next_virtual) + { + if (reg_field->ptr != next_virtual) + goto err; + next_virtual+= reg_field->pack_length_in_rec(); + } + else + { + first_virtual= reg_field->ptr; + next_virtual= first_virtual + reg_field->pack_length_in_rec(); + } + } + else // reg_field < next_stored. can happen if the i=0 field was virtual + { + if (next_virtual) + goto err; + first_virtual= first_stored; + next_virtual= next_stored; + first_stored= reg_field->ptr; + next_stored= first_stored + reg_field->pack_length_in_rec(); + } + if (attr.unireg_check == Field::TIMESTAMP_DNUN_FIELD || attr.unireg_check == Field::TIMESTAMP_DN_FIELD) { @@ -2810,7 +2885,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* We want to store the value for the last bits */ swap_variables(uchar*, null_pos, mysql57_vcol_null_pos); swap_variables(uint, null_bit_pos, mysql57_vcol_null_bit_pos); - DBUG_ASSERT((null_pos + (null_bit_pos + 7) / 8) <= share->field[0]->ptr); + if ((null_pos + (null_bit_pos + 7) / 8) > share->field[0]->ptr) + goto err; } /* Fix key->name and key_part->field */ @@ -2838,7 +2914,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, hash_keypart->fieldnr= hash_field_used_no + 1; hash_field= share->field[hash_field_used_no]; hash_field->flags|= LONG_UNIQUE_HASH_FIELD;//Used in parse_vcol_defs - DBUG_ASSERT(hash_field->invisible == INVISIBLE_FULL); + if (hash_field->invisible != INVISIBLE_FULL || + hash_field->pack_length() != HA_HASH_FIELD_LENGTH) + goto err; keyinfo->flags|= HA_NOSAME; share->virtual_fields++; share->stored_fields--; @@ -2867,7 +2945,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, key_part= keyinfo->key_part; for (i=0 ; i < keyinfo->user_defined_key_parts ;i++) { - DBUG_ASSERT(key_part[i].fieldnr > 0); + if (key_part[i].fieldnr <= 0 || key_part[i].fieldnr > share->fields) + goto err; // Table field corresponding to the i'th key part. Field *table_field= share->field[key_part[i].fieldnr - 1]; @@ -2935,6 +3014,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, for (i= 0; i < first_keyinfo.user_defined_key_parts; i++) { uint fieldnr= keyinfo[0].key_part[i].fieldnr; + if (fieldnr <= 0 || fieldnr > share->fields) + goto err; if (share->field[fieldnr-1]->key_length() != keyinfo[0].key_part[i].length) { @@ -2982,6 +3063,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, { uint length_bytes= 0; uint fieldnr= keyinfo->key_part[i].fieldnr; + if (fieldnr <= 0 || fieldnr > share->fields) + goto err; field= share->field[fieldnr-1]; if (field->null_ptr) @@ -3059,7 +3142,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->default_values, (uint) key_part->offset, (uint) key_part->length); - if (!key_part->fieldnr) + if (key_part->fieldnr <= 0 || key_part->fieldnr > share->fields) goto err; field= key_part->field= share->field[key_part->fieldnr-1]; @@ -3211,7 +3294,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } else share->primary_key= MAX_KEY; - if (new_field_pack_flag <= 1) + if (new_field_pack_flag <= 1 && share->null_fields) { /* Old file format with default as not null */ uint null_length= (share->null_fields+7)/8; @@ -3224,8 +3307,6 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* Handle virtual expressions */ if (vcol_screen_length && share->frm_version >= FRM_VER_EXPRESSSIONS) { - uchar *vcol_screen_end= vcol_screen_pos + vcol_screen_length; - /* Skip header */ vcol_screen_pos+= FRM_VCOL_NEW_BASE_SIZE; share->vcol_defs.str+= FRM_VCOL_NEW_BASE_SIZE; @@ -3238,6 +3319,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, while (vcol_screen_pos < vcol_screen_end) { Virtual_column_info *vcol_info; + if (vcol_screen_end - vcol_screen_pos < FRM_VCOL_NEW_HEADER_SIZE) + goto err; + uint type= (uint) vcol_screen_pos[0]; uint field_nr= uint2korr(vcol_screen_pos+1); uint expr_length= uint2korr(vcol_screen_pos+3); @@ -3250,17 +3334,21 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (field_nr != UINT_MAX16) { - DBUG_ASSERT(field_nr < share->fields); + if (field_nr >= share->fields) + goto err; reg_field= share->field[field_nr]; } else { reg_field= 0; - DBUG_ASSERT(name_length); + if (!name_length || type != VCOL_CHECK_TABLE) + goto err; } vcol_screen_pos+= FRM_VCOL_NEW_HEADER_SIZE; vcol_info->set_vcol_type((enum_vcol_info_type) type); + if ((uint)(vcol_screen_end-vcol_screen_pos) < name_length+expr_length) + goto err; if (name_length) { vcol_info->name.str= strmake_root(&share->mem_root, @@ -3274,7 +3362,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, switch (type) { case VCOL_GENERATED_VIRTUAL: { - uint recpos; + if (reg_field->vcol_info) + goto err; reg_field->vcol_info= vcol_info; share->virtual_fields++; share->stored_fields--; @@ -3283,37 +3372,43 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (reg_field->flags & PART_KEY_FLAG) vcol_info->set_vcol_type(VCOL_GENERATED_VIRTUAL_INDEXED); /* Correct stored_rec_length as non stored fields are last */ - recpos= (uint) (reg_field->ptr - record); + uint recpos= (uint) (reg_field->ptr - record); if (share->stored_rec_length >= recpos) share->stored_rec_length= recpos-1; break; } case VCOL_GENERATED_STORED: vcol_info->stored_in_db= 1; - DBUG_ASSERT(!reg_field->vcol_info); + if (reg_field->vcol_info) + goto err; reg_field->vcol_info= vcol_info; share->virtual_fields++; break; case VCOL_DEFAULT: vcol_info->stored_in_db= 1; - DBUG_ASSERT(!reg_field->default_value); + if (reg_field->default_value) + goto err; reg_field->default_value= vcol_info; share->default_expressions++; break; case VCOL_CHECK_FIELD: - DBUG_ASSERT(!reg_field->check_constraint); + if (reg_field->check_constraint) + goto err; reg_field->check_constraint= vcol_info; share->field_check_constraints++; break; case VCOL_CHECK_TABLE: - *(table_check_constraints++)= vcol_info; + if ((uint)(table_check_constr - share->check_constraints) >= + share->table_check_constraints) + goto err; + *(table_check_constr++)= vcol_info; break; } } } - DBUG_ASSERT((uint) (table_check_constraints - share->check_constraints) == - (uint) (share->table_check_constraints - - share->field_check_constraints)); + if (table_check_constr - share->check_constraints != + (int)share->table_check_constraints - (int)share->field_check_constraints) + goto err; if (options.str) { @@ -3366,6 +3461,19 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->can_cmp_whole_record= (share->blob_fields == 0 && share->varchar_fields == 0); + data_start= share->default_values; + data_end= data_start + share->reclength; + if (share->null_field_first) + data_start+= share->null_bytes; + else + data_end-= share->null_bytes; + + if (data_end < data_start || + (first_virtual && first_virtual != next_stored) || + first_stored < data_start || + (next_virtual ? next_virtual : next_stored) > data_end) + goto err; + share->column_bitmap_size= bitmap_buffer_size(share->fields); bitmap_count= 1; @@ -4817,19 +4925,20 @@ void open_table_error(TABLE_SHARE *share, enum open_frm_error error, } /* open_table_error */ - /* - ** fix a str_type to a array type - ** typeparts separated with some char. differents types are separated - ** with a '\0' - */ +/* + fix a str_type to a array type + typeparts separated with some char. differents types are separated + with a '\0' +*/ static bool -fix_type_pointers(const char ***typelib_value_names, +fix_type_pointers(const char ***typelib_value_names, const char **names_end, uint **typelib_value_lengths, TYPELIB *point_to_type, uint types, char *ptr, size_t length) { const char *end= ptr + length; + names_end--; // simplify the check below, reserve place for 0 at the end while (types--) { @@ -4861,7 +4970,7 @@ fix_type_pointers(const char ***typelib_value_names, { // Now scan the next value+sep pair char *vend= (char*) memchr(ptr, sep, end - ptr); - if (!vend) + if (!vend || *typelib_value_names >= names_end) return true; // Bad format *((*typelib_value_names)++)= ptr; *((*typelib_value_lengths)++)= (uint) (vend - ptr); From 3704a8a847db4daf8e2d26f23683a11051726cf3 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 3 Aug 2026 19:55:28 +0200 Subject: [PATCH 064/150] cleanup: sys_vars.secure_file_priv test --- .../suite/sys_vars/r/secure_file_priv.result | 7 +++- ...e_priv-master.opt => secure_file_priv.opt} | 0 .../suite/sys_vars/t/secure_file_priv.test | 40 +++++-------------- 3 files changed, 14 insertions(+), 33 deletions(-) rename mysql-test/suite/sys_vars/t/{secure_file_priv-master.opt => secure_file_priv.opt} (100%) diff --git a/mysql-test/suite/sys_vars/r/secure_file_priv.result b/mysql-test/suite/sys_vars/r/secure_file_priv.result index 74f816df59d..cab460c8828 100644 --- a/mysql-test/suite/sys_vars/r/secure_file_priv.result +++ b/mysql-test/suite/sys_vars/r/secure_file_priv.result @@ -6,14 +6,17 @@ INSERT INTO t1 VALUES ("one"),("two"),("three"),("four"),("five"); SHOW VARIABLES LIKE 'secure_file_priv'; Variable_name Value secure_file_priv -Warnings: -Warning 1287 ' INTO FROM...' instead +SELECT * INTO OUTFILE '$MYSQLTEST_VARDIR/../bug50373.txt' FROM t1; +DELETE FROM t1; +LOAD DATA INFILE '$MYSQLTEST_VARDIR/../bug50373.txt' INTO TABLE t1; +SELECT * FROM t1; c1 one two three four five +SELECT load_file('$MYSQLTEST_VARDIR/../bug50373.txt') AS loaded_file; loaded_file one two diff --git a/mysql-test/suite/sys_vars/t/secure_file_priv-master.opt b/mysql-test/suite/sys_vars/t/secure_file_priv.opt similarity index 100% rename from mysql-test/suite/sys_vars/t/secure_file_priv-master.opt rename to mysql-test/suite/sys_vars/t/secure_file_priv.opt diff --git a/mysql-test/suite/sys_vars/t/secure_file_priv.test b/mysql-test/suite/sys_vars/t/secure_file_priv.test index 395bdaed343..fec02db4241 100644 --- a/mysql-test/suite/sys_vars/t/secure_file_priv.test +++ b/mysql-test/suite/sys_vars/t/secure_file_priv.test @@ -4,7 +4,6 @@ CREATE TABLE t1 (c1 VARCHAR(50)); INSERT INTO t1 VALUES ("one"),("two"),("three"),("four"),("five"); SHOW VARIABLES LIKE 'secure_file_priv'; ---disable_query_log # Atempt to create a file where we normally aren't allowed to create one. # @@ -17,37 +16,16 @@ SHOW VARIABLES LIKE 'secure_file_priv'; # If we run tests with --mem, it will be /dev/shm. # If we run tests with --parallel, it will be mysql-test/var # (because MYSQLTEST_VARDIR in this case is mysql-test/var/N). + --disable_cursor_protocol - ---perl -use File::Basename; -my $protected_file= dirname($ENV{MYSQLTEST_VARDIR}).'/bug50373.txt'; -# Ensure bug50373.txt does not exist (e.g. leftover from previous -# test runs). -unlink $protected_file; -open(FILE, ">", "$ENV{MYSQL_TMP_DIR}/bug50373.inc") or die; -print FILE "--disable_ps2_protocol\n"; -print FILE "SELECT * FROM t1 INTO OUTFILE '".$protected_file."';\n"; -print FILE "--enable_ps2_protocol\n"; -print FILE "DELETE FROM t1;\n"; -print FILE "LOAD DATA INFILE '".$protected_file."' INTO TABLE t1;\n"; -print FILE "SELECT * FROM t1;\n"; -print FILE "SELECT load_file('",$protected_file,"') AS loaded_file;\n"; -close(FILE); -EOF - ---enable_prepare_warnings ---source $MYSQL_TMP_DIR/bug50373.inc ---disable_prepare_warnings ---remove_file $MYSQL_TMP_DIR/bug50373.inc ---enable_query_log - +--disable_ps2_protocol +evalp SELECT * INTO OUTFILE '$MYSQLTEST_VARDIR/../bug50373.txt' FROM t1; +--enable_ps2_protocol --enable_cursor_protocol - +DELETE FROM t1; +evalp LOAD DATA INFILE '$MYSQLTEST_VARDIR/../bug50373.txt' INTO TABLE t1; +SELECT * FROM t1; +evalp SELECT load_file('$MYSQLTEST_VARDIR/../bug50373.txt') AS loaded_file; DROP TABLE t1; ---perl -use File::Basename; -unlink dirname($ENV{MYSQLTEST_VARDIR}).'/bug50373.txt'; -EOF - +--remove_file $MYSQLTEST_VARDIR/../bug50373.txt From 26853cca28826d2b32f2f21bc756e041b47f7813 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 3 Aug 2026 20:01:42 +0200 Subject: [PATCH 065/150] MDEV-40589 default exclude list for secure-file-priv don't allow to access /proc if secure-file-priv="", set secure-file-priv=/ to access everything and disable the exclude list remove test for a conditon that can no longer happen --- mysql-test/main/mysqld--help,win.rdiff | 28 +- mysql-test/main/mysqld--help.result | 3 +- .../sys_vars/r/secure_file_priv,win.rdiff | 10 + .../suite/sys_vars/r/secure_file_priv.result | 8 + .../sys_vars/r/sysvars_server_embedded.result | 2 +- .../r/sysvars_server_notembedded,win.rdiff | 320 +++++++++--------- .../r/sysvars_server_notembedded.result | 2 +- .../suite/sys_vars/t/secure_file_priv.test | 12 + sql/mysqld.cc | 54 ++- sql/sys_vars.cc | 14 +- .../connect/r/drop-open-error.result | 8 - .../mysql-test/connect/t/drop-open-error.opt | 1 - .../mysql-test/connect/t/drop-open-error.test | 11 - 13 files changed, 246 insertions(+), 227 deletions(-) create mode 100644 mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff delete mode 100644 storage/connect/mysql-test/connect/t/drop-open-error.opt diff --git a/mysql-test/main/mysqld--help,win.rdiff b/mysql-test/main/mysqld--help,win.rdiff index 07b7c87b5d4..7594959fb07 100644 --- a/mysql-test/main/mysqld--help,win.rdiff +++ b/mysql-test/main/mysqld--help,win.rdiff @@ -1,6 +1,6 @@ --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result -@@ -180,6 +180,7 @@ The following specify which files/extra groups are read (specified before remain +@@ -190,6 +190,7 @@ The following specify which files/extra groups are read (specified before remain --console Write error output on screen; don't remove the console window on windows. --core-file Write core on crashes @@ -8,7 +8,7 @@ -h, --datadir=name Path to the database root directory --date-format=name The DATE format (ignored) --datetime-format=name -@@ -649,6 +650,7 @@ The following specify which files/extra groups are read (specified before remain +@@ -677,6 +678,7 @@ The following specify which files/extra groups are read (specified before remain Use MySQL-5.6 (instead of MariaDB-5.3) format for TIME, DATETIME, TIMESTAMP columns. (Defaults to on; use --skip-mysql56-temporal-format to disable.) @@ -16,7 +16,17 @@ --net-buffer-length=# Buffer length for TCP/IP and socket communication --net-read-timeout=# -@@ -1280,6 +1282,10 @@ The following specify which files/extra groups are read (specified before remain +@@ -1192,8 +1194,7 @@ The following specify which files/extra groups are read (specified before remain + (Defaults to on; use --skip-secure-auth to disable.) + --secure-file-priv=name + Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to +- files within specified directory. Empty value means no +- limits except /proc ++ files within specified directory. + --secure-timestamp=name + Restricts direct setting of a session timestamp. Possible + levels are: YES - timestamp cannot deviate from the +@@ -1346,6 +1347,10 @@ The following specify which files/extra groups are read (specified before remain Log slow queries to given log file. Defaults logging to 'hostname'-slow.log. Must be enabled to activate other slow log options @@ -27,7 +37,7 @@ --socket=name Socket file to use for connection --sort-buffer-size=# Each thread that needs to do a sort allocates a buffer of -@@ -1304,6 +1310,7 @@ The following specify which files/extra groups are read (specified before remain +@@ -1371,6 +1376,7 @@ The following specify which files/extra groups are read (specified before remain deleting or updating every row in a table. --stack-trace Print a symbolic stack trace on failure (Defaults to on; use --skip-stack-trace to disable.) @@ -35,7 +45,7 @@ --standard-compliant-cte Allow only CTEs compliant to SQL standard (Defaults to on; use --skip-standard-compliant-cte to disable.) -@@ -1379,6 +1386,12 @@ The following specify which files/extra groups are read (specified before remain +@@ -1446,6 +1452,12 @@ The following specify which files/extra groups are read (specified before remain --thread-pool-max-threads=# Maximum allowed number of worker threads in the thread pool @@ -48,7 +58,7 @@ --thread-pool-oversubscribe=# How many additional active worker threads in a group are allowed. -@@ -1417,8 +1430,8 @@ The following specify which files/extra groups are read (specified before remain +@@ -1485,8 +1497,8 @@ The following specify which files/extra groups are read (specified before remain automatically convert it to an on-disk MyISAM or Aria table. -t, --tmpdir=name Path for temporary files. Several paths may be specified, @@ -59,7 +69,7 @@ --transaction-alloc-block-size=# Allocation block size for transactions to be stored in binary log -@@ -1633,6 +1646,7 @@ myisam-sort-buffer-size 134216704 +@@ -1704,6 +1716,7 @@ myisam-sort-buffer-size 134216704 myisam-stats-method NULLS_UNEQUAL myisam-use-mmap FALSE mysql56-temporal-format TRUE @@ -67,7 +77,7 @@ net-buffer-length 16384 net-read-timeout 30 net-retry-count 10 -@@ -1787,6 +1801,7 @@ slave-transaction-retry-interval 0 +@@ -1862,6 +1875,7 @@ slave-transaction-retry-interval 0 slave-type-conversions slow-launch-time 2 slow-query-log FALSE @@ -75,7 +85,7 @@ sort-buffer-size 2097152 sql-mode STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION sql-safe-updates FALSE -@@ -1813,6 +1828,8 @@ thread-pool-dedicated-listener FALSE +@@ -1888,6 +1902,8 @@ thread-pool-dedicated-listener FALSE thread-pool-exact-stats FALSE thread-pool-idle-timeout 60 thread-pool-max-threads 65536 diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 7ce8ef6cd97..451e474c66e 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1192,7 +1192,8 @@ The following specify which files/extra groups are read (specified before remain (Defaults to on; use --skip-secure-auth to disable.) --secure-file-priv=name Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to - files within specified directory + files within specified directory. Empty value means no + limits except /proc --secure-timestamp=name Restricts direct setting of a session timestamp. Possible levels are: YES - timestamp cannot deviate from the diff --git a/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff b/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff new file mode 100644 index 00000000000..44674696c9c --- /dev/null +++ b/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff @@ -0,0 +1,10 @@ +--- secure_file_priv.result ++++ secure_file_priv.reject +@@ -30,6 +30,6 @@ + # + CREATE TABLE t1 (c1 VARCHAR(50)); + LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +-ERROR HY000: The MariaDB server is running with the --secure-file-priv option so it cannot execute this statement ++ERROR HY000: File 'C:\proc\cpuinfo' not found (Errcode: 2 "No such file or directory") + DROP TABLE t1; + # End of 10.6 tests diff --git a/mysql-test/suite/sys_vars/r/secure_file_priv.result b/mysql-test/suite/sys_vars/r/secure_file_priv.result index cab460c8828..06968a185e7 100644 --- a/mysql-test/suite/sys_vars/r/secure_file_priv.result +++ b/mysql-test/suite/sys_vars/r/secure_file_priv.result @@ -25,3 +25,11 @@ four five DROP TABLE t1; +# +# MDEV-40589 default exclude list for secure-file-priv +# +CREATE TABLE t1 (c1 VARCHAR(50)); +LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +ERROR HY000: The MariaDB server is running with the --secure-file-priv option so it cannot execute this statement +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index e1801313183..dc70fd1d09e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3115,7 +3115,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SECURE_FILE_PRIV VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR -VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory +VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff index acee8a39b43..77c7c9cf941 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff @@ -1,6 +1,6 @@ ---- suite/sys_vars/r/sysvars_server_notembedded.result -+++ suite/sys_vars/r/sysvars_server_notembedded.reject -@@ -34,7 +34,7 @@ +--- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result ++++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +@@ -44,7 +44,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_BLOCK_SIZE VARIABLE_SCOPE GLOBAL @@ -9,7 +9,7 @@ VARIABLE_COMMENT Block size to be used for Aria index pages. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 32768 -@@ -44,7 +44,7 @@ +@@ -54,7 +54,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_CHECKPOINT_INTERVAL VARIABLE_SCOPE GLOBAL @@ -18,7 +18,7 @@ VARIABLE_COMMENT Interval between tries to do an automatic checkpoints. In seconds; 0 means 'no automatic checkpoints' which makes sense only for testing. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -54,7 +54,7 @@ +@@ -64,7 +64,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_CHECKPOINT_LOG_ACTIVITY VARIABLE_SCOPE GLOBAL @@ -27,7 +27,7 @@ VARIABLE_COMMENT Number of bytes that the transaction log has to grow between checkpoints before a new checkpoint is written to the log. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -74,7 +74,7 @@ +@@ -84,7 +84,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ARIA_FORCE_START_AFTER_RECOVERY_FAILURES VARIABLE_SCOPE GLOBAL @@ -36,7 +36,7 @@ VARIABLE_COMMENT Number of consecutive log recovery failures after which logs will be automatically deleted to cure the problem; 0 (the default) disables the feature. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -94,7 +94,7 @@ +@@ -104,7 +104,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_GROUP_COMMIT_INTERVAL VARIABLE_SCOPE GLOBAL @@ -45,7 +45,7 @@ VARIABLE_COMMENT Interval between commits in microseconds (1/1000000 sec). 0 stands for no waiting for other threads to come and do a commit in "hard" mode and no sync()/commit at all in "soft" mode. Option has only an effect if aria_group_commit is used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -114,7 +114,7 @@ +@@ -124,7 +124,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_LOG_FILE_SIZE VARIABLE_SCOPE GLOBAL @@ -54,7 +54,7 @@ VARIABLE_COMMENT Limit for transaction log size NUMERIC_MIN_VALUE 8388608 NUMERIC_MAX_VALUE 4294967295 -@@ -144,10 +144,10 @@ +@@ -154,10 +154,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_AGE_THRESHOLD VARIABLE_SCOPE GLOBAL @@ -67,7 +67,7 @@ NUMERIC_BLOCK_SIZE 100 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -164,7 +164,7 @@ +@@ -174,7 +174,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_DIVISION_LIMIT VARIABLE_SCOPE GLOBAL @@ -76,7 +76,7 @@ VARIABLE_COMMENT The minimum percentage of warm blocks in key cache NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100 -@@ -174,7 +174,7 @@ +@@ -184,7 +184,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_FILE_HASH_SIZE VARIABLE_SCOPE GLOBAL @@ -85,7 +85,7 @@ VARIABLE_COMMENT Number of hash buckets for open and changed files. If you have a lot of Aria files open you should increase this for faster flush of changes. A good value is probably 1/10 of number of possible open Aria files. NUMERIC_MIN_VALUE 128 NUMERIC_MAX_VALUE 16384 -@@ -204,7 +204,7 @@ +@@ -214,7 +214,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ARIA_REPAIR_THREADS VARIABLE_SCOPE SESSION @@ -94,7 +94,7 @@ VARIABLE_COMMENT Number of threads to use when repairing Aria tables. The value of 1 disables parallel repair. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 128 -@@ -274,7 +274,7 @@ +@@ -284,7 +284,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME AUTO_INCREMENT_INCREMENT VARIABLE_SCOPE SESSION @@ -103,7 +103,7 @@ VARIABLE_COMMENT Auto-increment columns are incremented by this NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 65535 -@@ -284,7 +284,7 @@ +@@ -294,7 +294,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME AUTO_INCREMENT_OFFSET VARIABLE_SCOPE SESSION @@ -112,7 +112,7 @@ VARIABLE_COMMENT Offset added to Auto-increment columns. Used when auto-increment-increment != 1 NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 65535 -@@ -294,7 +294,7 @@ +@@ -304,7 +304,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME BACK_LOG VARIABLE_SCOPE GLOBAL @@ -121,7 +121,7 @@ VARIABLE_COMMENT The number of outstanding connection requests MariaDB can have. This comes into play when the main MariaDB thread gets very many connection requests in a very short time NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65535 -@@ -364,20 +364,20 @@ +@@ -374,20 +374,20 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME BINLOG_COMMIT_WAIT_COUNT VARIABLE_SCOPE GLOBAL @@ -146,7 +146,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -394,7 +394,7 @@ +@@ -404,7 +404,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME BINLOG_EXPIRE_LOGS_SECONDS VARIABLE_SCOPE GLOBAL @@ -155,7 +155,7 @@ VARIABLE_COMMENT If non-zero, binary logs will be purged after binlog_expire_logs_seconds seconds; It and expire_logs_days are linked, such that changes in one are converted into the other. Possible purges happen at startup and at binary log rotation. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 8553600 -@@ -654,7 +654,7 @@ +@@ -664,7 +664,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME CONNECT_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -164,7 +164,7 @@ VARIABLE_COMMENT The number of seconds the mysqld server is waiting for a connect packet before responding with 'Bad handshake' NUMERIC_MIN_VALUE 2 NUMERIC_MAX_VALUE 31536000 -@@ -704,7 +704,7 @@ +@@ -714,7 +714,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_SEARCH_DEPTH_LONG VARIABLE_SCOPE SESSION @@ -173,7 +173,7 @@ VARIABLE_COMMENT Long search depth for the two-step deadlock detection NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 33 -@@ -714,7 +714,7 @@ +@@ -724,7 +724,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_SEARCH_DEPTH_SHORT VARIABLE_SCOPE SESSION @@ -182,7 +182,7 @@ VARIABLE_COMMENT Short search depth for the two-step deadlock detection NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 32 -@@ -724,7 +724,7 @@ +@@ -734,7 +734,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_TIMEOUT_LONG VARIABLE_SCOPE SESSION @@ -191,7 +191,7 @@ VARIABLE_COMMENT Long timeout for the two-step deadlock detection (in microseconds) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -734,7 +734,7 @@ +@@ -744,7 +744,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_TIMEOUT_SHORT VARIABLE_SCOPE SESSION @@ -200,7 +200,7 @@ VARIABLE_COMMENT Short timeout for the two-step deadlock detection (in microseconds) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -794,7 +794,7 @@ +@@ -804,7 +804,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME DEFAULT_WEEK_FORMAT VARIABLE_SCOPE SESSION @@ -209,7 +209,7 @@ VARIABLE_COMMENT The default week format used by WEEK() functions NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 7 -@@ -804,7 +804,7 @@ +@@ -814,7 +814,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_INSERT_LIMIT VARIABLE_SCOPE GLOBAL @@ -218,7 +218,7 @@ VARIABLE_COMMENT After inserting delayed_insert_limit rows, the INSERT DELAYED handler will check if there are any SELECT statements pending. If so, it allows these to execute before continuing. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -814,7 +814,7 @@ +@@ -824,7 +824,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_INSERT_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -227,7 +227,7 @@ VARIABLE_COMMENT How long a INSERT DELAYED thread should wait for INSERT statements before terminating NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -824,7 +824,7 @@ +@@ -834,7 +834,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_QUEUE_SIZE VARIABLE_SCOPE GLOBAL @@ -236,7 +236,7 @@ VARIABLE_COMMENT What size queue (in rows) should be allocated for handling INSERT DELAYED. If the queue becomes full, any client that does INSERT DELAYED will wait until there is room in the queue again NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -854,7 +854,7 @@ +@@ -864,7 +864,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME DIV_PRECISION_INCREMENT VARIABLE_SCOPE SESSION @@ -245,7 +245,7 @@ VARIABLE_COMMENT Precision of the result of '/' operator will be increased on that value NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 38 -@@ -974,7 +974,7 @@ +@@ -984,7 +984,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME EXTRA_MAX_CONNECTIONS VARIABLE_SCOPE GLOBAL @@ -254,7 +254,7 @@ VARIABLE_COMMENT The number of connections on extra-port NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100000 -@@ -1004,7 +1004,7 @@ +@@ -1014,7 +1014,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME FLUSH_TIME VARIABLE_SCOPE GLOBAL @@ -263,7 +263,7 @@ VARIABLE_COMMENT A dedicated thread is created to flush all tables at the given interval NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -1034,7 +1034,7 @@ +@@ -1044,7 +1044,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_MAX_WORD_LEN VARIABLE_SCOPE GLOBAL @@ -272,7 +272,7 @@ VARIABLE_COMMENT The maximum length of the word to be included in a FULLTEXT index. Note: FULLTEXT indexes must be rebuilt after changing this variable NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 84 -@@ -1044,7 +1044,7 @@ +@@ -1054,7 +1054,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_MIN_WORD_LEN VARIABLE_SCOPE GLOBAL @@ -281,7 +281,7 @@ VARIABLE_COMMENT The minimum length of the word to be included in a FULLTEXT index. Note: FULLTEXT indexes must be rebuilt after changing this variable NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 84 -@@ -1054,7 +1054,7 @@ +@@ -1064,7 +1064,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_QUERY_EXPANSION_LIMIT VARIABLE_SCOPE GLOBAL @@ -290,7 +290,7 @@ VARIABLE_COMMENT Number of best matches to use for query expansion NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1000 -@@ -1304,7 +1304,7 @@ +@@ -1314,7 +1314,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME HISTOGRAM_SIZE VARIABLE_SCOPE SESSION @@ -299,7 +299,7 @@ VARIABLE_COMMENT Number of bytes used for a histogram. If set to 0, no histograms are created by ANALYZE. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -1334,7 +1334,7 @@ +@@ -1344,7 +1344,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME HOST_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -308,7 +308,7 @@ VARIABLE_COMMENT How many host names should be cached to avoid resolving. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65536 -@@ -1357,7 +1357,7 @@ +@@ -1367,7 +1367,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for read-only idle transaction NUMERIC_MIN_VALUE 0 @@ -317,7 +317,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1367,7 +1367,7 @@ +@@ -1377,7 +1377,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for idle transaction NUMERIC_MIN_VALUE 0 @@ -326,7 +326,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1377,7 +1377,7 @@ +@@ -1387,7 +1387,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for write idle transaction NUMERIC_MIN_VALUE 0 @@ -335,7 +335,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1444,7 +1444,7 @@ +@@ -1454,7 +1454,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME INTERACTIVE_TIMEOUT VARIABLE_SCOPE SESSION @@ -344,7 +344,7 @@ VARIABLE_COMMENT The number of seconds the server waits for activity on an interactive connection before closing it NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -1494,7 +1494,7 @@ +@@ -1504,7 +1504,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME JOIN_CACHE_LEVEL VARIABLE_SCOPE SESSION @@ -353,7 +353,7 @@ VARIABLE_COMMENT Controls what join operations can be executed with join buffers. Odd numbers are used for plain join buffers while even numbers are used for linked buffers NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 8 -@@ -1672,19 +1672,9 @@ +@@ -1682,19 +1682,9 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL @@ -374,7 +374,7 @@ VARIABLE_COMMENT Timeout in seconds to wait for a lock before returning an error. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -1834,7 +1824,7 @@ +@@ -1844,7 +1834,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_SLOW_MAX_WARNINGS VARIABLE_SCOPE SESSION @@ -383,7 +383,7 @@ VARIABLE_COMMENT Max numbers of warnings printed to slow query log per statement NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1000 -@@ -1844,7 +1834,7 @@ +@@ -1854,7 +1844,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_SLOW_RATE_LIMIT VARIABLE_SCOPE SESSION @@ -392,7 +392,7 @@ VARIABLE_COMMENT Write to slow log every #th slow query. Set to 1 to log everything. Increase it to reduce the size of the slow or the performance impact of slow logging NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -1874,7 +1864,7 @@ +@@ -1884,7 +1874,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_WARNINGS VARIABLE_SCOPE SESSION @@ -401,7 +401,7 @@ VARIABLE_COMMENT Log some non critical warnings to the error log.Value can be between 0 and 11. Higher values mean more verbosity NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -1934,7 +1924,7 @@ +@@ -1944,7 +1934,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME MAX_ALLOWED_PACKET VARIABLE_SCOPE SESSION @@ -410,7 +410,7 @@ VARIABLE_COMMENT Max packet length to send to or receive from the server NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -1954,7 +1944,7 @@ +@@ -1964,7 +1954,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_BINLOG_SIZE VARIABLE_SCOPE GLOBAL @@ -419,7 +419,7 @@ VARIABLE_COMMENT Binary log will be rotated automatically when the size exceeds this value. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 1073741824 -@@ -1974,7 +1964,7 @@ +@@ -1984,7 +1974,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_CONNECTIONS VARIABLE_SCOPE GLOBAL @@ -428,7 +428,7 @@ VARIABLE_COMMENT The number of simultaneous clients allowed NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 100000 -@@ -1984,7 +1974,7 @@ +@@ -1994,7 +1984,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_CONNECT_ERRORS VARIABLE_SCOPE GLOBAL @@ -437,7 +437,7 @@ VARIABLE_COMMENT If there is more than this number of interrupted connections from a host this host will be blocked from further connections NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -1994,7 +1984,7 @@ +@@ -2004,7 +1994,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_DELAYED_THREADS VARIABLE_SCOPE SESSION @@ -446,7 +446,7 @@ VARIABLE_COMMENT Don't start more than this number of threads to handle INSERT DELAYED statements. If set to zero INSERT DELAYED will be not used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -2014,7 +2004,7 @@ +@@ -2024,7 +2014,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_ERROR_COUNT VARIABLE_SCOPE SESSION @@ -455,7 +455,7 @@ VARIABLE_COMMENT Max number of errors/warnings to store for a statement NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65535 -@@ -2034,7 +2024,7 @@ +@@ -2044,7 +2034,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_INSERT_DELAYED_THREADS VARIABLE_SCOPE SESSION @@ -464,7 +464,7 @@ VARIABLE_COMMENT Don't start more than this number of threads to handle INSERT DELAYED statements. If set to zero INSERT DELAYED will be not used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -2054,7 +2044,7 @@ +@@ -2064,7 +2054,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_LENGTH_FOR_SORT_DATA VARIABLE_SCOPE SESSION @@ -473,7 +473,7 @@ VARIABLE_COMMENT Max number of bytes in sorted records NUMERIC_MIN_VALUE 4 NUMERIC_MAX_VALUE 8388608 -@@ -2084,7 +2074,7 @@ +@@ -2094,7 +2084,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_RECURSIVE_ITERATIONS VARIABLE_SCOPE SESSION @@ -482,7 +482,7 @@ VARIABLE_COMMENT Maximum number of iterations when executing recursive queries NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2114,7 +2104,7 @@ +@@ -2124,7 +2114,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SEEKS_FOR_KEY VARIABLE_SCOPE SESSION @@ -491,7 +491,7 @@ VARIABLE_COMMENT Limit assumed max number of seeks when looking up rows based on a key NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2134,7 +2124,7 @@ +@@ -2144,7 +2134,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SORT_LENGTH VARIABLE_SCOPE SESSION @@ -500,7 +500,7 @@ VARIABLE_COMMENT The number of bytes to use when sorting BLOB or TEXT values (only the first max_sort_length bytes of each value are used; the rest are ignored) NUMERIC_MIN_VALUE 64 NUMERIC_MAX_VALUE 8388608 -@@ -2144,7 +2134,7 @@ +@@ -2154,7 +2144,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SP_RECURSION_DEPTH VARIABLE_SCOPE SESSION @@ -509,7 +509,7 @@ VARIABLE_COMMENT Maximum stored procedure recursion depth NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -2164,7 +2154,7 @@ +@@ -2174,7 +2164,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_TMP_TABLES VARIABLE_SCOPE SESSION @@ -518,7 +518,7 @@ VARIABLE_COMMENT Unused, will be removed. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2184,7 +2174,7 @@ +@@ -2194,7 +2184,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_WRITE_LOCK_COUNT VARIABLE_SCOPE GLOBAL @@ -527,7 +527,7 @@ VARIABLE_COMMENT After this many write locks, allow some read locks to run in between NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2194,7 +2184,7 @@ +@@ -2204,7 +2194,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME METADATA_LOCKS_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -536,7 +536,7 @@ VARIABLE_COMMENT Unused NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1048576 -@@ -2204,7 +2194,7 @@ +@@ -2214,7 +2204,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME METADATA_LOCKS_HASH_INSTANCES VARIABLE_SCOPE GLOBAL @@ -545,7 +545,7 @@ VARIABLE_COMMENT Unused NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1024 -@@ -2214,7 +2204,7 @@ +@@ -2224,7 +2214,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MIN_EXAMINED_ROW_LIMIT VARIABLE_SCOPE SESSION @@ -554,7 +554,7 @@ VARIABLE_COMMENT Don't write queries to slow log that examine fewer rows than that NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2224,7 +2214,7 @@ +@@ -2234,7 +2224,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MRR_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -563,7 +563,7 @@ VARIABLE_COMMENT Size of buffer to use when using MRR with range access NUMERIC_MIN_VALUE 8192 NUMERIC_MAX_VALUE 2147483647 -@@ -2234,7 +2224,7 @@ +@@ -2244,7 +2234,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MYISAM_BLOCK_SIZE VARIABLE_SCOPE GLOBAL @@ -572,7 +572,7 @@ VARIABLE_COMMENT Block size to be used for MyISAM index pages NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 16384 -@@ -2244,7 +2234,7 @@ +@@ -2254,7 +2244,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MYISAM_DATA_POINTER_SIZE VARIABLE_SCOPE GLOBAL @@ -581,7 +581,7 @@ VARIABLE_COMMENT Default pointer size to be used for MyISAM tables NUMERIC_MIN_VALUE 2 NUMERIC_MAX_VALUE 7 -@@ -2284,10 +2274,10 @@ +@@ -2294,10 +2284,10 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME MYISAM_REPAIR_THREADS VARIABLE_SCOPE SESSION @@ -594,7 +594,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -2332,9 +2322,19 @@ +@@ -2342,9 +2332,19 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL @@ -615,7 +615,7 @@ VARIABLE_COMMENT Buffer length for TCP/IP and socket communication NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1048576 -@@ -2344,7 +2344,7 @@ +@@ -2354,7 +2354,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_READ_TIMEOUT VARIABLE_SCOPE SESSION @@ -624,7 +624,7 @@ VARIABLE_COMMENT Number of seconds to wait for more data from a connection before aborting the read NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -2354,7 +2354,7 @@ +@@ -2364,7 +2364,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_RETRY_COUNT VARIABLE_SCOPE SESSION @@ -633,7 +633,7 @@ VARIABLE_COMMENT If a read on a communication port is interrupted, retry this many times before giving up NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2364,7 +2364,7 @@ +@@ -2374,7 +2374,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_WRITE_TIMEOUT VARIABLE_SCOPE SESSION @@ -642,7 +642,7 @@ VARIABLE_COMMENT Number of seconds to wait for a block to be written to a connection before aborting the write NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -2424,7 +2424,7 @@ +@@ -2434,7 +2434,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME OPEN_FILES_LIMIT VARIABLE_SCOPE GLOBAL @@ -651,16 +651,7 @@ VARIABLE_COMMENT If this is not 0, then mysqld will use this value to reserve file descriptors to use with setrlimit(). If this value is 0 or autoset then mysqld will reserve max_connections*5 or max_connections + table_cache*2 (whichever is larger) number of file descriptors NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2434,7 +2434,7 @@ - COMMAND_LINE_ARGUMENT REQUIRED - VARIABLE_NAME OPTIMIZER_ADJUST_SECONDARY_KEY_COSTS - VARIABLE_SCOPE SESSION --VARIABLE_TYPE BIGINT UNSIGNED -+VARIABLE_TYPE INT UNSIGNED - VARIABLE_COMMENT 0 = No changes. 1 = Update secondary key costs for ranges to be at least 5x of clustered primary key costs. 2 = Remove 'max_seek optimization' for secondary keys and slight adjustment of filter cost. This option will be deleted in MariaDB 11.0 as it is not needed with the new 11.0 optimizer. - NUMERIC_MIN_VALUE 0 - NUMERIC_MAX_VALUE 2 -@@ -2444,7 +2444,7 @@ +@@ -2464,7 +2464,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_MAX_SEL_ARGS VARIABLE_SCOPE SESSION @@ -669,7 +660,7 @@ VARIABLE_COMMENT The maximum number of SEL_ARG objects created when optimizing a range. If more objects would be needed, the range will not be used by the optimizer. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2454,7 +2454,7 @@ +@@ -2474,7 +2474,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_MAX_SEL_ARG_WEIGHT VARIABLE_SCOPE SESSION @@ -678,7 +669,7 @@ VARIABLE_COMMENT The maximum weight of the SEL_ARG graph. Set to 0 for no limit NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2464,7 +2464,7 @@ +@@ -2484,7 +2484,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_PRUNE_LEVEL VARIABLE_SCOPE SESSION @@ -687,7 +678,7 @@ VARIABLE_COMMENT Controls the heuristic(s) applied during query optimization to prune less-promising partial plans from the optimizer search space. Meaning: 0 - do not apply any heuristic, thus perform exhaustive search; 1 - prune plans based on number of retrieved rows NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1 -@@ -2474,7 +2474,7 @@ +@@ -2494,7 +2494,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_SEARCH_DEPTH VARIABLE_SCOPE SESSION @@ -696,7 +687,7 @@ VARIABLE_COMMENT Maximum depth of search performed by the query optimizer. Values larger than the number of relations in a query result in better query plans, but take longer to compile a query. Values smaller than the number of tables in a relation result in faster optimization, but may produce very bad query plans. If set to 0, the system will automatically pick a reasonable value. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 62 -@@ -2484,7 +2484,7 @@ +@@ -2504,7 +2504,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_SELECTIVITY_SAMPLING_LIMIT VARIABLE_SCOPE SESSION @@ -705,7 +696,7 @@ VARIABLE_COMMENT Controls number of record samples to check condition selectivity NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 4294967295 -@@ -2514,17 +2514,17 @@ +@@ -2534,17 +2534,17 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_TRACE_MAX_MEM_SIZE VARIABLE_SCOPE SESSION @@ -726,7 +717,7 @@ VARIABLE_COMMENT Controls selectivity of which conditions the optimizer takes into account to calculate cardinality of a partial join when it searches for the best execution plan Meaning: 1 - use selectivity of index backed range conditions to calculate the cardinality of a partial join if the last joined table is accessed by full table scan or an index scan, 2 - use selectivity of index backed range conditions to calculate the cardinality of a partial join in any case, 3 - additionally always use selectivity of range conditions that are not backed by any index to calculate the cardinality of a partial join, 4 - use histograms to calculate selectivity of range conditions that are not backed by any index to calculate the cardinality of a partial join.5 - additionally use selectivity of certain non-range predicates calculated on record samples NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 5 -@@ -2544,7 +2544,7 @@ +@@ -2564,7 +2564,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME PERFORMANCE_SCHEMA_ACCOUNTS_SIZE VARIABLE_SCOPE GLOBAL @@ -735,7 +726,7 @@ VARIABLE_COMMENT Maximum number of instrumented user@host accounts. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2554,7 +2554,7 @@ +@@ -2574,7 +2574,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_DIGESTS_SIZE VARIABLE_SCOPE GLOBAL @@ -744,7 +735,7 @@ VARIABLE_COMMENT Size of the statement digest. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2564,7 +2564,7 @@ +@@ -2584,7 +2584,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STAGES_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -753,7 +744,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_STAGES_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2574,7 +2574,7 @@ +@@ -2594,7 +2594,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STAGES_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -762,7 +753,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STAGES_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2584,7 +2584,7 @@ +@@ -2604,7 +2604,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STATEMENTS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -771,7 +762,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_STATEMENTS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2594,7 +2594,7 @@ +@@ -2614,7 +2614,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STATEMENTS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -780,7 +771,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STATEMENTS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2604,7 +2604,7 @@ +@@ -2624,7 +2624,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_TRANSACTIONS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -789,7 +780,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_TRANSACTIONS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2614,7 +2614,7 @@ +@@ -2634,7 +2634,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_TRANSACTIONS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -798,7 +789,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_TRANSACTIONS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2624,7 +2624,7 @@ +@@ -2644,7 +2644,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_WAITS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -807,7 +798,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_WAITS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2634,7 +2634,7 @@ +@@ -2654,7 +2654,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_WAITS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -816,7 +807,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_WAITS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2644,7 +2644,7 @@ +@@ -2664,7 +2664,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_HOSTS_SIZE VARIABLE_SCOPE GLOBAL @@ -825,7 +816,7 @@ VARIABLE_COMMENT Maximum number of instrumented hosts. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2654,7 +2654,7 @@ +@@ -2674,7 +2674,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_COND_CLASSES VARIABLE_SCOPE GLOBAL @@ -834,7 +825,7 @@ VARIABLE_COMMENT Maximum number of condition instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2664,7 +2664,7 @@ +@@ -2684,7 +2684,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_COND_INSTANCES VARIABLE_SCOPE GLOBAL @@ -843,7 +834,7 @@ VARIABLE_COMMENT Maximum number of instrumented condition objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2674,7 +2674,7 @@ +@@ -2694,7 +2694,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_DIGEST_LENGTH VARIABLE_SCOPE GLOBAL @@ -852,7 +843,7 @@ VARIABLE_COMMENT Maximum length considered for digest text, when stored in performance_schema tables. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2684,7 +2684,7 @@ +@@ -2704,7 +2704,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_CLASSES VARIABLE_SCOPE GLOBAL @@ -861,7 +852,7 @@ VARIABLE_COMMENT Maximum number of file instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2694,7 +2694,7 @@ +@@ -2714,7 +2714,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_HANDLES VARIABLE_SCOPE GLOBAL @@ -870,7 +861,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented files. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2704,7 +2704,7 @@ +@@ -2724,7 +2724,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_INSTANCES VARIABLE_SCOPE GLOBAL @@ -879,7 +870,7 @@ VARIABLE_COMMENT Maximum number of instrumented files. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2714,7 +2714,7 @@ +@@ -2734,7 +2734,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_INDEX_STAT VARIABLE_SCOPE GLOBAL @@ -888,7 +879,7 @@ VARIABLE_COMMENT Maximum number of index statistics for instrumented tables. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2724,7 +2724,7 @@ +@@ -2744,7 +2744,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MEMORY_CLASSES VARIABLE_SCOPE GLOBAL @@ -897,7 +888,7 @@ VARIABLE_COMMENT Maximum number of memory pool instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1024 -@@ -2734,7 +2734,7 @@ +@@ -2754,7 +2754,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_METADATA_LOCKS VARIABLE_SCOPE GLOBAL @@ -906,7 +897,7 @@ VARIABLE_COMMENT Maximum number of metadata locks. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2744,7 +2744,7 @@ +@@ -2764,7 +2764,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MUTEX_CLASSES VARIABLE_SCOPE GLOBAL @@ -915,7 +906,7 @@ VARIABLE_COMMENT Maximum number of mutex instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2754,7 +2754,7 @@ +@@ -2774,7 +2774,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MUTEX_INSTANCES VARIABLE_SCOPE GLOBAL @@ -924,7 +915,7 @@ VARIABLE_COMMENT Maximum number of instrumented MUTEX objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2764,7 +2764,7 @@ +@@ -2784,7 +2784,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_PREPARED_STATEMENTS_INSTANCES VARIABLE_SCOPE GLOBAL @@ -933,7 +924,7 @@ VARIABLE_COMMENT Maximum number of instrumented prepared statements. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2774,7 +2774,7 @@ +@@ -2794,7 +2794,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_PROGRAM_INSTANCES VARIABLE_SCOPE GLOBAL @@ -942,7 +933,7 @@ VARIABLE_COMMENT Maximum number of instrumented programs. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2784,7 +2784,7 @@ +@@ -2804,7 +2804,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_RWLOCK_CLASSES VARIABLE_SCOPE GLOBAL @@ -951,7 +942,7 @@ VARIABLE_COMMENT Maximum number of rwlock instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2794,7 +2794,7 @@ +@@ -2814,7 +2814,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_RWLOCK_INSTANCES VARIABLE_SCOPE GLOBAL @@ -960,7 +951,7 @@ VARIABLE_COMMENT Maximum number of instrumented RWLOCK objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2804,7 +2804,7 @@ +@@ -2824,7 +2824,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SOCKET_CLASSES VARIABLE_SCOPE GLOBAL @@ -969,7 +960,7 @@ VARIABLE_COMMENT Maximum number of socket instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2814,7 +2814,7 @@ +@@ -2834,7 +2834,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SOCKET_INSTANCES VARIABLE_SCOPE GLOBAL @@ -978,7 +969,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented sockets. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2824,7 +2824,7 @@ +@@ -2844,7 +2844,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SQL_TEXT_LENGTH VARIABLE_SCOPE GLOBAL @@ -987,7 +978,7 @@ VARIABLE_COMMENT Maximum length of displayed sql text. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2834,7 +2834,7 @@ +@@ -2854,7 +2854,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STAGE_CLASSES VARIABLE_SCOPE GLOBAL @@ -996,7 +987,7 @@ VARIABLE_COMMENT Maximum number of stage instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2844,7 +2844,7 @@ +@@ -2864,7 +2864,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STATEMENT_CLASSES VARIABLE_SCOPE GLOBAL @@ -1005,7 +996,7 @@ VARIABLE_COMMENT Maximum number of statement instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2854,7 +2854,7 @@ +@@ -2874,7 +2874,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STATEMENT_STACK VARIABLE_SCOPE GLOBAL @@ -1014,7 +1005,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STATEMENTS_CURRENT. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 256 -@@ -2864,7 +2864,7 @@ +@@ -2884,7 +2884,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_HANDLES VARIABLE_SCOPE GLOBAL @@ -1023,7 +1014,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented tables. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2874,7 +2874,7 @@ +@@ -2894,7 +2894,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_INSTANCES VARIABLE_SCOPE GLOBAL @@ -1032,7 +1023,7 @@ VARIABLE_COMMENT Maximum number of instrumented tables. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2884,7 +2884,7 @@ +@@ -2904,7 +2904,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_LOCK_STAT VARIABLE_SCOPE GLOBAL @@ -1041,7 +1032,7 @@ VARIABLE_COMMENT Maximum number of lock statistics for instrumented tables. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2894,7 +2894,7 @@ +@@ -2914,7 +2914,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_THREAD_CLASSES VARIABLE_SCOPE GLOBAL @@ -1050,7 +1041,7 @@ VARIABLE_COMMENT Maximum number of thread instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2904,7 +2904,7 @@ +@@ -2924,7 +2924,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_THREAD_INSTANCES VARIABLE_SCOPE GLOBAL @@ -1059,7 +1050,7 @@ VARIABLE_COMMENT Maximum number of instrumented threads. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2914,7 +2914,7 @@ +@@ -2934,7 +2934,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SESSION_CONNECT_ATTRS_SIZE VARIABLE_SCOPE GLOBAL @@ -1068,7 +1059,7 @@ VARIABLE_COMMENT Size of session attribute string buffer per thread. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2924,7 +2924,7 @@ +@@ -2944,7 +2944,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SETUP_ACTORS_SIZE VARIABLE_SCOPE GLOBAL @@ -1077,7 +1068,7 @@ VARIABLE_COMMENT Maximum number of rows in SETUP_ACTORS. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2934,7 +2934,7 @@ +@@ -2954,7 +2954,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SETUP_OBJECTS_SIZE VARIABLE_SCOPE GLOBAL @@ -1086,7 +1077,7 @@ VARIABLE_COMMENT Maximum number of rows in SETUP_OBJECTS. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2944,7 +2944,7 @@ +@@ -2964,7 +2964,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_USERS_SIZE VARIABLE_SCOPE GLOBAL @@ -1095,7 +1086,7 @@ VARIABLE_COMMENT Maximum number of instrumented users. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2994,7 +2994,7 @@ +@@ -3014,7 +3014,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PRELOAD_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -1104,7 +1095,7 @@ VARIABLE_COMMENT The size of the buffer that is allocated when preloading indexes NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -3014,7 +3014,7 @@ +@@ -3034,7 +3034,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME PROFILING_HISTORY_SIZE VARIABLE_SCOPE SESSION @@ -1113,7 +1104,7 @@ VARIABLE_COMMENT Number of statements about which profiling information is maintained. If set to 0, no profiles are stored. See SHOW PROFILES. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -3024,7 +3024,7 @@ +@@ -3044,7 +3044,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PROGRESS_REPORT_TIME VARIABLE_SCOPE SESSION @@ -1122,7 +1113,7 @@ VARIABLE_COMMENT Seconds between sending progress reports to the client for time-consuming statements. Set to 0 to disable progress reporting. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3084,7 +3084,7 @@ +@@ -3104,7 +3104,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME QUERY_ALLOC_BLOCK_SIZE VARIABLE_SCOPE SESSION @@ -1131,7 +1122,7 @@ VARIABLE_COMMENT Allocation block size for query parsing and execution NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 4294967295 -@@ -3094,7 +3094,7 @@ +@@ -3114,7 +3114,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME QUERY_CACHE_LIMIT VARIABLE_SCOPE GLOBAL @@ -1140,7 +1131,7 @@ VARIABLE_COMMENT Don't cache results that are bigger than this NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3104,7 +3104,7 @@ +@@ -3124,7 +3124,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME QUERY_CACHE_MIN_RES_UNIT VARIABLE_SCOPE GLOBAL @@ -1149,7 +1140,7 @@ VARIABLE_COMMENT The minimum size for blocks allocated by the query cache NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3117,7 +3117,7 @@ +@@ -3137,7 +3137,7 @@ VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT The memory allocated to store results from old queries NUMERIC_MIN_VALUE 0 @@ -1158,7 +1149,7 @@ NUMERIC_BLOCK_SIZE 1024 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3154,7 +3154,7 @@ +@@ -3174,7 +3174,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME QUERY_PREALLOC_SIZE VARIABLE_SCOPE SESSION @@ -1167,7 +1158,7 @@ VARIABLE_COMMENT Persistent buffer for query parsing and execution NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 4294967295 -@@ -3167,7 +3167,7 @@ +@@ -3187,7 +3187,7 @@ VARIABLE_SCOPE SESSION ONLY VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Sets the internal state of the RAND() generator for replication purposes NUMERIC_MIN_VALUE 0 @@ -1176,7 +1167,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3177,14 +3177,14 @@ +@@ -3197,14 +3197,14 @@ VARIABLE_SCOPE SESSION ONLY VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Sets the internal state of the RAND() generator for replication purposes NUMERIC_MIN_VALUE 0 @@ -1193,7 +1184,7 @@ VARIABLE_COMMENT Allocation block size for storing ranges during optimization NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 4294967295 -@@ -3197,14 +3197,14 @@ +@@ -3217,14 +3217,14 @@ VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Maximum speed(KB/s) to read binlog from master (0 = no limit) NUMERIC_MIN_VALUE 0 @@ -1210,7 +1201,7 @@ VARIABLE_COMMENT Each thread that does a sequential scan allocates a buffer of this size for each table it scans. If you do many sequential scans, you may want to increase this value NUMERIC_MIN_VALUE 8192 NUMERIC_MAX_VALUE 2147483647 -@@ -3224,7 +3224,7 @@ +@@ -3244,7 +3244,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME READ_RND_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -1219,7 +1210,7 @@ VARIABLE_COMMENT When reading rows in sorted order after a sort, the rows are read through this buffer to avoid a disk seeks NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 2147483647 -@@ -3434,10 +3434,10 @@ +@@ -3454,10 +3454,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ROWID_MERGE_BUFF_SIZE VARIABLE_SCOPE SESSION @@ -1232,7 +1223,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3454,20 +3454,20 @@ +@@ -3474,20 +3474,20 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME RPL_SEMI_SYNC_MASTER_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -1257,7 +1248,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3524,10 +3524,10 @@ +@@ -3544,10 +3544,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME RPL_SEMI_SYNC_SLAVE_TRACE_LEVEL VARIABLE_SCOPE GLOBAL @@ -1270,7 +1261,16 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3564,7 +3564,7 @@ +@@ -3565,7 +3565,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL + VARIABLE_NAME SECURE_FILE_PRIV + VARIABLE_SCOPE GLOBAL + VARIABLE_TYPE VARCHAR +-VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc ++VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. + NUMERIC_MIN_VALUE NULL + NUMERIC_MAX_VALUE NULL + NUMERIC_BLOCK_SIZE NULL +@@ -3584,7 +3584,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SERVER_ID VARIABLE_SCOPE SESSION @@ -1279,7 +1279,7 @@ VARIABLE_COMMENT Uniquely identifies the server instance in the community of replication partners NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -3694,7 +3694,7 @@ +@@ -3724,7 +3724,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_DOMAIN_PARALLEL_THREADS VARIABLE_SCOPE GLOBAL @@ -1288,7 +1288,7 @@ VARIABLE_COMMENT Maximum number of parallel threads to use on slave for events in a single replication domain. When using multiple domains, this can be used to limit a single domain from grabbing all threads and thus stalling other domains. The default of 0 means to allow a domain to grab as many threads as it wants, up to the value of slave_parallel_threads. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3724,7 +3724,7 @@ +@@ -3754,7 +3754,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_MAX_ALLOWED_PACKET VARIABLE_SCOPE GLOBAL @@ -1297,7 +1297,7 @@ VARIABLE_COMMENT The maximum packet length to sent successfully from the master to slave. NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -3744,7 +3744,7 @@ +@@ -3774,7 +3774,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_PARALLEL_MAX_QUEUED VARIABLE_SCOPE GLOBAL @@ -1306,7 +1306,7 @@ VARIABLE_COMMENT Limit on how much memory SQL threads should use per parallel replication thread when reading ahead in the relay log looking for opportunities for parallel replication. Only used when --slave-parallel-threads > 0. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2147483647 -@@ -3764,7 +3764,7 @@ +@@ -3794,7 +3794,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME SLAVE_PARALLEL_THREADS VARIABLE_SCOPE GLOBAL @@ -1315,7 +1315,7 @@ VARIABLE_COMMENT If non-zero, number of threads to spawn to apply in parallel events on the slave that were group-committed on the master or were logged with GTID in different replication domains. Note that these threads are in addition to the IO and SQL threads, which are always created by a replication slave NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3774,7 +3774,7 @@ +@@ -3804,7 +3804,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_PARALLEL_WORKERS VARIABLE_SCOPE GLOBAL @@ -1324,7 +1324,7 @@ VARIABLE_COMMENT Alias for slave_parallel_threads NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3814,7 +3814,7 @@ +@@ -3844,7 +3844,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SLAVE_TRANSACTION_RETRIES VARIABLE_SCOPE GLOBAL @@ -1333,7 +1333,7 @@ VARIABLE_COMMENT Number of times the slave SQL thread will retry a transaction in case it failed with a deadlock, elapsed lock wait timeout or listed in slave_transaction_retry_errors, before giving up and stopping NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3834,7 +3834,7 @@ +@@ -3864,7 +3864,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_TRANSACTION_RETRY_INTERVAL VARIABLE_SCOPE GLOBAL @@ -1342,7 +1342,7 @@ VARIABLE_COMMENT Interval of the slave SQL thread will retry a transaction in case it failed with a deadlock or elapsed lock wait timeout or listed in slave_transaction_retry_errors NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 3600 -@@ -3854,7 +3854,7 @@ +@@ -3884,7 +3884,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLOW_LAUNCH_TIME VARIABLE_SCOPE GLOBAL @@ -1351,7 +1351,7 @@ VARIABLE_COMMENT If creating the thread takes longer than this value (in seconds), the Slow_launch_threads counter will be incremented NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -4124,7 +4124,7 @@ +@@ -4154,7 +4154,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME STORED_PROGRAM_CACHE VARIABLE_SCOPE GLOBAL @@ -1360,7 +1360,7 @@ VARIABLE_COMMENT The soft upper limit for number of cached stored routines for one connection. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 524288 -@@ -4224,7 +4224,7 @@ +@@ -4254,7 +4254,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME TABLE_DEFINITION_CACHE VARIABLE_SCOPE GLOBAL @@ -1369,7 +1369,7 @@ VARIABLE_COMMENT The number of cached table definitions NUMERIC_MIN_VALUE 400 NUMERIC_MAX_VALUE 2097152 -@@ -4234,7 +4234,7 @@ +@@ -4264,7 +4264,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TABLE_OPEN_CACHE VARIABLE_SCOPE GLOBAL @@ -1378,7 +1378,7 @@ VARIABLE_COMMENT The number of cached open tables NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 1048576 -@@ -4294,7 +4294,7 @@ +@@ -4324,7 +4324,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME THREAD_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -1387,7 +1387,7 @@ VARIABLE_COMMENT How many threads we should keep in a cache for reuse. These are freed after 5 minutes of idle time NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -4352,6 +4352,26 @@ +@@ -4382,6 +4382,26 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED @@ -1404,7 +1404,7 @@ +VARIABLE_NAME THREAD_POOL_MODE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE ENUM -+VARIABLE_COMMENT Chose implementation of the threadpool ++VARIABLE_COMMENT Chose implementation of the threadpool. Use 'windows' unless you have a workload with a lot of concurrent connections and minimal contention +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL @@ -1414,7 +1414,7 @@ VARIABLE_NAME THREAD_POOL_OVERSUBSCRIBE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED -@@ -4455,7 +4475,7 @@ +@@ -4485,7 +4505,7 @@ COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TMPDIR VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR @@ -1423,7 +1423,7 @@ NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -@@ -4494,7 +4514,7 @@ +@@ -4524,7 +4544,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TRANSACTION_ALLOC_BLOCK_SIZE VARIABLE_SCOPE SESSION @@ -1432,7 +1432,7 @@ VARIABLE_COMMENT Allocation block size for transactions to be stored in binary log NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 134217728 -@@ -4504,7 +4524,7 @@ +@@ -4534,7 +4554,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TRANSACTION_PREALLOC_SIZE VARIABLE_SCOPE SESSION @@ -1441,7 +1441,7 @@ VARIABLE_COMMENT Persistent buffer for transactions to be stored in binary log NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 134217728 -@@ -4644,10 +4664,10 @@ +@@ -4674,10 +4694,10 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME WAIT_TIMEOUT VARIABLE_SCOPE SESSION @@ -1454,7 +1454,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -4671,7 +4691,7 @@ +@@ -4701,7 +4721,7 @@ order by variable_name; VARIABLE_NAME LOG_TC_SIZE GLOBAL_VALUE_ORIGIN AUTO VARIABLE_SCOPE GLOBAL diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 5add3ed05dc..e6516a59b3a 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3565,7 +3565,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SECURE_FILE_PRIV VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR -VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory +VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL diff --git a/mysql-test/suite/sys_vars/t/secure_file_priv.test b/mysql-test/suite/sys_vars/t/secure_file_priv.test index fec02db4241..a6b7d0be138 100644 --- a/mysql-test/suite/sys_vars/t/secure_file_priv.test +++ b/mysql-test/suite/sys_vars/t/secure_file_priv.test @@ -1,3 +1,4 @@ +--source include/platform.inc --echo # --echo # Bug50373 --secure-file-priv="" --echo # @@ -29,3 +30,14 @@ evalp SELECT load_file('$MYSQLTEST_VARDIR/../bug50373.txt') AS loaded_file; DROP TABLE t1; --remove_file $MYSQLTEST_VARDIR/../bug50373.txt + +--echo # +--echo # MDEV-40589 default exclude list for secure-file-priv +--echo # +CREATE TABLE t1 (c1 VARCHAR(50)); +--disable_abort_on_error +LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +--enable_abort_on_error +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f4f0ba33938..9f4a48c6d5a 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -8860,45 +8860,39 @@ fn_format_relative_to_data_home(char * to, const char *name, bool is_secure_file_path(char *path) { - char buff1[FN_REFLEN], buff2[FN_REFLEN]; - size_t opt_secure_file_priv_len; - /* - All paths are secure if opt_secure_file_path is 0 - */ - if (!opt_secure_file_priv) - return TRUE; + char buf1[FN_REFLEN], buf2[FN_REFLEN]; + const char *cmp; - opt_secure_file_priv_len= strlen(opt_secure_file_priv); + if (opt_secure_file_priv) + cmp= opt_secure_file_priv; + else +#ifdef _WIN32 + return TRUE; // All paths are secure if opt_secure_file_priv is unset +#else + cmp= "/proc/"; // Check that it doesn't start with this prefix +#endif if (strlen(path) >= FN_REFLEN) return FALSE; - if (my_realpath(buff1, path, 0)) + if (my_realpath(buf1, path, 0)) { - /* - The supplied file path might have been a file and not a directory. - */ - size_t length= dirname_length(path); // Guaranteed to be < FN_REFLEN - memcpy(buff2, path, length); - buff2[length]= '\0'; - if (length == 0 || my_realpath(buff1, buff2, 0)) - return FALSE; - } - convert_dirname(buff2, buff1, NullS); - if (!lower_case_file_system) - { - if (strncmp(opt_secure_file_priv, buff2, opt_secure_file_priv_len)) + /* The supplied file path might have been a file and not a directory. */ + size_t length= dirname_length(path); // Guaranteed to be < FN_REFLEN + memcpy(buf2, path, length); + buf2[length]= '\0'; + if (length == 0 || my_realpath(buf1, buf2, 0)) return FALSE; } + convert_dirname(buf2, buf1, NullS); + + size_t cmp_len= strlen(cmp); + bool matched; + if (lower_case_file_system) + matched= !files_charset_info->strnncoll(buf2, strlen(buf2), cmp, cmp_len, 1); else - { - if (files_charset_info->strnncoll(buff2, strlen(buff2), - opt_secure_file_priv, - opt_secure_file_priv_len, - TRUE)) - return FALSE; - } - return TRUE; + matched= !strncmp(cmp, buf2, cmp_len); + return opt_secure_file_priv ? matched : !matched; } diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index eaa099e65ab..08b826c8ea6 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3357,11 +3357,15 @@ static Sys_var_mybool Sys_require_secure_transport( ON_CHECK(check_require_secure_transport), ON_UPDATE(0)); static Sys_var_charptr_fscs Sys_secure_file_priv( - "secure_file_priv", - "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files " - "within specified directory", - PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv), - CMD_LINE(REQUIRED_ARG), DEFAULT(0)); + "secure_file_priv", + "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files " + "within specified directory." +#ifndef _WIN32 + " Empty value means no limits except /proc" +#endif + , + PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv), + CMD_LINE(REQUIRED_ARG), DEFAULT(0)); static bool check_server_id(sys_var *self, THD *thd, set_var *var) { diff --git a/storage/connect/mysql-test/connect/r/drop-open-error.result b/storage/connect/mysql-test/connect/r/drop-open-error.result index f9b9b7e87d2..8f5341bbcce 100644 --- a/storage/connect/mysql-test/connect/r/drop-open-error.result +++ b/storage/connect/mysql-test/connect/r/drop-open-error.result @@ -5,11 +5,3 @@ drop table mdev9949; Warnings: Warning 1017 Can't find file: 'DATADIR/test/mdev9949.dos' (errno: 2 "No such file or directory") drop table t1; -select @@secure_file_priv 'must be NULL'; -must be NULL -NULL -create table t1 (a char(16)) engine=myisam; -insert into t1 values('Hello World!'); -create table t2 engine=connect file_name='foo/bar.txt' as select * from t1; -ERROR HY000: Got error 174 'Open(a+b) error 2 on foo/bar.txt: No such file or directory' from CONNECT -drop table t1; diff --git a/storage/connect/mysql-test/connect/t/drop-open-error.opt b/storage/connect/mysql-test/connect/t/drop-open-error.opt deleted file mode 100644 index 22520f0aa99..00000000000 --- a/storage/connect/mysql-test/connect/t/drop-open-error.opt +++ /dev/null @@ -1 +0,0 @@ ---secure-file-priv="" diff --git a/storage/connect/mysql-test/connect/t/drop-open-error.test b/storage/connect/mysql-test/connect/t/drop-open-error.test index dd286c96466..97d938e4310 100644 --- a/storage/connect/mysql-test/connect/t/drop-open-error.test +++ b/storage/connect/mysql-test/connect/t/drop-open-error.test @@ -18,14 +18,3 @@ copy_file $MTR_SUITE_DIR/std_data/mdev9949.frm $datadir/test/mdev9949.frm; --replace_result $MARIADB_DATADIR DATADIR/ './' 'DATADIR/' drop table mdev9949; drop table t1; - -# -# MDEV-7935 CREATE TABLE ... AS SELECT ... can cause a Server crash (Assertion `0' in Protocol::end_statement) -# -select @@secure_file_priv 'must be NULL'; # otherwise foo/bar.txt won't be allowed -create table t1 (a char(16)) engine=myisam; -insert into t1 values('Hello World!'); -replace_regex @on .*/foo/@on foo/@; -error ER_GET_ERRMSG; -create table t2 engine=connect file_name='foo/bar.txt' as select * from t1; -drop table t1; From f54998694758d03e20b30b58d0d71476c75a2af3 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 4 Aug 2026 10:05:35 +0200 Subject: [PATCH 066/150] Revert "MDEV-39622 OBJECT_INSTANCE_BEGIN in P_S are unstable, difficult to compare" Let's use MySQL's fix for compatibility This reverts commit 11c41cd93d2c3732862ba043afd18508021440c0. --- storage/perfschema/table_events_waits.cc | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/storage/perfschema/table_events_waits.cc b/storage/perfschema/table_events_waits.cc index dbfe98e105d..59b99dabc0f 100644 --- a/storage/perfschema/table_events_waits.cc +++ b/storage/perfschema/table_events_waits.cc @@ -39,8 +39,6 @@ THR_LOCK table_events_waits_current::m_table_lock; -#define OBJECT_INSTANCE_BEGIN(X) (((intptr)X) - ((intptr) &pfs_truncatable_acl)) - PFS_engine_table_share_state table_events_waits_current::m_share_state = { false /* m_checked */ @@ -253,7 +251,7 @@ int table_events_waits_common::make_table_object_columns(PFS_events_waits *wait) m_row.m_index_name_length= 0; } - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; return 0; } @@ -268,7 +266,7 @@ int table_events_waits_common::make_file_object_columns(PFS_events_waits *wait) m_row.m_object_type= "FILE"; m_row.m_object_type_length= 4; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; if (safe_file->get_version() == wait->m_weak_version) { @@ -300,7 +298,7 @@ int table_events_waits_common::make_socket_object_columns(PFS_events_waits *wait m_row.m_object_type= "SOCKET"; m_row.m_object_type_length= 6; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; if (safe_socket->get_version() == wait->m_weak_version) { @@ -434,7 +432,7 @@ int table_events_waits_common::make_metadata_lock_object_columns(PFS_events_wait if (m_row.m_object_name_length > 0) memcpy(m_row.m_object_name, mdl->name(), m_row.m_object_name_length); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; } else { @@ -500,17 +498,17 @@ void table_events_waits_common::make_row(PFS_events_waits *wait) break; case WAIT_CLASS_MUTEX: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; safe_class= sanitize_mutex_class((PFS_mutex_class*) wait->m_class); break; case WAIT_CLASS_RWLOCK: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; safe_class= sanitize_rwlock_class((PFS_rwlock_class*) wait->m_class); break; case WAIT_CLASS_COND: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; safe_class= sanitize_cond_class((PFS_cond_class*) wait->m_class); break; case WAIT_CLASS_TABLE: From 93092e11297dc9cd607dd578e57a75b20dd28044 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 4 Aug 2026 11:07:43 +0200 Subject: [PATCH 067/150] BUG#39449066 Refactor performance schema OBJECT_INSTANCE_BEGIN columns Fix for MariaDB 10.6 --- storage/perfschema/pfs.cc | 8 +- storage/perfschema/pfs.h | 5 +- storage/perfschema/pfs_account.cc | 2 +- storage/perfschema/pfs_buffer_container.cc | 21 ++- storage/perfschema/pfs_buffer_container.h | 140 +++++------------- storage/perfschema/pfs_events_waits.h | 6 +- storage/perfschema/pfs_host.cc | 2 +- storage/perfschema/pfs_instr.cc | 51 ++++--- storage/perfschema/pfs_instr.h | 27 ++-- storage/perfschema/pfs_instr_class.cc | 9 +- storage/perfschema/pfs_prepared_stmt.cc | 8 +- storage/perfschema/pfs_prepared_stmt.h | 2 +- storage/perfschema/pfs_program.cc | 2 +- storage/perfschema/pfs_setup_actor.cc | 2 +- storage/perfschema/pfs_setup_object.cc | 2 +- storage/perfschema/pfs_user.cc | 2 +- storage/perfschema/table_events_waits.cc | 15 +- storage/perfschema/table_events_waits.h | 2 +- .../perfschema/table_events_waits_summary.cc | 17 +-- .../perfschema/table_events_waits_summary.h | 4 +- .../table_file_summary_by_instance.cc | 2 +- .../table_file_summary_by_instance.h | 5 +- storage/perfschema/table_helper.h | 1 - storage/perfschema/table_md_locks.cc | 3 +- storage/perfschema/table_md_locks.h | 2 +- .../table_prepared_stmt_instances.cc | 3 +- .../table_prepared_stmt_instances.h | 2 +- storage/perfschema/table_socket_instances.cc | 2 +- storage/perfschema/table_socket_instances.h | 2 +- .../table_socket_summary_by_instance.cc | 2 +- .../table_socket_summary_by_instance.h | 2 +- storage/perfschema/table_sync_instances.cc | 7 +- storage/perfschema/table_sync_instances.h | 6 +- storage/perfschema/table_table_handles.cc | 3 +- storage/perfschema/table_table_handles.h | 2 +- 35 files changed, 160 insertions(+), 211 deletions(-) diff --git a/storage/perfschema/pfs.cc b/storage/perfschema/pfs.cc index 195a646da35..1d867e44975 100644 --- a/storage/perfschema/pfs.cc +++ b/storage/perfschema/pfs.cc @@ -3298,7 +3298,7 @@ pfs_get_thread_file_name_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= NULL; + wait->m_object_instance_addr= 0; wait->m_weak_file= NULL; wait->m_weak_version= 0; wait->m_event_id= pfs_thread->m_event_id++; @@ -3378,7 +3378,7 @@ pfs_get_thread_file_stream_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= pfs_file; + wait->m_object_instance_addr= pfs_file->m_identity; wait->m_weak_file= pfs_file; wait->m_weak_version= pfs_file->get_version(); wait->m_event_id= pfs_thread->m_event_id++; @@ -3487,7 +3487,7 @@ pfs_get_thread_file_descriptor_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= pfs_file; + wait->m_object_instance_addr= pfs_file->m_identity; wait->m_weak_file= pfs_file; wait->m_weak_version= pfs_file->get_version(); wait->m_event_id= pfs_thread->m_event_id++; @@ -4667,7 +4667,7 @@ void pfs_end_file_wait_v1(PSI_file_locker *locker, wait->m_timer_end= timer_end; wait->m_number_of_bytes= bytes; wait->m_end_event_id= thread->m_event_id; - wait->m_object_instance_addr= file; + wait->m_object_instance_addr= (file ? file->m_identity : 0); wait->m_weak_file= file; wait->m_weak_version= (file ? file->get_version() : 0); diff --git a/storage/perfschema/pfs.h b/storage/perfschema/pfs.h index 6e0cf258c91..b73e79498a9 100644 --- a/storage/perfschema/pfs.h +++ b/storage/perfschema/pfs.h @@ -23,11 +23,15 @@ #ifndef PFS_H #define PFS_H +#include + /** @file storage/perfschema/pfs.h Performance schema instrumentation (declarations). */ +typedef std::uint64_t pfs_identity; + #define HAVE_PSI_1 #include @@ -60,4 +64,3 @@ extern bool THR_PFS_initialized; #define PSI_COUNT_VOLATILITY 2 #endif - diff --git a/storage/perfschema/pfs_account.cc b/storage/perfschema/pfs_account.cc index 2a877836649..88038e947e1 100644 --- a/storage/perfschema/pfs_account.cc +++ b/storage/perfschema/pfs_account.cc @@ -180,7 +180,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_account_container.allocate(& dirty_state); + pfs= global_account_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/pfs_buffer_container.cc b/storage/perfschema/pfs_buffer_container.cc index 04f6f5219a3..4b96366c4a9 100644 --- a/storage/perfschema/pfs_buffer_container.cc +++ b/storage/perfschema/pfs_buffer_container.cc @@ -29,6 +29,26 @@ #include "pfs_buffer_container.h" #include "pfs_builtin_memory.h" +pfs_identity make_identity(pfs_container_id container_id, pfs_page_id page_id, + pfs_object_id object_id, + pfs_dirty_state *dirty_state) { + /* See pfs_lock, the version number is stored in the high 30 bits. */ + uint32_t version_32 = (dirty_state->m_version_state & VERSION_MASK) >> 2; + /* Isolate 16 least significant bits from the version number. */ + uint16_t version_16 = version_32 & 0xFFFF; + pfs_identity id; + id = container_id; + id <<= 16; + id += page_id; + id <<= 16; + id += object_id; + id <<= 16; + id += version_16; + return id; +} + +std::atomic global_container_id{1}; + PFS_buffer_default_allocator default_mutex_allocator(& builtin_memory_mutex); PFS_mutex_container global_mutex_container(& default_mutex_allocator); @@ -880,4 +900,3 @@ void PFS_user_allocator::free_array(PFS_user_array *array) PFS_user_allocator user_allocator; PFS_user_container global_user_container(& user_allocator); - diff --git a/storage/perfschema/pfs_buffer_container.h b/storage/perfschema/pfs_buffer_container.h index c1cd9e644ef..c4305795892 100644 --- a/storage/perfschema/pfs_buffer_container.h +++ b/storage/perfschema/pfs_buffer_container.h @@ -33,7 +33,26 @@ #include "pfs_prepared_stmt.h" #include "pfs_builtin_memory.h" -#define USE_SCALABLE +typedef std::uint16_t pfs_container_id; +typedef std::uint16_t pfs_page_id; +typedef std::uint16_t pfs_object_id; + +/** + * Build an artificial object identity, for OBJECT_INSTANCE_BEGIN columns. + * To be globally unique, identity consists of: + * - the container id, so objects A and B from different containers + * (instrument classes) will not collide. Note that partitioned containers + * get a container id per partition. + * - the page id within the container + * - the object index within the page + * - the version number from the pfs_lock dirty_state, to resolve ABA + * problems + */ +extern pfs_identity make_identity(pfs_container_id container_id, + pfs_page_id page_id, pfs_object_id object_id, + pfs_dirty_state *dirty_state); + +extern std::atomic global_container_id; class PFS_opaque_container_page; class PFS_opaque_container; @@ -77,7 +96,9 @@ class PFS_buffer_default_array public: typedef T value_type; - value_type *allocate(pfs_dirty_state *dirty_state) + value_type *allocate(pfs_dirty_state *dirty_state, + pfs_container_id container_id, pfs_page_id page_id, + pfs_identity *id) { uint index; uint monotonic; @@ -97,10 +118,11 @@ public: if (pfs->m_lock.free_to_dirty(dirty_state)) { + if (id != nullptr) + *id = make_identity(container_id, page_id, index, dirty_state); return pfs; } monotonic= m_monotonic.m_u32.fetch_add(1); - } m_full= true; @@ -400,6 +422,11 @@ template ::max(), + "pfs_object_id field is only 16 bits"); + static_assert(PFS_PAGE_COUNT <= std::numeric_limits::max(), + "pfs_page_id field is only 16 bits"); + friend class PFS_buffer_scalable_iterator; /** @@ -427,6 +454,7 @@ public: PFS_buffer_scalable_container(allocator_type *allocator) { + m_container_id = global_container_id++; m_allocator= allocator; m_initialized= false; m_lost= 0; @@ -533,7 +561,7 @@ public: return get_row_count() * get_row_size(); } - value_type *allocate(pfs_dirty_state *dirty_state) + value_type *allocate(pfs_dirty_state *dirty_state, pfs_identity *id) { if (m_full) { @@ -578,7 +606,7 @@ public: if (array != NULL) { - pfs= array->allocate(dirty_state); + pfs = array->allocate(dirty_state, m_container_id, index, id); if (pfs != NULL) { /* Keep a pointer to the parent page, for deallocate(). */ @@ -695,7 +723,7 @@ public: } assert(array != NULL); - pfs= array->allocate(dirty_state); + pfs= array->allocate(dirty_state, m_container_id, current_page_count, id); if (pfs != NULL) { /* Keep a pointer to the parent page, for deallocate(). */ @@ -1019,6 +1047,7 @@ private: array_type * m_pages[PFS_PAGE_COUNT]; allocator_type *m_allocator; pthread_mutex_t m_critical_section; + pfs_container_id m_container_id; }; template @@ -1178,11 +1207,12 @@ public: return sum; } - value_type *allocate(pfs_dirty_state *dirty_state, uint partition) + value_type *allocate(pfs_dirty_state *dirty_state, uint partition, + pfs_identity *id) { assert(partition < PFS_PARTITION_COUNT); - return m_partitions[partition]->allocate(dirty_state); + return m_partitions[partition]->allocate(dirty_state, id); } void deallocate(value_type *safe_pfs) @@ -1373,116 +1403,60 @@ private: uint m_sub_index; }; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_mutex_basic_container; typedef PFS_partitioned_buffer_scalable_container PFS_mutex_container; -#else -typedef PFS_buffer_container PFS_mutex_container; -#endif typedef PFS_mutex_container::iterator_type PFS_mutex_iterator; extern PFS_mutex_container global_mutex_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_rwlock_container; -#else -typedef PFS_buffer_container PFS_rwlock_container; -#endif typedef PFS_rwlock_container::iterator_type PFS_rwlock_iterator; extern PFS_rwlock_container global_rwlock_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_cond_container; -#else -typedef PFS_buffer_container PFS_cond_container; -#endif typedef PFS_cond_container::iterator_type PFS_cond_iterator; extern PFS_cond_container global_cond_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_file_container; -#else -typedef PFS_buffer_container PFS_file_container; -#endif typedef PFS_file_container::iterator_type PFS_file_iterator; extern PFS_file_container global_file_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_socket_container; -#else -typedef PFS_buffer_container PFS_socket_container; -#endif typedef PFS_socket_container::iterator_type PFS_socket_iterator; extern PFS_socket_container global_socket_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_mdl_container; -#else -typedef PFS_buffer_container PFS_mdl_container; -#endif typedef PFS_mdl_container::iterator_type PFS_mdl_iterator; extern PFS_mdl_container global_mdl_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_setup_actor_container; -#else -typedef PFS_buffer_container PFS_setup_actor_container; -#endif typedef PFS_setup_actor_container::iterator_type PFS_setup_actor_iterator; extern PFS_setup_actor_container global_setup_actor_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_setup_object_container; -#else -typedef PFS_buffer_container PFS_setup_object_container; -#endif typedef PFS_setup_object_container::iterator_type PFS_setup_object_iterator; extern PFS_setup_object_container global_setup_object_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_container; -#else -typedef PFS_buffer_container PFS_table_container; -#endif typedef PFS_table_container::iterator_type PFS_table_iterator; extern PFS_table_container global_table_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_container; -#else -typedef PFS_buffer_container PFS_table_share_container; -#endif typedef PFS_table_share_container::iterator_type PFS_table_share_iterator; extern PFS_table_share_container global_table_share_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_index_container; -#else -typedef PFS_buffer_container PFS_table_share_index_container; -#endif typedef PFS_table_share_index_container::iterator_type PFS_table_share_index_iterator; extern PFS_table_share_index_container global_table_share_index_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_lock_container; -#else -typedef PFS_buffer_container PFS_table_share_lock_container; -#endif typedef PFS_table_share_lock_container::iterator_type PFS_table_share_lock_iterator; extern PFS_table_share_lock_container global_table_share_lock_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_program_container; -#else -typedef PFS_buffer_container PFS_program_container; -#endif typedef PFS_program_container::iterator_type PFS_program_iterator; extern PFS_program_container global_program_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_prepared_stmt_container; -#else -typedef PFS_buffer_container PFS_prepared_stmt_container; -#endif typedef PFS_prepared_stmt_container::iterator_type PFS_prepared_stmt_iterator; extern PFS_prepared_stmt_container global_prepared_stmt_container; @@ -1503,17 +1477,9 @@ public: void free_array(PFS_account_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_account_container; -#else -typedef PFS_buffer_container PFS_account_container; -#endif typedef PFS_account_container::iterator_type PFS_account_iterator; extern PFS_account_container global_account_container; @@ -1534,17 +1500,11 @@ public: void free_array(PFS_host_array *array); }; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_host_container; -#else -typedef PFS_buffer_container PFS_host_container; -#endif typedef PFS_host_container::iterator_type PFS_host_iterator; extern PFS_host_container global_host_container; @@ -1577,17 +1537,8 @@ public: void free_array(PFS_thread_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_thread_container; -#else -typedef PFS_buffer_container PFS_thread_container; -#endif typedef PFS_thread_container::iterator_type PFS_thread_iterator; extern PFS_thread_container global_thread_container; @@ -1608,17 +1559,8 @@ public: void free_array(PFS_user_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_user_container; -#else -typedef PFS_buffer_container PFS_user_container; -#endif typedef PFS_user_container::iterator_type PFS_user_iterator; extern PFS_user_container global_user_container; diff --git a/storage/perfschema/pfs_events_waits.h b/storage/perfschema/pfs_events_waits.h index 93dcb136834..0c950c84a33 100644 --- a/storage/perfschema/pfs_events_waits.h +++ b/storage/perfschema/pfs_events_waits.h @@ -29,6 +29,7 @@ Events waits data structures (declarations). */ +#include "pfs.h" #include "pfs_column_types.h" #include "pfs_lock.h" #include "pfs_events.h" @@ -88,8 +89,8 @@ struct PFS_events_waits : public PFS_events PFS_metadata_lock *m_weak_metadata_lock; /** For weak pointers, target object version. */ uint32 m_weak_version; - /** Address in memory of the object instance waited on. */ - const void *m_object_instance_addr; + /** Identity of the object instance waited on. */ + pfs_identity m_object_instance_addr; /** Operation performed. */ enum_operation_type m_operation; /** @@ -154,4 +155,3 @@ void reset_table_io_waits_by_table_handle(); void reset_table_lock_waits_by_table_handle(); #endif - diff --git a/storage/perfschema/pfs_host.cc b/storage/perfschema/pfs_host.cc index bf8421909ba..408a45c4d84 100644 --- a/storage/perfschema/pfs_host.cc +++ b/storage/perfschema/pfs_host.cc @@ -169,7 +169,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_host_container.allocate(& dirty_state); + pfs= global_host_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/pfs_instr.cc b/storage/perfschema/pfs_instr.cc index dad6125fa83..550b37b3efa 100644 --- a/storage/perfschema/pfs_instr.cc +++ b/storage/perfschema/pfs_instr.cc @@ -297,18 +297,18 @@ void cleanup_file_hash(void) /** Create instrumentation for a mutex instance. @param klass the mutex class - @param identity the mutex address @return a mutex instance, or NULL */ -PFS_mutex* create_mutex(PFS_mutex_class *klass, const void *identity) +PFS_mutex* create_mutex(PFS_mutex_class *klass, const void * /* identity */) { PFS_mutex *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_mutex_container.allocate(& dirty_state, klass->m_volatility); + pfs= global_mutex_container.allocate(& dirty_state, klass->m_volatility, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -343,18 +343,18 @@ void destroy_mutex(PFS_mutex *pfs) /** Create instrumentation for a rwlock instance. @param klass the rwlock class - @param identity the rwlock address @return a rwlock instance, or NULL */ -PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void *identity) +PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void * /* identity */) { PFS_rwlock *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_rwlock_container.allocate(& dirty_state); + pfs= global_rwlock_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -391,18 +391,18 @@ void destroy_rwlock(PFS_rwlock *pfs) /** Create instrumentation for a condition instance. @param klass the condition class - @param identity the condition address @return a condition instance, or NULL */ -PFS_cond* create_cond(PFS_cond_class *klass, const void *identity) +PFS_cond* create_cond(PFS_cond_class *klass, const void * /* identity */) { PFS_cond *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_cond_container.allocate(& dirty_state); + pfs= global_cond_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -523,7 +523,7 @@ PFS_thread* create_thread(PFS_thread_class *klass, const void *identity, PFS_thread *pfs; pfs_dirty_state dirty_state; - pfs= global_thread_container.allocate(& dirty_state); + pfs= global_thread_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_thread_internal_id= @@ -848,6 +848,7 @@ find_or_create_file(PFS_thread *thread, PFS_file_class *klass, uint retry_count= 0; const uint retry_max= 3; pfs_dirty_state dirty_state; + pfs_identity id; search: @@ -870,9 +871,10 @@ search: return NULL; } - pfs= global_file_container.allocate(& dirty_state); + pfs= global_file_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity = id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -881,7 +883,6 @@ search: pfs->m_filename_length= normalized_length; pfs->m_file_stat.m_open_count= 1; pfs->m_file_stat.m_io_stat.reset(); - pfs->m_identity= (const void *)pfs; pfs->m_temporary= false; int res; @@ -1110,19 +1111,19 @@ void destroy_file(PFS_thread *thread, PFS_file *pfs) Create instrumentation for a table instance. @param share the table share @param opening_thread the opening thread - @param identity the table address @return a table instance, or NULL */ PFS_table* create_table(PFS_table_share *share, PFS_thread *opening_thread, - const void *identity) + const void * /* identity */) { PFS_table *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_table_container.allocate(& dirty_state); + pfs= global_table_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_share= share; pfs->m_io_enabled= share->m_enabled && flag_global_instrumentation && global_table_io_class.m_enabled; @@ -1294,6 +1295,7 @@ PFS_socket* create_socket(PFS_socket_class *klass, const my_socket *fd, { PFS_socket *pfs; pfs_dirty_state dirty_state; + pfs_identity id; uint fd_used= 0; uint addr_len_used= addr_len; @@ -1304,13 +1306,13 @@ PFS_socket* create_socket(PFS_socket_class *klass, const my_socket *fd, if (addr_len_used > sizeof(sockaddr_storage)) addr_len_used= sizeof(sockaddr_storage); - pfs= global_socket_container.allocate(& dirty_state); + pfs= global_socket_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity= id; pfs->m_fd= fd_used; /* There is no socket object, so we use the instrumentation. */ - pfs->m_identity= pfs; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -1378,7 +1380,7 @@ void destroy_socket(PFS_socket *pfs) global_socket_container.deallocate(pfs); } -PFS_metadata_lock* create_metadata_lock(void *identity, +PFS_metadata_lock* create_metadata_lock(void * /* identity */, const MDL_key *mdl_key, opaque_mdl_type mdl_type, opaque_mdl_duration mdl_duration, @@ -1388,11 +1390,12 @@ PFS_metadata_lock* create_metadata_lock(void *identity, { PFS_metadata_lock *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_mdl_container.allocate(& dirty_state); + pfs= global_mdl_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_enabled= global_metadata_class.m_enabled && flag_global_instrumentation; pfs->m_timed= global_metadata_class.m_timed; pfs->m_mdl_key.mdl_key_init(mdl_key); diff --git a/storage/perfschema/pfs_instr.h b/storage/perfschema/pfs_instr.h index 868f772267c..2fe5b05f7b8 100644 --- a/storage/perfschema/pfs_instr.h +++ b/storage/perfschema/pfs_instr.h @@ -91,8 +91,8 @@ struct PFS_instr /** Instrumented mutex implementation. @see PSI_mutex. */ struct PFS_ALIGNED PFS_mutex : public PFS_instr { - /** Mutex identity, typically a pthread_mutex_t. */ - const void *m_identity; + /** Mutex identity. */ + pfs_identity m_identity; /** Mutex class. */ PFS_mutex_class *m_class; /** Instrument statistics. */ @@ -109,8 +109,8 @@ struct PFS_ALIGNED PFS_mutex : public PFS_instr /** Instrumented rwlock implementation. @see PSI_rwlock. */ struct PFS_ALIGNED PFS_rwlock : public PFS_instr { - /** RWLock identity, typically a pthread_rwlock_t. */ - const void *m_identity; + /** RWLock identity. */ + pfs_identity m_identity; /** RWLock class. */ PFS_rwlock_class *m_class; /** Instrument statistics. */ @@ -134,8 +134,8 @@ struct PFS_ALIGNED PFS_rwlock : public PFS_instr /** Instrumented cond implementation. @see PSI_cond. */ struct PFS_ALIGNED PFS_cond : public PFS_instr { - /** Condition identity, typically a pthread_cond_t. */ - const void *m_identity; + /** Condition identity. */ + pfs_identity m_identity; /** Condition class. */ PFS_cond_class *m_class; /** Condition instance usage statistics. */ @@ -148,8 +148,8 @@ struct PFS_ALIGNED PFS_file : public PFS_instr uint32 get_version() { return m_lock.get_version(); } - /** File identity */ - const void *m_identity; + /** File identity. */ + pfs_identity m_identity; /** File name. */ char m_filename[FN_REFLEN]; /** File name length in bytes. */ @@ -241,8 +241,8 @@ public: ulonglong m_owner_event_id; /** Table share. */ PFS_table_share *m_share; - /** Table identity, typically a handler. */ - const void *m_identity; + /** Table identity. */ + pfs_identity m_identity; /** Table statistics. */ PFS_table_stat m_table_stat; /** Current internal lock. */ @@ -266,8 +266,8 @@ struct PFS_ALIGNED PFS_socket : public PFS_instr uint32 get_version() { return m_lock.get_version(); } - /** Socket identity, typically int */ - const void *m_identity; + /** Socket identity. */ + pfs_identity m_identity; /** Owning thread, if applicable */ PFS_thread *m_thread_owner; /** Socket file descriptor */ @@ -291,7 +291,7 @@ struct PFS_ALIGNED PFS_metadata_lock : public PFS_instr { return m_lock.get_version(); } /** Lock identity. */ - const void *m_identity; + pfs_identity m_identity; MDL_key m_mdl_key; opaque_mdl_type m_mdl_type; opaque_mdl_duration m_mdl_duration; @@ -814,4 +814,3 @@ extern LF_HASH pfs_filename_hash; /** @} */ #endif - diff --git a/storage/perfschema/pfs_instr_class.cc b/storage/perfschema/pfs_instr_class.cc index 86cc6365c31..ea2e7a747bc 100644 --- a/storage/perfschema/pfs_instr_class.cc +++ b/storage/perfschema/pfs_instr_class.cc @@ -716,7 +716,7 @@ create_table_share_lock_stat() pfs_dirty_state dirty_state; /* Create a new record in table stat array. */ - pfs= global_table_share_lock_container.allocate(& dirty_state); + pfs= global_table_share_lock_container.allocate(& dirty_state, NULL); if (pfs != NULL) { /* Reset the stats. */ @@ -769,13 +769,11 @@ create_table_share_index_stat(const TABLE_SHARE *server_share, uint server_index pfs_dirty_state dirty_state; /* Create a new record in index stat array. */ - pfs= global_table_share_index_container.allocate(& dirty_state); + pfs= global_table_share_index_container.allocate(& dirty_state, NULL); if (pfs != NULL) { if (server_index == MAX_INDEXES) - { pfs->m_key.m_name_length= 0; - } else { KEY *key_info= server_share->key_info + server_index; @@ -1797,7 +1795,7 @@ search: */ } - pfs= global_table_share_container.allocate(& dirty_state); + pfs= global_table_share_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; @@ -2061,4 +2059,3 @@ void update_program_share_derived_flags(PFS_thread *thread) } /** @} */ - diff --git a/storage/perfschema/pfs_prepared_stmt.cc b/storage/perfschema/pfs_prepared_stmt.cc index 50e4e27bb1e..6985720eef1 100644 --- a/storage/perfschema/pfs_prepared_stmt.cc +++ b/storage/perfschema/pfs_prepared_stmt.cc @@ -75,23 +75,23 @@ void reset_prepared_stmt_instances() } PFS_prepared_stmt* -create_prepared_stmt(void *identity, +create_prepared_stmt(void * /* identity */, PFS_thread *thread, PFS_program *pfs_program, PFS_events_statements *pfs_stmt, uint stmt_id, const char* stmt_name, uint stmt_name_length) { PFS_prepared_stmt *pfs= NULL; pfs_dirty_state dirty_state; + pfs_identity id; /* Create a new record in prepared stmt stat array. */ - pfs= global_prepared_stmt_container.allocate(& dirty_state); + pfs= global_prepared_stmt_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity= id; /* Reset the stats. */ pfs->reset_data(); /* Do the assignments. */ - pfs->m_identity= identity; - pfs->m_sqltext_length= 0; if (stmt_name != NULL) diff --git a/storage/perfschema/pfs_prepared_stmt.h b/storage/perfschema/pfs_prepared_stmt.h index 1a06122382c..395b6e2c015 100644 --- a/storage/perfschema/pfs_prepared_stmt.h +++ b/storage/perfschema/pfs_prepared_stmt.h @@ -38,7 +38,7 @@ struct PFS_ALIGNED PFS_prepared_stmt : public PFS_instr { /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** STATEMENT_ID */ ulonglong m_stmt_id; diff --git a/storage/perfschema/pfs_program.cc b/storage/perfschema/pfs_program.cc index 20b0aaa8489..b10f00b7d52 100644 --- a/storage/perfschema/pfs_program.cc +++ b/storage/perfschema/pfs_program.cc @@ -232,7 +232,7 @@ search: &is_enabled, &is_timed); /* Else create a new record in program stat array. */ - pfs= global_program_container.allocate(& dirty_state); + pfs= global_program_container.allocate(& dirty_state, NULL); if (pfs != NULL) { /* Do the assignments. */ diff --git a/storage/perfschema/pfs_setup_actor.cc b/storage/perfschema/pfs_setup_actor.cc index 638b2e5648d..07b2dbd21cd 100644 --- a/storage/perfschema/pfs_setup_actor.cc +++ b/storage/perfschema/pfs_setup_actor.cc @@ -154,7 +154,7 @@ int insert_setup_actor(const String *user, const String *host, const String *rol PFS_setup_actor *pfs; pfs_dirty_state dirty_state; - pfs= global_setup_actor_container.allocate(& dirty_state); + pfs= global_setup_actor_container.allocate(& dirty_state, NULL); if (pfs != NULL) { set_setup_actor_key(&pfs->m_key, diff --git a/storage/perfschema/pfs_setup_object.cc b/storage/perfschema/pfs_setup_object.cc index a961c51d0e9..989c49979e8 100644 --- a/storage/perfschema/pfs_setup_object.cc +++ b/storage/perfschema/pfs_setup_object.cc @@ -157,7 +157,7 @@ int insert_setup_object(enum_object_type object_type, const String *schema, PFS_setup_object *pfs; pfs_dirty_state dirty_state; - pfs= global_setup_object_container.allocate(& dirty_state); + pfs= global_setup_object_container.allocate(& dirty_state, NULL); if (pfs != NULL) { set_setup_object_key(&pfs->m_key, object_type, diff --git a/storage/perfschema/pfs_user.cc b/storage/perfschema/pfs_user.cc index d83ee52a77d..e3ec2992fa1 100644 --- a/storage/perfschema/pfs_user.cc +++ b/storage/perfschema/pfs_user.cc @@ -166,7 +166,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_user_container.allocate(& dirty_state); + pfs= global_user_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/table_events_waits.cc b/storage/perfschema/table_events_waits.cc index 59b99dabc0f..b6d3a34379e 100644 --- a/storage/perfschema/table_events_waits.cc +++ b/storage/perfschema/table_events_waits.cc @@ -251,7 +251,7 @@ int table_events_waits_common::make_table_object_columns(PFS_events_waits *wait) m_row.m_index_name_length= 0; } - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; return 0; } @@ -266,7 +266,7 @@ int table_events_waits_common::make_file_object_columns(PFS_events_waits *wait) m_row.m_object_type= "FILE"; m_row.m_object_type_length= 4; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; if (safe_file->get_version() == wait->m_weak_version) { @@ -298,7 +298,7 @@ int table_events_waits_common::make_socket_object_columns(PFS_events_waits *wait m_row.m_object_type= "SOCKET"; m_row.m_object_type_length= 6; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; if (safe_socket->get_version() == wait->m_weak_version) { @@ -432,7 +432,7 @@ int table_events_waits_common::make_metadata_lock_object_columns(PFS_events_wait if (m_row.m_object_name_length > 0) memcpy(m_row.m_object_name, mdl->name(), m_row.m_object_name_length); - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; } else { @@ -498,17 +498,17 @@ void table_events_waits_common::make_row(PFS_events_waits *wait) break; case WAIT_CLASS_MUTEX: clear_object_columns(); - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_mutex_class((PFS_mutex_class*) wait->m_class); break; case WAIT_CLASS_RWLOCK: clear_object_columns(); - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_rwlock_class((PFS_rwlock_class*) wait->m_class); break; case WAIT_CLASS_COND: clear_object_columns(); - m_row.m_object_instance_addr= (intptr) wait->m_object_instance_addr; + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_cond_class((PFS_cond_class*) wait->m_class); break; case WAIT_CLASS_TABLE: @@ -1161,4 +1161,3 @@ table_events_waits_history_long::get_row_count(void) { return events_waits_history_long_size; } - diff --git a/storage/perfschema/table_events_waits.h b/storage/perfschema/table_events_waits.h index 85a9b146745..5ddf57582d1 100644 --- a/storage/perfschema/table_events_waits.h +++ b/storage/perfschema/table_events_waits.h @@ -79,7 +79,7 @@ struct row_events_waits /** Length in bytes of @c m_index_name. */ uint m_index_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - intptr m_object_instance_addr; + pfs_identity m_object_instance_addr; /** Column SOURCE. */ char m_source[COL_SOURCE_SIZE]; /** Length in bytes of @c m_source. */ diff --git a/storage/perfschema/table_events_waits_summary.cc b/storage/perfschema/table_events_waits_summary.cc index c848ebf4418..d0502932ec7 100644 --- a/storage/perfschema/table_events_waits_summary.cc +++ b/storage/perfschema/table_events_waits_summary.cc @@ -83,7 +83,7 @@ table_events_waits_summary_by_instance void table_events_waits_summary_by_instance ::make_instr_row(PFS_instr *pfs, PFS_instr_class *klass, - const void *object_instance_begin, + pfs_identity object_instance_begin, PFS_single_stat *pfs_stat) { pfs_optimistic_state lock; @@ -97,7 +97,7 @@ void table_events_waits_summary_by_instance m_row.m_name= klass->m_name; m_row.m_name_length= klass->m_name_length; - m_row.m_object_instance_addr= (intptr) object_instance_begin; + m_row.m_object_instance_addr= object_instance_begin; get_normalizer(klass); m_row.m_stat.set(m_normalizer, pfs_stat); @@ -161,11 +161,7 @@ void table_events_waits_summary_by_instance::make_file_row(PFS_file *pfs) PFS_single_stat sum; pfs->m_file_stat.m_io_stat.sum_waits(& sum); - /* - Files don't have a in memory structure associated to it, - so we use the address of the PFS_file buffer as object_instance_begin - */ - make_instr_row(pfs, safe_class, pfs, & sum); + make_instr_row(pfs, safe_class, pfs->m_identity, & sum); } /** @@ -186,11 +182,7 @@ void table_events_waits_summary_by_instance::make_socket_row(PFS_socket *pfs) PFS_byte_stat pfs_stat; pfs->m_socket_stat.m_io_stat.sum(&pfs_stat); - /* - Sockets don't have an associated in-memory structure, so use the address of - the PFS_socket buffer as object_instance_begin. - */ - make_instr_row(pfs, safe_class, pfs, &pfs_stat); + make_instr_row(pfs, safe_class, pfs->m_identity, &pfs_stat); } int table_events_waits_summary_by_instance @@ -240,4 +232,3 @@ int table_events_waits_summary_by_instance return 0; } - diff --git a/storage/perfschema/table_events_waits_summary.h b/storage/perfschema/table_events_waits_summary.h index 74f3a9ac959..0135b5e9aa9 100644 --- a/storage/perfschema/table_events_waits_summary.h +++ b/storage/perfschema/table_events_waits_summary.h @@ -48,7 +48,7 @@ struct row_events_waits_summary_by_instance /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - intptr m_object_instance_addr; + pfs_identity m_object_instance_addr; /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER_WAIT. */ PFS_stat_row m_stat; }; @@ -65,7 +65,7 @@ public: protected: void make_instr_row(PFS_instr *pfs, PFS_instr_class *klass, - const void *object_instance_begin, + pfs_identity object_instance_begin, PFS_single_stat *pfs_stat); void make_mutex_row(PFS_mutex *pfs) override; void make_rwlock_row(PFS_rwlock *pfs) override; diff --git a/storage/perfschema/table_file_summary_by_instance.cc b/storage/perfschema/table_file_summary_by_instance.cc index 89e08d49161..a622430df88 100644 --- a/storage/perfschema/table_file_summary_by_instance.cc +++ b/storage/perfschema/table_file_summary_by_instance.cc @@ -203,7 +203,7 @@ int table_file_summary_by_instance::read_row_values(TABLE *table, m_row.m_event_name.set_field(f); break; case 2: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (ulonglong)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 3:/* COUNT_STAR */ diff --git a/storage/perfschema/table_file_summary_by_instance.h b/storage/perfschema/table_file_summary_by_instance.h index 09f4383394d..34a91663b8e 100644 --- a/storage/perfschema/table_file_summary_by_instance.h +++ b/storage/perfschema/table_file_summary_by_instance.h @@ -50,8 +50,9 @@ struct row_file_summary_by_instance /** Column EVENT_NAME. */ PFS_event_name_row m_event_name; - /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + /** Column OBJECT_INSTANCE_BEGIN. */ + pfs_identity m_identity; + /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER and NUMBER_OF_BYTES for READ, WRITE and MISC operation types. diff --git a/storage/perfschema/table_helper.h b/storage/perfschema/table_helper.h index 87572ef2525..9e260b56838 100644 --- a/storage/perfschema/table_helper.h +++ b/storage/perfschema/table_helper.h @@ -699,4 +699,3 @@ private: /** @} */ #endif - diff --git a/storage/perfschema/table_md_locks.cc b/storage/perfschema/table_md_locks.cc index e7b76c12764..54d91623012 100644 --- a/storage/perfschema/table_md_locks.cc +++ b/storage/perfschema/table_md_locks.cc @@ -177,7 +177,7 @@ int table_metadata_locks::read_row_values(TABLE *table, m_row.m_object.set_nullable_field(f->field_index, f); break; case 3: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 4: /* LOCK_TYPE */ set_field_mdl_type(f, m_row.m_mdl_type, m_row.m_object.m_object_type == OBJECT_TYPE_BACKUP); @@ -211,4 +211,3 @@ int table_metadata_locks::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_md_locks.h b/storage/perfschema/table_md_locks.h index 62f681435f6..47b8f6f78ae 100644 --- a/storage/perfschema/table_md_locks.h +++ b/storage/perfschema/table_md_locks.h @@ -43,7 +43,7 @@ struct PFS_metadata_lock; struct row_metadata_lock { /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; opaque_mdl_type m_mdl_type; opaque_mdl_duration m_mdl_duration; opaque_mdl_status m_mdl_status; diff --git a/storage/perfschema/table_prepared_stmt_instances.cc b/storage/perfschema/table_prepared_stmt_instances.cc index f064d8f03ae..0ad60048be5 100644 --- a/storage/perfschema/table_prepared_stmt_instances.cc +++ b/storage/perfschema/table_prepared_stmt_instances.cc @@ -235,7 +235,7 @@ int table_prepared_stmt_instances switch(f->field_index) { case 0: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 1: /* STATEMENT_ID */ set_field_ulonglong(f, m_row.m_stmt_id); @@ -298,4 +298,3 @@ int table_prepared_stmt_instances return 0; } - diff --git a/storage/perfschema/table_prepared_stmt_instances.h b/storage/perfschema/table_prepared_stmt_instances.h index 838deec55a0..80ccdd620f6 100644 --- a/storage/perfschema/table_prepared_stmt_instances.h +++ b/storage/perfschema/table_prepared_stmt_instances.h @@ -43,7 +43,7 @@ struct row_prepared_stmt_instances { /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** Column STMT_ID. */ ulonglong m_stmt_id; diff --git a/storage/perfschema/table_socket_instances.cc b/storage/perfschema/table_socket_instances.cc index 2435d19df3c..8d2d8d92335 100644 --- a/storage/perfschema/table_socket_instances.cc +++ b/storage/perfschema/table_socket_instances.cc @@ -184,7 +184,7 @@ int table_socket_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_event_name, m_row.m_event_name_length); break; case 1: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* THREAD_ID */ if (m_row.m_thread_id_set) diff --git a/storage/perfschema/table_socket_instances.h b/storage/perfschema/table_socket_instances.h index 61cb121afd4..e5e0f7ea0a1 100644 --- a/storage/perfschema/table_socket_instances.h +++ b/storage/perfschema/table_socket_instances.h @@ -44,7 +44,7 @@ struct row_socket_instances /** Length in bytes of @c m_event_name. */ uint m_event_name_length; /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** Column THREAD_ID */ ulonglong m_thread_id; /** True if thread_is is set */ diff --git a/storage/perfschema/table_socket_summary_by_instance.cc b/storage/perfschema/table_socket_summary_by_instance.cc index eb0568c05c2..3182e8f3deb 100644 --- a/storage/perfschema/table_socket_summary_by_instance.cc +++ b/storage/perfschema/table_socket_summary_by_instance.cc @@ -195,7 +195,7 @@ int table_socket_summary_by_instance::read_row_values(TABLE *table, m_row.m_event_name.set_field(f); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2:/* COUNT_STAR */ diff --git a/storage/perfschema/table_socket_summary_by_instance.h b/storage/perfschema/table_socket_summary_by_instance.h index 2f51d8b5e39..1ed679621fd 100644 --- a/storage/perfschema/table_socket_summary_by_instance.h +++ b/storage/perfschema/table_socket_summary_by_instance.h @@ -49,7 +49,7 @@ struct row_socket_summary_by_instance PFS_event_name_row m_event_name; /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER and NUMBER_OF_BYTES for each operation. */ PFS_socket_io_stat_row m_io_stat; diff --git a/storage/perfschema/table_sync_instances.cc b/storage/perfschema/table_sync_instances.cc index 497f7fa1854..2cf8cc65db4 100644 --- a/storage/perfschema/table_sync_instances.cc +++ b/storage/perfschema/table_sync_instances.cc @@ -174,7 +174,7 @@ int table_mutex_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* LOCKED_BY_THREAD_ID */ if (m_row.m_locked) @@ -334,7 +334,7 @@ int table_rwlock_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* WRITE_LOCKED_BY_THREAD_ID */ if (m_row.m_write_locked) @@ -480,7 +480,7 @@ int table_cond_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; default: assert(false); @@ -490,4 +490,3 @@ int table_cond_instances::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_sync_instances.h b/storage/perfschema/table_sync_instances.h index 1a6cf81a173..2807b45d7c2 100644 --- a/storage/perfschema/table_sync_instances.h +++ b/storage/perfschema/table_sync_instances.h @@ -48,7 +48,7 @@ struct row_mutex_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** True if column LOCKED_BY_THREAD_ID is not null. */ bool m_locked; /** Column LOCKED_BY_THREAD_ID. */ @@ -104,7 +104,7 @@ struct row_rwlock_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** True if column WRITE_LOCKED_BY_THREAD_ID is not null. */ bool m_write_locked; /** Column WRITE_LOCKED_BY_THREAD_ID. */ @@ -162,7 +162,7 @@ struct row_cond_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; }; /** Table PERFORMANCE_SCHEMA.COND_INSTANCES. */ diff --git a/storage/perfschema/table_table_handles.cc b/storage/perfschema/table_table_handles.cc index 7ea2b0a3316..82b5f9dd6a2 100644 --- a/storage/perfschema/table_table_handles.cc +++ b/storage/perfschema/table_table_handles.cc @@ -196,7 +196,7 @@ int table_table_handles::read_row_values(TABLE *table, m_row.m_object.set_field(f->field_index, f); break; case 3: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 4: /* OWNER_THREAD_ID */ set_field_ulonglong(f, m_row.m_owner_thread_id); @@ -218,4 +218,3 @@ int table_table_handles::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_table_handles.h b/storage/perfschema/table_table_handles.h index d2330104e5d..883849d415d 100644 --- a/storage/perfschema/table_table_handles.h +++ b/storage/perfschema/table_table_handles.h @@ -48,7 +48,7 @@ struct row_table_handles /** Column OBJECT_TYPE, SCHEMA_NAME, OBJECT_NAME. */ PFS_object_row m_object; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** Column OWNER_THREAD_ID. */ ulonglong m_owner_thread_id; /** Column OWNER_EVENT_ID. */ From 34854f5e43ea1ac7b8c17aa9a263831b92acae03 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Wed, 13 May 2026 22:55:45 +0000 Subject: [PATCH 068/150] MDEV-39459 Fix bad sync pattern for chain replication MTR tests In chain replication (1->2->3), syncing only server_3 after save_master_gtid on server_1 does not guarantee server_2 has committed, because server_2's binlog dump thread can send events to server_3 before commit_ordered() completes on server_2. Fix affected rpl tests by syncing server_2 before server_3, and update result files accordingly. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../rpl/include/rpl_row_img_sequence.inc | 9 + .../rpl_xa_empty_transaction_test_case.inc | 8 + .../rpl/r/rpl_row_img_sequence_full.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_min.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_noblob.result | 72 ++++++++ .../rpl/r/rpl_start_alter_chain_basic.result | 3 + .../rpl/r/rpl_xa_empty_transaction.result | 171 ++++++++++++++++++ .../rpl/t/rpl_start_alter_chain_basic.test | 3 + .../suite/rpl/t/rpl_xa_empty_transaction.test | 3 + 9 files changed, 413 insertions(+) diff --git a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc index d9ed6f278b9..ebb811b1f3b 100644 --- a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc +++ b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc @@ -28,6 +28,9 @@ SELECT SETVAL(s1, 10); --source include/save_master_gtid.inc --echo # Validate SETVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -60,6 +63,9 @@ SELECT NEXTVAL(s1); --source include/save_master_gtid.inc --echo # Validate NEXTVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -88,5 +94,8 @@ FLUSH LOGS; --connection server_1 DROP TABLE s1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc index 6368336b8e3..aa67fab9d40 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc @@ -34,6 +34,10 @@ CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc @@ -127,5 +131,9 @@ FLUSH LOGS; DROP TABLE ti,tm; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result index 2303911637f..4dd186db922 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result index eaf770f245c..b387742d549 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result @@ -54,6 +54,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -91,6 +94,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -125,6 +131,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -145,6 +154,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -182,6 +194,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -216,6 +231,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -236,6 +254,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -273,6 +294,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -307,6 +331,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -327,6 +354,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -364,6 +394,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -398,6 +431,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -418,6 +454,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -455,6 +494,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -489,6 +531,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -509,6 +554,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -546,6 +594,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -580,6 +631,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -600,6 +654,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -637,6 +694,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -671,6 +731,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -691,6 +754,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -728,6 +794,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -762,6 +831,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result index 496bd8dc6b8..8ca8ea3e3f8 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result index a62b02b2f44..bdb4c2ae123 100644 --- a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result +++ b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result @@ -66,6 +66,9 @@ domain_id seq_no 0 12 connection server_1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 917041c148d..37b0b06d222 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -13,6 +13,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -37,6 +40,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -46,6 +52,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -77,6 +86,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -86,6 +98,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -110,6 +125,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -119,6 +137,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -150,6 +171,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -162,6 +186,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -188,6 +215,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -197,6 +227,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -230,6 +263,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -239,6 +275,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -265,6 +304,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -274,6 +316,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -307,6 +352,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -323,6 +371,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -349,6 +400,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -358,6 +412,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -391,6 +448,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -400,6 +460,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -428,6 +491,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -437,6 +503,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -470,6 +539,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -485,6 +557,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -513,6 +588,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -522,6 +600,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -557,6 +638,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -566,6 +650,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -594,6 +681,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -603,6 +693,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -638,6 +731,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -655,6 +751,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -683,6 +782,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -692,6 +794,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -727,6 +832,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -736,6 +844,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -766,6 +877,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -775,6 +889,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -810,6 +927,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -830,6 +950,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -858,6 +981,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -867,6 +993,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -902,6 +1031,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -911,6 +1043,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -941,6 +1076,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -950,6 +1088,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -985,6 +1126,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1004,6 +1148,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1032,6 +1179,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1041,6 +1191,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1076,6 +1229,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1085,6 +1241,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1115,6 +1274,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1124,6 +1286,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1159,6 +1324,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1173,6 +1341,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/stop_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test index efb667ad6fa..7918e517b4d 100644 --- a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test +++ b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test @@ -41,6 +41,9 @@ select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; --connection server_1 --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index 62ac69cef22..a33262bc47a 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -187,6 +187,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --source include/stop_slave.inc From b369925f6855d3e1f0ebcf453492724b99fda6f8 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 5 Aug 2026 13:14:27 +0200 Subject: [PATCH 069/150] MDEV-40637 CONNECT crashes on double(255,50) in DOS table cap the length correctly --- storage/connect/mysql-test/connect/r/fix.result | 10 ++++++++++ storage/connect/mysql-test/connect/t/fix.test | 11 +++++++++++ storage/connect/value.cpp | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/storage/connect/mysql-test/connect/r/fix.result b/storage/connect/mysql-test/connect/r/fix.result index d6cdad0373b..2b5f2595da2 100644 --- a/storage/connect/mysql-test/connect/r/fix.result +++ b/storage/connect/mysql-test/connect/r/fix.result @@ -129,3 +129,13 @@ Sam Chicago 1979-11-22 2007-10-10 James Dallas 1992-05-13 2009-12-14 Bill Boston 1986-09-11 2008-02-10 DROP TABLE t1; +# +# MDEV-40637 CONNECT crashes on double(255,50) in DOS table +# +create table t (x double(255,30)) engine=connect table_type='dos' file_name='t.dos'; +insert into t values (1e50); +select * from t; +x +1000000000000000000000000000000.000000000000000000000000000000 +drop table t; +# End of 10.6 tests diff --git a/storage/connect/mysql-test/connect/t/fix.test b/storage/connect/mysql-test/connect/t/fix.test index f05abbb9b92..a38fe5623a6 100644 --- a/storage/connect/mysql-test/connect/t/fix.test +++ b/storage/connect/mysql-test/connect/t/fix.test @@ -106,3 +106,14 @@ DROP TABLE t1; --remove_file $MYSQLD_DATADIR/test/dept.dat --remove_file $MYSQLD_DATADIR/test/boys.txt --remove_file $MYSQLD_DATADIR/test/boyswin.txt + +--echo # +--echo # MDEV-40637 CONNECT crashes on double(255,50) in DOS table +--echo # +create table t (x double(255,30)) engine=connect table_type='dos' file_name='t.dos'; +insert into t values (1e50); +select * from t; +drop table t; +--remove_file $MYSQLD_DATADIR/test/t.dos + +--echo # End of 10.6 tests diff --git a/storage/connect/value.cpp b/storage/connect/value.cpp index 344b0dea50f..90f57d8f770 100644 --- a/storage/connect/value.cpp +++ b/storage/connect/value.cpp @@ -746,7 +746,7 @@ bool TYPVAL::SetValue_char(const char *p, int n) for (; n > 0 && *p == ' '; p++) n--; - memcpy(buf, p, MY_MIN(n, 31)); + memcpy(buf, p, n= MY_MIN(n, 31)); buf[n] = '\0'; Tval = atof(buf); From d842d6e994a8b0d67c280bf785a11357d6b316a2 Mon Sep 17 00:00:00 2001 From: forkfun Date: Wed, 5 Aug 2026 15:02:05 +0200 Subject: [PATCH 070/150] on rpm: don't set mysql user's $HOME to datadir useradd set --home to %{mysqldatadir}, matching datadir. Set --home to /nonexistent (as in deb) --- support-files/rpm/server-prein.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/support-files/rpm/server-prein.sh b/support-files/rpm/server-prein.sh index af04b343910..95dd464830a 100644 --- a/support-files/rpm/server-prein.sh +++ b/support-files/rpm/server-prein.sh @@ -68,6 +68,8 @@ fi # Create a MariaDB user and group. Do not report any problems if it already exists. groupadd -r %{mysqld_group} 2> /dev/null || true -useradd -M -r --home %{mysqldatadir} --shell /sbin/nologin --comment "MariaDB server" --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true +useradd -M -r --home /nonexistent --shell /sbin/nologin --comment "MariaDB server" --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true # The user may already exist, make sure it has the proper group nevertheless (BUG#12823) usermod --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true +# Make sure the home dir is correct too (must not be datadir) +usermod -d /nonexistent %{mysqld_user} 2> /dev/null || true From 43565b932e52fcecea6587db9bb1ec6af1292f63 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 5 Aug 2026 15:10:15 +0200 Subject: [PATCH 071/150] MDEV-40636 CSV crashes on DELETE chain_size is the number of tina_set elements, not number of bytes Assisted-By: Claude:claude-5-opus --- mysql-test/suite/csv/delete.result | 11 +++++++++++ mysql-test/suite/csv/delete.test | 13 +++++++++++++ storage/csv/ha_tina.cc | 4 +++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/csv/delete.result create mode 100644 mysql-test/suite/csv/delete.test diff --git a/mysql-test/suite/csv/delete.result b/mysql-test/suite/csv/delete.result new file mode 100644 index 00000000000..73dc585fc26 --- /dev/null +++ b/mysql-test/suite/csv/delete.result @@ -0,0 +1,11 @@ +# +# MDEV-40636 CSV crashes on DELETE +# +create table t1 (id int not null, pad char(10) not null) engine=csv; +insert t1 select seq,'AAAAAAAAAA' from seq_1_to_2048; +delete from t1 where id % 2 = 0; +select count(*), sum(id) from t1; +count(*) sum(id) +1024 1048576 +drop table t1; +# End of 10.6 tests diff --git a/mysql-test/suite/csv/delete.test b/mysql-test/suite/csv/delete.test new file mode 100644 index 00000000000..2b4b1eea6c9 --- /dev/null +++ b/mysql-test/suite/csv/delete.test @@ -0,0 +1,13 @@ +--source include/have_csv.inc +--source include/have_sequence.inc + +--echo # +--echo # MDEV-40636 CSV crashes on DELETE +--echo # +create table t1 (id int not null, pad char(10) not null) engine=csv; +insert t1 select seq,'AAAAAAAAAA' from seq_1_to_2048; +delete from t1 where id % 2 = 0; +select count(*), sum(id) from t1; +drop table t1; + +--echo # End of 10.6 tests diff --git a/storage/csv/ha_tina.cc b/storage/csv/ha_tina.cc index 71c06776cfe..2251b75c973 100644 --- a/storage/csv/ha_tina.cc +++ b/storage/csv/ha_tina.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2004, 2011, Oracle and/or its affiliates. + Copyright (c) 2026, MariaDB plc 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 @@ -646,7 +647,8 @@ int ha_tina::chain_append() if (chain_alloced) { if ((chain= (tina_set *) my_realloc(csv_key_memory_tina_set, - (uchar*)chain, chain_size, + (uchar*)chain, + chain_size * sizeof(tina_set), MYF(MY_WME))) == NULL) return -1; } From 6fb7e416462ff02af3bbc81ccebc840482b382b1 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Wed, 5 Aug 2026 18:03:56 +0100 Subject: [PATCH 072/150] fix: MDEV-40610 fix for SQL injection in DEFAULT expression. --- storage/duckdb/convertor/ddl_convertor.cc | 16 ++++++- .../r/duckdb_default_value_injection.result | 25 +++++++++++ .../t/duckdb_default_value_injection.test | 45 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/duckdb_default_value_injection.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index f349d1fd700..fe87a77c7d9 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -288,7 +288,21 @@ static std::string get_field_default_for_duckdb(Field *field, String str(buf, sizeof(buf), system_charset_info); String *val= field->val_str(&str); if (val && val->length() > 0) - default_value= "'" + std::string(val->ptr(), val->length()) + "'"; + { + /* + Escape the literal by doubling any embedded single quote so a crafted + DEFAULT string cannot terminate the generated DuckDB literal and inject + further DuckDB statements (MDEV-40610). escape_quotes_for_mysql() is + charset aware and performs exactly this doubling. + */ + std::string escaped(2 * val->length(), '\0'); + my_bool overflow; + size_t escaped_len= escape_quotes_for_mysql(val->charset(), &escaped[0], + 0, val->ptr(), val->length(), + &overflow); + escaped.resize(escaped_len); + default_value= "'" + escaped + "'"; + } else default_value= "NULL"; } diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_default_value_injection.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_default_value_injection.result new file mode 100644 index 00000000000..cb96fbbf5ba --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_default_value_injection.result @@ -0,0 +1,25 @@ +# +# MDEV-40610: an unescaped column DEFAULT string must not inject DuckDB SQL +# +# get_field_default_for_duckdb() wrapped a string DEFAULT in single quotes +# without doubling an embedded quote, so a crafted default closed the +# generated DuckDB literal and the rest was parsed as DuckDB SQL. The gate +# below fails while the injection works and passes once literals are escaped. +SET NAMES utf8mb4; +# The crafted DEFAULT closes the generated literal and appends a DuckDB COPY +# that would write a file in the data directory. With the literal escaped the +# payload is stored as data and no statement is injected. +CREATE TABLE t (id INT PRIMARY KEY, c VARCHAR(200) DEFAULT 'a'');COPY(SELECT 2)TO ''zz_def.csv'';--') ENGINE=DuckDB CHARSET=utf8mb4| +# No injected COPY ran: zz_def.csv must not exist anywhere under the vardir. +# The default round-trips through MariaDB as plain data. +INSERT INTO t (id) VALUES (1); +SELECT c FROM t WHERE id = 1; +c +a');COPY(SELECT 2)TO 'zz_def.csv';-- +# A legitimate quote in a DEFAULT is stored and returned verbatim. +ALTER TABLE t ADD COLUMN d VARCHAR(20) DEFAULT 'a''b'; +INSERT INTO t (id) VALUES (2); +SELECT d FROM t WHERE id = 2; +d +a'b +DROP TABLE t; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test new file mode 100644 index 00000000000..8713fecb3f5 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test @@ -0,0 +1,45 @@ +--source ../include/have_duckdb.inc + +--echo # +--echo # MDEV-40610: an unescaped column DEFAULT string must not inject DuckDB SQL +--echo # +--echo # get_field_default_for_duckdb() wrapped a string DEFAULT in single quotes +--echo # without doubling an embedded quote, so a crafted default closed the +--echo # generated DuckDB literal and the rest was parsed as DuckDB SQL. The gate +--echo # below fails while the injection works and passes once literals are escaped. + +--disable_query_log +CREATE DATABASE IF NOT EXISTS injdb CHARACTER SET utf8mb4; +USE injdb; +--enable_query_log +SET NAMES utf8mb4; + +--echo # The crafted DEFAULT closes the generated literal and appends a DuckDB COPY +--echo # that would write a file in the data directory. With the literal escaped the +--echo # payload is stored as data and no statement is injected. +--delimiter | +CREATE TABLE t (id INT PRIMARY KEY, c VARCHAR(200) DEFAULT 'a'');COPY(SELECT 2)TO ''zz_def.csv'';--') ENGINE=DuckDB CHARSET=utf8mb4| +--delimiter ; + +--echo # No injected COPY ran: zz_def.csv must not exist anywhere under the vardir. +perl; + use File::Find; + my $found= 0; + find(sub { $found= 1 if $_ eq 'zz_def.csv' }, $ENV{MYSQLTEST_VARDIR}); + die "MDEV-40610: crafted DEFAULT injected a COPY (zz_def.csv written)\n" if $found; +EOF + +--echo # The default round-trips through MariaDB as plain data. +INSERT INTO t (id) VALUES (1); +SELECT c FROM t WHERE id = 1; + +--echo # A legitimate quote in a DEFAULT is stored and returned verbatim. +ALTER TABLE t ADD COLUMN d VARCHAR(20) DEFAULT 'a''b'; +INSERT INTO t (id) VALUES (2); +SELECT d FROM t WHERE id = 2; + +DROP TABLE t; + +--disable_query_log +DROP DATABASE injdb; +--enable_query_log From 02a8d47e0e4c8fae16716b19db3bebce2b6cbf81 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Wed, 5 Aug 2026 21:48:40 +0100 Subject: [PATCH 073/150] fix: MDEV-40386 disable MTR tests for MSAN builds b/c MSAN build is unstable. --- storage/duckdb/build.sh | 23 ++++++++++++++++--- .../duckdb/t/alter_default_debug.test | 1 + .../duckdb/t/alter_duckdb_column.test | 1 + .../duckdb/t/alter_duckdb_column_copy.test | 1 + .../duckdb/t/alter_duckdb_index.test | 1 + .../duckdb/t/alter_engine_duckdb.test | 1 + .../mysql-test/duckdb/t/batch_disconnect.test | 1 + .../mysql-test/duckdb/t/batch_rollback.test | 1 + .../t/bugfix_crash_after_commit_error.test | 1 + .../t/bugfix_temp_and_system_database.test | 1 + .../duckdb/t/charset_and_collation.test | 1 + .../duckdb/t/create_table_column.test | 1 + .../t/create_table_column_timestamp.test | 1 + .../duckdb/t/create_table_constraint.test | 1 + .../duckdb/t/cross_engine_join.test | 1 + .../duckdb/t/cross_engine_ryow.test | 1 + .../duckdb/t/cross_engine_ryow_dup.test | 1 + .../duckdb/t/cross_engine_ryow_limit.test | 1 + .../duckdb/t/cross_engine_union.test | 1 + .../duckdb/t/cross_engine_where.test | 1 + .../duckdb/t/decimal_high_precision.test | 1 + .../decimal_precision_all_possibilities.test | 1 + .../mysql-test/duckdb/t/dml_delete.test | 1 + .../mysql-test/duckdb/t/dml_update.test | 1 + .../mysql-test/duckdb/t/drop_database.test | 1 + .../duckdb/t/duckdb_add_backticks.test | 1 + .../mysql-test/duckdb/t/duckdb_agg_func.test | 1 + .../duckdb/t/duckdb_allow_encryption.test | 1 + .../duckdb/t/duckdb_alter_table_engine.test | 1 + ...db_appender_allocator_flush_threshold.test | 1 + .../duckdb/t/duckdb_auto_increment.test | 1 + .../duckdb/t/duckdb_bit_string.test | 1 + .../mysql-test/duckdb/t/duckdb_collate.test | 1 + .../mysql-test/duckdb/t/duckdb_cte.test | 1 + .../duckdb/t/duckdb_db_table_strconvert.test | 1 + .../t/duckdb_ddl_during_transaction.test | 1 + .../duckdb/t/duckdb_default_expr.test | 1 + .../t/duckdb_default_value_injection.test | 1 + .../mysql-test/duckdb/t/duckdb_fix_sql.test | 1 + .../mysql-test/duckdb/t/duckdb_json.test | 1 + .../mysql-test/duckdb/t/duckdb_kill.test | 1 + .../mysql-test/duckdb/t/duckdb_monitor.test | 3 +++ .../duckdb/t/duckdb_numeric_func.test | 1 + .../mysql-test/duckdb/t/duckdb_refuse_xa.test | 1 + .../duckdb/t/duckdb_require_primary_key.test | 1 + .../duckdb/t/duckdb_set_operation.test | 1 + .../mysql-test/duckdb/t/duckdb_sql_mode.test | 1 + .../duckdb/t/duckdb_sql_syntax.test | 1 + .../duckdb/t/duckdb_string_func.test | 1 + .../mysql-test/duckdb/t/duckdb_time_func.test | 1 + .../t/duckdb_time_func_unsupported.test | 1 + .../mysql-test/duckdb/t/duckdb_uuid.test | 1 + .../duckdb/t/feature_duckdb_data_type.test | 1 + .../duckdb/mysql-test/duckdb/t/ha_duckdb.test | 1 + .../mysql-test/duckdb/t/mdev_40379.test | 1 + .../mysql-test/duckdb/t/pushdown_rewrite.test | 1 + .../duckdb/t/rename_duckdb_table.test | 1 + .../duckdb/t/run_in_duckdb_access.test | 1 + .../mysql-test/duckdb/t/space_reclaim.test | 1 + .../mysql-test/duckdb/t/streaming_select.test | 1 + .../duckdb/t/supported_copy_ddl.test | 1 + .../mysql-test/duckdb/t/system_timezone.test | 1 + .../mysql-test/duckdb/t/transaction.test | 1 + ...truncate_and_maintenance_duckdb_table.test | 1 + 64 files changed, 85 insertions(+), 3 deletions(-) diff --git a/storage/duckdb/build.sh b/storage/duckdb/build.sh index 18383f0d622..c7d179a2c64 100755 --- a/storage/duckdb/build.sh +++ b/storage/duckdb/build.sh @@ -11,7 +11,7 @@ BUILD_PATH=$(realpath "$MDB_SOURCE_PATH"/../DuckdbBuildOf_$(basename "$MDB_SOURC CPUS=$(getconf _NPROCESSORS_ONLN) BUILD_TYPE_OPTIONS=("Debug" "RelWithDebInfo") BUILD_TYPE="${BUILD_TYPE:-}" -DISTRO_OPTIONS=("ubuntu:22.04" "ubuntu:24.04" "debian:12" "rockylinux:8" "rockylinux:9") +DISTRO_OPTIONS=("ubuntu:22.04" "ubuntu:24.04" "debian:12" "debian:13" "rockylinux:8" "rockylinux:9") DEFAULT_MDB_DATADIR="/var/lib/mysql" USER="mysql" GROUP="mysql" @@ -30,6 +30,7 @@ usage() { echo " -D Install build prerequisites (requires root/sudo)" echo " -u Build unit tests" echo " -a Enable ASAN/UBSAN (WITH_ASAN, WITH_ASAN_SCOPE, WITH_UBSAN)" + echo " -m Enable MSAN (WITH_MSAN)" echo " -R Use gcc-toolset-\${GCC_VERSION} on Rocky 8" echo " -h Show this help" exit 0 @@ -43,9 +44,10 @@ INSTALL_DEPS=false GCC_TOOLSET=false UNIT_TESTS=false WITH_ASAN=false +WITH_MSAN=false OS="" -while getopts "t:d:j:cpSnDRuah" opt; do +while getopts "t:d:j:cpSnDRuamh" opt; do case $opt in t) BUILD_TYPE="$OPTARG" ;; d) OS="$OPTARG" ;; @@ -58,6 +60,7 @@ while getopts "t:d:j:cpSnDRuah" opt; do R) GCC_TOOLSET=true ;; u) UNIT_TESTS=true ;; a) WITH_ASAN=true ;; + m) WITH_MSAN=true ;; h) usage ;; *) usage ;; esac @@ -110,6 +113,7 @@ info "Build dir: ${_CLR_YELLOW}$BUILD_PATH" info "Build type: ${_CLR_YELLOW}$BUILD_TYPE" info "Jobs: ${_CLR_YELLOW}$CPUS" info "ASAN/UBSAN: ${_CLR_YELLOW}$WITH_ASAN" +info "MSAN: ${_CLR_YELLOW}$WITH_MSAN" info "Unit tests: ${_CLR_YELLOW}$UNIT_TESTS" if [[ $BUILD_PACKAGES = true ]]; then info "Packages: ${_CLR_YELLOW}$PKG_FORMAT ($OS)" @@ -234,6 +238,13 @@ construct_cmake_flags() { ) fi + if [[ $WITH_MSAN = true ]]; then + MDB_CMAKE_FLAGS+=( + -DWITH_MSAN=ON + -DWITH_UNIT_TESTS=OFF + ) + fi + if [[ $BUILD_PACKAGES = true ]]; then if [[ "$PKG_FORMAT" == "rpm" ]]; then local os_version=${OS//[^0-9]/} @@ -244,6 +255,7 @@ construct_cmake_flags() { local codename="" case "$OS" in debian:12*) codename="bookworm" ;; + debian:13*) codename="trixie" ;; ubuntu:22.04) codename="jammy" ;; ubuntu:24.04) codename="noble" ;; *) fail "Unknown DEB codename for $OS" ;; @@ -279,9 +291,14 @@ install_deps() { local DEB_DEPS="build-essential git cmake ninja-build bison flex \ libncurses-dev libreadline-dev libssl-dev zlib1g-dev libbz2-dev \ libzstd-dev libcurl4-openssl-dev libaio-dev libxml2-dev libpcre2-dev \ - libxcrypt-dev liblzma-dev libpam0g-dev libperl-dev python3 python3-dev \ + liblz4-dev liblzma-dev liblzo2-dev libsnappy-dev libpam0g-dev libperl-dev python3 python3-dev \ ccache devscripts equivs debhelper libdistro-info-perl" + # libxcrypt-dev is not available on Debian 13 (Trixie) + if [[ "$OS" != debian:13* ]]; then + DEB_DEPS="$DEB_DEPS libxcrypt-dev" + fi + local command="" case "$OS" in rockylinux:8|rocky:8) diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test b/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test index a1a5cec984c..7ebcc4d25ed 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE t(id INT PRIMARY KEY, a INT, b INT, c INT) ENGINE = DuckDB; INSERT INTO t VALUES(1, 1, 1, 1); diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test index 0ae239d698c..2e5076e8b19 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo #################### --echo # TEST FOR INSTANT # --echo #################### diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test index 2a88f36f9c8..dd862e488dc 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo ################# --echo # TEST FOR COPY # --echo ################# diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test index 8df5bdd951b..5d08f86baa4 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo #################### --echo # TEST FOR INSTANT # --echo #################### diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test b/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test index 7d047686055..6240c455bf7 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc SET GLOBAL duckdb_copy_ddl_in_batch=ON; --source ../include/alter_engine_duckdb.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test b/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test index 6a4de6d2282..0ef4f782ea7 100644 --- a/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test +++ b/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --disable_query_log SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; diff --git a/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test b/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test index 60bc3e591d6..94bf421487c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test +++ b/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --disable_query_log SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; diff --git a/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test b/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test index bc6757d2401..da058f62026 100644 --- a/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test +++ b/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --skip TODO create table t1(id int primary key) engine = duckdb; diff --git a/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test b/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test index e802f5c4c35..420fd8c26aa 100644 --- a/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test +++ b/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # DuckDB has default databases named 'temp' and 'system', if we try to create # schema named 'temp' or 'system', we get an error. diff --git a/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test b/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test index 38ee163154c..54ea1077c1d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test +++ b/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # --echo # check collation config when execute --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_column.test b/storage/duckdb/mysql-test/duckdb/t/create_table_column.test index 4d56714c484..fadf29f6c0d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_column.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_column.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc # file: duckdb_create_table.test # # Test case for creating tables with various MySQL field types in DuckDB. diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test b/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test index 1b9ecfc4bcd..ffdc20b657d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --source include/have_debug.inc --disable_query_log diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test b/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test index cccf1e06d35..3875082690b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --source include/have_debug.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test index 8fb4ea50aac..43d1e2fa2db 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # Cross-engine join: DuckDB + InnoDB via _mdb_scan replacement scan --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test index 2936b1dd9d2..61dbe0076f2 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test index b19b13d33d1..d572787dfa0 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # --echo # Cross-engine RYOW: the same external table referenced more than once. diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test index c7daf2b5e3c..84cc677bd9c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test index d303b974cb0..0ae97958ccd 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # Cross-engine UNION: DuckDB + InnoDB via select_handler unit pushdown --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test index 4736fb64a97..51102d6b29f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test b/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test index ec7bb24c7bd..3570bf4a2db 100644 --- a/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test +++ b/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test b/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test index 2ea44599475..1ec9348e5d0 100644 --- a/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test +++ b/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc set global duckdb_dml_in_batch = OFF; --source ../include/decimal_precision_all_possibilities.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test index 9d8e4322cd7..8fedb60aaed 100644 --- a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test +++ b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # DuckDB DELETE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/dml_update.test b/storage/duckdb/mysql-test/duckdb/t/dml_update.test index 360dcfc1436..681c86315a9 100644 --- a/storage/duckdb/mysql-test/duckdb/t/dml_update.test +++ b/storage/duckdb/mysql-test/duckdb/t/dml_update.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # DuckDB UPDATE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/drop_database.test b/storage/duckdb/mysql-test/duckdb/t/drop_database.test index 75877300a67..9f3ed4955b0 100644 --- a/storage/duckdb/mysql-test/duckdb/t/drop_database.test +++ b/storage/duckdb/mysql-test/duckdb/t/drop_database.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # DuckDB DROP DATABASE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test index e354231ed18..a38ef13a7a9 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc SET GLOBAL duckdb_require_primary_key=OFF; --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test index bc50c1bf8cd..8a97c539156 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE t_innodb ( id INT PRIMARY KEY, diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test index 8f7ba7574ed..f0144c977ee 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --disable_warnings DROP DATABASE IF EXISTS encryption_test; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test index 80ef3297927..52a5cdb78d6 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # --echo # Test DDL: alter table engine = innodb from duckdb tables diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test index 3a154d82679..d3d8a8a2b77 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc SHOW GLOBAL VARIABLES LIKE "duckdb_appender_allocator_flush_threshold"; SELECT run_in_duckdb("FROM duckdb_settings() WHERE name = 'allocator_flush_threshold'"); diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test index b0614b1992e..4648aa05efa 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --disable_query_log CREATE DATABASE IF NOT EXISTS db_autoinc CHARACTER SET utf8mb4; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test index 22e82e4c3e5..f7c8543a54d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE t1 (id INT PRIMARY KEY, col1 VARCHAR(100)) ENGINE=DuckDB; CREATE TABLE t2 (id INT PRIMARY KEY, col1 BLOB) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test index c575cbc9201..1d29484ce32 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --disable_query_log --disable_warnings DROP TABLE IF EXISTS t1; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test index b0210d0b558..c714205c845 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc CREATE DATABASE IF NOT EXISTS duckdb_cte; USE duckdb_cte; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test index 0d50b5a1c9b..884e10f721f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc # Test for escape characters in the table name or database name. --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test index b0146bf54cb..1d192f2e214 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo ########################################################################### --echo # Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test index 15cc00024f8..c5a8ae25ec3 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --disable_query_log CREATE DATABASE IF NOT EXISTS db_default_expr CHARACTER SET utf8mb4; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test index 8713fecb3f5..170e8b809c6 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # --echo # MDEV-40610: an unescaped column DEFAULT string must not inject DuckDB SQL diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test index a005b802703..38acb142797 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE fake_innodb_table (id INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO fake_innodb_table VALUES (0); diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test index f607d971e4f..7bd522a04f7 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE t1_innodb (id INT PRIMARY KEY, col1 JSON) ENGINE=InnoDB; CREATE TABLE t1_duckdb (id INT PRIMARY KEY, col1 JSON) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test index 2915977740b..a9344b6be5b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --source include/have_debug.inc --source include/have_debug_sync.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test index 00293b87cc2..9480f9a588d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test @@ -1,4 +1,7 @@ --source ../include/have_duckdb.inc +# MDEV-40449: MSan reports use-of-uninitialized-value in +# duckdb::JemallocExtension::ThreadFlush (false positive from uninstrumented jemalloc). +--source include/not_msan.inc # # DuckDB monitoring status variables test. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test index 5f992cb23b5..3d0fdaf1ac8 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # 1. CONV(), CRC32(), MOD, DIV, TRUNCATE() is not support yet. # 2. The RAND() function in duckdb and mysql uses different random number generation algorithms, so it cannot support the seed argument. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test index 0e34bd12c61..4489a54fd33 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5)) ENGINE=duckdb; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test index cc8ffb15acb..f972509960a 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test index e245e7c9575..04db6e8cc53 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc CREATE TABLE t1 (id INT PRIMARY KEY, col1 INT) ENGINE=DuckDB; INSERT INTO t1 VALUES (1, 1), (2, 1), (3, 2), (4, 2); CREATE TABLE t2 (id INT PRIMARY KEY, col1 INT) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test index baeca66b9db..772254a650e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo --echo 1. ONLY_FULL_GROUP_BY diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test index c9db95ddd25..aee922d26f1 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc # WITH ROLLUP CREATE TABLE t1 (id INT PRIMARY KEY, col1 INT, col2 INT); SELECT id, col1, col2 FROM t1 GROUP BY id, col1, col2 WITH ROLLUP; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test index a14478edc65..59489ef568b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # 1. In Duckdb, some string function does not support blob fields as input # 2. Regex-related functions are basically incompatible, and regex functions will be processed uniformly in the future. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test index bc437121db8..8a57fd81aa5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # Compared with MySQL, duckdb's date functions generally have the following incompatibilities: # 1. Conversion between integer type and date/timestamp/time type diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test index 0c21437ed4d..61b390edfd7 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # Unsupported / incompatible date-time functions for DuckDB pushdown. # This is the companion of duckdb_time_func.test which keeps only the diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test index 161d588487f..c65fca8b8a5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test b/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test index e21b2cb311e..06f0163991b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test +++ b/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # Feature ORC data type test # Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test b/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test index ffdfff0e07e..14c5ce546ec 100644 --- a/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test +++ b/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # Basic DuckDB handler test: DDL and DML operations --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test index 0c21a7d0c60..82a48ceac59 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc # # MDEV-40379: Assertion failure in CreateTableConvertor::translate # upon CREATE TEMPORARY TABLE with ENGINE=DuckDB diff --git a/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test b/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test index bfc8cbf4750..d01a7e29a9e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test +++ b/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --disable_warnings DROP DATABASE IF EXISTS pushdown_rewrite; diff --git a/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test b/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test index 6ea744b0fd0..2511b3b41c1 100644 --- a/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test +++ b/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --echo # --echo # 1) Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test b/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test index 268b9858205..1e7afdabdfa 100644 --- a/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test +++ b/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc SET @saved_allow_run_in_duckdb = @@GLOBAL.duckdb_allow_run_in_duckdb; # The suite enables duckdb_allow_run_in_duckdb globally (suite.opt), and MTR may diff --git a/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test b/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test index f23b82603fd..2748253d487 100644 --- a/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test +++ b/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/streaming_select.test b/storage/duckdb/mysql-test/duckdb/t/streaming_select.test index bf4e0d8fffc..4999fa997d5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/streaming_select.test +++ b/storage/duckdb/mysql-test/duckdb/t/streaming_select.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --source include/have_sequence.inc --disable_warnings diff --git a/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test b/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test index 97069c1b09f..ad37d55159b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test +++ b/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/not_msan.inc # Test for DDL which are supported by DuckDB using COPY algorithm --echo # --echo # RENAME TABLE WITH DIFFERENT DATABASES diff --git a/storage/duckdb/mysql-test/duckdb/t/system_timezone.test b/storage/duckdb/mysql-test/duckdb/t/system_timezone.test index 59aa9d106a5..1d034ce0d6e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/system_timezone.test +++ b/storage/duckdb/mysql-test/duckdb/t/system_timezone.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --source ../include/have_mysqld_safe.inc # 1) Set valiables to be used in parameters of mysqld_safe. diff --git a/storage/duckdb/mysql-test/duckdb/t/transaction.test b/storage/duckdb/mysql-test/duckdb/t/transaction.test index 7f080ce7456..84688d7a379 100644 --- a/storage/duckdb/mysql-test/duckdb/t/transaction.test +++ b/storage/duckdb/mysql-test/duckdb/t/transaction.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # DuckDB transaction tests --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test b/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test index 3c2108ff356..d3f9ee37e4d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test +++ b/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test @@ -1,3 +1,4 @@ +--source include/not_msan.inc --echo # --echo # 1) PREPARE --echo # From 367efd5674fc67821078af2aea0675c9089114f7 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 26 May 2026 12:43:43 +1000 Subject: [PATCH 074/150] MDEV-31808 crash upon altering table with NEXTVAL for default under exclusive lock A segfault in mysql_lock_merge waa the result of a null argument when called from open_and_lock_internal_tables. As the argument was null there was no lock to merge and thd->lock was already in place, so no futher action is required in open_and_lock_internal_tables. After passing the previous correctly on open_table of the sequence used in the table, we get to the checking the thd->open_table list where tdc is flushed. This puts the open_table into an error condition to reopen all tables. When we are just opening a sequence, this condition isn't one we want to fail on. --- mysql-test/main/alter_table_combinations.result | 9 +++++++++ mysql-test/main/alter_table_combinations.test | 14 ++++++++++++++ sql/sql_base.cc | 6 +++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/alter_table_combinations.result b/mysql-test/main/alter_table_combinations.result index 08cf6c4707c..51588d8e705 100644 --- a/mysql-test/main/alter_table_combinations.result +++ b/mysql-test/main/alter_table_combinations.result @@ -321,4 +321,13 @@ drop table t1; # # End of 10.5 tests # +# +# MDEV-31808 Server crash upon altering table with NEXTVAL for default under exclusive lock +# +CREATE SEQUENCE s ENGINE=Aria; +CREATE TABLE t (a INT DEFAULT(NEXTVAL(s)), b INT); +ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=EXCLUSIVE; +DROP TABLE t; +DROP SEQUENCE s; +# End of 10.6 tests set @@default_storage_engine= @save_default_engine; diff --git a/mysql-test/main/alter_table_combinations.test b/mysql-test/main/alter_table_combinations.test index 7c8d7f42096..b208f0e4416 100644 --- a/mysql-test/main/alter_table_combinations.test +++ b/mysql-test/main/alter_table_combinations.test @@ -260,4 +260,18 @@ drop table t1; --echo # End of 10.5 tests --echo # +--echo # +--echo # MDEV-31808 Server crash upon altering table with NEXTVAL for default under exclusive lock +--echo # + +CREATE SEQUENCE s ENGINE=Aria; +CREATE TABLE t (a INT DEFAULT(NEXTVAL(s)), b INT); +ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=EXCLUSIVE; + +# Cleanup +DROP TABLE t; +DROP SEQUENCE s; + +--echo # End of 10.6 tests + set @@default_storage_engine= @save_default_engine; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 379337af99c..cbac955df1f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2085,7 +2085,7 @@ retry_share: goto retry_share; } - if (thd->open_tables && thd->open_tables->s->tdc->flushed) + if (!table_list->sequence && thd->open_tables && thd->open_tables->s->tdc->flushed) { /* If the version changes while we're opening the tables, @@ -5006,6 +5006,10 @@ bool open_and_lock_internal_tables(TABLE *table, bool lock_table) MYSQL_LOCK_USE_MALLOC)) goto err; + /* no existing lock to merge with */ + if (save_lock == nullptr) + DBUG_RETURN(0); + if (!(new_lock= mysql_lock_merge(save_lock, thd->lock))) { thd->lock= save_lock; From 574ba66b2d6c5d71db83d416677876e9b592858e Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 6 Aug 2026 10:26:21 +0200 Subject: [PATCH 075/150] MDEV-40362 fix test followup for f277eefa424 --- .../suite/galera/r/galera_sst_mariabackup_lost_found.result | 1 - .../suite/galera/t/galera_sst_mariabackup_lost_found.test | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_lost_found.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_lost_found.result index 2d5705c6e35..9272c8a5b1a 100644 --- a/mysql-test/suite/galera/r/galera_sst_mariabackup_lost_found.result +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_lost_found.result @@ -13,7 +13,6 @@ SELECT * FROM `lost+found`.t1; id 1 2 -CREATE DATABASE `#mysql50#not_lost+found`; USE `#mysql50#not_lost+found`; CREATE TABLE t1(id INT); INSERT INTO t1 VALUES (1), (2); diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_lost_found.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_lost_found.test index e8dcbd849d8..e266e674dc9 100644 --- a/mysql-test/suite/galera/t/galera_sst_mariabackup_lost_found.test +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_lost_found.test @@ -25,7 +25,8 @@ CREATE TABLE t1(id INT); INSERT INTO t1 VALUES (1), (2); SELECT * FROM `lost+found`.t1; -CREATE DATABASE `#mysql50#not_lost+found`; +--let $MYSQLD_DATADIR= `select @@datadir` +--mkdir $MYSQLD_DATADIR/not_lost+found USE `#mysql50#not_lost+found`; CREATE TABLE t1(id INT); INSERT INTO t1 VALUES (1), (2); From e3d62d4e78fd4941cef3f5053e6a50d0b32d740e Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 5 Aug 2026 20:57:25 +0200 Subject: [PATCH 076/150] MDEV-40629 environment injection via wsrep bootstrap in the service file * don't create mariadb-wsrep-new-cluster in the mariadbd-writable path, the server should not be able to poison the environment with OUTFILE. Create it in /run * As in /run it must be deleted by root, let galera_new_cluster delete it, not the service * wsrep-start-position cannot be created by root, so avoid a file for it at all Assisted-By: Claude:claude-5-opus --- cmake/install_layout.cmake | 16 +++++++++++----- scripts/galera_new_cluster.sh | 12 +++++++++--- support-files/CMakeLists.txt | 5 +---- support-files/mariadb.service.in | 31 +++++++++++++------------------ support-files/tmpfiles.conf.in | 4 ---- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/cmake/install_layout.cmake b/cmake/install_layout.cmake index 9410bc7a48a..1ebf5013730 100644 --- a/cmake/install_layout.cmake +++ b/cmake/install_layout.cmake @@ -64,6 +64,9 @@ # # - INSTALL_UNIX_ADDRDIR (path to mysql.sock) # +# - INSTALL_RUNDIR (runtime state files that mariadbd must not write, +# e.g. what systemd reads as EnvironmentFile) +# IF(NOT INSTALL_LAYOUT) IF(DEB) @@ -163,7 +166,7 @@ SET(INSTALL_UNIX_ADDRDIR_RPM "${INSTALL_MYSQLDATADIR_RPM}/mysql.sock" SET(INSTALL_SYSTEMD_UNITDIR_RPM "/usr/lib/systemd/system") SET(INSTALL_SYSTEMD_SYSUSERSDIR_RPM "/usr/lib/sysusers.d") SET(INSTALL_SYSTEMD_TMPFILESDIR_RPM "/usr/lib/tmpfiles.d") -SET(INSTALL_RUNDATADIR_RPM "/run/mariadb") +SET(INSTALL_RUNDIR_RPM "/run") SET(INSTALL_PAMDIR_RPM "/${INSTALL_LIBDIR_RPM}/security") SET(INSTALL_PAMDATADIR_RPM "/etc/security") @@ -194,8 +197,8 @@ SET(INSTALL_SUPPORTFILESDIR_DEB "share/mysql") # SET(INSTALL_MYSQLDATADIR_DEB "/var/lib/mysql") -SET(INSTALL_RUNDATADIR_DEB "/run/mysqld") -SET(INSTALL_UNIX_ADDRDIR_DEB "${INSTALL_RUNDATADIR_DEB}/mysqld.sock") +SET(INSTALL_RUNDIR_DEB "/run") +SET(INSTALL_UNIX_ADDRDIR_DEB "${INSTALL_RUNDIR_DEB}/mysqld/mysqld.sock") SET(INSTALL_SYSTEMD_UNITDIR_DEB "/lib/systemd/system") SET(INSTALL_SYSTEMD_SYSUSERSDIR_DEB "/usr/lib/sysusers.d") SET(INSTALL_SYSTEMD_TMPFILESDIR_DEB "/usr/lib/tmpfiles.d") @@ -259,7 +262,10 @@ IF(NOT MYSQL_UNIX_ADDR) SET(MYSQL_UNIX_ADDR ${INSTALL_UNIX_ADDRDIR}) ENDIF() -IF(NOT INSTALL_RUNDATADIR) +IF(NOT INSTALL_RUNDIR) + # No /run in a tarball installation - fall back to the socket directory. + # Note that mariadbd can write there, so the isolation that /run gives to + # packages (see EnvironmentFile= in mariadb.service) is not achieved. get_filename_component(MYSQL_UNIX_DIR ${MYSQL_UNIX_ADDR} DIRECTORY) - SET(INSTALL_RUNDATADIR "${MYSQL_UNIX_DIR}" CACHE FILEPATH "Rundata installation directory" ${FORCE}) + SET(INSTALL_RUNDIR "${MYSQL_UNIX_DIR}" CACHE FILEPATH "Runtime state directory" ${FORCE}) ENDIF() diff --git a/scripts/galera_new_cluster.sh b/scripts/galera_new_cluster.sh index b0aef8e05d2..056f0984aa1 100755 --- a/scripts/galera_new_cluster.sh +++ b/scripts/galera_new_cluster.sh @@ -21,11 +21,17 @@ EOF exit 0 fi -echo _WSREP_NEW_CLUSTER='--wsrep-new-cluster' > "@INSTALL_RUNDATADIR@/wsrep-new-cluster" && \ +# INSTALL_RUNDIR, not the socket directory - the latter is writable by +# mariadbd, which could then inject arbitrary variables into the service +# environment. Removing the file here and not in the service file, +# because that runs as mysql and cannot unlink in /run. +new_cluster=@INSTALL_RUNDIR@/mariadb-wsrep-new-cluster +trap 'rm -f "$new_cluster"' EXIT +trap 'exit 1' HUP INT TERM + +echo _WSREP_NEW_CLUSTER='--wsrep-new-cluster' > "$new_cluster" && \ systemctl restart mariadb.service extcode=$? -rm -f "@INSTALL_RUNDATADIR@/wsrep-new-cluster" - exit $extcode diff --git a/support-files/CMakeLists.txt b/support-files/CMakeLists.txt index 86cfc06e8f0..dd3cfe8cd15 100644 --- a/support-files/CMakeLists.txt +++ b/support-files/CMakeLists.txt @@ -198,11 +198,8 @@ IF(UNIX AND NOT WITHOUT_SERVER) ENDIF() IF((HAVE_SYSTEMD OR INSTALL_SYSTEMD_TMPUSERS) AND INSTALL_SYSTEMD_TMPFILESDIR) - IF(MYSQL_DATADIR STREQUAL INSTALL_RUNDATADIR) - SET(DISABLE_RUNDATADIR "#") - ENDIF() get_filename_component(MYSQL_UNIX_ADDRDIR ${MYSQL_UNIX_ADDR} DIRECTORY) - IF(MYSQL_UNIX_ADDRDIR STREQUAL MYSQL_DATADIR OR MYSQL_UNIX_ADDRDIR STREQUAL INSTALL_RUNDATADIR) + IF(MYSQL_UNIX_ADDRDIR STREQUAL MYSQL_DATADIR OR MYSQL_UNIX_ADDRDIR STREQUAL INSTALL_RUNDIR) SET(DISABLE_MYSQL_UNIX_ADDRDIR "#") ENDIF() diff --git a/support-files/mariadb.service.in b/support-files/mariadb.service.in index d832589dee5..22cd8ac49f4 100644 --- a/support-files/mariadb.service.in +++ b/support-files/mariadb.service.in @@ -66,21 +66,9 @@ ProtectSystem=full # Prevent accessing /home, /root and /run/user ProtectHome=true -# Use an environment file to pass variable _WSREP_NEW_CLUSTER -EnvironmentFile=-@INSTALL_RUNDATADIR@/wsrep-new-cluster - -# Use an environment file to pass variable _WSREP_START_POSITION -EnvironmentFile=-@INSTALL_RUNDATADIR@/wsrep-start-position - -# Perform automatic wsrep recovery. When server is started without wsrep, -# galera_recovery simply returns an empty string. In any case, however, -# the script is not expected to return with a non-zero status. -# It is always safe to remove @INSTALL_RUNDATADIR@/wsrep-start-position -# environment file. -# Do not panic if galera_recovery script is not available. (MDEV-10538) -ExecStartPre=/bin/sh -c "[ ! -e @bindir@/galera_recovery ] && VAR= || \ - VAR=`@bindir@/galera_recovery`; [ $? -eq 0 ] \ - && echo _WSREP_START_POSITION=$VAR > @INSTALL_RUNDATADIR@/wsrep-start-position || exit 1" +# Use an environment file to pass variable _WSREP_NEW_CLUSTER. +# Must not be writable by mysql user. +EnvironmentFile=-@INSTALL_RUNDIR@/mariadb-wsrep-new-cluster # Needed to create system tables etc. # ExecStartPre=@scriptdir@/mysql_install_db -u mysql @@ -90,10 +78,17 @@ ExecStartPre=/bin/sh -c "[ ! -e @bindir@/galera_recovery ] && VAR= || \ # Use the [Service] section and Environment="MYSQLD_OPTS=...". # This isn't a replacement for my.cnf. # _WSREP_NEW_CLUSTER is for the exclusive use of the script galera_new_cluster -ExecStart=@sbindir@/mariadbd $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION +# +# Perform automatic wsrep recovery. When server is started without wsrep, +# galera_recovery simply returns an empty string. In any case, however, it is +# not expected to return a non-zero status. +ExecStart=/bin/sh -c "set -f; [ ! -e @bindir@/galera_recovery ] && VAR= || \ + VAR=`@bindir@/galera_recovery`; [ $? -eq 0 ] || exit 1; \ + exec @sbindir@/mariadbd $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $VAR" -# Unset _WSREP_START_POSITION/_WSREP_NEW_CLUSTER environment variable. -ExecStartPost=/bin/rm -f @INSTALL_RUNDATADIR@/wsrep-start-position @INSTALL_RUNDATADIR@/wsrep-new-cluster +# have to set the name explicitly now, otherwise it'll be the +# first name from ExecStart +SyslogIdentifier=mariadbd @SYSTEMD_EXECSTARTPOST@ diff --git a/support-files/tmpfiles.conf.in b/support-files/tmpfiles.conf.in index 6a2a28b59a3..e41585c08a8 100644 --- a/support-files/tmpfiles.conf.in +++ b/support-files/tmpfiles.conf.in @@ -1,9 +1,6 @@ # This is the directory where the unix socket # of MariaDB may be created. # -# Under Galera this is where an envfile of -# the Galera start position is stored. -# # Other temporary directories can be created here like: # * tmpdir # * innodb_tmpdir @@ -12,5 +9,4 @@ # etc. # It shouldn't be used for datadir which is why it # may be disabled. -@DISABLE_RUNDATADIR@d @INSTALL_RUNDATADIR@ 0755 @MYSQLD_USER@ @MYSQLD_USER@ - @DISABLE_MYSQL_UNIX_ADDRDIR@d @MYSQL_UNIX_ADDRDIR@ 0755 @MYSQLD_USER@ root - From b82153efed4c74085e70e52b30d952dd7881d8fa Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 8 Jul 2026 13:06:04 -0400 Subject: [PATCH 077/150] Clarify NULL handling comment in next_min() --- sql/opt_range.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index b042445a7aa..2515d281841 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -15797,11 +15797,15 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_min() } /* + The reason that next_min has more complexity than next_max() is + for NULLs handling. In MariaDB, NULLs appear first in a result + and by default sort as less-than every other value. + If the min/max argument field is NULL, skip subsequent rows in the same group with NULL in it. Notice that: - - if the first row in a group doesn't have a NULL in the field, no row - in the same group has (because NULL < any other value), - - min_max_arg_part->field->ptr points to some place in 'record'. + - if the first row in a group doesn't have a NULL in the field, no row + in the same group has (because NULL < any other value), + - min_max_arg_part->field->ptr points to some place in 'record'. */ if (min_max_arg_part && min_max_arg_part->field->is_null()) { From 65d4e16e79f094ae5b9fc884a9ac57dc1c6e4854 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 17 Jun 2026 11:25:22 -0400 Subject: [PATCH 078/150] MDEV-25964: Unexpected bypass of lock When an uncommitted transaction inserts rows into a table and another statement locks rows in the same table (SELECT ... FOR UPDATE) while computing a MIN or MAX, then: 1. In a Debug build, the server aborts on an assertion 2. In a Release build, the server returns wrong results These errors occur because, while reading a group of rows for computing a MAX, the transaction timeout error was swallowed. Under the scenario described above and captured in the new test at this commit, QUICK_GROUP_MIN_MAX_SELECT::next_max() emits a lock timeout error during QUICK_GROUP_MIN_MAX_SELECT::get_next() but the error was suppressed if we computed a MIN. The InnoDB storage engine has an unwritten convention that after it has returned a fatal error (which is any error except HA_ERR_END_OF_FILE or HA_ERR_KEY_NOT_FOUND), then the SQL layer should not try to make any further reads. This is because InnoDB might have rolled back the current transaction already. So in the case of an error, return immediately from QUICK_GROUP_MIN_MAX_SELECT::get_next(). --- mysql-test/main/group_min_max_innodb.result | 54 +++++++++++++ mysql-test/main/group_min_max_innodb.test | 90 +++++++++++++++++++++ sql/opt_range.cc | 47 ++++++++++- 3 files changed, 190 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/group_min_max_innodb.result b/mysql-test/main/group_min_max_innodb.result index 27656374aee..f9cccf60f6b 100644 --- a/mysql-test/main/group_min_max_innodb.result +++ b/mysql-test/main/group_min_max_innodb.result @@ -440,3 +440,57 @@ SET GLOBAL innodb_lru_scan_depth= @lru_depth.save; set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; +# +# Begin 10.11 tests +# +CREATE TABLE t1 (pk INT, a INT, b INT, PRIMARY KEY (pk), KEY (a,b)) ENGINE=InnoDB; +INSERT INTO t1 (pk,a) VALUES (1,11),(2,12),(3,13),(4,14); +connect con1,localhost,root,,; +DELETE FROM t1 WHERE pk <= 3; +connection default; +INSERT INTO t1 (pk) VALUES (5); +connection con1; +START TRANSACTION; +INSERT INTO t1 (pk) VALUES (6); +connection default; +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +EXPLAIN SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 10 NULL # Using index for group-by +SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; +connection con1; +COMMIT; +DROP TABLE t1; +disconnect con1; +connection default; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT, +KEY (a,b,c)) ENGINE=InnoDB; +INSERT INTO t1 (a,b,c) VALUES +(1,1,10),(1,1,11),(1,2,20),(1,2,21), +(2,1,10),(2,1,11),(2,2,20),(2,2,21); +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +connect con1,localhost,root,,; +START TRANSACTION; +INSERT INTO t1 (a,b,c) VALUES (1,2,0); +connection default; +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +EXPLAIN SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 15 NULL # Using where; Using index for group-by +SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; +connection con1; +COMMIT; +DROP TABLE t1; +disconnect con1; +connection default; +# +# End 10.11 tests +# diff --git a/mysql-test/main/group_min_max_innodb.test b/mysql-test/main/group_min_max_innodb.test index 33a3a8888a5..37270b7a204 100644 --- a/mysql-test/main/group_min_max_innodb.test +++ b/mysql-test/main/group_min_max_innodb.test @@ -323,3 +323,93 @@ SET GLOBAL innodb_lru_scan_depth= @lru_depth.save; set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; + +--echo # +--echo # Begin 10.11 tests +--echo # + +# Default client connection to the database. +CREATE TABLE t1 (pk INT, a INT, b INT, PRIMARY KEY (pk), KEY (a,b)) ENGINE=InnoDB; +INSERT INTO t1 (pk,a) VALUES (1,11),(2,12),(3,13),(4,14); + +# Start a new, separate client connection to the database called 'con1'. +--connect (con1,localhost,root,,) +--send +DELETE FROM t1 WHERE pk <= 3; + +# On the default connection, insert a value into the table. +--connection default +INSERT INTO t1 (pk) VALUES (5); + +# On 'con1', insert a value within a transaction and hold the transaction while +# on the default connection try to compute an aggregate. +--connection con1 +--reap + +START TRANSACTION; +INSERT INTO t1 (pk) VALUES (6); + +--connection default +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +# SELECT ... FOR UPDATE locks the rows for write operations and prevents other transactions +# from modifying or reading the selected rows until the transaction ends. +# This should lead to the error, not a crash. +--replace_column 9 # +EXPLAIN SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +--error ER_LOCK_WAIT_TIMEOUT +SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; + +# Cleanup, commit the 'con1' transaction. +--connection con1 +COMMIT; +DROP TABLE t1; +--disconnect con1 + +# This scenario passes with or without the patch. The query requests only a +# MIN, so the max branch never runs. The scenario is kept for coverage, to +# execute the early return, which the patch adds in the min branch for symmetry +# with the max branch and to honor the convention that no further reads follow +# a fatal handler error. +--connection default +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT, + KEY (a,b,c)) ENGINE=InnoDB; + +# Two groups over key part a. Each group has rows with b=1 and rows with +# b=2, with enough rows overall that the optimizer chooses the loose +# index scan ("Using index for group-by") over a full index scan. +INSERT INTO t1 (a,b,c) VALUES + (1,1,10),(1,1,11),(1,2,20),(1,2,21), + (2,1,10),(2,1,11),(2,2,20),(2,2,21); +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; + +--connect (con1,localhost,root,,) +START TRANSACTION; +# This uncommitted row becomes the first row of the a=1, b=2 prefix because its +# c is smaller than every committed c in that subgroup. The MIN read seeks +# exactly this position, so it waits for con1. +INSERT INTO t1 (a,b,c) VALUES (1,2,0); + +--connection default +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +# The MIN read inside the loose index scan lands on the row locked by con1. +--replace_column 9 # +EXPLAIN SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +--error ER_LOCK_WAIT_TIMEOUT +SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; + +# Cleanup, commit the 'con1' transaction. +--connection con1 +COMMIT; +DROP TABLE t1; +--disconnect con1 +--connection default + +--echo # +--echo # End 10.11 tests +--echo # diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 2515d281841..7a5b713c35a 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -15718,15 +15718,51 @@ int QUICK_GROUP_MIN_MAX_SELECT::get_next() min_res= next_min(); if (min_res == 0) update_min_result(); + else + { + /* + If next_min returns an unexpected handler error (HA_ERR_...), + then that error will be propagated to the caller of this + method by returning immediately here. + */ + if (min_res != HA_ERR_END_OF_FILE && min_res != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(min_res); // Unrecoverable error. + } } /* If there is no MIN in the group, there is no MAX either. */ if ((have_max && !have_min) || (have_max && have_min && (min_res == 0))) { + /* + next_min() and next_max() are not symmetrical with respect to + how they access the underlying table. next_min() may read the + index when (key_infix_len > 0) but next_max() makes no such check. + Hence the lock wait timeout may be seen by next_max() but not by + next_min() because it reads blindly. + */ max_res= next_max(); if (max_res == 0) update_max_result(); - /* If a MIN was found, a MAX must have been found as well. */ + else + { + /* + If next_max returns an unexpected handler error (HA_ERR_...), + then that error will be propagated to the caller of this + method by returning immediately here. + */ + if (max_res != HA_ERR_END_OF_FILE && max_res != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(max_res); // Unrecoverable error. + } + + /* + If we're computing both MIN and MAX (have_min && have_max) then: + - Since execution has reached here, we have a record with value for + MIN (min_res==0). + - This means we must also have a record for MAX (max_res==0). It can + be the same record that we've found for MIN, or a different one. + We could also have encountered an unrecoverable error while reading + the MAX record but that has been handled above. + */ DBUG_ASSERT((have_max && !have_min) || (have_max && have_min && (max_res == 0))); } @@ -15740,6 +15776,15 @@ int QUICK_GROUP_MIN_MAX_SELECT::get_next() make_prev_keypart_map(real_key_parts), HA_READ_KEY_EXACT); + /* + Set the result of checking this GROUP BY group. + 1. If we're computing a MIN, take min_res. MAX was either not computed + at all or it has been computed successully. Fatal errors have + already been checked. + 2. Otherwise, if we're computing a MAX only, take max_res. + 3. Otherwise, we're just enumerating GROUP BY groups and the 'result' + variable already has the outcome of the check. + */ result= have_min ? min_res : have_max ? max_res : result; } while (result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE); From 940b8daa5132a8e8ba661f499659eb9ddf4722ed Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 6 Aug 2026 20:36:41 +0200 Subject: [PATCH 079/150] cleanup: encryption.filekeys_encfile_badfile combine all tests for wrong FILE: values into one test add a test for a wrong key. --- .../r/filekeys_encfile_badfile.result | 26 ++++++++++++-- .../r/filekeys_secret_too_long.result | 10 ------ .../encryption/t/filekeys-data-too-long.key | 4 --- .../encryption/t/filekeys_encfile_badfile.opt | 2 +- .../t/filekeys_encfile_badfile.test | 36 +++++++++++++++++-- .../encryption/t/filekeys_secret_too_long.opt | 3 -- .../t/filekeys_secret_too_long.test | 4 --- 7 files changed, 59 insertions(+), 26 deletions(-) delete mode 100644 mysql-test/suite/encryption/r/filekeys_secret_too_long.result delete mode 100644 mysql-test/suite/encryption/t/filekeys-data-too-long.key delete mode 100644 mysql-test/suite/encryption/t/filekeys_secret_too_long.opt delete mode 100644 mysql-test/suite/encryption/t/filekeys_secret_too_long.test diff --git a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result index 7e244c2c381..7adc13eb85e 100644 --- a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result +++ b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result @@ -1,7 +1,29 @@ -call mtr.add_suppression("File 'bad' not found"); +call mtr.add_suppression("File 'bad.key' not found"); call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); -FOUND 1 /File 'bad' not found/ in mysqld.1.err +FOUND 1 /File 'bad.key' not found/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +install soname 'file_key_management'; +ERROR HY000: Cannot decrypt MTR_SUITE_DIR/t/filekeys-data.enc. Wrong key? +call mtr.add_suppression("Cannot decrypt .*filekeys-data.enc. Wrong key"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /Cannot decrypt .*filekeys-data.enc. Wrong key/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +install soname 'file_key_management'; +ERROR HY000: Cannot read bad.key, the filekey is too long, max secret size is 256B +call mtr.add_suppression("the filekey is too long"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /the filekey is too long/ in mysqld.1.err create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") select plugin_status from information_schema.plugins diff --git a/mysql-test/suite/encryption/r/filekeys_secret_too_long.result b/mysql-test/suite/encryption/r/filekeys_secret_too_long.result deleted file mode 100644 index bd11e8d925e..00000000000 --- a/mysql-test/suite/encryption/r/filekeys_secret_too_long.result +++ /dev/null @@ -1,10 +0,0 @@ -call mtr.add_suppression("the filekey is too long"); -call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); -call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); -FOUND 1 /the filekey is too long/ in mysqld.1.err -create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; -ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") -select plugin_status from information_schema.plugins -where plugin_name = 'file_key_management'; -plugin_status -# Test checks if opening an too large secret does not crash the server. diff --git a/mysql-test/suite/encryption/t/filekeys-data-too-long.key b/mysql-test/suite/encryption/t/filekeys-data-too-long.key deleted file mode 100644 index ba1624fb324..00000000000 --- a/mysql-test/suite/encryption/t/filekeys-data-too-long.key +++ /dev/null @@ -1,4 +0,0 @@ -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret - diff --git a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt index 374a41395b8..713ce507eac 100644 --- a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt +++ b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt @@ -1,2 +1,2 @@ ---loose-file-key-management-filekey=FILE:bad +--loose-file-key-management-filekey=FILE:bad.key --loose-file-key-management-filename=$MTR_SUITE_DIR/t/filekeys-data.enc diff --git a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test index e0b07ac62b1..453d86a2e23 100644 --- a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test +++ b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test @@ -1,2 +1,34 @@ -let SEARCH_PATTERN=File 'bad' not found; -source filekeys_badtest.inc; +# +# first test: no file +# +--let SEARCH_PATTERN=File 'bad.key' not found +--source filekeys_badtest.inc + +# +# second test: wrong key +# +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key +wrong key +EOF +--replace_result $MTR_SUITE_DIR MTR_SUITE_DIR +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=Cannot decrypt .*filekeys-data.enc. Wrong key +--source filekeys_badtest.inc +--remove_file $datadir/bad.key + +# +# third test: too long key +# +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +EOF +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=the filekey is too long +--source filekeys_badtest.inc +--remove_file $datadir/bad.key diff --git a/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt b/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt deleted file mode 100644 index c3f95019f2a..00000000000 --- a/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt +++ /dev/null @@ -1,3 +0,0 @@ ---loose-file-key-management-filekey=FILE:$MTR_SUITE_DIR/t/filekeys-data-too-long.key ---loose-file-key-management-filename=$MTR_SUITE_DIR/t/filekeys-data.enc - diff --git a/mysql-test/suite/encryption/t/filekeys_secret_too_long.test b/mysql-test/suite/encryption/t/filekeys_secret_too_long.test deleted file mode 100644 index 0032e94de37..00000000000 --- a/mysql-test/suite/encryption/t/filekeys_secret_too_long.test +++ /dev/null @@ -1,4 +0,0 @@ -let SEARCH_PATTERN=the filekey is too long; -source filekeys_badtest.inc; - ---echo # Test checks if opening an too large secret does not crash the server. From 69ce9cbcc2d8443777895ea808aba62dd5a568a2 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 6 Aug 2026 20:46:00 +0200 Subject: [PATCH 080/150] MDEV-40658 file_key_management crash on empty FILE: file --- .../r/filekeys_encfile_badfile.result | 15 +++++++++++++++ .../encryption/t/filekeys_encfile_badfile.test | 17 +++++++++++++++++ plugin/file_key_management/parser.cc | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result index 7adc13eb85e..5dcf1857c44 100644 --- a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result +++ b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result @@ -29,3 +29,18 @@ ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") select plugin_status from information_schema.plugins where plugin_name = 'file_key_management'; plugin_status +# +# MDEV-40658 file_key_management crash on empty FILE: file +# +install soname 'file_key_management'; +ERROR HY000: Cannot decrypt MTR_SUITE_DIR/t/filekeys-data.enc. Wrong key? +call mtr.add_suppression("Cannot decrypt .*filekeys-data.enc. Wrong key"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /Cannot decrypt .*filekeys-data.enc. Wrong key/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +# End of 10.6 tests diff --git a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test index 453d86a2e23..6af2e1c2f01 100644 --- a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test +++ b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test @@ -32,3 +32,20 @@ install soname 'file_key_management'; --let SEARCH_PATTERN=the filekey is too long --source filekeys_badtest.inc --remove_file $datadir/bad.key + +--echo # +--echo # MDEV-40658 file_key_management crash on empty FILE: file +--echo # +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key + + +EOF +--replace_result $MTR_SUITE_DIR MTR_SUITE_DIR +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=Cannot decrypt .*filekeys-data.enc. Wrong key +--source filekeys_badtest.inc +--remove_file $datadir/bad.key + +--echo # End of 10.6 tests diff --git a/plugin/file_key_management/parser.cc b/plugin/file_key_management/parser.cc index a7b39f0ad16..804cded05c3 100644 --- a/plugin/file_key_management/parser.cc +++ b/plugin/file_key_management/parser.cc @@ -175,7 +175,7 @@ bool Parser::read_filekey(const char *filekey, char *secret) } close(f); - while (secret[len - 1] == '\r' || secret[len - 1] == '\n') len--; + while (len && (secret[len - 1] == '\r' || secret[len - 1] == '\n')) len--; if (len > MAX_SECRET_SIZE) { my_printf_error(EE_READ, From 040824c33ac363f250ae1c24cda7eadabd4cc2d6 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Wed, 13 May 2026 22:55:45 +0000 Subject: [PATCH 081/150] MDEV-39459 Fix bad sync pattern for chain replication MTR tests In chain replication (1->2->3), syncing only server_3 after save_master_gtid on server_1 does not guarantee server_2 has committed, because server_2's binlog dump thread can send events to server_3 before commit_ordered() completes on server_2. Fix affected rpl tests by syncing server_2 before server_3, and update result files accordingly. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../rpl/include/rpl_row_img_sequence.inc | 9 + .../rpl_xa_empty_transaction_test_case.inc | 8 + .../rpl/r/rpl_row_img_sequence_full.result | 72 ++++++++ .../r/rpl_row_img_sequence_full_nodup.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_min.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_noblob.result | 72 ++++++++ .../rpl/r/rpl_start_alter_chain_basic.result | 3 + .../rpl/r/rpl_xa_empty_transaction.result | 171 ++++++++++++++++++ .../rpl/t/rpl_start_alter_chain_basic.test | 3 + .../suite/rpl/t/rpl_xa_empty_transaction.test | 3 + 10 files changed, 485 insertions(+) diff --git a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc index d9ed6f278b9..ebb811b1f3b 100644 --- a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc +++ b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc @@ -28,6 +28,9 @@ SELECT SETVAL(s1, 10); --source include/save_master_gtid.inc --echo # Validate SETVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -60,6 +63,9 @@ SELECT NEXTVAL(s1); --source include/save_master_gtid.inc --echo # Validate NEXTVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -88,5 +94,8 @@ FLUSH LOGS; --connection server_1 DROP TABLE s1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc index 6368336b8e3..aa67fab9d40 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc @@ -34,6 +34,10 @@ CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc @@ -127,5 +131,9 @@ FLUSH LOGS; DROP TABLE ti,tm; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result index 2303911637f..4dd186db922 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result index c6afb2ecb7e..068e0f5df12 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result index eaf770f245c..b387742d549 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result @@ -54,6 +54,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -91,6 +94,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -125,6 +131,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -145,6 +154,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -182,6 +194,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -216,6 +231,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -236,6 +254,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -273,6 +294,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -307,6 +331,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -327,6 +354,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -364,6 +394,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -398,6 +431,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -418,6 +454,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -455,6 +494,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -489,6 +531,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -509,6 +554,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -546,6 +594,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -580,6 +631,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -600,6 +654,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -637,6 +694,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -671,6 +731,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -691,6 +754,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -728,6 +794,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -762,6 +831,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result index 496bd8dc6b8..8ca8ea3e3f8 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result index efe4cfa0a69..9e92b921bf8 100644 --- a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result +++ b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result @@ -66,6 +66,9 @@ domain_id seq_no 0 12 connection server_1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 917041c148d..37b0b06d222 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -13,6 +13,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -37,6 +40,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -46,6 +52,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -77,6 +86,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -86,6 +98,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -110,6 +125,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -119,6 +137,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -150,6 +171,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -162,6 +186,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -188,6 +215,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -197,6 +227,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -230,6 +263,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -239,6 +275,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -265,6 +304,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -274,6 +316,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -307,6 +352,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -323,6 +371,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -349,6 +400,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -358,6 +412,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -391,6 +448,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -400,6 +460,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -428,6 +491,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -437,6 +503,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -470,6 +539,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -485,6 +557,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -513,6 +588,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -522,6 +600,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -557,6 +638,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -566,6 +650,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -594,6 +681,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -603,6 +693,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -638,6 +731,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -655,6 +751,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -683,6 +782,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -692,6 +794,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -727,6 +832,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -736,6 +844,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -766,6 +877,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -775,6 +889,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -810,6 +927,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -830,6 +950,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -858,6 +981,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -867,6 +993,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -902,6 +1031,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -911,6 +1043,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -941,6 +1076,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -950,6 +1088,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -985,6 +1126,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1004,6 +1148,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1032,6 +1179,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1041,6 +1191,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1076,6 +1229,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1085,6 +1241,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1115,6 +1274,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1124,6 +1286,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1159,6 +1324,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1173,6 +1341,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/stop_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test index efb667ad6fa..7918e517b4d 100644 --- a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test +++ b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test @@ -41,6 +41,9 @@ select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; --connection server_1 --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index 62ac69cef22..a33262bc47a 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -187,6 +187,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --source include/stop_slave.inc From 90bea36bfad065f860a30bfa23fecef638a55335 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Wed, 13 May 2026 22:55:45 +0000 Subject: [PATCH 082/150] MDEV-39459 Fix bad sync pattern for chain replication MTR tests In chain replication (1->2->3), syncing only server_3 after save_master_gtid on server_1 does not guarantee server_2 has committed, because server_2's binlog dump thread can send events to server_3 before commit_ordered() completes on server_2. Fix affected rpl tests by syncing server_2 before server_3, and update result files accordingly. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../rpl/include/rpl_row_img_sequence.inc | 9 + .../rpl_xa_empty_transaction_test_case.inc | 8 + .../rpl/r/rpl_row_img_sequence_full.result | 72 ++++++++ .../r/rpl_row_img_sequence_full_nodup.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_min.result | 72 ++++++++ .../rpl/r/rpl_row_img_sequence_noblob.result | 72 ++++++++ .../rpl/r/rpl_start_alter_chain_basic.result | 3 + .../rpl/r/rpl_xa_empty_transaction.result | 171 ++++++++++++++++++ .../rpl/t/rpl_start_alter_chain_basic.test | 3 + .../suite/rpl/t/rpl_xa_empty_transaction.test | 3 + 10 files changed, 485 insertions(+) diff --git a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc index d9ed6f278b9..ebb811b1f3b 100644 --- a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc +++ b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc @@ -28,6 +28,9 @@ SELECT SETVAL(s1, 10); --source include/save_master_gtid.inc --echo # Validate SETVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -60,6 +63,9 @@ SELECT NEXTVAL(s1); --source include/save_master_gtid.inc --echo # Validate NEXTVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -88,5 +94,8 @@ FLUSH LOGS; --connection server_1 DROP TABLE s1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc index 6368336b8e3..aa67fab9d40 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc @@ -34,6 +34,10 @@ CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc @@ -127,5 +131,9 @@ FLUSH LOGS; DROP TABLE ti,tm; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result index 2303911637f..4dd186db922 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result index c6afb2ecb7e..068e0f5df12 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result index eaf770f245c..b387742d549 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result @@ -54,6 +54,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -91,6 +94,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -125,6 +131,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -145,6 +154,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -182,6 +194,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -216,6 +231,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -236,6 +254,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -273,6 +294,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -307,6 +331,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -327,6 +354,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -364,6 +394,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -398,6 +431,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -418,6 +454,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -455,6 +494,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -489,6 +531,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -509,6 +554,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -546,6 +594,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -580,6 +631,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -600,6 +654,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -637,6 +694,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -671,6 +731,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -691,6 +754,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -728,6 +794,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -762,6 +831,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result index 496bd8dc6b8..8ca8ea3e3f8 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result index a62b02b2f44..bdb4c2ae123 100644 --- a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result +++ b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result @@ -66,6 +66,9 @@ domain_id seq_no 0 12 connection server_1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 917041c148d..37b0b06d222 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -13,6 +13,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -37,6 +40,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -46,6 +52,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -77,6 +86,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -86,6 +98,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -110,6 +125,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -119,6 +137,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -150,6 +171,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -162,6 +186,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -188,6 +215,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -197,6 +227,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -230,6 +263,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -239,6 +275,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -265,6 +304,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -274,6 +316,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -307,6 +352,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -323,6 +371,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -349,6 +400,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -358,6 +412,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -391,6 +448,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -400,6 +460,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -428,6 +491,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -437,6 +503,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -470,6 +539,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -485,6 +557,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -513,6 +588,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -522,6 +600,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -557,6 +638,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -566,6 +650,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -594,6 +681,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -603,6 +693,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -638,6 +731,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -655,6 +751,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -683,6 +782,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -692,6 +794,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -727,6 +832,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -736,6 +844,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -766,6 +877,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -775,6 +889,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -810,6 +927,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -830,6 +950,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -858,6 +981,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -867,6 +993,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -902,6 +1031,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -911,6 +1043,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -941,6 +1076,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -950,6 +1088,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -985,6 +1126,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1004,6 +1148,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1032,6 +1179,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1041,6 +1191,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1076,6 +1229,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1085,6 +1241,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1115,6 +1274,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1124,6 +1286,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1159,6 +1324,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1173,6 +1341,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/stop_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test index efb667ad6fa..7918e517b4d 100644 --- a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test +++ b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test @@ -41,6 +41,9 @@ select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; --connection server_1 --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index 62ac69cef22..a33262bc47a 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -187,6 +187,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --source include/stop_slave.inc From ec4853f72f093cdfcacec4177ee0a10fcb369926 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 7 Aug 2026 12:17:53 +0200 Subject: [PATCH 083/150] MDEV-39689 update the test --- mysql-test/suite/galera/r/mdev-31651.result | 2 +- mysql-test/suite/galera/t/mdev-31651.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/galera/r/mdev-31651.result b/mysql-test/suite/galera/r/mdev-31651.result index 15c56a71344..0e573db9dfa 100644 --- a/mysql-test/suite/galera/r/mdev-31651.result +++ b/mysql-test/suite/galera/r/mdev-31651.result @@ -5,7 +5,7 @@ call mtr.add_suppression("BINLOG_BASE64_EVENT: Could not execute Write_rows_v1 e CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b INT, KEY(b)) engine=innodb; BINLOG 'AMqaOw8BAAAAdAAAAHgAAAAAAAQANS42LjM0LTc5LjEtZGVidWctbG9nAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAEzgNAAgAEgAEBAQEEgAAXAAEGggAAAAICAgCAAAACgoKGRkAAYVx w2w='; BINLOG 'wlZOTxMBAAAAKgAAADwCAAAAACkAAAAAAAEABHRlc3QAAnQxAAIDAwAC wlZOTxcBAAAAJgAAAGICAAAAACkAAAAAAAEAAv/8AgAAAAgAAAA='; -ERROR HY000: Got error 171 "The event was corrupt, leading to illegal data being read" from storage engine InnoDB +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use SELECT * FROM t1; a b DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/mdev-31651.test b/mysql-test/suite/galera/t/mdev-31651.test index ebd6ff6ac44..5cc8d07518f 100644 --- a/mysql-test/suite/galera/t/mdev-31651.test +++ b/mysql-test/suite/galera/t/mdev-31651.test @@ -4,7 +4,7 @@ call mtr.add_suppression("BINLOG_BASE64_EVENT: Could not read field"); call mtr.add_suppression("BINLOG_BASE64_EVENT: Could not execute Write_rows_v1 event on table"); CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b INT, KEY(b)) engine=innodb; BINLOG 'AMqaOw8BAAAAdAAAAHgAAAAAAAQANS42LjM0LTc5LjEtZGVidWctbG9nAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAEzgNAAgAEgAEBAQEEgAAXAAEGggAAAAICAgCAAAACgoKGRkAAYVx w2w='; ---error ER_GET_ERRNO +--error ER_SYNTAX_ERROR BINLOG 'wlZOTxMBAAAAKgAAADwCAAAAACkAAAAAAAEABHRlc3QAAnQxAAIDAwAC wlZOTxcBAAAAJgAAAGICAAAAACkAAAAAAAEAAv/8AgAAAAgAAAA='; SELECT * FROM t1; DROP TABLE t1; From 6b174bfb25be06f7efdab149183665e1a36b3dd5 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Wed, 13 May 2026 22:55:45 +0000 Subject: [PATCH 084/150] MDEV-39459 Fix bad sync pattern for chain replication MTR tests In chain replication (1->2->3), syncing only server_3 after save_master_gtid on server_1 does not guarantee server_2 has committed, because server_2's binlog dump thread can send events to server_3 before commit_ordered() completes on server_2. Fix affected rpl tests by syncing server_2 before server_3, and update result files accordingly. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../rpl/include/rpl_row_img_sequence.inc | 9 + .../rpl_xa_empty_transaction_test_case.inc | 8 + .../rpl/r/rpl_extra_col_slave_rebinlog.result | 5 + ...aster_slave_mismatched_columns_full.result | 1158 +++++++++++++++++ .../rpl/r/rpl_row_img_sequence_full.result | 72 + .../r/rpl_row_img_sequence_full_nodup.result | 72 + .../rpl/r/rpl_row_img_sequence_min.result | 72 + .../rpl/r/rpl_row_img_sequence_noblob.result | 72 + .../rpl/r/rpl_start_alter_chain_basic.result | 3 + .../rpl/r/rpl_xa_empty_transaction.result | 171 +++ .../rpl/t/rpl_extra_col_slave_rebinlog.test | 6 + ...l_master_slave_mismatched_columns_full.inc | 8 + .../rpl/t/rpl_start_alter_chain_basic.test | 3 + .../suite/rpl/t/rpl_xa_empty_transaction.test | 3 + 14 files changed, 1662 insertions(+) diff --git a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc index d9ed6f278b9..ebb811b1f3b 100644 --- a/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc +++ b/mysql-test/suite/rpl/include/rpl_row_img_sequence.inc @@ -28,6 +28,9 @@ SELECT SETVAL(s1, 10); --source include/save_master_gtid.inc --echo # Validate SETVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -60,6 +63,9 @@ SELECT NEXTVAL(s1); --source include/save_master_gtid.inc --echo # Validate NEXTVAL replicated correctly to other servers +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --let $diff_tables= server_1:test.s1,server_2:test.s1,server_3:test.s1 @@ -88,5 +94,8 @@ FLUSH LOGS; --connection server_1 DROP TABLE s1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc index 6368336b8e3..aa67fab9d40 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc @@ -34,6 +34,10 @@ CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc @@ -127,5 +131,9 @@ FLUSH LOGS; DROP TABLE ti,tm; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/r/rpl_extra_col_slave_rebinlog.result b/mysql-test/suite/rpl/r/rpl_extra_col_slave_rebinlog.result index 499256e3630..1c37bdedb1b 100644 --- a/mysql-test/suite/rpl/r/rpl_extra_col_slave_rebinlog.result +++ b/mysql-test/suite/rpl/r/rpl_extra_col_slave_rebinlog.result @@ -6,6 +6,7 @@ create table t1 (a int) engine=innodb; include/save_master_gtid.inc connection server_2; include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -21,6 +22,7 @@ insert into t1 values (1); include/save_master_gtid.inc connection server_2; include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Slave 3 t1 should match slave 2 t1 @@ -38,6 +40,9 @@ include/diff_tables.inc [server_2:test.t1, server_3:test.t1] connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_master_slave_mismatched_columns_full.result b/mysql-test/suite/rpl/r/rpl_master_slave_mismatched_columns_full.result index 023f9954282..33cf5da3ee1 100644 --- a/mysql-test/suite/rpl/r/rpl_master_slave_mismatched_columns_full.result +++ b/mysql-test/suite/rpl/r/rpl_master_slave_mismatched_columns_full.result @@ -38,6 +38,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -56,6 +59,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -80,6 +86,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -98,6 +107,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -122,6 +134,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -140,6 +155,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -165,6 +183,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -184,6 +205,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -208,6 +232,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -227,6 +254,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -251,6 +281,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -270,6 +303,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -294,6 +330,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -313,6 +352,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -338,6 +380,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -358,6 +403,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -384,6 +432,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -402,6 +453,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -426,6 +480,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -444,6 +501,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -468,6 +528,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -486,6 +549,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -511,6 +577,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -530,6 +599,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -554,6 +626,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -573,6 +648,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -597,6 +675,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -616,6 +697,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -640,6 +724,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -659,6 +746,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -684,6 +774,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -704,6 +797,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -742,6 +838,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -760,6 +859,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -784,6 +886,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -802,6 +907,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -826,6 +934,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -844,6 +955,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -869,6 +983,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -888,6 +1005,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -912,6 +1032,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -931,6 +1054,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -955,6 +1081,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -974,6 +1103,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -998,6 +1130,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1017,6 +1152,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1042,6 +1180,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1062,6 +1203,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -1088,6 +1232,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1106,6 +1253,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1130,6 +1280,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1148,6 +1301,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1172,6 +1328,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1190,6 +1349,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -1215,6 +1377,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1234,6 +1399,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1258,6 +1426,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1277,6 +1448,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1301,6 +1475,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1320,6 +1497,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1344,6 +1524,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1363,6 +1546,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1388,6 +1574,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1408,6 +1597,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1446,6 +1638,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1464,6 +1659,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1488,6 +1686,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1506,6 +1707,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1530,6 +1734,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1548,6 +1755,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -1573,6 +1783,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1592,6 +1805,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1616,6 +1832,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1635,6 +1854,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1659,6 +1881,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1678,6 +1903,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1702,6 +1930,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1721,6 +1952,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1746,6 +1980,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1766,6 +2003,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -1792,6 +2032,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1810,6 +2053,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1834,6 +2080,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1852,6 +2101,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1876,6 +2128,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1894,6 +2149,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -1919,6 +2177,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1938,6 +2199,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -1962,6 +2226,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -1981,6 +2248,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2005,6 +2275,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2024,6 +2297,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2048,6 +2324,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2067,6 +2346,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2092,6 +2374,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2112,6 +2397,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2150,6 +2438,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2168,6 +2459,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2192,6 +2486,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2210,6 +2507,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2234,6 +2534,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2252,6 +2555,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -2277,6 +2583,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2296,6 +2605,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2320,6 +2632,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2339,6 +2654,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2363,6 +2681,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2382,6 +2703,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2406,6 +2730,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2425,6 +2752,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2450,6 +2780,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2470,6 +2803,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -2496,6 +2832,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2514,6 +2853,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2538,6 +2880,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2556,6 +2901,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2580,6 +2928,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2598,6 +2949,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -2623,6 +2977,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2642,6 +2999,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2666,6 +3026,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2685,6 +3048,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2709,6 +3075,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2728,6 +3097,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2752,6 +3124,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2771,6 +3146,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2796,6 +3174,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2816,6 +3197,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2859,6 +3243,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2877,6 +3264,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2901,6 +3291,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2919,6 +3312,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -2943,6 +3339,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -2961,6 +3360,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -2986,6 +3388,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3005,6 +3410,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3029,6 +3437,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3048,6 +3459,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3072,6 +3486,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3091,6 +3508,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3115,6 +3535,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3134,6 +3557,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3159,6 +3585,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3179,6 +3608,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -3205,6 +3637,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3223,6 +3658,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3247,6 +3685,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3265,6 +3706,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3289,6 +3733,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3307,6 +3754,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -3332,6 +3782,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3351,6 +3804,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3375,6 +3831,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3394,6 +3853,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3418,6 +3880,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3437,6 +3902,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3461,6 +3929,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3480,6 +3951,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3505,6 +3979,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3525,6 +4002,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3563,6 +4043,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3581,6 +4064,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3605,6 +4091,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3623,6 +4112,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3647,6 +4139,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3665,6 +4160,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -3690,6 +4188,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3709,6 +4210,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3733,6 +4237,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3752,6 +4259,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3776,6 +4286,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3795,6 +4308,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3819,6 +4335,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3838,6 +4357,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3863,6 +4385,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3883,6 +4408,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -3909,6 +4437,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3927,6 +4458,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3951,6 +4485,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -3969,6 +4506,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -3993,6 +4533,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4011,6 +4554,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -4036,6 +4582,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4055,6 +4604,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4079,6 +4631,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4098,6 +4653,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4122,6 +4680,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4141,6 +4702,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4165,6 +4729,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4184,6 +4751,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4209,6 +4779,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4229,6 +4802,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4267,6 +4843,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4285,6 +4864,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4309,6 +4891,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4327,6 +4912,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4351,6 +4939,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4369,6 +4960,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -4394,6 +4988,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4413,6 +5010,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4437,6 +5037,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4456,6 +5059,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4480,6 +5086,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4499,6 +5108,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4523,6 +5135,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4542,6 +5157,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4567,6 +5185,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4587,6 +5208,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -4613,6 +5237,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4631,6 +5258,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4655,6 +5285,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4673,6 +5306,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4697,6 +5333,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4715,6 +5354,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -4740,6 +5382,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4759,6 +5404,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4783,6 +5431,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4802,6 +5453,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4826,6 +5480,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4845,6 +5502,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4869,6 +5529,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4888,6 +5551,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4913,6 +5579,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4933,6 +5602,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -4971,6 +5643,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -4989,6 +5664,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5013,6 +5691,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5031,6 +5712,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5055,6 +5739,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5073,6 +5760,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -5098,6 +5788,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5117,6 +5810,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5141,6 +5837,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5160,6 +5859,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5184,6 +5886,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5203,6 +5908,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5227,6 +5935,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5246,6 +5957,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5271,6 +5985,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5291,6 +6008,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -5317,6 +6037,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5335,6 +6058,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5359,6 +6085,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5377,6 +6106,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5401,6 +6133,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5419,6 +6154,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -5444,6 +6182,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5463,6 +6204,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5487,6 +6231,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5506,6 +6253,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5530,6 +6280,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5549,6 +6302,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5573,6 +6329,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5592,6 +6351,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5617,6 +6379,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5637,6 +6402,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5680,6 +6448,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5698,6 +6469,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5722,6 +6496,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5740,6 +6517,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5764,6 +6544,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5782,6 +6565,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -5807,6 +6593,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5826,6 +6615,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5850,6 +6642,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5869,6 +6664,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5893,6 +6691,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5912,6 +6713,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5936,6 +6740,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -5955,6 +6762,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -5980,6 +6790,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6000,6 +6813,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -6026,6 +6842,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6044,6 +6863,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6068,6 +6890,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6086,6 +6911,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6110,6 +6938,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6128,6 +6959,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -6153,6 +6987,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6172,6 +7009,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6196,6 +7036,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6215,6 +7058,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6239,6 +7085,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6258,6 +7107,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6282,6 +7134,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6301,6 +7156,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6326,6 +7184,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6346,6 +7207,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6384,6 +7248,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6402,6 +7269,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6426,6 +7296,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6444,6 +7317,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6468,6 +7344,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6486,6 +7365,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -6511,6 +7393,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6530,6 +7415,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6554,6 +7442,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6573,6 +7464,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6597,6 +7491,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6616,6 +7513,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6640,6 +7540,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6659,6 +7562,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6684,6 +7590,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6704,6 +7613,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -6730,6 +7642,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6748,6 +7663,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6772,6 +7690,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6790,6 +7711,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6814,6 +7738,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6832,6 +7759,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -6857,6 +7787,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6876,6 +7809,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6900,6 +7836,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6919,6 +7858,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6943,6 +7885,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -6962,6 +7907,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -6986,6 +7934,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7005,6 +7956,9 @@ NULL aaa 2000 connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7030,6 +7984,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = NULL WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7050,6 +8007,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7088,6 +8048,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7106,6 +8069,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7130,6 +8096,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7148,6 +8117,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7172,6 +8144,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7190,6 +8165,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -7215,6 +8193,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7234,6 +8215,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7258,6 +8242,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7277,6 +8264,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7301,6 +8291,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7320,6 +8313,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7344,6 +8340,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7363,6 +8362,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7388,6 +8390,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7408,6 +8413,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -7434,6 +8442,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7452,6 +8463,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7476,6 +8490,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7494,6 +8511,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7518,6 +8538,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7536,6 +8559,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -7561,6 +8587,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7580,6 +8609,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7604,6 +8636,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7623,6 +8658,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7647,6 +8685,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7666,6 +8707,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7690,6 +8734,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7709,6 +8756,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7734,6 +8784,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, NULL, 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7754,6 +8807,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7792,6 +8848,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7810,6 +8869,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7834,6 +8896,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7852,6 +8917,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7876,6 +8944,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7894,6 +8965,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -7919,6 +8993,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7938,6 +9015,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -7962,6 +9042,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -7981,6 +9064,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8005,6 +9091,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8024,6 +9113,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8048,6 +9140,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8067,6 +9162,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8092,6 +9190,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8112,6 +9213,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Simple type conversion @@ -8138,6 +9242,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8156,6 +9263,9 @@ new_col pk a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8180,6 +9290,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8198,6 +9311,9 @@ pk a new_col b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8222,6 +9338,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8240,6 +9359,9 @@ pk a b c new_col connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # ALTER DROP COLUMN tests @@ -8265,6 +9387,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8284,6 +9409,9 @@ pk b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8308,6 +9436,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8327,6 +9458,9 @@ pk a c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8351,6 +9485,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8370,6 +9507,9 @@ pk a b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8394,6 +9534,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8413,6 +9556,9 @@ a b c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8438,6 +9584,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', NULL); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8458,6 +9607,9 @@ pk b connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_2; @@ -8489,6 +9641,9 @@ connection server_1; INSERT INTO t1 (pk, a, b, c) VALUES(1, 999, 'aaa', 2000); UPDATE t1 set a = 1000 WHERE pk = 1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # Ensure server_2 and server_3 are consistent with one another.. @@ -8508,6 +9663,9 @@ pk a new_col c connection server_1; drop table t1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result index 2303911637f..4dd186db922 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result index c6afb2ecb7e..068e0f5df12 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_full_nodup.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result index eaf770f245c..b387742d549 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_min.result @@ -54,6 +54,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -91,6 +94,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -125,6 +131,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -145,6 +154,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -182,6 +194,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -216,6 +231,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -236,6 +254,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -273,6 +294,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -307,6 +331,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -327,6 +354,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -364,6 +394,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -398,6 +431,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -418,6 +454,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -455,6 +494,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -489,6 +531,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -509,6 +554,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -546,6 +594,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -580,6 +631,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -600,6 +654,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -637,6 +694,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -671,6 +731,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -691,6 +754,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -728,6 +794,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -762,6 +831,9 @@ include/ensure_binlog_row_event_columns.inc [(1,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result index 496bd8dc6b8..8ca8ea3e3f8 100644 --- a/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result +++ b/mysql-test/suite/rpl/r/rpl_row_img_sequence_noblob.result @@ -53,6 +53,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -90,6 +93,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -124,6 +130,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, MyISAM, InnoDB @@ -144,6 +153,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -181,6 +193,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -215,6 +230,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, MyISAM @@ -235,6 +253,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -272,6 +293,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -306,6 +330,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: MyISAM, InnoDB, InnoDB @@ -326,6 +353,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -363,6 +393,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -397,6 +430,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, MyISAM @@ -417,6 +453,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -454,6 +493,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -488,6 +530,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, MyISAM, InnoDB @@ -508,6 +553,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -545,6 +593,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -579,6 +630,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, MyISAM @@ -599,6 +653,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -636,6 +693,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -670,6 +730,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc ### engines: InnoDB, InnoDB, InnoDB @@ -690,6 +753,9 @@ SETVAL(s1, 10) 10 include/save_master_gtid.inc # Validate SETVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -727,6 +793,9 @@ NEXTVAL(s1) 11 include/save_master_gtid.inc # Validate NEXTVAL replicated correctly to other servers +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/diff_tables.inc [server_1:test.s1,server_2:test.s1,server_3:test.s1] @@ -761,6 +830,9 @@ include/ensure_binlog_row_event_columns.inc [(1,2,3,4,5,6,7,8)] connection server_1; DROP TABLE s1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc CON: 'server_1', IMG: 'FULL', RESTART SLAVE: 'N' diff --git a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result index efe4cfa0a69..9e92b921bf8 100644 --- a/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result +++ b/mysql-test/suite/rpl/r/rpl_start_alter_chain_basic.result @@ -66,6 +66,9 @@ domain_id seq_no 0 12 connection server_1; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 917041c148d..37b0b06d222 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -13,6 +13,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -37,6 +40,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -46,6 +52,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -77,6 +86,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -86,6 +98,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -110,6 +125,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -119,6 +137,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -150,6 +171,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -162,6 +186,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -188,6 +215,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -197,6 +227,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -230,6 +263,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -239,6 +275,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -265,6 +304,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -274,6 +316,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -307,6 +352,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -323,6 +371,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -349,6 +400,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -358,6 +412,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -391,6 +448,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -400,6 +460,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -428,6 +491,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -437,6 +503,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -470,6 +539,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -485,6 +557,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -513,6 +588,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -522,6 +600,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -557,6 +638,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -566,6 +650,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -594,6 +681,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -603,6 +693,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -638,6 +731,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc # @@ -655,6 +751,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -683,6 +782,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -692,6 +794,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -727,6 +832,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -736,6 +844,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -766,6 +877,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -775,6 +889,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -810,6 +927,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -830,6 +950,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -858,6 +981,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -867,6 +993,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -902,6 +1031,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -911,6 +1043,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -941,6 +1076,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -950,6 +1088,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -985,6 +1126,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1004,6 +1148,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1032,6 +1179,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1041,6 +1191,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1076,6 +1229,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1085,6 +1241,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1115,6 +1274,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1124,6 +1286,9 @@ connection server_1; CREATE TABLE tm (a INT PRIMARY KEY) engine=myisam; CREATE TABLE ti (a INT PRIMARY KEY) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1159,6 +1324,9 @@ include/assert_grep.inc [server_3 should not binlog XA transaction] connection server_1; DROP TABLE ti,tm; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc connection server_1; @@ -1173,6 +1341,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +include/save_master_gtid.inc connection server_3; include/sync_with_master_gtid.inc include/stop_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_extra_col_slave_rebinlog.test b/mysql-test/suite/rpl/t/rpl_extra_col_slave_rebinlog.test index 455ea3f4fde..70d8381d322 100644 --- a/mysql-test/suite/rpl/t/rpl_extra_col_slave_rebinlog.test +++ b/mysql-test/suite/rpl/t/rpl_extra_col_slave_rebinlog.test @@ -17,6 +17,7 @@ create table t1 (a int) engine=innodb; --connection server_2 --source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc @@ -37,6 +38,7 @@ insert into t1 values (1); --connection server_2 --source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc @@ -55,6 +57,10 @@ select * from t1; drop table t1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_master_slave_mismatched_columns_full.inc b/mysql-test/suite/rpl/t/rpl_master_slave_mismatched_columns_full.inc index 961c8ef086d..79191f305ba 100644 --- a/mysql-test/suite/rpl/t/rpl_master_slave_mismatched_columns_full.inc +++ b/mysql-test/suite/rpl/t/rpl_master_slave_mismatched_columns_full.inc @@ -116,6 +116,10 @@ eval INSERT INTO t1 (pk, a, b, c) VALUES($master_pk, $master_a_pre, $master_b, $ eval UPDATE t1 set a = $master_a WHERE pk = $master_pk; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc @@ -195,5 +199,9 @@ if (!$can_convert) { drop table t1; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc + --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test index efb667ad6fa..7918e517b4d 100644 --- a/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test +++ b/mysql-test/suite/rpl/t/rpl_start_alter_chain_basic.test @@ -41,6 +41,9 @@ select domain_id, seq_no from mysql.gtid_slave_pos order by seq_no desc limit 1; --connection server_1 --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index 62ac69cef22..a33262bc47a 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -187,6 +187,9 @@ create database db1; create database db2; create table db1.t1 (a int) engine=innodb; --source include/save_master_gtid.inc +--connection server_2 +--source include/sync_with_master_gtid.inc +--source include/save_master_gtid.inc --connection server_3 --source include/sync_with_master_gtid.inc --source include/stop_slave.inc From b41e88430d384761dc9f9b0e3b693bc70750b7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 7 Aug 2026 17:05:09 +0300 Subject: [PATCH 085/150] MDEV-40667 Infinite loop on SET GLOBAL innodb_log_archive=ON log_t::set_archive(): Always invoke buf_flush_wait(wait_lsn, false) on the latest sampled get_lsn(), so that if a checkpoint is possible, one will be executed. --- storage/innobase/log/log0log.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index ee8d43a68e5..c8aa06b02dc 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -811,14 +811,15 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (archive) { - wait_lsn-= (wait_lsn - first_lsn) % capacity(); + const lsn_t limit{wait_lsn - (wait_lsn - first_lsn) % capacity()}; /* We are in innodb_log_archive=OFF. If the file has wrapped around between the checkpoint and the current position, we must wait for a log checkpoint not before the desired first_lsn of our innodb_log_archive=ON log file, because that format does not allow any wrap-around. */ - if (checkpoint < wait_lsn) + if (checkpoint < limit) goto retry_after_checkpoint; + wait_lsn= limit; } else if (circular_recovery_from_sequence_bit_0) { From 9b4f6932ed676bc956f7570328b9da0f113e4651 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 7 Aug 2026 15:43:13 +0100 Subject: [PATCH 086/150] fix: resolve SQL injection imposed by lexer semantics difference b/w MariaDB and DuckDB. --- storage/duckdb/build.sh | 19 +- storage/duckdb/common/duckdb_types.cc | 15 ++ storage/duckdb/common/duckdb_types.h | 16 ++ storage/duckdb/convertor/ddl_convertor.cc | 64 +++--- storage/duckdb/convertor/dml_convertor.cc | 42 ++-- .../docs/mariadb-duckdb-incompatibilities.md | 8 +- storage/duckdb/ha_duckdb.cc | 75 ++++--- storage/duckdb/ha_duckdb_pushdown.cc | 47 ++--- .../r/duckdb_identifier_escaping.result | 102 ++++++++++ .../duckdb/t/duckdb_identifier_escaping.test | 88 +++++++++ storage/duckdb/runtime/delta_appender.cc | 53 ++--- storage/duckdb/runtime/duckdb_context.cc | 5 +- storage/duckdb/runtime/duckdb_query.cc | 183 +++++++++++++++++- storage/duckdb/runtime/duckdb_query.h | 15 ++ 14 files changed, 599 insertions(+), 133 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/duckdb_identifier_escaping.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/duckdb_identifier_escaping.test diff --git a/storage/duckdb/build.sh b/storage/duckdb/build.sh index c7d179a2c64..c9a9956913c 100755 --- a/storage/duckdb/build.sh +++ b/storage/duckdb/build.sh @@ -286,7 +286,13 @@ install_deps() { ncurses-devel readline-devel openssl-devel zlib-devel bzip2-devel \ libzstd-devel libcurl-devel libaio-devel libxml2-devel pcre2-devel \ libxcrypt-devel xz-devel pam-devel perl-DBI python3 python3-devel \ - ccache rpm-build" + libatomic ccache" + + # rpm-build is only needed to build RPM packages; on some bases (e.g. UBI 9) + # installing it forces an rpm upgrade that conflicts with pinned @System rpm. + if [[ $BUILD_PACKAGES = true ]]; then + RPM_DEPS="$RPM_DEPS rpm-build" + fi local DEB_DEPS="build-essential git cmake ninja-build bison flex \ libncurses-dev libreadline-dev libssl-dev zlib1g-dev libbz2-dev \ @@ -313,9 +319,16 @@ install_deps() { warn "Rocky 8 default gcc 8 lacks C++20 -- consider re-running with -R" fi ;; - rockylinux:9|rocky:9|rocky:10) + rockylinux:9|rocky:9|rocky:10|rhel:9*|redhat:9*|red:9*) + # Enable EPEL and the CodeReady Builder / PowerTools-equivalent repo. + # Rocky/Alma expose it as the 'crb' alias; genuine RHEL enables it via + # subscription-manager. Enabling is best-effort: if neither mechanism + # is available we warn and continue so the dnf install below still runs. command="$SUDO dnf install -y 'dnf-command(config-manager)' epel-release && \ - $SUDO dnf config-manager --set-enabled crb && \ + { $SUDO dnf config-manager --set-enabled crb 2>/dev/null || \ + $SUDO dnf config-manager --set-enabled ubi-9-codeready-builder-rpms 2>/dev/null || \ + $SUDO subscription-manager repos --enable codeready-builder-for-rhel-9-\$(uname -m)-rpms 2>/dev/null || \ + warn 'Could not enable CRB/CodeReady Builder repo; continuing without it'; } && \ $SUDO dnf install -y gcc gcc-c++ ${RPM_DEPS}" ;; ubuntu:*|debian:*) diff --git a/storage/duckdb/common/duckdb_types.cc b/storage/duckdb/common/duckdb_types.cc index 625e1c0b168..e95fe198667 100644 --- a/storage/duckdb/common/duckdb_types.cc +++ b/storage/duckdb/common/duckdb_types.cc @@ -92,6 +92,21 @@ DatabaseTableNames::DatabaseTableNames(const char *name) db_name= std::string(ori_db_name, db_name_length); } +std::string quote_duckdb_identifier(const char *name, size_t length) +{ + std::string out; + out.reserve(length + 2); + out.push_back('"'); + for (size_t i= 0; i < length; i++) + { + if (name[i] == '"') + out.push_back('"'); + out.push_back(name[i]); + } + out.push_back('"'); + return out; +} + Databasename::Databasename(const char *path_name) { char dbname[FN_REFLEN]; diff --git a/storage/duckdb/common/duckdb_types.h b/storage/duckdb/common/duckdb_types.h index 2e39c1aeb11..6642467ccde 100644 --- a/storage/duckdb/common/duckdb_types.h +++ b/storage/duckdb/common/duckdb_types.h @@ -33,6 +33,22 @@ public: std::string table_name; }; +/** + Quote an SQL identifier for DuckDB. + + DuckDB (SQL standard) delimits identifiers with double quotes and escapes + an embedded double quote by doubling it. MariaDB identifiers (column names + in particular) may contain arbitrary characters, so failing to escape lets + a crafted name break out of the quoted identifier and inject DuckDB SQL + (MDEV-40653). Returns the name wrapped in double quotes with any embedded + double quote doubled. +*/ +std::string quote_duckdb_identifier(const char *name, size_t length); +inline std::string quote_duckdb_identifier(const std::string &name) +{ + return quote_duckdb_identifier(name.data(), name.size()); +} + /** Utility class to extract the database name from a path like "./db/". */ diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index fe87a77c7d9..2ce9cab6b8f 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -235,8 +235,24 @@ Field *find_autoinc_field(const TABLE *table) static std::string autoinc_nextval_expr(const std::string &schema_name, const std::string &table_name) { - return "nextval('\"" + schema_name + "\".\"" + - autoinc_sequence_name(table_name) + "\"')"; + /* + The qualified sequence name is a quoted identifier nested inside a + single-quoted string literal argument to nextval(). Escape the inner + identifiers (doubling ") and then escape the resulting string for the + enclosing literal (doubling '). + */ + std::string qualified= + quote_duckdb_identifier(schema_name) + "." + + quote_duckdb_identifier(autoinc_sequence_name(table_name)); + std::string escaped; + escaped.reserve(qualified.size()); + for (char c : qualified) + { + if (c == '\'') + escaped.push_back('\''); + escaped.push_back(c); + } + return "nextval('" + escaped + "')"; } /** @@ -334,8 +350,8 @@ static void append_stmt_alter_table(std::ostringstream &output, const std::string &schema_name, const std::string &table_name) { - output << "USE \"" << schema_name << "\";"; - output << ALTER_TABLE_OP_STR << '"' << table_name << '"'; + output << "USE " << quote_duckdb_identifier(schema_name) << ";"; + output << ALTER_TABLE_OP_STR << quote_duckdb_identifier(table_name); } static void append_stmt_column_add(std::ostringstream &output, @@ -349,7 +365,7 @@ static void append_stmt_column_add(std::ostringstream &output, assert(!schema_name.empty() && !table_name.empty() && !column_name.empty() && !column_type.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ADD_COLUMN_OP_STR << '"' << column_name << '"' << " " + output << ADD_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << " " << column_type; if (has_default) output << DEFINE_DEFAULT_STR << default_value; @@ -363,7 +379,7 @@ static void append_stmt_column_drop(std::ostringstream &output, { assert(!schema_name.empty() && !table_name.empty() && !column_name.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << DROP_COLUMN_OP_STR << '"' << column_name << '"' << ";"; + output << DROP_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << ";"; } static void append_stmt_column_change_type(std::ostringstream &output, @@ -375,7 +391,7 @@ static void append_stmt_column_change_type(std::ostringstream &output, assert(!schema_name.empty() && !table_name.empty() && !column_name.empty() && !column_type.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ALTER_COLUMN_OP_STR << '"' << column_name << '"' + output << ALTER_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << SET_DATA_TYPE_STR << column_type << ";"; } @@ -388,8 +404,8 @@ static void append_stmt_column_rename(std::ostringstream &output, assert(!schema_name.empty() && !table_name.empty() && !old_column_name.empty() && !new_column_name.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << RENAME_COLUMN_OP_STR << '"' << old_column_name << '"' << " TO " - << '"' << new_column_name << '"' << ";"; + output << RENAME_COLUMN_OP_STR << quote_duckdb_identifier(old_column_name) + << " TO " << quote_duckdb_identifier(new_column_name) << ";"; } static void append_stmt_column_set_default(std::ostringstream &output, @@ -401,8 +417,8 @@ static void append_stmt_column_set_default(std::ostringstream &output, assert(!schema_name.empty() && !table_name.empty() && !column_name.empty() && !default_value.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ALTER_COLUMN_OP_STR << '"' << column_name << '"' << SET_DEFAULT_STR - << default_value << ";"; + output << ALTER_COLUMN_OP_STR << quote_duckdb_identifier(column_name) + << SET_DEFAULT_STR << default_value << ";"; } static void append_stmt_column_drop_default(std::ostringstream &output, @@ -412,7 +428,7 @@ static void append_stmt_column_drop_default(std::ostringstream &output, { assert(!schema_name.empty() && !table_name.empty() && !column_name.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ALTER_COLUMN_OP_STR << '"' << column_name << '"' + output << ALTER_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << DROP_DEFAULT_STR << ";"; } @@ -423,7 +439,7 @@ static void append_stmt_column_set_not_null(std::ostringstream &output, { assert(!schema_name.empty() && !table_name.empty() && !column_name.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ALTER_COLUMN_OP_STR << '"' << column_name << '"' + output << ALTER_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << SET_NOT_NULL_STR << ";"; } @@ -434,7 +450,7 @@ static void append_stmt_column_drop_not_null(std::ostringstream &output, { assert(!schema_name.empty() && !table_name.empty() && !column_name.empty()); append_stmt_alter_table(output, schema_name, table_name); - output << ALTER_COLUMN_OP_STR << '"' << column_name << '"' + output << ALTER_COLUMN_OP_STR << quote_duckdb_identifier(column_name) << DROP_NOT_NULL_STR << ";"; } @@ -449,7 +465,8 @@ static void append_stmt_table_rename(std::ostringstream &output, !new_schema_name.empty() && !new_table_name.empty()); assert(old_schema_name == new_schema_name); append_stmt_alter_table(output, old_schema_name, old_table_name); - output << RENAME_TABLE_OP_STR << '"' << new_table_name << '"' << ";"; + output << RENAME_TABLE_OP_STR << quote_duckdb_identifier(new_table_name) + << ";"; } /* ----- FieldConvertor ----- */ @@ -508,7 +525,9 @@ std::string FieldConvertor::translate() std::ostringstream result; - result << '"' << field->field_name.str << '"' << " "; + result << quote_duckdb_identifier(field->field_name.str, + field->field_name.length) + << " "; result << convert_type(m_field); if (field->flags & NOT_NULL_FLAG) @@ -733,10 +752,10 @@ std::string CreateTableConvertor::translate() std::ostringstream result; assert((m_create_info->options & HA_LEX_CREATE_TMP_TABLE) == 0); - result << "CREATE SCHEMA IF NOT EXISTS " << '"' << m_schema_name << '"' - << ";"; + result << "CREATE SCHEMA IF NOT EXISTS " + << quote_duckdb_identifier(m_schema_name) << ";"; - result << "USE " << '"' << m_schema_name << '"' << ";"; + result << "USE " << quote_duckdb_identifier(m_schema_name) << ";"; /* The sequence must exist before the table, because the AUTO_INCREMENT @@ -751,8 +770,9 @@ std::string CreateTableConvertor::translate() if (start > (ulonglong) INT64_MAX) start= (ulonglong) INT64_MAX; - result << "CREATE SEQUENCE IF NOT EXISTS " << '"' << m_schema_name << '"' - << "." << '"' << autoinc_sequence_name(m_table_name) << '"' + result << "CREATE SEQUENCE IF NOT EXISTS " + << quote_duckdb_identifier(m_schema_name) << "." + << quote_duckdb_identifier(autoinc_sequence_name(m_table_name)) << " START WITH " << start << ";"; } @@ -761,7 +781,7 @@ std::string CreateTableConvertor::translate() Always use IF NOT EXISTS for safety in DuckDB. */ result << IF_NOT_EXISTS_STR; - result << '"' << m_table_name << '"'; + result << quote_duckdb_identifier(m_table_name); result << " ("; append_column_definition(result); diff --git a/storage/duckdb/convertor/dml_convertor.cc b/storage/duckdb/convertor/dml_convertor.cc index 76108e00d53..4685ed5f04d 100644 --- a/storage/duckdb/convertor/dml_convertor.cc +++ b/storage/duckdb/convertor/dml_convertor.cc @@ -32,6 +32,24 @@ namespace myduck { extern my_bool use_double_for_decimal; } static const uint sizeof_trailing_comma= sizeof(", ") - 1; static const uint sizeof_trailing_and= sizeof(" AND ") - 1; +/* + Append an SQL identifier to a String, quoted for DuckDB. DuckDB escapes an + embedded double quote by doubling it; failing to escape lets a crafted + identifier break out of the quoted name and inject SQL (MDEV-40653). +*/ +static void append_quoted_identifier(String &target, const char *name, + size_t length) +{ + target.append(STRING_WITH_LEN("\"")); + for (size_t i= 0; i < length; i++) + { + if (name[i] == '"') + target.append(STRING_WITH_LEN("\"")); + target.append(&name[i], 1); + } + target.append(STRING_WITH_LEN("\"")); +} + void append_field_value_to_sql(String &target_str, Field *field) { if (field->is_null()) @@ -165,13 +183,10 @@ static inline void append_table_name(TABLE *table, String &query) the temp name. */ DatabaseTableNames dt(table->s->normalized_path.str); - query.append(STRING_WITH_LEN("\"")); - query.append(dt.db_name.c_str(), dt.db_name.length()); - query.append(STRING_WITH_LEN("\"")); + append_quoted_identifier(query, dt.db_name.c_str(), dt.db_name.length()); query.append(STRING_WITH_LEN(".")); - query.append(STRING_WITH_LEN("\"")); - query.append(dt.table_name.c_str(), dt.table_name.length()); - query.append(STRING_WITH_LEN("\"")); + append_quoted_identifier(query, dt.table_name.c_str(), + dt.table_name.length()); } static inline void get_write_fields(TABLE *table, std::vector &fields) @@ -232,9 +247,8 @@ void DMLConvertor::generate_where_clause(String &query) for (auto field : fields) { - query.append(STRING_WITH_LEN("\"")); - query.append(field->field_name.str, field->field_name.length); - query.append(STRING_WITH_LEN("\"")); + append_quoted_identifier(query, field->field_name.str, + field->field_name.length); query.append(STRING_WITH_LEN(" = ")); append_where_value(query, field); @@ -260,9 +274,8 @@ void InsertConvertor::generate_fields_and_values(String &query) query.append(STRING_WITH_LEN(" (")); for (auto field : fields) { - query.append(STRING_WITH_LEN("\"")); - query.append(field->field_name.str, field->field_name.length); - query.append(STRING_WITH_LEN("\"")); + append_quoted_identifier(query, field->field_name.str, + field->field_name.length); query.append(STRING_WITH_LEN(", ")); } query.length(query.length() - sizeof_trailing_comma); @@ -293,9 +306,8 @@ void UpdateConvertor::generate_fields_and_values(String &query) for (auto field : fields) { - query.append(STRING_WITH_LEN("\"")); - query.append(field->field_name.str, field->field_name.length); - query.append(STRING_WITH_LEN("\"")); + append_quoted_identifier(query, field->field_name.str, + field->field_name.length); query.append(STRING_WITH_LEN(" = ")); append_field_value_to_sql(query, field); diff --git a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md index d391478119c..8288215bf56 100644 --- a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md +++ b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md @@ -88,14 +88,15 @@ SELECT pushdown uses the original SQL text from `THD::query()`. MariaDB-specific | `HIGH_PRIORITY`, `SQL_NO_CACHE`, `SQL_CACHE`, `SQL_BUFFER_RESULT`, `SQL_SMALL_RESULT`, `SQL_BIG_RESULT`, `SQL_CALC_FOUND_ROWS` | -- | Stripped | | `FORCE INDEX(...)`, `USE INDEX(...)`, `IGNORE INDEX(...)` | -- | Stripped | -### Known unhandled cases (currently cause query failures) +### Known unhandled cases (currently fail or change semantics) -These MariaDB constructs are **not yet rewritten** and fail when pushed down. Because pushdown forwards the original `THD::query()` text (only backticks are converted to double quotes), MariaDB-specific token semantics survive into DuckDB. Discovered while running an analytical query set (402 queries) against DuckDB-engine tables. +These MariaDB constructs are **not yet rewritten** and either fail or have different semantics when pushed down. Because pushdown forwards the original `THD::query()` text (only backticks are converted to double quotes), MariaDB-specific token semantics survive into DuckDB. Discovered while running an analytical query set (402 queries) against DuckDB-engine tables. | MariaDB construct | Sent to DuckDB as | DuckDB result | Root cause | |---|---|---|---| | Double-quoted **string literal**, e.g. `JSON_OBJECT("month", ...)` | `"month"` (verbatim) | `Binder Error: Referenced column "month" not found` | MariaDB without `ANSI_QUOTES` treats `"x"` as a string literal; DuckDB treats `"x"` as an identifier. The forwarded literal is read as a column reference. | | Unquoted column **alias equal to a DuckDB reserved keyword**, e.g. `SELECT expr name` / `SELECT expr year` | `... name` / `... year` (verbatim) | `Parser Error: syntax error at or near "name"` | DuckDB forbids reserved keywords as unquoted identifiers. `AS name` or `"name"` work; bare `name` / `year` / `month` do not. This is why most implicit aliases pass but keyword aliases fail. | +| MariaDB **executable/versioned comments**, e.g. `/*! + 1 */`, `/*!100000 + 1 */`, or `/*M! + 1 */` | Comment text (verbatim) | Contents are ignored as an ordinary block comment | MariaDB executes eligible `/*! ... */` and `/*M! ... */` contents as SQL, optionally gated by a version number; DuckDB treats the entire region as a comment. Forwarded queries can therefore silently use different predicates or expressions. | Reproductions (against any DuckDB-engine table `t`): @@ -104,9 +105,10 @@ SELECT JSON_OBJECT("k", 1) FROM t; -- Binder Error: column "k" not found SELECT JSON_OBJECT('k', 1) FROM t; -- OK SELECT col name FROM t; -- Parser Error at "name" SELECT col AS name FROM t; -- OK +SELECT 1 /*! + 1 */ FROM t; -- MariaDB: 2; DuckDB pushdown: 1 ``` -**Fix direction**: in `ha_duckdb_pushdown.cc`, convert double-quoted string literals to single-quoted form and quote (or `AS`-prefix) aliases that are DuckDB reserved keywords. Both require lexer-aware handling of the query text, not naive replacement — `backticks_to_double_quotes()` already produces legitimate double-quoted identifiers that must not be altered. +**Fix direction**: in `ha_duckdb_pushdown.cc`, convert double-quoted string literals to single-quoted form and quote (or `AS`-prefix) aliases that are DuckDB reserved keywords. Both require lexer-aware handling of the query text, not naive replacement — `backticks_to_double_quotes()` already produces legitimate double-quoted identifiers that must not be altered. Executable/versioned comments must either be expanded according to MariaDB's version rules or make the query ineligible for raw SQL forwarding. --- diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 34400e647de..e5b3b989e78 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -37,7 +37,6 @@ #include "duckdb_select.h" #include "ddl_convertor.h" #include "dml_convertor.h" -#include "delta_appender.h" #include "row_helpers.h" #include "ha_duckdb_pushdown.h" #include "duckdb_log.h" @@ -212,9 +211,7 @@ static void duckdb_drop_database(handlerton *hton, char *path) Databasename db(path); - std::string query= "DROP SCHEMA IF EXISTS \""; - query.append(db.name); - query.append("\""); + std::string query= "DROP SCHEMA IF EXISTS " + quote_duckdb_identifier(db.name); if (duckdb_register_trx(thd)) DBUG_VOID_RETURN; @@ -544,10 +541,27 @@ int ha_duckdb::write_row(const uchar *buf) static ulonglong reserve_autoinc_block(THD *thd, const char *db_name, const char *table_name, ulonglong want) { + /* + The qualified sequence name is a quoted identifier nested inside a + single-quoted string literal argument to nextval(). Escape the inner + identifiers (doubling ") and then escape the result for the enclosing + literal (doubling ') so a crafted schema/table name cannot inject SQL + (MDEV-40653). + */ + std::string qualified= + quote_duckdb_identifier(std::string(db_name)) + "." + + quote_duckdb_identifier(autoinc_sequence_name(table_name)); + std::string escaped; + escaped.reserve(qualified.size()); + for (char c : qualified) + { + if (c == '\'') + escaped.push_back('\''); + escaped.push_back(c); + } std::string query= - "SELECT min(x), max(x) FROM (SELECT nextval('\"" + - std::string(db_name) + "\".\"" + autoinc_sequence_name(table_name) + - "\"') AS x FROM range(" + std::to_string(want) + ")) t"; + "SELECT min(x), max(x) FROM (SELECT nextval('" + escaped + + "') AS x FROM range(" + std::to_string(want) + ")) t"; auto *ctx= get_duckdb_context(thd); for (int attempt= 0; attempt < 3; attempt++) @@ -775,8 +789,8 @@ int ha_duckdb::rnd_init(bool) else DBUG_RETURN(HA_ERR_INTERNAL_ERROR); - std::string query= - "SELECT * FROM \"" + schema_name + "\".\"" + table_name + "\""; + std::string query= "SELECT * FROM " + quote_duckdb_identifier(schema_name) + + "." + quote_duckdb_identifier(table_name); auto *ctx= get_duckdb_context(thd); query_result= myduck::duckdb_query(ctx->get_connection(), query); @@ -938,8 +952,8 @@ int ha_duckdb::delete_all_rows() ctx->delete_appender(dt.db_name, dt.table_name); /* Execute DELETE FROM "schema"."table" */ - std::string query= - "DELETE FROM \"" + dt.db_name + "\".\"" + dt.table_name + "\""; + std::string query= "DELETE FROM " + quote_duckdb_identifier(dt.db_name) + + "." + quote_duckdb_identifier(dt.table_name); auto query_result= myduck::duckdb_query(ctx->get_connection(), query); if (query_result->HasError()) @@ -951,13 +965,9 @@ int ha_duckdb::delete_all_rows() DBUG_RETURN(0); } -const COND *ha_duckdb::cond_push(const COND *cond) +const COND *ha_duckdb::cond_push(const COND *) { DBUG_ENTER("ha_duckdb::cond_push"); - /* - Accept all conditions — DuckDB will evaluate the WHERE clause - from the original SQL query in direct_delete_rows(). - */ DBUG_RETURN(NULL); } @@ -972,6 +982,15 @@ int ha_duckdb::direct_delete_rows(ha_rows *delete_rows) DBUG_ENTER("ha_duckdb::direct_delete_rows"); int ret= 0; THD *thd= ha_thd(); + LEX_STRING *source_query= thd_query_string(thd); + if (myduck::mariadb_query_has_unsafe_quote_escape( + thd, source_query->str, source_query->length)) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_DML_ERROR, + "Unsafe MariaDB backslash quote escape in forwarded SQL", + "DuckDB"); + DBUG_RETURN(HA_DUCKDB_DML_ERROR); + } ret= duckdb_register_trx(thd); if (ret) @@ -1021,6 +1040,15 @@ int ha_duckdb::direct_update_rows(ha_rows *update_rows, ha_rows *found_rows) DBUG_ENTER("ha_duckdb::direct_update_rows"); int ret= 0; THD *thd= ha_thd(); + LEX_STRING *source_query= thd_query_string(thd); + if (myduck::mariadb_query_has_unsafe_quote_escape( + thd, source_query->str, source_query->length)) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_DML_ERROR, + "Unsafe MariaDB backslash quote escape in forwarded SQL", + "DuckDB"); + DBUG_RETURN(HA_DUCKDB_DML_ERROR); + } ret= duckdb_register_trx(thd); if (ret) @@ -1135,8 +1163,9 @@ int ha_duckdb::delete_table(const char *name) DatabaseTableNames dt(name); - std::string query= - "DROP TABLE IF EXISTS \"" + dt.db_name + "\".\"" + dt.table_name + "\""; + std::string query= "DROP TABLE IF EXISTS " + + quote_duckdb_identifier(dt.db_name) + "." + + quote_duckdb_identifier(dt.table_name); auto *ctx= get_duckdb_context(thd); auto query_result= myduck::duckdb_query(ctx->get_connection(), query); @@ -1150,8 +1179,10 @@ int ha_duckdb::delete_table(const char *name) The name is derived from the table alone, so no lookup is needed and the statement is harmless for tables without AUTO_INCREMENT. */ - std::string seq_query= "DROP SEQUENCE IF EXISTS \"" + dt.db_name + "\".\"" + - autoinc_sequence_name(dt.table_name) + "\""; + std::string seq_query= "DROP SEQUENCE IF EXISTS " + + quote_duckdb_identifier(dt.db_name) + "." + + quote_duckdb_identifier( + autoinc_sequence_name(dt.table_name)); auto seq_result= myduck::duckdb_query(ctx->get_connection(), seq_query); if (seq_result == nullptr || seq_result->HasError()) @@ -1208,8 +1239,8 @@ int ha_duckdb::truncate() table->s->table_name.length); std::ostringstream query; - query << "USE \"" << schema_name << "\";"; - query << "TRUNCATE TABLE \"" << table_name << "\";"; + query << "USE " << quote_duckdb_identifier(schema_name) << ";"; + query << "TRUNCATE TABLE " << quote_duckdb_identifier(table_name) << ";"; auto *ctx= get_duckdb_context(thd); auto query_result= myduck::duckdb_query(ctx->get_connection(), query.str()); diff --git a/storage/duckdb/ha_duckdb_pushdown.cc b/storage/duckdb/ha_duckdb_pushdown.cc index c865162243c..74e00f823ad 100644 --- a/storage/duckdb/ha_duckdb_pushdown.cc +++ b/storage/duckdb/ha_duckdb_pushdown.cc @@ -106,6 +106,8 @@ static bool is_query_token(const std::string &sql, size_t start, size_t length) static bool extract_source_query(THD *thd, std::string &source) { const std::string sql(thd->query(), thd->query_length()); + if (myduck::mariadb_query_has_unsafe_quote_escape(thd, sql.data(), sql.size())) + return false; const bool strip_prefix= thd->lex->sql_command == SQLCOM_INSERT_SELECT; const bool backslash_escapes= thd->backslash_escapes(); size_t i= 0; @@ -119,48 +121,21 @@ static bool extract_source_query(THD *thd, std::string &source) i++; continue; } - if (c == '/' && i + 1 < sql.size() && sql[i + 1] == '*') + size_t end; + myduck::SqlRegionType region= + myduck::scan_sql_region(sql, i, backslash_escapes, end); + if (region == myduck::SqlRegionType::UNTERMINATED) + return false; + if (region == myduck::SqlRegionType::COMMENT) { - size_t end= sql.find("*/", i + 2); - if (end == std::string::npos) - return false; - i= end + 2; + i= end; continue; } - if (c == '#' || - (c == '-' && i + 1 < sql.size() && sql[i + 1] == '-' && - (i + 2 == sql.size() || isspace((unsigned char) sql[i + 2])))) - { - size_t end= sql.find('\n', i + (c == '#' ? 1 : 2)); - i= end == std::string::npos ? sql.size() : end + 1; - continue; - } - if (c == '\'' || c == '"' || c == '`') + if (region == myduck::SqlRegionType::QUOTED) { if (!strip_prefix && depth == 0) return false; - const char quote= (char) c; - bool closed= false; - for (i++; i < sql.size(); i++) - { - if (sql[i] == '\\' && backslash_escapes && i + 1 < sql.size()) - { - i++; - continue; - } - if (sql[i] != quote) - continue; - if (i + 1 < sql.size() && sql[i + 1] == quote) - { - i++; - continue; - } - i++; - closed= true; - break; - } - if (!closed) - return false; + i= end; continue; } if (c == '(') diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_identifier_escaping.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_identifier_escaping.result new file mode 100644 index 00000000000..7e7a441c402 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_identifier_escaping.result @@ -0,0 +1,102 @@ +CREATE DATABASE db_ident_esc; +USE db_ident_esc; +# +# 1) Column names containing a double quote (CREATE/INSERT/SELECT) +# +CREATE TABLE t1 (`a"b` INT PRIMARY KEY, `c"d` INT) ENGINE=DuckDB; +INSERT INTO t1 VALUES (1, 10), (2, 20); +SELECT * FROM t1 ORDER BY `a"b`; +a"b c"d +1 10 +2 20 +SELECT /* user's ' column */ * FROM t1 ORDER BY `a"b`; +a"b c"d +1 10 +2 20 +SELECT * -- user's ' columns +FROM t1 ORDER BY `a"b`; +a"b c"d +1 10 +2 20 +# +# 2) UPDATE/DELETE route the quoted names through the WHERE/SET builders +# +UPDATE t1 SET `c"d` = 99 WHERE `a"b` = 1; +SELECT * FROM t1 ORDER BY `a"b`; +a"b c"d +1 99 +2 20 +DELETE FROM t1 WHERE `a"b` = 2; +SELECT * FROM t1 ORDER BY `a"b`; +a"b c"d +1 99 +# +# 3) ALTER RENAME/ADD/DROP COLUMN with quoted names +# +ALTER TABLE t1 RENAME COLUMN `c"d` TO `e""f`; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a"b` int(11) NOT NULL, + `e""f` int(11) DEFAULT NULL, + PRIMARY KEY (`a"b`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT `e""f` FROM t1; +e""f +99 +ALTER TABLE t1 ADD COLUMN `g"h` INT; +SELECT * FROM t1; +a"b e""f g"h +1 99 NULL +ALTER TABLE t1 DROP COLUMN `g"h`; +# +# 4) Table name containing a double quote +# +CREATE TABLE `x"y` (`id` INT PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO `x"y` VALUES (1), (2); +SELECT * FROM `x"y` ORDER BY `id`; +id +1 +2 +TRUNCATE TABLE `x"y`; +SELECT COUNT(*) FROM `x"y`; +COUNT(*) +0 +DROP TABLE `x"y`; +# +# 5) Backslash quote-boundary mismatches disable raw SQL forwarding +# +CREATE TABLE lexical_guard (id INT PRIMARY KEY, v VARCHAR(100)) ENGINE=DuckDB; +INSERT INTO lexical_guard VALUES (1, 'safe'); +SELECT COUNT(*) FROM lexical_guard +WHERE v = 'missing\' OR true -- '; +COUNT(*) +0 +UPDATE lexical_guard SET v = 'changed\' WHERE true -- ' WHERE id = 999; +ERROR HY000: Got error 168 'Unsafe MariaDB backslash quote escape in forwarded SQL' from DuckDB +SELECT * FROM lexical_guard; +id v +1 safe +DELETE FROM lexical_guard WHERE v = 'missing\' OR true -- '; +ERROR HY000: Got error 168 'Unsafe MariaDB backslash quote escape in forwarded SQL' from DuckDB +SELECT COUNT(*) FROM lexical_guard; +COUNT(*) +1 +UPDATE lexical_guard SET v = 'safe\''; +ERROR HY000: Got error 168 'Unsafe MariaDB backslash quote escape in forwarded SQL' from DuckDB +SELECT * FROM lexical_guard; +id v +1 safe +UPDATE /* user's \' comment */ lexical_guard SET v = 'unchanged' WHERE id = 999; +UPDATE lexical_guard SET v = 'unchanged\n' WHERE id = 999; +UPDATE lexical_guard SET v = "changed\" WHERE true -- " WHERE id = 999; +ERROR HY000: Got error 168 'Unsafe MariaDB backslash quote escape in forwarded SQL' from DuckDB +SELECT * FROM lexical_guard; +id v +1 safe +DROP TABLE lexical_guard; +# +# 6) Cleanup +# +DROP TABLE t1; +DROP DATABASE db_ident_esc; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_identifier_escaping.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_identifier_escaping.test new file mode 100644 index 00000000000..3ffeb7adef4 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_identifier_escaping.test @@ -0,0 +1,88 @@ +# MDEV-40653: identifiers (schema/table/column names) must be escaped when +# building DuckDB SQL. DuckDB delimits identifiers with double quotes and +# escapes an embedded double quote by doubling it. A MariaDB identifier may +# contain a literal double quote, so without escaping a crafted name breaks +# out of the quoted identifier (parser error / SQL injection). + +--disable_query_log +SET @saved_duckdb_require_primary_key = @@GLOBAL.duckdb_require_primary_key; +SET @saved_sql_mode = @@SESSION.sql_mode; +SET GLOBAL duckdb_require_primary_key = OFF; +SET SESSION sql_mode = ''; +--enable_query_log + +CREATE DATABASE db_ident_esc; +USE db_ident_esc; + +--echo # +--echo # 1) Column names containing a double quote (CREATE/INSERT/SELECT) +--echo # +CREATE TABLE t1 (`a"b` INT PRIMARY KEY, `c"d` INT) ENGINE=DuckDB; +INSERT INTO t1 VALUES (1, 10), (2, 20); +SELECT * FROM t1 ORDER BY `a"b`; +SELECT /* user's ' column */ * FROM t1 ORDER BY `a"b`; +SELECT * -- user's ' columns +FROM t1 ORDER BY `a"b`; + +--echo # +--echo # 2) UPDATE/DELETE route the quoted names through the WHERE/SET builders +--echo # +UPDATE t1 SET `c"d` = 99 WHERE `a"b` = 1; +SELECT * FROM t1 ORDER BY `a"b`; +DELETE FROM t1 WHERE `a"b` = 2; +SELECT * FROM t1 ORDER BY `a"b`; + +--echo # +--echo # 3) ALTER RENAME/ADD/DROP COLUMN with quoted names +--echo # +ALTER TABLE t1 RENAME COLUMN `c"d` TO `e""f`; +SHOW CREATE TABLE t1; +SELECT `e""f` FROM t1; +ALTER TABLE t1 ADD COLUMN `g"h` INT; +SELECT * FROM t1; +ALTER TABLE t1 DROP COLUMN `g"h`; + +--echo # +--echo # 4) Table name containing a double quote +--echo # +CREATE TABLE `x"y` (`id` INT PRIMARY KEY) ENGINE=DuckDB; +INSERT INTO `x"y` VALUES (1), (2); +SELECT * FROM `x"y` ORDER BY `id`; +TRUNCATE TABLE `x"y`; +SELECT COUNT(*) FROM `x"y`; +DROP TABLE `x"y`; + +--echo # +--echo # 5) Backslash quote-boundary mismatches disable raw SQL forwarding +--echo # +CREATE TABLE lexical_guard (id INT PRIMARY KEY, v VARCHAR(100)) ENGINE=DuckDB; +INSERT INTO lexical_guard VALUES (1, 'safe'); +SELECT COUNT(*) FROM lexical_guard +WHERE v = 'missing\' OR true -- '; +--error ER_GET_ERRMSG +UPDATE lexical_guard SET v = 'changed\' WHERE true -- ' WHERE id = 999; +SELECT * FROM lexical_guard; +--error ER_GET_ERRMSG +DELETE FROM lexical_guard WHERE v = 'missing\' OR true -- '; +SELECT COUNT(*) FROM lexical_guard; +--error ER_GET_ERRMSG +UPDATE lexical_guard SET v = 'safe\''; +SELECT * FROM lexical_guard; +UPDATE /* user's \' comment */ lexical_guard SET v = 'unchanged' WHERE id = 999; +UPDATE lexical_guard SET v = 'unchanged\n' WHERE id = 999; +--error ER_GET_ERRMSG +UPDATE lexical_guard SET v = "changed\" WHERE true -- " WHERE id = 999; +SELECT * FROM lexical_guard; +DROP TABLE lexical_guard; + +--echo # +--echo # 6) Cleanup +--echo # +DROP TABLE t1; +DROP DATABASE db_ident_esc; + +--disable_query_log +SET GLOBAL duckdb_require_primary_key = @saved_duckdb_require_primary_key; +SET SESSION sql_mode = @saved_sql_mode; +--enable_query_log +--source ../include/cleanup_duckdb.inc diff --git a/storage/duckdb/runtime/delta_appender.cc b/storage/duckdb/runtime/delta_appender.cc index 3d6b91623b0..46c4259b649 100644 --- a/storage/duckdb/runtime/delta_appender.cc +++ b/storage/duckdb/runtime/delta_appender.cc @@ -17,8 +17,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ +#define MYSQL_SERVER 1 + #include -#include "sql_class.h" #include "log.h" #undef UNKNOWN @@ -29,7 +30,6 @@ #include "ddl_convertor.h" #include "duckdb_timezone.h" #include "duckdb_handler_errors.h" -#include "tztime.h" #include "my_decimal.h" #include "duckdb/common/hugeint.hpp" @@ -214,15 +214,16 @@ bool DeltaAppender::Initialize(TABLE *table) m_tmp_table_name= buf_table_name(m_schema_name, m_table_name); std::stringstream ss; - ss << "CREATE TEMPORARY TABLE IF NOT EXISTS main.\"" << m_tmp_table_name - << "\" AS FROM \"" << m_schema_name << "\".\"" << m_table_name - << "\" LIMIT 0;"; - ss << "ALTER TABLE main.\"" << m_tmp_table_name - << "\" ADD COLUMN \"#mdb_delete_flag\" BOOL;"; - ss << "ALTER TABLE main.\"" << m_tmp_table_name - << "\" ADD COLUMN \"#mdb_row_no\" INT;"; - ss << "ALTER TABLE main.\"" << m_tmp_table_name - << "\" ADD COLUMN \"#mdb_trx_no\" INT;"; + ss << "CREATE TEMPORARY TABLE IF NOT EXISTS main." + << quote_duckdb_identifier(m_tmp_table_name) << " AS FROM " + << quote_duckdb_identifier(m_schema_name) << "." + << quote_duckdb_identifier(m_table_name) << " LIMIT 0;"; + ss << "ALTER TABLE main." << quote_duckdb_identifier(m_tmp_table_name) + << " ADD COLUMN \"#mdb_delete_flag\" BOOL;"; + ss << "ALTER TABLE main." << quote_duckdb_identifier(m_tmp_table_name) + << " ADD COLUMN \"#mdb_row_no\" INT;"; + ss << "ALTER TABLE main." << quote_duckdb_identifier(m_tmp_table_name) + << " ADD COLUMN \"#mdb_trx_no\" INT;"; auto ret= myduck::duckdb_query(*m_con, ss.str()); if (ret->HasError()) @@ -249,9 +250,8 @@ bool DeltaAppender::Initialize(TABLE *table) { if (i) m_pk_list+= ", "; - m_pk_list+= "\""; - m_pk_list+= key_part->field->field_name.str; - m_pk_list+= "\""; + m_pk_list+= quote_duckdb_identifier(key_part->field->field_name.str, + key_part->field->field_name.length); bitmap_set_bit(&m_pk_bitmap, key_part->field->field_index); } @@ -259,9 +259,8 @@ bool DeltaAppender::Initialize(TABLE *table) { if (i) m_col_list+= ", "; - m_col_list+= "\""; - m_col_list+= table->field[i]->field_name.str; - m_col_list+= "\""; + m_col_list+= quote_duckdb_identifier(table->field[i]->field_name.str, + table->field[i]->field_name.length); } } else @@ -479,8 +478,8 @@ static void appendSelectQuery(std::stringstream &ss, ss << "SELECT UNNEST(r) FROM (SELECT LAST(ROW(" << select_list << ") ORDER BY \"#mdb_row_no\") AS r, " "LAST(\"#mdb_delete_flag\" ORDER BY \"#mdb_row_no\") AS " - "\"#mdb_delete_flag\" FROM main.\"" - << table_name << "\" GROUP BY " << pk_list << ")"; + "\"#mdb_delete_flag\" FROM main." + << quote_duckdb_identifier(table_name) << " GROUP BY " << pk_list << ")"; if (!delete_flag) ss << " WHERE \"#mdb_delete_flag\" = " << delete_flag; } @@ -488,20 +487,21 @@ static void appendSelectQuery(std::stringstream &ss, void DeltaAppender::generateQuery(std::stringstream &ss, bool delete_flag) { ss.str(""); - ss << "USE \"" << m_schema_name << "\"; "; + ss << "USE " << quote_duckdb_identifier(m_schema_name) << "; "; if (!delete_flag) { - ss << "INSERT INTO \"" << m_schema_name << "\".\"" << m_table_name - << "\" "; + ss << "INSERT INTO " << quote_duckdb_identifier(m_schema_name) << "." + << quote_duckdb_identifier(m_table_name) << " "; appendSelectQuery(ss, m_col_list, m_pk_list, m_tmp_table_name, delete_flag); ss << ";"; } else { - ss << "DELETE FROM \"" << m_schema_name << "\".\"" << m_table_name - << "\" WHERE (" << m_pk_list << ") IN ("; + ss << "DELETE FROM " << quote_duckdb_identifier(m_schema_name) << "." + << quote_duckdb_identifier(m_table_name) << " WHERE (" << m_pk_list + << ") IN ("; appendSelectQuery(ss, m_pk_list, m_pk_list, m_tmp_table_name, delete_flag); ss << ");"; } @@ -532,7 +532,7 @@ bool DeltaAppender::flush(bool idempotent_flag) } ss.str(""); - ss << "DROP TABLE main.\"" << m_tmp_table_name << "\""; + ss << "DROP TABLE main." << quote_duckdb_identifier(m_tmp_table_name); auto ret= myduck::duckdb_query(*m_con, ss.str()); if (ret->HasError()) return true; @@ -553,7 +553,8 @@ void DeltaAppender::cleanup() { my_bitmap_free(&m_pk_bitmap); std::stringstream ss; - ss << "DROP TABLE IF EXISTS main.\"" << m_tmp_table_name << "\";"; + ss << "DROP TABLE IF EXISTS main." << quote_duckdb_identifier(m_tmp_table_name) + << ";"; myduck::duckdb_query(*m_con, ss.str()); } } diff --git a/storage/duckdb/runtime/duckdb_context.cc b/storage/duckdb/runtime/duckdb_context.cc index 9d1f05557c2..03d8da01ddc 100644 --- a/storage/duckdb/runtime/duckdb_context.cc +++ b/storage/duckdb/runtime/duckdb_context.cc @@ -65,8 +65,9 @@ void DuckdbThdContext::config_duckdb_env(const std::string &schema) if (schema.empty() || schema == m_current_schema) return; - std::string sql1= "CREATE SCHEMA IF NOT EXISTS \"" + schema + "\""; - std::string sql2= "USE \"" + schema + "\""; + std::string sql1= "CREATE SCHEMA IF NOT EXISTS " + + quote_duckdb_identifier(schema); + std::string sql2= "USE " + quote_duckdb_identifier(schema); m_current_schema= schema; for (auto &sql : {sql1, sql2}) diff --git a/storage/duckdb/runtime/duckdb_query.cc b/storage/duckdb/runtime/duckdb_query.cc index 9c177679a01..2b1236b52d5 100644 --- a/storage/duckdb/runtime/duckdb_query.cc +++ b/storage/duckdb/runtime/duckdb_query.cc @@ -30,17 +30,184 @@ #include "duckdb_manager.h" #include "duckdb_log.h" +#include + extern handlerton *duckdb_hton; namespace myduck { +SqlRegionType scan_sql_region(const std::string &sql, size_t start, + bool backslash_escapes, size_t &end) +{ + end= start; + if (start >= sql.size()) + return SqlRegionType::NONE; + + char c= sql[start]; + if (c == '/' && start + 1 < sql.size() && sql[start + 1] == '*') + { + size_t close= sql.find("*/", start + 2); + if (close == std::string::npos) + { + end= sql.size(); + return SqlRegionType::UNTERMINATED; + } + end= close + 2; + return SqlRegionType::COMMENT; + } + + if (c == '#' || + (c == '-' && start + 1 < sql.size() && sql[start + 1] == '-' && + (start + 2 == sql.size() || + isspace(static_cast(sql[start + 2]))))) + { + size_t newline= sql.find('\n', start + (c == '#' ? 1 : 2)); + end= newline == std::string::npos ? sql.size() : newline + 1; + return SqlRegionType::COMMENT; + } + + if (c != '\'' && c != '"' && c != '`') + return SqlRegionType::NONE; + + for (size_t i= start + 1; i < sql.size(); i++) + { + if (sql[i] == '\\' && backslash_escapes && i + 1 < sql.size()) + { + i++; + continue; + } + if (sql[i] != c) + continue; + if (i + 1 < sql.size() && sql[i + 1] == c) + { + i++; + continue; + } + end= i + 1; + return SqlRegionType::QUOTED; + } + + end= sql.size(); + return SqlRegionType::UNTERMINATED; +} + +bool mariadb_query_has_unsafe_quote_escape(THD *thd, const char *query, + size_t length) +{ + if (!thd->backslash_escapes() || length == 0) + return false; + + const std::string sql(query, length); + const bool ansi_quotes= thd->variables.sql_mode & MODE_ANSI_QUOTES; + for (size_t i= 0; i < sql.size();) + { + size_t duckdb_end; + SqlRegionType duckdb_region= scan_sql_region(sql, i, false, duckdb_end); + if (duckdb_region == SqlRegionType::COMMENT) + { + i= duckdb_end; + continue; + } + + const bool string_literal= + sql[i] == '\'' || (sql[i] == '"' && !ansi_quotes); + if (string_literal) + { + size_t mariadb_end; + SqlRegionType mariadb_region= + scan_sql_region(sql, i, true, mariadb_end); + if (mariadb_region != duckdb_region || mariadb_end != duckdb_end) + return true; + i= mariadb_end; + continue; + } + + if (duckdb_region == SqlRegionType::QUOTED) + i= duckdb_end; + else if (duckdb_region == SqlRegionType::UNTERMINATED) + return true; + else + i++; + } + return false; +} + +/* + Convert MariaDB's printed SQL (backtick-quoted identifiers) into DuckDB SQL + (double-quoted identifiers). + + MariaDB delimits identifiers with backticks and doubles an embedded backtick; + DuckDB delimits with double quotes and doubles an embedded double quote. A + naive character-by-character swap breaks identifiers that contain a double + quote (MDEV-40653) and also corrupts backticks that appear inside string + literals. Walk the string instead: copy string literals and already + double-quoted identifiers verbatim, and rewrite only backtick-delimited + identifiers, escaping any embedded double quote. +*/ static std::string backticks_to_double_quotes(const std::string &sql) { - std::string out(sql); - for (auto &ch : out) - if (ch == '`') - ch= '"'; + std::string out; + out.reserve(sql.size()); + const size_t n= sql.size(); + size_t i= 0; + + while (i < n) + { + char c= sql[i]; + size_t end; + SqlRegionType region= scan_sql_region(sql, i, false, end); + + if (region == SqlRegionType::COMMENT || + region == SqlRegionType::UNTERMINATED) + { + out.append(sql, i, end - i); + i= end; + continue; + } + + /* Single-quoted string literal: copy verbatim ('' and \' escapes). */ + if (region == SqlRegionType::QUOTED && c == '\'') + { + out.append(sql, i, end - i); + i= end; + continue; + } + + /* Already double-quoted identifier: copy verbatim ("" escape). */ + if (region == SqlRegionType::QUOTED && c == '"') + { + out.append(sql, i, end - i); + i= end; + continue; + } + + /* Backtick identifier: rewrite as a double-quoted identifier. */ + if (region == SqlRegionType::QUOTED && c == '`') + { + out.push_back('"'); + for (i++; i + 1 < end; i++) + { + char d= sql[i]; + if (d == '`' && i + 2 < end && sql[i + 1] == '`') + { + out.push_back('`'); + i++; + continue; + } + if (d == '"') + out.push_back('"'); /* escape " inside a DuckDB identifier */ + out.push_back(d); + } + out.push_back('"'); + i= end; + continue; + } + + out.push_back(c); + i++; + } + return out; } @@ -116,6 +283,10 @@ static std::string get_thd_schema(THD *thd) duckdb::unique_ptr duckdb_query(THD *thd, const std::string &query, bool need_config) { + if (mariadb_query_has_unsafe_quote_escape(thd, query.data(), query.size())) + return duckdb::make_uniq( + duckdb::ErrorData("Unsafe MariaDB backslash quote escape in forwarded SQL")); + auto *ctx= static_cast(thd_get_ha_data(thd, duckdb_hton)); if (!ctx) @@ -136,6 +307,10 @@ duckdb_query(THD *thd, const std::string &query, bool need_config) duckdb::unique_ptr duckdb_stream_query(THD *thd, const std::string &query, bool need_config) { + if (mariadb_query_has_unsafe_quote_escape(thd, query.data(), query.size())) + return duckdb::make_uniq( + duckdb::ErrorData("Unsafe MariaDB backslash quote escape in forwarded SQL")); + auto *ctx= static_cast(thd_get_ha_data(thd, duckdb_hton)); if (!ctx) diff --git a/storage/duckdb/runtime/duckdb_query.h b/storage/duckdb/runtime/duckdb_query.h index 21f8cd12f94..6ec9db6da78 100644 --- a/storage/duckdb/runtime/duckdb_query.h +++ b/storage/duckdb/runtime/duckdb_query.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -31,6 +32,20 @@ class THD; namespace myduck { +enum class SqlRegionType +{ + NONE, + COMMENT, + QUOTED, + UNTERMINATED +}; + +SqlRegionType scan_sql_region(const std::string &sql, size_t start, + bool backslash_escapes, size_t &end); + +bool mariadb_query_has_unsafe_quote_escape(THD *thd, const char *query, + size_t length); + duckdb::unique_ptr duckdb_query(duckdb::Connection &connection, const std::string &query); From d8bb2d50c1c4eb356e349defc04b3ec83223de0c Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 7 Aug 2026 17:43:00 +0100 Subject: [PATCH 087/150] fix: hardening against unknown SQL injection vectors running DuckDB. --- storage/duckdb/runtime/duckdb_query.cc | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/storage/duckdb/runtime/duckdb_query.cc b/storage/duckdb/runtime/duckdb_query.cc index 2b1236b52d5..a3d9030e3fe 100644 --- a/storage/duckdb/runtime/duckdb_query.cc +++ b/storage/duckdb/runtime/duckdb_query.cc @@ -26,6 +26,7 @@ #include "duckdb_query.h" #include "duckdb/common/exception.hpp" +#include "duckdb/main/pending_query_result.hpp" #include "duckdb_context.h" #include "duckdb_manager.h" #include "duckdb_log.h" @@ -244,8 +245,9 @@ duckdb_query(duckdb::Connection &connection, const std::string &query) } } -duckdb::unique_ptr -duckdb_stream_query(duckdb::Connection &connection, const std::string &query) +static duckdb::unique_ptr +duckdb_pending_query(duckdb::Connection &connection, const std::string &query, + duckdb::QueryResultOutputType output_type) { const std::string q= backticks_to_double_quotes(query); @@ -254,7 +256,13 @@ duckdb_stream_query(duckdb::Connection &connection, const std::string &query) try { - auto res= connection.SendQuery(q, duckdb::QueryResultOutputType::ALLOW_STREAMING); + auto pending= connection.PendingQuery(q, output_type); + duckdb::unique_ptr res; + if (pending->HasError()) + res= duckdb::make_uniq( + pending->GetErrorObject()); + else + res= pending->Execute(); if ((myduck::duckdb_log_options & LOG_DUCKDB_QUERY_RESULT) && res->HasError()) @@ -273,6 +281,24 @@ duckdb_stream_query(duckdb::Connection &connection, const std::string &query) } } +static duckdb::unique_ptr +duckdb_query_single(duckdb::Connection &connection, const std::string &query) +{ + auto res= duckdb_pending_query( + connection, query, duckdb::QueryResultOutputType::FORCE_MATERIALIZED); + DBUG_ASSERT(res->type == duckdb::QueryResultType::MATERIALIZED_RESULT); + return duckdb::unique_ptr_cast( + std::move(res)); +} + +duckdb::unique_ptr +duckdb_stream_query(duckdb::Connection &connection, const std::string &query) +{ + return duckdb_pending_query( + connection, query, duckdb::QueryResultOutputType::ALLOW_STREAMING); +} + static std::string get_thd_schema(THD *thd) { if (thd->db.str && thd->db.length > 0) @@ -301,7 +327,7 @@ duckdb_query(THD *thd, const std::string &query, bool need_config) ctx->config_duckdb_session(thd); } - return duckdb_query(ctx->get_connection(), query); + return duckdb_query_single(ctx->get_connection(), query); } duckdb::unique_ptr From 16ddaf82e24605aedbb543bf2dd39312553c7661 Mon Sep 17 00:00:00 2001 From: "aleksei.bukhalov" Date: Fri, 7 Aug 2026 15:58:24 +0000 Subject: [PATCH 088/150] docs: add public dataset tutorials for the DuckDB engine Add two step-by-step tutorials: - NYC Taxi Trips: load TLC Parquet via run_in_duckdb() + read_parquet(), keep the zone lookup in InnoDB, then join the two engines in a plain SELECT. Follows the structure of the ClickHouse taxi tutorial. - OWID CO2 Emissions: load a commit-pinned CSV revision, run aggregations and a LAG() window function. Both tutorials point at security-model.md before telling the reader to enable duckdb_allow_run_in_duckdb, and both note that ordinary INSERT/UPDATE/DELETE also work on ENGINE=DuckDB tables so that run_in_duckdb() does not look like the only write path. Also documents TIMESTAMPDIFF in mariadb-duckdb-incompatibilities.md and adds a tutorials section to the README. Every SQL block and every expected output was extracted from the markdown, executed, and compared cell by cell on MariaDB 11.4.13 built from this branch; both data sets are pinned so results stay reproducible. --- storage/duckdb/README.md | 7 + .../docs/mariadb-duckdb-incompatibilities.md | 6 + .../duckdb/docs/tutorials/nyc-taxi-trips.md | 380 ++++++++++++++++++ .../docs/tutorials/owid-co2-emissions.md | 219 ++++++++++ 4 files changed, 612 insertions(+) create mode 100644 storage/duckdb/docs/tutorials/nyc-taxi-trips.md create mode 100644 storage/duckdb/docs/tutorials/owid-co2-emissions.md diff --git a/storage/duckdb/README.md b/storage/duckdb/README.md index cfdf8eb84ee..842863bc097 100644 --- a/storage/duckdb/README.md +++ b/storage/duckdb/README.md @@ -43,6 +43,13 @@ TPC-H Scale Factor 10 (~11 GB raw data, 86.6M rows total, ~60M in `lineitem`). Each query also pays a small fixed cost (~40 ms) for client connect and pushdown setup — noticeable on the cheapest queries, negligible on the heavy analytical ones. See [`docs/tpch_sf10_query_benchmark.md`](docs/tpch_sf10_query_benchmark.md) and [`docs/tpch_sf10_ingestion_benchmark.md`](docs/tpch_sf10_ingestion_benchmark.md) for full methodology. +## Tutorials + +Step-by-step guides for loading and analyzing open datasets with the DuckDB storage engine: + +- [NYC Taxi Trips](docs/tutorials/nyc-taxi-trips.md) — loading Parquet via `run_in_duckdb()`, then cross-engine joins between `ENGINE=DuckDB` and `ENGINE=InnoDB` tables. Follows the ClickHouse taxi tutorial. +- [OWID CO₂ Emissions](docs/tutorials/owid-co2-emissions.md) — loading a CSV dataset and running aggregations and window functions. + ## How It Works DuckDB is an in-process analytical database. Its performance rests on three pillars: diff --git a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md index 8288215bf56..512488a5b7d 100644 --- a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md +++ b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md @@ -63,6 +63,12 @@ MariaDB function semantics differ from DuckDB in several areas. These are handle | `POWER()` | `pow()` | Supported natively | | `SUBSTRING()` | `substr()` | Supported natively | +### Not translated by the pushdown layer + +| Function | Issue | Workaround | +|---|---|---| +| `TIMESTAMPDIFF(unit, dt1, dt2)` | The `unit` keyword is not bindable, so the call reaches DuckDB as an unknown `timestampdiff` function and the query fails. DuckDB's own `date_diff()` takes the unit as a string and is not a drop-in match | Use `UNIX_TIMESTAMP()` arithmetic, e.g. `FLOOR((UNIX_TIMESTAMP(dt2) - UNIX_TIMESTAMP(dt1)) / 600) * 10` for minute buckets | + ### Potentially incompatible (not yet triggered) | User writes | MariaDB canonical | DuckDB status | diff --git a/storage/duckdb/docs/tutorials/nyc-taxi-trips.md b/storage/duckdb/docs/tutorials/nyc-taxi-trips.md new file mode 100644 index 00000000000..dc459a2482e --- /dev/null +++ b/storage/duckdb/docs/tutorials/nyc-taxi-trips.md @@ -0,0 +1,380 @@ +# MariaDB DuckDB Tutorial: NYC Taxi Trips + +## Overview + +This tutorial follows the [ClickHouse taxi tutorial][clickhouse-tutorial] and its +[pg_clickhouse port][pg-clickhouse-tutorial], but runs everything on MariaDB with the DuckDB +storage engine. The trip data goes into an `ENGINE=DuckDB` table, the taxi zone lookup goes into an +`ENGINE=InnoDB` table, and a single MariaDB `SELECT` joins the two. + +The data is published by the [NYC Taxi and Limousine Commission][tlc-data] and is subject to the +[NYC Open Data terms of use][nyc-open-data]. The TLC notes that the trip records come from +authorised technology providers rather than from the TLC itself, and makes no accuracy guarantees. + +## Connect to MariaDB + +The DuckDB plugin must be loaded and `run_in_duckdb()` must be enabled: + +```ini +[mysqld] +plugin-maturity=gamma +plugin-load-add=ha_duckdb.so +duckdb-allow-run-in-duckdb=ON +``` + +See the [DuckDB storage engine README](../../README.md#building) for build and installation +instructions. + +> **Note:** `run_in_duckdb()` is OFF by default and requires the `SUPER` privilege. It executes +> arbitrary DuckDB SQL in-process as the server OS user, and DuckDB applies no access control of its +> own. Enable it only on a host you control, and read +> [`security-model.md`](../security-model.md) before using it in a shared or multi-tenant +> deployment. + +Start the client with UTF-8 and local file loading enabled: + +```sh +mariadb --default-character-set=utf8mb4 --local-infile=1 +``` + +Check that the engine, `run_in_duckdb()`, and local file loading are available: + +```sql +SHOW ENGINES; +SELECT @@duckdb_allow_run_in_duckdb; +SHOW VARIABLES LIKE 'local_infile'; +``` + +`SHOW ENGINES` should list `DUCKDB` with `Support` set to `YES`, +`duckdb_allow_run_in_duckdb` should be `1`, and `local_infile` should be `ON`. + +## Download the Data Set + +Run these commands on the MariaDB server host. DuckDB reads the Parquet file inside the server +process, so the account running `mariadbd` must be able to read it. The lookup CSV is loaded from +the client host via `LOAD DATA LOCAL INFILE`. + +Take one month of Yellow Taxi trips plus the taxi zone lookup: + +```sh +curl --fail --location -o /tmp/yellow_tripdata_2024-01.parquet \ + https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet + +curl --fail --location -o /tmp/taxi_zone_lookup.csv \ + https://d37ci6vzurychx.cloudfront.net/misc/taxi_zone_lookup.csv +``` + +The Parquet file is about 48 MiB and holds 2,964,624 trips. The lookup CSV holds 265 zones. + +## Create a Table + +```sql +CREATE DATABASE IF NOT EXISTS taxi; +USE taxi; + +CREATE TABLE trips ( + trip_id BIGINT NOT NULL, + vendor_id INT, + pickup_datetime DATETIME, + dropoff_datetime DATETIME, + passenger_count INT, + trip_distance DOUBLE, + pu_location_id INT, + do_location_id INT, + payment_type INT, + fare_amount DECIMAL(10,2), + tip_amount DECIMAL(10,2), + total_amount DECIMAL(10,2), + PRIMARY KEY (trip_id) +) ENGINE=DuckDB DEFAULT CHARSET=utf8mb4; +``` + +With the default `duckdb_require_primary_key=ON`, the table needs a primary key. The TLC files have +no trip identifier, so the load below generates `trip_id`. DuckDB table columns accept `utf8`, +`utf8mb3`, `utf8mb4`, and `ascii`; this example uses `utf8mb4`. + +## Add the Data Set + +`run_in_duckdb()` sends the SQL string to DuckDB's vectorized engine for execution and returns DuckDB's own textual rendering of the result as a single string value. DuckDB reads the Parquet file directly, so the load is a single statement: + +```sql +SELECT run_in_duckdb('INSERT INTO taxi.trips + (trip_id, vendor_id, pickup_datetime, dropoff_datetime, passenger_count, + trip_distance, pu_location_id, do_location_id, payment_type, + fare_amount, tip_amount, total_amount) +SELECT + ROW_NUMBER() OVER () AS trip_id, + "VendorID", tpep_pickup_datetime, tpep_dropoff_datetime, passenger_count, + trip_distance, "PULocationID", "DOLocationID", payment_type, + fare_amount, tip_amount, total_amount +FROM read_parquet(''/tmp/yellow_tripdata_2024-01.parquet'')'); +``` + +The TLC column names are mixed case, so they appear as double-quoted DuckDB identifiers. Only the +twelve selected columns are read from the file. + +`run_in_duckdb()` is the bulk-load path, not the only write path: ordinary `INSERT`, `UPDATE`, and +`DELETE` work against an `ENGINE=DuckDB` table too. With the default `duckdb_dml_in_batch=ON` those +writes are buffered and applied to DuckDB at commit, so a write is not visible to later reads in the +same transaction. This tutorial only reads, so it loads once and queries from there. + +Make sure we can query it: + +```sql +SELECT COUNT(*) AS total_trips FROM trips; +``` + +```text +total_trips +2964624 +``` + +The pickup timestamps are worth a look before trusting any date filter: + +```sql +SELECT MIN(pickup_datetime) AS earliest, MAX(pickup_datetime) AS latest FROM trips; +``` + +```text +earliest latest +2002-12-31 22:59:39 2024-02-01 00:01:15 +``` + +The January 2024 file holds 18 rows with timestamps outside the month, too few to move any of the +averages that follow. The queries that group or rank by time still filter on the month explicitly, +so their buckets cover exactly the period they claim to. + +## Analyze the Data + +Calculate the average tip amount: + +```sql +SELECT ROUND(AVG(tip_amount), 2) AS avg_tip FROM trips; +``` + +```text +avg_tip +3.34 +``` + +Calculate the average cost based on the number of passengers: + +```sql +SELECT passenger_count, + COUNT(*) AS trips, + ROUND(AVG(total_amount), 2) AS avg_total +FROM trips +WHERE passenger_count IS NOT NULL +GROUP BY passenger_count +ORDER BY passenger_count; +``` + +```text +passenger_count trips avg_total +0 31465 25.33 +1 2188739 26.21 +2 405103 29.52 +3 91262 29.14 +4 51974 30.88 +5 33506 26.27 +6 22353 25.80 +7 8 57.74 +8 51 95.67 +9 1 18.45 +``` + +Show the busiest pickup hours: + +```sql +SELECT HOUR(pickup_datetime) AS pickup_hour, + COUNT(*) AS trips, + ROUND(AVG(trip_distance), 2) AS avg_miles +FROM trips +WHERE pickup_datetime >= '2024-01-01' AND pickup_datetime < '2024-02-01' +GROUP BY HOUR(pickup_datetime) +ORDER BY trips DESC +LIMIT 5; +``` + +```text +pickup_hour trips avg_miles +18 212788 2.81 +17 206257 3.01 +16 190201 3.35 +15 189359 3.88 +19 184032 3.11 +``` + +Group trips into ten-minute buckets by duration: + +```sql +SELECT FLOOR((UNIX_TIMESTAMP(dropoff_datetime) - + UNIX_TIMESTAMP(pickup_datetime)) / 600) * 10 AS trip_minutes, + COUNT(*) AS trips, + ROUND(AVG(fare_amount), 2) AS avg_fare, + ROUND(AVG(tip_amount), 2) AS avg_tip +FROM trips +WHERE dropoff_datetime > pickup_datetime +GROUP BY FLOOR((UNIX_TIMESTAMP(dropoff_datetime) - + UNIX_TIMESTAMP(pickup_datetime)) / 600) * 10 +ORDER BY trip_minutes +LIMIT 6; +``` + +```text +trip_minutes trips avg_fare avg_tip +0 1229720 8.77 1.98 +10 1080498 15.77 3.00 +20 382804 29.13 5.06 +30 142500 47.72 7.84 +40 64520 59.82 9.35 +50 33057 65.80 9.64 +``` + +`TIMESTAMPDIFF()` is not pushed down — its `unit` keyword is not bindable, so the call reaches +DuckDB as an unknown function and fails on `ENGINE=DuckDB` tables. This query uses +`UNIX_TIMESTAMP()` arithmetic instead. See +[`mariadb-duckdb-incompatibilities.md`](../mariadb-duckdb-incompatibilities.md) for the full list of +pushdown-incompatible functions and syntax. + +## Add the Zone Lookup Table + +The trips carry zone IDs rather than names. The lookup table maps each ID to a borough and zone +name; `132` is JFK Airport and `138` is LaGuardia Airport. Keep it in InnoDB: + +```sql +CREATE TABLE taxi_zones ( + location_id INT PRIMARY KEY, + borough VARCHAR(30), + zone VARCHAR(60), + service_zone VARCHAR(20) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +LOAD DATA LOCAL INFILE '/tmp/taxi_zone_lookup.csv' +INTO TABLE taxi_zones +FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' +LINES TERMINATED BY '\r\n' +IGNORE 1 LINES +(location_id, borough, zone, @service_zone) +SET service_zone = NULLIF(@service_zone, ''); +``` + +The lookup CSV uses CRLF line endings, hence `LINES TERMINATED BY '\r\n'`. + +Confirm the row count and the engines: + +```sql +SELECT COUNT(*) AS zones FROM taxi_zones; +SELECT TABLE_NAME, ENGINE +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = 'taxi' +ORDER BY TABLE_NAME; +``` + +```text +zones +265 + +TABLE_NAME ENGINE +taxi_zones InnoDB +trips DUCKDB +``` + +## Perform a Cross-Engine Join + +The zone lookup is small and changes rarely, so it stays in InnoDB. The trip data is large and +analytical, so it lives in DuckDB. A single MariaDB `SELECT` joins them — no `run_in_duckdb()`, no +data copying, no ETL. The engine pushes the entire query down to DuckDB and streams the InnoDB rows +in on demand. + +Count the trips ending at JFK or LaGuardia, grouped by the borough where the passenger got in: + +```sql +SELECT z.borough, + COUNT(*) AS airport_trips, + ROUND(AVG(t.total_amount), 2) AS avg_total +FROM trips t +JOIN taxi_zones z ON t.pu_location_id = z.location_id +WHERE t.do_location_id IN (132, 138) +GROUP BY z.borough +ORDER BY airport_trips DESC; +``` + +```text +borough airport_trips avg_total +Manhattan 48077 78.09 +Queens 13789 43.41 +Brooklyn 738 60.82 +Unknown 109 64.48 +Bronx 27 63.63 +N/A 21 94.95 +EWR 1 135.18 +Staten Island 1 11.50 +``` + +`trips` stays in DuckDB and `taxi_zones` stays in InnoDB. There is no `run_in_duckdb()` call and no +copy of the data: this is a plain MariaDB `SELECT` across two storage engines. + +To see what MariaDB sent to DuckDB, turn on query logging and run the join again: + +```sql +SET GLOBAL duckdb_log_options = 'DUCKDB_QUERY'; +``` + +The server error log then shows the pushed-down statement and the external table: + +```text +[Note] DuckDB: cross-engine pushdown with 1 external table(s) +[Note] DuckDB query: SELECT z.borough, COUNT(*) AS airport_trips, ROUND(AVG(t.total_amount), 2) ... +``` + +DuckDB received the whole query, joins and aggregates included, and MariaDB streamed the InnoDB +rows into it. Turn logging off again: + +```sql +SET GLOBAL duckdb_log_options = ''; +``` + +One more join, this time ranking pickup zones for the month: + +```sql +SELECT z.zone, + COUNT(*) AS pickups, + ROUND(AVG(t.tip_amount), 2) AS avg_tip +FROM trips t +JOIN taxi_zones z ON t.pu_location_id = z.location_id +WHERE t.pickup_datetime >= '2024-01-01' AND t.pickup_datetime < '2024-02-01' +GROUP BY z.zone +ORDER BY pickups DESC +LIMIT 10; +``` + +```text +zone pickups avg_tip +JFK Airport 145240 8.86 +Midtown Center 143469 3.08 +Upper East Side South 142707 2.59 +Upper East Side North 136464 2.64 +Midtown East 106717 3.02 +Times Sq/Theatre District 106324 3.30 +Penn Station/Madison Sq West 104522 3.09 +Lincoln Square East 104080 2.79 +LaGuardia Airport 89530 8.67 +Upper West Side South 88474 2.79 +``` + +Airport pickups tip roughly three times as much as midtown ones. + +## Clean Up + +```sql +DROP DATABASE taxi; +``` + +The [OWID CO₂ tutorial](owid-co2-emissions.md) loads CSV instead of Parquet and computes +year-over-year change with a `LAG()` window function. + +[clickhouse-tutorial]: https://clickhouse.com/docs/tutorial +[nyc-open-data]: https://opendata.cityofnewyork.us/overview/ +[pg-clickhouse-tutorial]: https://github.com/ClickHouse/pg_clickhouse/blob/b2bacc51e1205f840046fe9f48f806baa6ae169e/doc/tutorial.md +[tlc-data]: https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page diff --git a/storage/duckdb/docs/tutorials/owid-co2-emissions.md b/storage/duckdb/docs/tutorials/owid-co2-emissions.md new file mode 100644 index 00000000000..5b135cb3d15 --- /dev/null +++ b/storage/duckdb/docs/tutorials/owid-co2-emissions.md @@ -0,0 +1,219 @@ +# MariaDB DuckDB Tutorial: OWID CO₂ Emissions + +## Overview + +This tutorial loads the [Our World in Data CO₂ and Greenhouse Gas Emissions dataset][owid-data] +into an `ENGINE=DuckDB` table and analyzes it through the regular `mariadb` client. + +OWID publishes its work under [Creative Commons BY 4.0][owid-license] and notes that data from +third-party providers remains subject to the providers' license terms. + +## Connect to MariaDB + +The DuckDB plugin must be loaded and `run_in_duckdb()` must be enabled: + +```ini +[mysqld] +plugin-maturity=gamma +plugin-load-add=ha_duckdb.so +duckdb-allow-run-in-duckdb=ON +``` + +See the [DuckDB storage engine README](../../README.md#building) for build and installation +instructions. + +> **Note:** `run_in_duckdb()` is OFF by default and requires the `SUPER` privilege. It executes +> arbitrary DuckDB SQL in-process as the server OS user, and DuckDB applies no access control of its +> own. Enable it only on a host you control, and read +> [`security-model.md`](../security-model.md) before using it in a shared or multi-tenant +> deployment. + +Start the client with a UTF-8 character set: + +```sh +mariadb --default-character-set=utf8mb4 +``` + +Check that the engine and `run_in_duckdb()` are available: + +```sql +SHOW ENGINES; +SELECT @@duckdb_allow_run_in_duckdb; +``` + +`SHOW ENGINES` should list `DUCKDB` with `Support` set to `YES`. The variable should be `1`. + +## Download the Data Set + +Download a copy pinned to a Git commit so that the results below remain reproducible: + +```sh +curl --fail --location -o /tmp/owid-co2-data.csv \ + https://raw.githubusercontent.com/owid/co2-data/382ee6c662b0ece26e111f263b44c029afad7787/owid-co2-data.csv +``` + +Run this command on the MariaDB server host and make sure the account running `mariadbd` can read +the file. + +## Create a Table + +Create a database and a DuckDB table for a subset of the CSV columns: + +```sql +CREATE DATABASE IF NOT EXISTS owid; +USE owid; + +CREATE TABLE co2_emissions ( + country VARCHAR(60) NOT NULL, + year INT NOT NULL, + iso_code VARCHAR(10), + population BIGINT, + gdp DOUBLE, + co2 DOUBLE, + co2_growth_prct DOUBLE, + co2_per_capita DOUBLE, + coal_co2 DOUBLE, + gas_co2 DOUBLE, + oil_co2 DOUBLE, + cement_co2 DOUBLE, + cumulative_co2 DOUBLE, + share_global_co2 DOUBLE, + share_global_cumulative_co2 DOUBLE, + temperature_change_from_co2 DOUBLE, + PRIMARY KEY (country, year) +) ENGINE=DuckDB DEFAULT CHARSET=utf8mb4; +``` + +With the default `duckdb_require_primary_key=ON`, the table needs a primary key. DuckDB table +columns accept `utf8`, `utf8mb3`, `utf8mb4`, and `ascii`; this example uses `utf8mb4`. +`VARCHAR(60)` keeps the `country` key part below MariaDB's 255-byte key length limit (60 × 4 bytes). + +## Add the Data Set + +`run_in_duckdb()` sends the SQL string to DuckDB's vectorized engine for execution and returns DuckDB's own textual rendering of the result as a single string value. Use DuckDB's CSV reader to load the file inside the server process: + +```sql +SELECT run_in_duckdb('INSERT INTO owid.co2_emissions + (country, year, iso_code, population, gdp, co2, co2_growth_prct, + co2_per_capita, coal_co2, gas_co2, oil_co2, cement_co2, + cumulative_co2, share_global_co2, share_global_cumulative_co2, + temperature_change_from_co2) +SELECT + country, year, iso_code, population, gdp, co2, co2_growth_prct, + co2_per_capita, coal_co2, gas_co2, oil_co2, cement_co2, + cumulative_co2, share_global_co2, share_global_cumulative_co2, + temperature_change_from_co2 +FROM read_csv_auto(''/tmp/owid-co2-data.csv'')'); +``` + +`run_in_duckdb()` is the bulk-load path, not the only write path: ordinary `INSERT`, `UPDATE`, and +`DELETE` work against an `ENGINE=DuckDB` table too. With the default `duckdb_dml_in_batch=ON` those +writes are buffered and applied to DuckDB at commit, so a write is not visible to later reads in the +same transaction. This tutorial only reads, so it loads once and queries from there. + +Make sure the data was imported: + +```sql +SELECT COUNT(*) AS total_rows FROM co2_emissions; +SELECT MIN(year) AS earliest, + MAX(year) AS latest, + COUNT(DISTINCT country) AS locations +FROM co2_emissions; +``` + +```text +total_rows +50411 + +earliest latest locations +1750 2024 254 +``` + +## Analyze the Data + +Start with the ten largest country-level emitters in the latest year: + +```sql +SELECT country, + ROUND(co2, 1) AS co2_mt, + ROUND(co2_per_capita, 2) AS co2_per_capita_t +FROM co2_emissions +WHERE year = (SELECT MAX(year) FROM co2_emissions WHERE co2 IS NOT NULL) + AND co2 IS NOT NULL + AND iso_code IS NOT NULL +ORDER BY co2 DESC +LIMIT 10; +``` + +```text +country co2_mt co2_per_capita_t +China 12289.0 8.66 +United States 4904.1 14.20 +India 3193.5 2.20 +Russia 1780.5 12.30 +Japan 961.9 7.77 +Indonesia 812.2 2.87 +Iran 792.6 8.66 +Saudi Arabia 692.1 20.38 +South Korea 583.7 11.29 +Germany 572.3 6.77 +``` + +`iso_code IS NOT NULL` removes aggregate rows such as `World` and `Europe` from the ranking. + +Next, group country-level emissions by decade: + +```sql +SELECT FLOOR(year / 10) * 10 AS decade, + ROUND(SUM(co2), 1) AS total_co2_mt +FROM co2_emissions +WHERE iso_code IS NOT NULL + AND co2 IS NOT NULL +GROUP BY FLOOR(year / 10) * 10 +ORDER BY decade DESC +LIMIT 5; +``` + +```text +decade total_co2_mt +2020 181243.9 +2010 342507.4 +2000 279781.4 +1990 229151.2 +1980 196853.2 +``` + +Use `LAG()` to calculate China's annual change: + +```sql +SELECT year, + ROUND(co2, 1) AS co2_mt, + ROUND((co2 - LAG(co2) OVER (ORDER BY year)) / + LAG(co2) OVER (ORDER BY year) * 100, 2) AS yoy_growth_pct +FROM co2_emissions +WHERE country = 'China' + AND co2 IS NOT NULL +ORDER BY year DESC +LIMIT 5; +``` + +```text +year co2_mt yoy_growth_pct +2024 12289.0 0.96 +2023 12172.0 3.93 +2022 11711.8 3.79 +2021 11284.4 3.56 +2020 10896.5 1.71 +``` + +## Clean Up + +```sql +DROP DATABASE owid; +``` + +The [NYC taxi tutorial](nyc-taxi-trips.md) loads Parquet instead of CSV and joins DuckDB data with +an InnoDB reference table. + +[owid-data]: https://github.com/owid/co2-data +[owid-license]: https://github.com/owid/co2-data/tree/382ee6c662b0ece26e111f263b44c029afad7787#license From ee74a71df6d7583df778916dc35b0bb4f778f454 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 7 Aug 2026 20:26:14 +0200 Subject: [PATCH 089/150] mariadb-test: don't use strlen/strend, support lines with embedded \0 add a test --- client/mysqltest.cc | 54 +++++++++++++++++++------------- mysql-test/main/mysqltest.result | 7 ++++- mysql-test/main/mysqltest.test | 7 +++-- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 4eddd590aef..68fc2ad5f35 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -206,7 +206,7 @@ static char TMPDIR[FN_REFLEN]; static char global_subst_from[200]; static char global_subst_to[200]; static char *global_subst= NULL; -static char *read_command_buf= NULL; +static char *read_command_buf= NULL, *read_command_buf_end; static MEM_ROOT require_file_root; static const my_bool my_true= 1; static const my_bool my_false= 0; @@ -9635,7 +9635,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", delimiter, cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if ((c == '{' && (!my_strnncoll_simple(charset_info, (const uchar*) "while", 5, @@ -9648,7 +9648,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '\'' || c == '"' || c == '`') { @@ -9668,7 +9668,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found newline in comment at line: %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } break; @@ -9688,7 +9688,7 @@ int read_line() DBUG_PRINT("info", ("Found two new lines in a row")); *p++= c; *p= 0; - DBUG_RETURN(0); + goto ret; } /* Query hasn't started yet */ @@ -9705,7 +9705,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", delimiter, cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '}') { @@ -9714,7 +9714,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found '}' in beginning of a line at line: %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '\'' || c == '"' || c == '`') { @@ -9773,6 +9773,8 @@ int read_line() } } } +ret: + read_command_buf_end= p; DBUG_RETURN(0); } @@ -9788,14 +9790,13 @@ int read_line() */ -void convert_to_format_v1(char* query) +void convert_to_format_v1(char* query, char **end) { int last_c_was_quote= 0; char *p= query, *to= query; - char *end= strend(query); char last_c; - while (p <= end) + while (p <= *end) { if (*p == '\n' && !last_c_was_quote) { @@ -9826,6 +9827,7 @@ void convert_to_format_v1(char* query) last_c_was_quote= 0; } } + *end= to; } @@ -9942,9 +9944,9 @@ int read_command(struct st_command** command_ptr) } if (opt_result_format_version == 1) - convert_to_format_v1(read_command_buf); + convert_to_format_v1(read_command_buf, &read_command_buf_end); - char *p= read_command_buf; + char *p= read_command_buf, *end= read_command_buf_end; DBUG_PRINT("info", ("query: '%s'", read_command_buf)); if (*p == '#') { @@ -9961,29 +9963,32 @@ int read_command(struct st_command** command_ptr) } /* Skip leading spaces */ - while (*p && my_isspace(charset_info, *p)) + while (p < end && my_isspace(charset_info, *p)) p++; - if (!(command->query_buf= command->query= my_strdup(PSI_NOT_INSTRUMENTED, p, MYF(MY_WME)))) + if (!(command->query_buf= command->query= + (char*)my_memdup(PSI_NOT_INSTRUMENTED, p, end - p, MYF(MY_WME)))) die("Out of memory"); + command->query_len= (int)(end - p - 1); + command->end= command->query + command->query_len; + p= command->query; + end= command->end; /* Calculate first word length(the command), terminated - by 'space' , '(' or 'delimiter' */ - p= command->query; - while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) + by 'space' , '(' or 'delimiter' + */ + while (p < end && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) p++; command->first_word_len= (uint) (p - command->query); DBUG_PRINT("info", ("first_word: %.*s", command->first_word_len, command->query)); /* Skip spaces between command and first argument */ - while (*p && my_isspace(charset_info, *p)) + while (p < end && my_isspace(charset_info, *p)) p++; command->first_argument= p; - command->end= strend(command->query); - command->query_len= (int)(command->end - command->query); parser.read_lines++; DBUG_RETURN(0); } @@ -12282,7 +12287,7 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) else { query = command->query; - query_len = strlen(query); + query_len = command->query_len; } /* @@ -12672,6 +12677,7 @@ void get_command_type(struct st_command* command) if (type == Q_QUERY) { /* Skip the "query" part */ + command->query_len-= (int)(command->first_argument - command->query); command->query= command->first_argument; } } @@ -13247,7 +13253,8 @@ int main(int argc, char **argv) if (command->query == command->query_buf) { /* Skip the first part of command, i.e query_xxx */ - command->query= command->first_argument; + command->query_len-= (int)(command->first_argument - command->query); + command->query= command->first_argument; command->first_word_len= 0; } /* fall through */ @@ -13309,7 +13316,10 @@ int main(int argc, char **argv) /* Remove "send" if this is first iteration */ if (command->query == command->query_buf) + { + command->query_len-= (int)(command->first_argument - command->query); command->query= command->first_argument; + } /* run_query() can execute a query partially, depending on the flags. diff --git a/mysql-test/main/mysqltest.result b/mysql-test/main/mysqltest.result index 46234ba89a2..6e40f7eb4c4 100644 --- a/mysql-test/main/mysqltest.result +++ b/mysql-test/main/mysqltest.result @@ -1025,5 +1025,10 @@ the false block has written nothing mysqltest: At line 1: End of file encountered before 'eval' delimiter was found # Only 'eval' is allowed as the third argument mysqltest: At line 1: Invalid argument 'junk' to 'write_file', only 'eval' is allowed +# +# \0 in a query +# +select hex("12"); +hex("1\x002") +310032 # End of 10.6 tests -End of tests diff --git a/mysql-test/main/mysqltest.test b/mysql-test/main/mysqltest.test index ba01acbf6c6..d2061749140 100644 --- a/mysql-test/main/mysqltest.test +++ b/mysql-test/main/mysqltest.test @@ -3057,9 +3057,12 @@ EOF --error 1 --exec echo "--write_file $file EOF junk" | $MYSQL_TEST 2>&1 ---echo # End of 10.6 tests +--echo # +--echo # \0 in a query +--echo # +select hex("12"); ---echo End of tests +--echo # End of 10.6 tests # Wait till we reached the initial number of concurrent sessions --source include/wait_until_count_sessions.inc From 0931994096b84ecc9ffc8eb3517c7e09e6e8631e Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 7 Aug 2026 21:18:52 +0200 Subject: [PATCH 090/150] MDEV-40670 qc_info OOB read if query contains \0 qc_info cannot parse query cache key, this cannot be done without changing query cache key format. let's limit the (possibly incorrect) db_length to make sure it does not extend beyond the db name end, this way there will be no OOB reads and no information leak. --- mysql-test/suite/plugins/r/qc_info.result | Bin 3894 -> 4262 bytes mysql-test/suite/plugins/t/qc_info.test | Bin 859 -> 1224 bytes plugin/qc_info/qc_info.cc | 1 + 3 files changed, 1 insertion(+) diff --git a/mysql-test/suite/plugins/r/qc_info.result b/mysql-test/suite/plugins/r/qc_info.result index afab7671b5cc18022adb2b47ed1cbc5b089d9d8c..5ad4e13f1e1e10ae7e19c1c67a78a525b29e941a 100644 GIT binary patch delta 319 zcmZXQF-yZh6vtC=um=^~97LXEXrx#!BFPYN5IagYr$RU{?=X11Gr1S3lb@jkzk;(M zxOV85a;ul>Aas2E{{Q!Wyswk@(~qmcew?IfdJ!j|kAt_~Ysf;ly_wvdU-Bg7P|3VB z%EEMd4K<1aN(EJe^$SRANR-CGjI)qQ4+(+9y~eIYk$}?BS;l+>Yw8BO1@J6%gD#3a zbEr`gxJu_z$OnYsC|Ix7Dcq69g59vCD;qP wI&WW}G9fLwvf~cTrQN04E+AvXTc#5~lfIE*tFzi`kmo-BMqBa+K z;GC#Tw@@#@g2@*3Fu8ML6qQ1EnS!@UlPczl3~`cyMv2^jZe+n3pCN=hoAqYRSO}G( z7ci*Js4L3tsds$XqZJF+Bsh~`KfKUBc$Z13X#S5X54OlKsY{>=l6aK%*R<<2usRvo K@$SIA?EDunuxTs+ delta 33 pcmX@Xd7EuR71Lx@CK*m!g_P8^#L}FS$qQN3Cof=%pPb8L1^~fN3oQTu diff --git a/plugin/qc_info/qc_info.cc b/plugin/qc_info/qc_info.cc index a46e2d9fbf2..ff25b946240 100644 --- a/plugin/qc_info/qc_info.cc +++ b/plugin/qc_info/qc_info.cc @@ -233,6 +233,7 @@ static int qc_info_fill_table(THD *thd, TABLE_LIST *tables, compile_time_assert(QUERY_CACHE_DB_LENGTH_SIZE == 2); db= key + statement_text_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE; db_length= uint2korr(db - QUERY_CACHE_DB_LENGTH_SIZE); + set_if_smaller(db_length, (size_t)(key + key_length - QUERY_CACHE_FLAGS_SIZE - db)); table->field[COLUMN_STATEMENT_SCHEMA]->store(db, db_length, scs); From da0beac410cc761be4ee52b90fded6c7fa2f8947 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 5 Jun 2026 11:58:07 +0300 Subject: [PATCH 091/150] MDEV-39858: Reloading COSINE metric index from disk degrades search recall due to abs2 quantization noise When a vector is created in-memory using FVector::create() during normal inserts, its squared magnitude (abs2) under the COSINE metric is hardcoded to 0.5f. However, when the index is reloaded from disk (after a server restart, FLUSH TABLES, or ALTER TABLE), the index uses FVectorNode::load_from_record(). This method reads the stored scale and quantized int16 coordinates from the database record, and runs postprocess(). Inside postprocess(), abs2 is dynamically recomputed using floating-point math: abs2 = subabs2 + scale * scale * dot_product(d, d, vec_len) / 2; Because the coordinates stored on disk are quantized int16 values, this recalculation introduces rounding noise. This affects high dimensions datasets, and it is increasing as M increases. Added hardcoded abs2=0.5 to FVectorNode::load_from_record and removed postprocess() --- sql/vector_mhnsw.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sql/vector_mhnsw.cc b/sql/vector_mhnsw.cc index 14942735e88..d018ffd71f1 100644 --- a/sql/vector_mhnsw.cc +++ b/sql/vector_mhnsw.cc @@ -103,7 +103,8 @@ struct FVector vec->scale= std::nextafter(vec->scale, FLT_MAX); for (size_t i= 0; i < vec_len; i++) vec->dims[i] = static_cast(std::round(get_float(v + i) / vec->scale)); - vec->postprocess(vec_len); + vec->fix_tail(vec_len); + vec->abs2= vec->scale * vec->scale * dot_product(vec->dims, vec->dims, vec_len) / 2; if (metric == COSINE) { if (vec->abs2 > 0.0f) @@ -113,12 +114,6 @@ struct FVector return vec; } - void postprocess(size_t vec_len) - { - fix_tail(vec_len); - abs2= scale * scale * dot_product(dims, dims, vec_len) / 2; - } - #ifdef AVX2_IMPLEMENTATION /************* AVX2 *****************************************************/ static constexpr size_t AVX2_bytes= 256/8; @@ -882,7 +877,12 @@ int FVectorNode::load_from_record(TABLE *graph) return my_errno= HA_ERR_CRASHED; FVector *vec_ptr= FVector::align_ptr(tref() + tref_len()); memcpy(vec_ptr->data(), v->ptr(), v->length()); - vec_ptr->postprocess(ctx->vec_len); + vec_ptr->fix_tail(ctx->vec_len); + if (ctx->metric == COSINE) + vec_ptr->abs2= 0.5f; + else + vec_ptr->abs2= vec_ptr->scale * vec_ptr->scale * + vec_ptr->dot_product(vec_ptr->dims, vec_ptr->dims, ctx->vec_len) / 2; longlong layer= graph->field[FIELD_LAYER]->val_int(); if (layer > 100) // 10e30 nodes at M=2, more at larger M's From f6418ff7b2a0ecbc158f53331735994446bed0ac Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 7 Aug 2026 21:44:28 +0100 Subject: [PATCH 092/150] fix: MDEV-40651 ALTER TABLE and DDL in general is now executed in an atomic manner b/c previously MariaDB DDL triggered multiple autocommit DDL statements in DuckDB some of those can fail leaving table in unsable state. --- storage/duckdb/CLAUDE.md | 13 ++++- storage/duckdb/README.md | 1 + storage/duckdb/convertor/ddl_convertor.cc | 47 +++++++++++++----- storage/duckdb/convertor/ddl_convertor.h | 3 ++ storage/duckdb/docs/architecture.md | 7 ++- storage/duckdb/ha_duckdb.cc | 49 ++++++++++++++----- .../duckdb/r/alter_duckdb_column.result | 4 +- .../mysql-test/duckdb/r/mdev_40651.result | 25 ++++++++++ .../mysql-test/duckdb/t/mdev_40651.test | 24 +++++++++ storage/duckdb/runtime/duckdb_query.cc | 3 +- 10 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/mdev_40651.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40651.test diff --git a/storage/duckdb/CLAUDE.md b/storage/duckdb/CLAUDE.md index b6cea1a72ef..2f1bdb7de75 100644 --- a/storage/duckdb/CLAUDE.md +++ b/storage/duckdb/CLAUDE.md @@ -46,6 +46,8 @@ Tests use MariaDB's MTR (MySQL Test Runner) framework. Test files live in `mysql All engine code is in the `myduck` namespace (except `ha_duckdb` which is in global scope per MariaDB handler convention). +> **Doc convention:** when this file (or a code comment) names a specific API to describe control flow (e.g. `SELECT_LEX::print()`), it must also name the file/function where that API is actually called. If no call site can be pointed to, describe the behavior instead of naming the API — do not imply a code path that isn't there. + ### Key components - **`ha_duckdb`** (`ha_duckdb.cc/h`) — MariaDB `handler` subclass. Entry point for all storage engine operations (open, close, read, write, DDL). Implements row-at-a-time interface for MariaDB, translating to DuckDB batch operations. @@ -66,7 +68,16 @@ All engine code is in the `myduck` namespace (except `ha_duckdb` which is in glo ### SQL generation conventions -All generated SQL must use **double quotes** for identifiers (DuckDB follows SQL standard), not backticks. The `SELECT_LEX::print()` output from MariaDB uses backticks and must be post-processed. See `docs/mariadb-duckdb-incompatibilities.md` for known function name rewrites and type mapping issues. +DuckDB follows the SQL standard and delimits identifiers with **double quotes**, not backticks. + +SQL reaches DuckDB by two routes: + +- **Whole queries are forwarded verbatim.** SELECT and INSERT … SELECT are pushed down as the raw `thd->query()` text — see `extract_source_query()` in `ha_duckdb_pushdown.cc`. The engine does **not** re-print the whole query via `SELECT_LEX::print()`. +- **Only fragments are printed.** `Item`/`COND::print()` is used for per-table WHERE conditions in cross-engine scan (`ha_duckdb_pushdown.cc`) and for DDL default / `nextval` expressions (`ddl_convertor.cc`). DDL/DML convertors also build identifier strings directly from `Field`/`TABLE` metadata. + +Both routes then pass through `backticks_to_double_quotes()` (`runtime/duckdb_query.cc`), which is where identifier requoting actually happens: backtick-delimited identifiers are rewritten to double-quoted ones and any embedded double quote is escaped (MDEV-40653). Raw forwarding additionally goes through `mariadb_query_has_lexical_mismatch()`, which refuses to forward SQL whose backslash-escape semantics differ between MariaDB and DuckDB. + +See `docs/mariadb-duckdb-incompatibilities.md` for known function name rewrites and type mapping issues. ### DuckDB source and patches diff --git a/storage/duckdb/README.md b/storage/duckdb/README.md index 842863bc097..f72cd9aa0f6 100644 --- a/storage/duckdb/README.md +++ b/storage/duckdb/README.md @@ -137,6 +137,7 @@ DuckDB handles the join, aggregation, and sorting; InnoDB rows are produced on d - **Some MariaDB functions are yet not pushdown-compatible** — `GROUP_CONCAT()`, `DATE_FORMAT()`, `JSON_CONTAINS()`, `FOUND_ROWS()`, `LAST_INSERT_ID()`, and a few others have no DuckDB equivalent or differ in syntax. Such queries fall back to MariaDB execution. - **Strict GROUP BY** — DuckDB rejects `SELECT` columns not in `GROUP BY` and not aggregated, even when MariaDB's `sql_mode` allows it. - **XA transactions** — `XA PREPARE` is not supported by the engine. +- **Table partitioning** — `CREATE TABLE ... PARTITION BY` and converting a DuckDB table with `ALTER TABLE ... PARTITION BY` are not supported; the engine declares `HTON_NO_PARTITION`. - **Collations** — MariaDB UCA-based collation rules are approximated via DuckDB's built-in `NOCASE`/`NOACCENT` collations for UTF-8 charsets; non-UTF8 charsets fall back to binary comparison. See [`docs/collation-mapping.md`](docs/collation-mapping.md) for the full mapping and known gaps. - **Cross-engine scan is yet single-threaded** — each external (non-DuckDB) table is produced by a single fiber-driven MariaDB query (`_mdb_scan` reports `MaxThreads() == 1`); only the DuckDB side of the query is parallelized. - **ALTER COLUMN DROP DEFAULT** — not propagated to DuckDB catalog. diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index 2ce9cab6b8f..2432a95389d 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -259,17 +259,19 @@ static std::string autoinc_nextval_expr(const std::string &schema_name, Read the literal default value of a field from the default record and return it as a string suitable for DuckDB SQL. - BIT fields are converted to DuckDB blob literal format: '\xHH...'::BLOB. - Other fields use standard quoted literal format: 'value'. + BIT fields are converted to DuckDB blob literal format, optionally with an + explicit BLOB cast. Other fields use standard quoted literal format. @param field Field whose default value to read (must not be at offset) @param offset Offset from record[0] to default_values (s->default_values - record[0]) + @param cast_bit Add an explicit BLOB cast for BIT fields @return Default value string, or "NULL" if field is null at default record */ static std::string get_field_default_for_duckdb(Field *field, - my_ptrdiff_t offset) + my_ptrdiff_t offset, + bool cast_bit= true) { field->move_field_offset(offset); @@ -295,7 +297,9 @@ static std::string get_field_default_for_duckdb(Field *field, ss << hx; } } - ss << "'::BLOB"; + ss << "'"; + if (cast_bit) + ss << "::BLOB"; default_value= ss.str(); } else @@ -303,7 +307,7 @@ static std::string get_field_default_for_duckdb(Field *field, char buf[MAX_FIELD_WIDTH]; String str(buf, sizeof(buf), system_charset_info); String *val= field->val_str(&str); - if (val && val->length() > 0) + if (val) { /* Escape the literal by doubling any embedded single quote so a crafted @@ -312,11 +316,14 @@ static std::string get_field_default_for_duckdb(Field *field, charset aware and performs exactly this doubling. */ std::string escaped(2 * val->length(), '\0'); - my_bool overflow; - size_t escaped_len= escape_quotes_for_mysql(val->charset(), &escaped[0], - 0, val->ptr(), val->length(), - &overflow); - escaped.resize(escaped_len); + if (val->length()) + { + my_bool overflow; + size_t escaped_len= escape_quotes_for_mysql( + val->charset(), &escaped[0], 0, val->ptr(), val->length(), + &overflow); + escaped.resize(escaped_len); + } default_value= "'" + escaped + "'"; } else @@ -848,7 +855,11 @@ void AddColumnConvertor::prepare_columns() m_columns_to_add.emplace_back(new_field, field); if ((new_field->flags & NOT_NULL_FLAG) != 0) + { m_columns_to_set_not_null.emplace_back(new_field, field); + if ((field->flags & NO_DEFAULT_VALUE_FLAG) != 0) + m_columns_to_drop_default.emplace_back(new_field, field); + } } } @@ -896,9 +907,16 @@ std::string AddColumnConvertor::translate() my_ptrdiff_t offset= field->table->s->default_values - field->table->record[0]; has_default= true; - default_value= get_field_default_for_duckdb(field, offset); + default_value= get_field_default_for_duckdb(field, offset, false); } } + else if (field->flags & NOT_NULL_FLAG) + { + my_ptrdiff_t offset= + field->table->s->default_values - field->table->record[0]; + has_default= true; + default_value= get_field_default_for_duckdb(field, offset, false); + } append_stmt_column_add(result, m_schema_name, m_table_name, new_field->field_name.str, type, has_default, @@ -913,6 +931,13 @@ std::string AddColumnConvertor::translate() new_field->field_name.str); } + for (auto &pair : m_columns_to_drop_default) + { + Create_field *new_field= pair.first; + append_stmt_column_drop_default(result, m_schema_name, m_table_name, + new_field->field_name.str); + } + return result.str(); } diff --git a/storage/duckdb/convertor/ddl_convertor.h b/storage/duckdb/convertor/ddl_convertor.h index 75871fc066a..6cb33633ca1 100644 --- a/storage/duckdb/convertor/ddl_convertor.h +++ b/storage/duckdb/convertor/ddl_convertor.h @@ -245,6 +245,9 @@ private: /** Columns to set not null */ Columns m_columns_to_set_not_null; + /** Columns whose temporary default must be dropped */ + Columns m_columns_to_drop_default; + /** Prepare columns to add and set not null. */ void prepare_columns(); }; diff --git a/storage/duckdb/docs/architecture.md b/storage/duckdb/docs/architecture.md index 4e773431716..b87a027b984 100644 --- a/storage/duckdb/docs/architecture.md +++ b/storage/duckdb/docs/architecture.md @@ -190,10 +190,13 @@ check_if_supported_inplace_alter() → HA_ALTER_INPLACE_NO_LOCK commit_inplace_alter_table() → AddColumnConvertor / DropColumnConvertor / ChangeColumnConvertor / ChangeColumnDefaultConvertor / ChangeColumnForPrimaryKeyConvertor - → each operation executes in a separate auto-commit context - (DuckDB v1.5+ disallows compound DDL mixing structural + constraint changes) + → all generated operations execute in one explicit DuckDB transaction ``` +Table partitioning is disabled with `HTON_NO_PARTITION`; both +`CREATE TABLE ... PARTITION BY` and `ALTER TABLE ... PARTITION BY` are rejected +before MariaDB creates a partition handler or starts the table-copy protocol. + DROP DATABASE: `duckdb_drop_database()` → `DROP SCHEMA IF EXISTS "db"`. ### Path 3: Row-by-Row DML diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index e5b3b989e78..62d08c8a075 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -229,7 +229,7 @@ static int duckdb_init_func(void *p) duckdb_hton= (handlerton *) p; duckdb_hton->db_type= DB_TYPE_AUTOASSIGN; duckdb_hton->create= duckdb_create_handler; - duckdb_hton->flags= HTON_TEMPORARY_NOT_SUPPORTED; + duckdb_hton->flags= HTON_TEMPORARY_NOT_SUPPORTED | HTON_NO_PARTITION; duckdb_hton->prepare= duckdb_prepare; duckdb_hton->commit= duckdb_commit; duckdb_hton->rollback= duckdb_rollback; @@ -1274,6 +1274,9 @@ ha_duckdb::check_if_supported_inplace_alter(TABLE *altered_table, if (ha_alter_info->alter_info->flags & ALTER_COLUMN_ORDER) DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + if (ha_alter_info->error_if_not_empty) + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + /* Reject ALTER on tables without PK when require_primary_key is ON */ if (myduck::require_primary_key && table->s->primary_key == MAX_KEY) { @@ -1353,29 +1356,53 @@ bool ha_duckdb::commit_inplace_alter_table(TABLE *altered_table, if (convertors.empty()) DBUG_RETURN(false); - /* Execute each ALTER operation in its own auto-commit context. - DuckDB v1.5+ does not allow compound DDL that mixes structural - changes (ADD COLUMN) with constraint updates (SET DEFAULT) - within the same transaction. */ - auto con= myduck::DuckdbManager::CreateConnection(); - + std::vector statements; for (auto &conv : convertors) { if (!conv || conv->check()) DBUG_RETURN(true); std::string sql= conv->translate(); - if (sql.empty()) - continue; + if (!sql.empty()) + statements.push_back(std::move(sql)); + } - auto query_result= myduck::duckdb_query(*con, sql); + if (statements.empty()) + DBUG_RETURN(false); + + /* A single MariaDB ALTER TABLE can produce multiple DuckDB statements. + Execute the generated operations atomically on a dedicated connection. */ + auto con= myduck::DuckdbManager::CreateConnection(); + auto query_result= myduck::duckdb_query(*con, "BEGIN"); + if (query_result->HasError()) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_GENERIC, + query_result->GetError().c_str(), "DuckDB"); + DBUG_RETURN(true); + } + + for (const auto &sql : statements) + { + query_result= myduck::duckdb_query(*con, sql); if (query_result->HasError()) { - my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_GENERIC, query_result->GetError().c_str(), "DuckDB"); + std::string error= query_result->GetError(); + myduck::duckdb_query(*con, "ROLLBACK"); + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_GENERIC, error.c_str(), "DuckDB"); DBUG_RETURN(true); } } + query_result= myduck::duckdb_query(*con, "COMMIT"); + if (query_result->HasError()) + { + std::string error= query_result->GetError(); + if (con->HasActiveTransaction()) + myduck::duckdb_query(*con, "ROLLBACK"); + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_GENERIC, error.c_str(), "DuckDB"); + DBUG_RETURN(true); + } + DBUG_RETURN(false); } diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result index 0e3040ddcc7..7be07dd0294 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result @@ -625,7 +625,7 @@ VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR db_alter_col t id NULL NO INTEGER NULL db_alter_col t B0 CAST('\x00' AS "BLOB") NO BLOB NULL db_alter_col t B1 CAST('\x00\x00\x0D\x05' AS "BLOB") NO BLOB NULL -db_alter_col t B2 CAST('\x00\x00\x00\x00\x00\x00\x00\x1F' AS "BLOB") NO BLOB NULL +db_alter_col t B2 '\x00\x00\x00\x00\x00\x00\x00\x1F' NO BLOB NULL SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, COLUMN_COMMENT FROM information_schema.columns WHERE TABLE_NAME = 't'; @@ -1503,7 +1503,7 @@ VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR VARCHAR db_alter_col t id NULL NO INTEGER NULL db_alter_col t B0 CAST('\x00' AS "BLOB") NO BLOB NULL db_alter_col t B1 CAST('\x00\x00\x0D\x05' AS "BLOB") NO BLOB NULL -db_alter_col t B2 CAST('\x00\x00\x00\x00\x00\x00\x00\x1F' AS "BLOB") NO BLOB NULL +db_alter_col t B2 '\x00\x00\x00\x00\x00\x00\x00\x1F' NO BLOB NULL SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, COLUMN_COMMENT FROM information_schema.columns WHERE TABLE_NAME = 't'; diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40651.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40651.result new file mode 100644 index 00000000000..53ec412f791 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40651.result @@ -0,0 +1,25 @@ +CREATE TABLE t (c1 INT KEY) ENGINE=DuckDB; +INSERT INTO t VALUES (1),(1); +ALTER TABLE t ADD c2 INT NOT NULL; +SELECT * FROM t ORDER BY c1, c2; +c1 c2 +1 0 +1 0 +INSERT INTO t (c1) VALUES (2); +ERROR HY000: Field 'c2' doesn't have a default value +ALTER TABLE t PARTITION BY HASH (c1) (PARTITION p1, PARTITION p2); +ERROR HY000: Engine cannot be used in partitioned tables +SELECT * FROM t ORDER BY c1, c2; +c1 c2 +1 0 +1 0 +DROP TABLE t; +CREATE TABLE t (id INT PRIMARY KEY, c1 INT) ENGINE=DuckDB; +INSERT INTO t VALUES (1, NULL); +ALTER TABLE t ADD c2 INT, MODIFY c1 INT NOT NULL; +ERROR HY000: Got error 168 'Constraint Error: NOT NULL constraint failed: t.c1' from DuckDB +ALTER TABLE t ADD c2 INT; +SELECT * FROM t ORDER BY id; +id c1 c2 +1 NULL NULL +DROP TABLE t; diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test new file mode 100644 index 00000000000..08624224661 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test @@ -0,0 +1,24 @@ +--source ../include/have_duckdb.inc +--source include/have_partition.inc +--source include/not_msan.inc + +# MDEV-40651: keep DuckDB DDL atomic and preserve implicit defaults. +CREATE TABLE t (c1 INT KEY) ENGINE=DuckDB; +INSERT INTO t VALUES (1),(1); +ALTER TABLE t ADD c2 INT NOT NULL; +SELECT * FROM t ORDER BY c1, c2; +--error ER_NO_DEFAULT_FOR_FIELD +INSERT INTO t (c1) VALUES (2); +--error ER_PARTITION_MERGE_ERROR +ALTER TABLE t PARTITION BY HASH (c1) (PARTITION p1, PARTITION p2); +SELECT * FROM t ORDER BY c1, c2; +DROP TABLE t; + +# A later DuckDB DDL error rolls back preceding generated statements. +CREATE TABLE t (id INT PRIMARY KEY, c1 INT) ENGINE=DuckDB; +INSERT INTO t VALUES (1, NULL); +--error ER_GET_ERRMSG +ALTER TABLE t ADD c2 INT, MODIFY c1 INT NOT NULL; +ALTER TABLE t ADD c2 INT; +SELECT * FROM t ORDER BY id; +DROP TABLE t; diff --git a/storage/duckdb/runtime/duckdb_query.cc b/storage/duckdb/runtime/duckdb_query.cc index a3d9030e3fe..fc0c39a1715 100644 --- a/storage/duckdb/runtime/duckdb_query.cc +++ b/storage/duckdb/runtime/duckdb_query.cc @@ -135,7 +135,8 @@ bool mariadb_query_has_unsafe_quote_escape(THD *thd, const char *query, } /* - Convert MariaDB's printed SQL (backtick-quoted identifiers) into DuckDB SQL + Convert forwarded MariaDB SQL (the raw thd->query() text, plus any + Item::print() fragments) from backtick-quoted identifiers into DuckDB SQL (double-quoted identifiers). MariaDB delimits identifiers with backticks and doubles an embedded backtick; From 851f52470d470a96a66e0b7e5599f3b6c2e45907 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 9 Aug 2026 09:53:09 +0200 Subject: [PATCH 093/150] MDEV-40678 mysql_json plugin OOB reads --- .../main/mysql_json_table_recreate.result | 10 ++++- .../main/mysql_json_table_recreate.test | 38 ++++++++++++++++++- plugin/type_mysql_json/mysql_json.cc | 8 ++-- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/mysql-test/main/mysql_json_table_recreate.result b/mysql-test/main/mysql_json_table_recreate.result index c8c53c9036a..25a72885317 100644 --- a/mysql-test/main/mysql_json_table_recreate.result +++ b/mysql-test/main/mysql_json_table_recreate.result @@ -245,6 +245,14 @@ DECLARE a mysql_json; END; $$ ERROR HY000: 'MYSQL_JSON' is not allowed in this context -# # End of 10.5 tests # +# MDEV-40678 mysql_json plugin OOB reads +# +alter table t1 force; +ERROR HY000: Error parsing MySQL JSON format, please dump this table from MySQL and then restore it to be able to use it in MariaDB. +flush tables; +alter table t1 force; +ERROR HY000: Error parsing MySQL JSON format, please dump this table from MySQL and then restore it to be able to use it in MariaDB. +drop table t1; +# End of 10.6 tests diff --git a/mysql-test/main/mysql_json_table_recreate.test b/mysql-test/main/mysql_json_table_recreate.test index 94477d4f328..26cc7a6cc6b 100644 --- a/mysql-test/main/mysql_json_table_recreate.test +++ b/mysql-test/main/mysql_json_table_recreate.test @@ -17,7 +17,7 @@ call mtr.add_suppression("Table rebuild required"); call mtr.add_suppression("is marked as crashed"); call mtr.add_suppression("Checking"); -let $MYSQLD_DATADIR= `select @@datadir`; +let MYSQLD_DATADIR= `select @@datadir`; SET NAMES utf8; @@ -161,6 +161,40 @@ END; $$ DELIMITER ;$$ ---echo # --echo # End of 10.5 tests + --echo # +--echo # MDEV-40678 mysql_json plugin OOB reads +--echo # + +--copy_file std_data/mysql_json/mysql_json_test.frm $MYSQLD_DATADIR/test/t1.frm +--copy_file std_data/mysql_json/mysql_json_test.MYI $MYSQLD_DATADIR/test/t1.MYI +--copy_file std_data/mysql_json/mysql_json_test.MYD $MYSQLD_DATADIR/test/t1.MYD + +perl; + my $f="$ENV{MYSQLD_DATADIR}/test/t1.MYD"; + open F, '+<', $f or die "open($f): $!"; + sysseek F, 0x3c5, 0 or die "sysseek: $!"; + syswrite F, '(' or die "syswrite: $!"; +EOF + +--error ER_UNKNOWN_ERROR +alter table t1 force; +flush tables; + +--remove_file $MYSQLD_DATADIR/test/t1.MYD +--copy_file std_data/mysql_json/mysql_json_test.MYD $MYSQLD_DATADIR/test/t1.MYD + +perl; + my $f="$ENV{MYSQLD_DATADIR}/test/t1.MYD"; + open F, '+<', $f or die "open($f): $!"; + sysseek F, 0x3bc, 0 or die "sysseek: $!"; + syswrite F, '(' or die "syswrite: $!"; +EOF + +--error ER_UNKNOWN_ERROR +alter table t1 force; + +drop table t1; + +--echo # End of 10.6 tests diff --git a/plugin/type_mysql_json/mysql_json.cc b/plugin/type_mysql_json/mysql_json.cc index c28a403a603..6b267d404bd 100644 --- a/plugin/type_mysql_json/mysql_json.cc +++ b/plugin/type_mysql_json/mysql_json.cc @@ -373,7 +373,8 @@ static bool parse_mysql_scalar_or_value(String *buffer, const uchar *data, if (type_is_stored_inline(value_type, large)) { const size_t value_start = value_type_offset + 1; - if (parse_mysql_scalar(buffer, value_type, data + value_start, + if (value_start >= len || + parse_mysql_scalar(buffer, value_type, data + value_start, len - value_start)) return true; } @@ -383,7 +384,8 @@ static bool parse_mysql_scalar_or_value(String *buffer, const uchar *data, of the Object / Array */ const size_t value_start= read_offset_or_size( data + value_type_offset + 1, large); - if (parse_mysql_json_value(buffer, value_type, data + value_start, + if (value_start >= len || + parse_mysql_json_value(buffer, value_type, data + value_start, len - value_start, depth)) return true; } @@ -453,7 +455,7 @@ static bool parse_array_or_object(String *buffer, const uchar *data, size_t len, i * value_size(large)); /* First print the key. */ - if (buffer->append('"') || + if (key_start + key_len > bytes || buffer->append('"') || append_string_json(buffer, data + key_start, key_len) || buffer->append(STRING_WITH_LEN("\": "))) { From 5785e9cbc283c9257a665ce5f3e0bf51b489bea6 Mon Sep 17 00:00:00 2001 From: Alexey Yurchenko Date: Tue, 30 Jun 2026 14:41:35 +0200 Subject: [PATCH 094/150] MDEV-38147 error 1950 after mariabackup SST with gtid_strict_mode=ON After a mariabackup SST the joiner could fail with ER_GTID_STRICT_OUT_OF_ORDER (error 1950) while re-binlogging transactions received over IST. The cause is that the binary log copied from the donor carries a Gtid_list whose position can be ahead of the storage-engine snapshot because getting snapshot and binlog are no mutually atomic, so transactions can be present in the copied binlog that are not committed in the copied engine snapshot. After the SST the joiner reports the (committed) engine position to the cluster, IST resends those transactions, and re-binlogging them under gtid_strict_mode=ON collides with the ahead Gtid_list -> error 1950. (MDEV-34483 made the engine snapshot stop short of the binlog, which is what exposed this.) The copied binary log carries no transactions the joiner needs - only a Gtid_list - so instead of shipping and then having to truncate/reconcile it, the joiner now starts a fresh binary log and seeds its GTID position from the storage-engine checkpoint during recovery. That checkpoint is the committed cluster position, i.e. exactly where IST resumes, so the joiner's binary log stays in lockstep with the rest of the cluster and no out-of-order GTID can occur. This works for both wsrep_gtid_mode settings; only the binlog domain of the cluster stream differs: - wsrep_gtid_mode=ON : wsrep_gtid_domain_id (cluster writes are re-tagged to it), which is the domain stored in the checkpoint; - wsrep_gtid_mode=OFF: gtid_domain_id (cluster writes keep the node's configured domain). Async-replica positions (mysql.gtid_slave_pos) are part of the engine snapshot and survive the SST unchanged, so a Galera node can still serve as an async master or replica across the SST. This commit: - sql/log.cc: adds wsrep_seed_binlog_gtid_state(), called from do_binlog_recovery() when the joiner has no binary log, seeding the binlog GTID state for the cluster domain to the SE checkpoint position. - scripts/wsrep_sst_mariabackup.sh: no longer moves the donor's binary log into place on the joiner. - extra/mariabackup: backward compatibility: keep shipping binlog file in SST but - on donor fix the race between rotation and shipping so that the file shipped is the one that had been rotated; - on joiner discard shipped binlog in favour of one generated by wsrep_seed_binlog_gtid_state(). - sql/wsrep_sst.cc: logs the position actually adopted from storage (the authoritative post-SST position) rather than the script-reported one. - sql/handler.cc: downgrades the "Discovered discontinuity in recovered wsrep transaction XIDs" message in wsrep_order_and_check_continuity() from warning to debug level. With parallel appliers a snapshot routinely captures prepared XIDs that are not contiguous with the engine checkpoint, so this is normal during SST recovery and of no value in regular operation; the transactions past the checkpoint are re-delivered by the cluster (IST/SST) regardless. - Adds an MDEV-38147 MTR test reproducing the issue. Co-Authored-By: Claude Opus 4.8 --- extra/mariabackup/backup_mysql.cc | 87 ++++++- extra/mariabackup/backup_mysql.h | 1 + .../r/galera_log_bin_ext_mariabackup.result | 2 - .../suite/galera_3nodes/r/MDEV-38147.result | 43 ++++ .../suite/galera_3nodes/t/MDEV-38147.cnf | 25 ++ .../suite/galera_3nodes/t/MDEV-38147.test | 229 ++++++++++++++++++ scripts/wsrep_sst_mariabackup.sh | 52 ++-- sql/handler.cc | 2 +- sql/log.cc | 56 +++++ sql/wsrep_sst.cc | 26 +- 10 files changed, 482 insertions(+), 41 deletions(-) create mode 100644 mysql-test/suite/galera_3nodes/r/MDEV-38147.result create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147.test diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc index 453e9bb052e..ffca4a64ad7 100644 --- a/extra/mariabackup/backup_mysql.cc +++ b/extra/mariabackup/backup_mysql.cc @@ -88,6 +88,14 @@ static mysql_cond_t kill_query_thread_stop; bool sql_thread_started = false; char *mysql_slave_position = NULL; char *mysql_binlog_position = NULL; +/* + MDEV-38147: the exact binary log file name that + write_current_binlog_file() rotated to and shipped into the backup under + --galera-info. Remembered here so that write_binlog_info() records the very + same file name in xtrabackup_binlog_info, i.e. the file the SST joiner looks + for is guaranteed to be the file that was actually sent (no rotation race). +*/ +char *mysql_binlog_file = NULL; char *buffer_pool_filename = NULL; /* History on server */ @@ -1528,6 +1536,23 @@ write_galera_info(ds_ctxt *datasink, MYSQL *connection) domain_id ? domain_id : domain_id55); } + /* + MDEV-38147: Flush and copy the donor's current binary log into + the backup so that it is shipped to the SST joiner. + + A new joiner discards this file and starts a fresh binary log seeded from + the storage-engine checkpoint (see wsrep_seed_binlog_gtid_state() in + sql/log.cc and the joiner code in scripts/wsrep_sst_mariabackup.sh), which + avoids error 1950 with gtid_strict_mode=ON. The file is still shipped for + backward compatibility with an old joiner that expects it, and so that a + new joiner can deterministically identify and remove exactly the file that + was sent instead of colliding with it. + + write_current_binlog_file() remembers the rotated file name in + mysql_binlog_file so that write_binlog_info() records the same file in + xtrabackup_binlog_info - closing the old race where a concurrent rotation + could make the shipped file and the recorded file diverge. + */ if (result) write_current_binlog_file(datasink, connection); @@ -1548,7 +1573,16 @@ cleanup: /*********************************************************************//** Flush and copy the current binary log file into the backup, -if GTID is enabled */ +if GTID is enabled. + +MDEV-38147: the file name that FLUSH BINARY LOGS rotates to is +remembered in the global mysql_binlog_file. write_binlog_info() then records +that exact name in xtrabackup_binlog_info, so the file the SST joiner looks +for is guaranteed to be the file that was shipped. Previously the shipped file +(determined here) and the recorded file (determined independently later by +write_binlog_info()) were read by two separate SHOW MASTER STATUS calls; a +binary log rotation happening in between made them diverge and the wrong file +was sent. */ bool write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) { @@ -1601,6 +1635,22 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) log_bin_dir = strdup("./"); } + if (log_bin_dir == NULL || log_bin_file == NULL) { + msg("Failed to get master binlog coordinates from " + "SHOW MASTER STATUS"); + result = false; + goto cleanup; + } + + /* + Remember the file we just rotated to (before any further + rotation can happen) so that write_binlog_info() records this + very file in xtrabackup_binlog_info and the joiner looks for + exactly the file that is shipped below. + */ + free(mysql_binlog_file); + mysql_binlog_file = strdup(log_bin_file); + dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length); /* strip final slash if it is not the only path component */ @@ -1609,13 +1659,6 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) log_bin_dir[log_bin_dir_length - 1] = 0; } - if (log_bin_dir == NULL || log_bin_file == NULL) { - msg("Failed to get master binlog coordinates from " - "SHOW MASTER STATUS"); - result = false; - goto cleanup; - } - snprintf(filepath, sizeof(filepath), "%s%c%s", log_bin_dir, FN_LIBCHAR, log_bin_file); result = datasink->copy_file(filepath, log_bin_file, 0); @@ -1637,6 +1680,7 @@ bool write_binlog_info(ds_ctxt *datasink, MYSQL *connection) { char *filename = NULL; + const char *out_filename; char *position = NULL; char *gtid_mode = NULL; char *gtid_current_pos = NULL; @@ -1669,6 +1713,24 @@ write_binlog_info(ds_ctxt *datasink, MYSQL *connection) goto cleanup; } + /* + MDEV-38147: if write_current_binlog_file() already rotated + and shipped a binary log under --galera-info, record that exact file + name here rather than whatever SHOW MASTER STATUS reports now. The two + are normally identical, but a binary log rotation between the two + SHOW MASTER STATUS calls would otherwise make xtrabackup_binlog_info + name a file different from the one that was shipped, so the SST joiner + would look for a file that is not there. Use a separate pointer so the + string owned by the status[] array is still freed at cleanup. + */ + out_filename = filename; + if (mysql_binlog_file != NULL && strcmp(filename, mysql_binlog_file)) { + msg("Binary log rotated to '%s' after '%s' was shipped; " + "recording the shipped file in " XTRABACKUP_BINLOG_INFO, + filename, mysql_binlog_file); + out_filename = mysql_binlog_file; + } + mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0)); mariadb_gtid = (gtid_current_pos != NULL); @@ -1678,16 +1740,16 @@ write_binlog_info(ds_ctxt *datasink, MYSQL *connection) ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s', " "GTID of the last change '%s'", - filename, position, gtid) != -1); + out_filename, position, gtid) != -1); result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO, - "%s\t%s\t%s\n", filename, position, + "%s\t%s\t%s\n", out_filename, position, gtid); } else { ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s'", - filename, position) != -1); + out_filename, position) != -1); result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO, - "%s\t%s\n", filename, position); + "%s\t%s\n", out_filename, position); } cleanup: @@ -2021,6 +2083,7 @@ backup_cleanup() { free(mysql_slave_position); free(mysql_binlog_position); + free(mysql_binlog_file); free(buffer_pool_filename); if (mysql_connection) { diff --git a/extra/mariabackup/backup_mysql.h b/extra/mariabackup/backup_mysql.h index 76f4bc666b3..5bdd34c7160 100644 --- a/extra/mariabackup/backup_mysql.h +++ b/extra/mariabackup/backup_mysql.h @@ -27,6 +27,7 @@ extern time_t history_lock_time; extern bool sql_thread_started; extern char *mysql_slave_position; extern char *mysql_binlog_position; +extern char *mysql_binlog_file; extern char *buffer_pool_filename; /** connection to mysql server */ diff --git a/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result b/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result index 9d7ea473241..5f63c971ec9 100644 --- a/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result +++ b/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result @@ -58,8 +58,6 @@ SELECT COUNT(*) = 2 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 't1'; COUNT(*) = 2 1 include/show_binlog_events.inc -Log_name Pos Event_type Server_id End_log_pos Info -hostname1-bin.000002 # Binlog_checkpoint # # hostname1-bin.000002 DROP TABLE t1; DROP TABLE t2; #cleanup diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result new file mode 100644 index 00000000000..7e7199aa322 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result @@ -0,0 +1,43 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +# gtid_strict_mode must be enabled on all nodes +SELECT @@global.gtid_strict_mode AS gtid_strict_mode; +gtid_strict_mode +1 +connection node_2; +connection node_3; +connect node_1_load, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_2_load, 127.0.0.1, root, , test, $NODE_MYPORT_2; +connection node_1_load; +CALL p_load(); +connection node_2_load; +CALL p_load(); +connection node_1; +connection node_2; +connection node_3; +connection node_1; +UPDATE ctrl SET stop = 1 WHERE id = 1; +connection node_1_load; +connection node_2_load; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_1; +connection node_2; +connection node_3; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf new file mode 100644 index 00000000000..a8c4cfe8137 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf @@ -0,0 +1,25 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +gtid_strict_mode=ON +wsrep_gtid_mode=ON +wsrep_gtid_domain_id=100 +gtid_domain_id=10 +log_bin +log_slave_updates=ON +wsrep_slave_threads=4 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.test b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test new file mode 100644 index 00000000000..8b7eb371529 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test @@ -0,0 +1,229 @@ +# +# MDEV-38147 +# +# Three node cluster with gtid_strict_mode enabled and mariabackup SST. +# Two nodes (node_1 and node_2) run a continuous load, inserting batches +# of rows in a throttled loop, while the third node (node_3) is repeatedly +# stopped, has its data directory purged and is started again, forcing a +# full mariabackup SST on every rejoin (8 times). +# +# At the end the cluster must reconverge to three nodes and all three +# nodes must hold identical data and GTID positions. +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +# Number of stop/purge/start cycles for node_3. +--let $restarts = 8 + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted multiple times. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +# +# Schema: t1 holds the load, ctrl carries the stop flag for the loaders. +# +--connection node_1 +--disable_query_log +CREATE TABLE t1 (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB; +CREATE TABLE ctrl (id INT PRIMARY KEY, stop INT) ENGINE=InnoDB; +INSERT INTO ctrl VALUES (1, 0); + +DELIMITER |; +CREATE PROCEDURE p_load() +BEGIN + DECLARE v_stop INT DEFAULT 0; + DECLARE v_i INT; + # Keep the loop alive across transient cluster errors (BF aborts, + # certification failures, donor desync timeouts, ...). + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + END; + WHILE v_stop = 0 DO + START TRANSACTION; + SET v_i = 0; + WHILE v_i < 16 DO + INSERT INTO t1 (val) VALUES (v_i); + SET v_i = v_i + 1; + END WHILE; + COMMIT; + # Throttle slightly between transactions so that a freshly joined node can + # catch up its replication queue instead of being starved by the load. + DO SLEEP(0.01); + SELECT stop INTO v_stop FROM ctrl WHERE id = 1; + END WHILE; +END| +DELIMITER ;| +--enable_query_log + +--echo # gtid_strict_mode must be enabled on all nodes +SELECT @@global.gtid_strict_mode AS gtid_strict_mode; + +# Make sure the schema reached the other nodes before starting the load. +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc + +# +# Start the continuous load on node_1 and node_2. +# +--connect node_1_load, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connect node_2_load, 127.0.0.1, root, , test, $NODE_MYPORT_2 + +--connection node_1_load +--send CALL p_load() + +--connection node_2_load +--send CALL p_load() + +# +# While the load is running, repeatedly stop node_3, purge its data +# directory and start it again. An empty data directory forces a full +# mariabackup SST on every rejoin. +# +--disable_query_log +--let $i = $restarts +while ($i) +{ + --connection node_3 + --source include/shutdown_mysqld.inc + --disable_query_log + + # Wait until node_3 has actually left the cluster. + # (shutdown_mysqld.inc / wait_condition.inc / start_mysqld.inc / + # galera_wait_ready.inc each re-enable the query log, so re-disable it after + # every such include to keep the loop output out of the result file.) + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + # Purge node_3's data directory. + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + + # Start node_3 again (rejoins via mariabackup SST). + --connection node_3 + --let $restart_noprint = 2 + --source include/start_mysqld.inc + --disable_query_log + --source include/galera_wait_ready.inc + --disable_query_log + + # Wait until the cluster is back to three nodes before the next cycle. + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + --dec $i +} +--enable_query_log + +# +# Make sure the whole cluster is healthy before stopping the load, so that +# any donor that desynced during SST has resynced and the loaders can read +# the stop flag without blocking. +# +--connection node_1 +--source include/galera_wait_ready.inc +--connection node_2 +--source include/galera_wait_ready.inc +--connection node_3 +--source include/galera_wait_ready.inc + +# +# Signal the loaders to stop and collect them. +# +--connection node_1 +UPDATE ctrl SET stop = 1 WHERE id = 1; + +--connection node_1_load +--reap +--connection node_2_load +--reap + +# +# Verify reconvergence and data / GTID consistency across all nodes. +# +--connection node_1 +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + +--let $expect_count = `SELECT COUNT(*) FROM t1` +--let $expect_sum = `SELECT COALESCE(SUM(pk), 0) + COALESCE(SUM(val), 0) FROM t1` +--let $expect_gtid = `SELECT @@global.gtid_binlog_pos` + +--connection node_2 +SET SESSION wsrep_sync_wait = 15; +--let $wait_condition = SELECT COUNT(*) = $expect_count FROM t1 +--source include/wait_condition.inc +--disable_query_log +--eval SELECT $expect_count = (SELECT COUNT(*) FROM t1) AS count_match, $expect_sum = (SELECT COALESCE(SUM(pk), 0) + COALESCE(SUM(val), 0) FROM t1) AS checksum_match, '$expect_gtid' = @@global.gtid_binlog_pos AS gtid_match +--enable_query_log + +--connection node_3 +SET SESSION wsrep_sync_wait = 15; +--let $wait_condition = SELECT COUNT(*) = $expect_count FROM t1 +--source include/wait_condition.inc +--disable_query_log +--eval SELECT $expect_count = (SELECT COUNT(*) FROM t1) AS count_match, $expect_sum = (SELECT COALESCE(SUM(pk), 0) + COALESCE(SUM(val), 0) FROM t1) AS checksum_match, '$expect_gtid' = @@global.gtid_binlog_pos AS gtid_match +--enable_query_log + +# +# Cleanup. +# +--connection node_1 +--disable_query_log +DROP PROCEDURE p_load; +DROP TABLE t1; +DROP TABLE ctrl; + +CALL mtr.add_suppression("WSREP: Failed to prepare for incremental state transfer"); +CALL mtr.add_suppression("WSREP: Member .* requested state transfer"); +CALL mtr.add_suppression("WSREP: .* returned an error: Not connected to Primary Component"); +--enable_query_log + +--connection node_2 +--disable_query_log +CALL mtr.add_suppression("WSREP: Failed to prepare for incremental state transfer"); +CALL mtr.add_suppression("WSREP: Member .* requested state transfer"); +CALL mtr.add_suppression("WSREP: Did not find domain ID from SST script output"); +CALL mtr.add_suppression("WSREP: Ignoring server id for non bootstrap node"); +--enable_query_log + +--connection node_3 +--disable_query_log +CALL mtr.add_suppression("WSREP: Failed to prepare for incremental state transfer"); +CALL mtr.add_suppression("WSREP: Member .* requested state transfer"); +CALL mtr.add_suppression("InnoDB: Table \"mysql\"\\.\"innodb_index_stats\" not found"); +CALL mtr.add_suppression("InnoDB: Table \"mysql\"\\.\"innodb_table_stats\" not found"); +CALL mtr.add_suppression("Can't open and lock time zone table"); +CALL mtr.add_suppression("Can't open and lock privilege tables"); +CALL mtr.add_suppression("Table 'mysql\\.gtid_slave_pos' doesn't exist"); +CALL mtr.add_suppression("Native table .* has the wrong structure"); +CALL mtr.add_suppression("WSREP: Did not find domain ID from SST script output"); +CALL mtr.add_suppression("WSREP: Ignoring server id for non bootstrap node"); +--enable_query_log + +# Restore original auto_increment_offset values. +--source ../galera/include/auto_increment_offset_restore.inc + +--source include/galera_end.inc diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index f290c9b7e7a..237f128a104 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -1454,37 +1454,49 @@ else # joiner if [ -n "$WSREP_SST_OPT_BINLOG" ]; then cd "$DATA" + # + # MDEV-38147: do NOT move the donor's binary log into + # place on the joiner. + # + # The donor still ships its (freshly rotated) current binary log so + # that an old joiner keeps working and a new joiner can identify + # exactly which file was sent. That file, however, only carries a + # Gtid_list, and its position can be ahead of the engine snapshot + # (BACKUP STAGE BLOCK_COMMIT blocks the engine commit but not the + # binary log write). With gtid_strict_mode=ON that ahead position + # makes the joiner raise error 1950 when it re-binlogs transactions + # during IST, and keeping the file could also collide with the + # joiner's own binary log numbering. + # + # Therefore the joiner removes the received binary log file(s) and + # starts a fresh binary log, seeding its GTID position from the + # storage-engine checkpoint during recovery (see + # wsrep_seed_binlog_gtid_state() in sql/log.cc) - the exact position + # from which IST resumes, which keeps the joiner's binary log in + # lockstep with the rest of the cluster. + # binlogs="" if [ -f 'xtrabackup_binlog_info' ]; then - NL=$'\n' while read bin_string || [ -n "$bin_string" ]; do bin_file=$(echo "$bin_string" | cut -f1) - if [ -f "$bin_file" ]; then - binlogs="$binlogs${binlogs:+$NL}$bin_file" + if [ -n "$bin_file" -a -f "$bin_file" ]; then + binlogs="$binlogs${binlogs:+ }$bin_file" fi done < 'xtrabackup_binlog_info' else binlogs=$(ls -d -1 "$binlog_base".[0-9]* 2>/dev/null || :) fi - cd "$DATA_DIR" - if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ - "$binlog_dir" != "$DATA_DIR" ] - then - [ ! -d "$binlog_dir" ] && mkdir -p "$binlog_dir" - fi - index_dir=$(dirname "$binlog_index"); - if [ -n "$index_dir" -a "$index_dir" != '.' -a \ - "$index_dir" != "$DATA_DIR" ] - then - [ ! -d "$index_dir" ] && mkdir -p "$index_dir" - fi if [ -n "$binlogs" ]; then - wsrep_log_info "Moving binary logs to $binlog_dir" - echo "$binlogs" | \ - while read bin_file || [ -n "$bin_file" ]; do - mv "$DATA/$bin_file" "$binlog_dir" - echo "$binlog_dir${binlog_dir:+/}$bin_file" >> "$binlog_index" + wsrep_log_info "Removing received binary log(s) so the joiner" \ + "starts a fresh binary log seeded from the" \ + "storage-engine checkpoint (MDEV-38147)" + for bin_file in $binlogs; do + rm -f "$DATA/$bin_file" done + else + wsrep_log_info "No binary log received from donor; the joiner" \ + "will start a fresh binary log seeded from the" \ + "storage-engine checkpoint (MDEV-38147)" fi cd "$OLD_PWD" fi diff --git a/sql/handler.cc b/sql/handler.cc index af27370e261..84e78b83740 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2556,7 +2556,7 @@ static my_xid wsrep_order_and_check_continuity(XID *list, int len) if (!wsrep_is_wsrep_xid(list + i) || wsrep_xid_seqno(list + i) != cur_seqno + 1) { - WSREP_WARN("Discovered discontinuity in recovered wsrep " + WSREP_INFO("Discovered discontinuity in recovered wsrep " "transaction XIDs. Truncating the recovery list to " "%d entries", i); break; diff --git a/sql/log.cc b/sql/log.cc index d70fe6b9170..07c717a3c6f 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -64,6 +64,7 @@ #ifdef WITH_WSREP #include "wsrep_trans_observer.h" #include "wsrep_status.h" +#include "wsrep_xid.h" #endif /* WITH_WSREP */ #ifdef HAVE_REPLICATION @@ -12014,6 +12015,57 @@ err1: +#if defined(WITH_WSREP) && defined(HAVE_REPLICATION) +/* + MDEV-38147: A Galera mariabackup SST no longer ships the donor's binary log + (the only thing it carried was a Gtid_list whose position was ahead of the + snapshot, causing error 1950). Instead the joiner starts a fresh binary log, + so its Gtid_list / @@gtid_binlog_pos must be seeded from the recovered wsrep + position - otherwise the joiner would report an empty binlog position until + it re-binlogs new transactions, which breaks its use as an async master. + + The wsrep cluster position lives in the storage-engine checkpoint (restored + by the SST). Async-replica source positions live in mysql.gtid_slave_pos + (also restored from the engine) and are handled separately, so they are not + seeded here. + + The whole cluster binlogs cluster writes under one consistent stream (the + seqno stays in lockstep because every node applies in the same total order). + The domain of that stream depends on the mode: + - wsrep_gtid_mode=ON : wsrep_gtid_domain_id (cluster writes are re-tagged + to it, see [wsrep_mysqld.cc:2983]); this is the domain in the checkpoint. + - wsrep_gtid_mode=OFF: gtid_domain_id (cluster writes keep the node's + configured domain, no re-tag). + In both modes the committed cluster seqno is the SE checkpoint seqno, so we + seed that domain's binlog state to the checkpoint position. This is also the + exact position from which IST will resume re-binlogging, so the joiner stays + in lockstep with the rest of the cluster (and, in ON mode, avoids error 1950 + from re-binlogging over an ahead position). +*/ +static void wsrep_seed_binlog_gtid_state() +{ + wsrep_server_gtid_t const eng= wsrep_get_SE_checkpoint(); + if (eng.seqno <= 0) + return; /* not a wsrep node / no position */ + + rpl_gtid eng_gtid; + eng_gtid.domain_id= wsrep_gtid_mode ? eng.domain_id + : global_system_variables.gtid_domain_id; + eng_gtid.server_id= eng.server_id; + eng_gtid.seq_no= eng.seqno; + + rpl_gtid *cur= rpl_global_gtid_binlog_state.find_most_recent(eng_gtid.domain_id); + if (cur && cur->seq_no >= eng_gtid.seq_no) + return; /* binlog state already at or ahead of the checkpoint */ + + sql_print_information("WSREP: seeding binlog GTID state to %u-%u-%llu " + "from the storage-engine checkpoint", + eng_gtid.domain_id, eng_gtid.server_id, + (unsigned long long) eng_gtid.seq_no); + rpl_global_gtid_binlog_state.update_nolock(&eng_gtid, false); +} +#endif /* WITH_WSREP && HAVE_REPLICATION */ + int MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery) { @@ -12048,6 +12100,10 @@ MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery) error= 0; } } +#if defined(WITH_WSREP) && defined(HAVE_REPLICATION) + if (!error && WSREP_PROVIDER_EXISTS) + wsrep_seed_binlog_gtid_state(); +#endif return error; } diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc index 0e192768357..71f0325967d 100644 --- a/sql/wsrep_sst.cc +++ b/sql/wsrep_sst.cc @@ -402,9 +402,6 @@ static bool wsrep_sst_complete (THD* thd, Wsrep_server_state& server_state= Wsrep_server_state::instance(); enum wsrep::server_state::state state= server_state.state(); bool failed= false; - char start_pos_buf[FN_REFLEN]; - ssize_t len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN-1); - start_pos_buf[len]='\0'; // Do not call sst_received if we are not in joiner or // initialized state on server. This is because it @@ -419,14 +416,31 @@ static bool wsrep_sst_complete (THD* thd, } else { - WSREP_INFO("SST succeeded for position %s", start_pos_buf); + /* + Note: sst_received() does NOT use sst_gtid (the position reported by + the SST script). It determines the position internally from storage via + Wsrep_server_service::get_position(). + For physical SST methods these two may differ (e.g. the joiner's storage + recovers to an earlier position than the script reported). Log the + position actually adopted, not the script-reported one, to avoid + confusion. + */ + wsrep::gtid const received_gtid(wsrep_get_SE_checkpoint()); + char recv_pos_buf[FN_REFLEN]; + ssize_t const recv_len= + wsrep::print_to_c_str(received_gtid, recv_pos_buf, FN_REFLEN-1); + recv_pos_buf[recv_len > 0 ? recv_len : 0]= '\0'; + WSREP_INFO("SST succeeded for position %s", recv_pos_buf); } } else { + char start_pos_buf[FN_REFLEN]; + ssize_t const len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN - 1); + start_pos_buf[len]= '\0'; + WSREP_ERROR("SST failed for position %s initialized %d server_state %s", - start_pos_buf, - server_state.is_initialized(), + start_pos_buf, server_state.is_initialized(), wsrep::to_c_string(state)); failed= true; } From aba2caa893cdfa880dbb9a4f2d8e6dfaa7ed8ebe Mon Sep 17 00:00:00 2001 From: Alexey Yurchenko Date: Sun, 28 Jun 2026 11:58:22 +0200 Subject: [PATCH 095/150] MDEV-40179 Found N prepared transactions after mariabackup SST With log_bin=ON a transaction is committed via two-phase commit (the binary log is the second participant), so it passes through the InnoDB XA-prepare state. While a donor is held in BLOCK_COMMIT for a mariabackup backup, its parallel appliers (wsrep_slave_threads > 1) leave one or more such writesets prepared-but-not-yet-committed, and the snapshot captures them. On a freshly SST'd joiner nothing resolves these prepared transactions: binlog crash recovery does not run (the joiner has no in-use binlog to recover from), and the wsrep continuity-based commit is inactive because wsrep_emulate_bin_log is FALSE when log_bin is ON. The leftover prepared transactions then abort startup with "Found prepared transactions!". Note this does not depend on the prepared set being non-contiguous - even a contiguous run aborts, because nothing commits or rolls it back. Rollback these transactions in xarecover_handlerton(). If rollback fails flag error to cause unireg_abort(). --- extra/mariabackup/backup_mysql.cc | 71 +++- extra/mariabackup/xtrabackup.cc | 13 + .../suite/galera_3nodes/r/MDEV-38147.result | 45 ++- .../r/MDEV-38147_binlogdir.result | 20 + .../suite/galera_3nodes/r/MDEV-40179.result | 47 +++ .../r/MDEV-40179_nobinlog.result | 47 +++ .../suite/galera_3nodes/t/MDEV-38147.cnf | 8 +- .../suite/galera_3nodes/t/MDEV-38147.test | 309 +++++++--------- .../galera_3nodes/t/MDEV-38147_binlogdir.cnf | 22 ++ .../galera_3nodes/t/MDEV-38147_binlogdir.test | 106 ++++++ .../galera_3nodes/t/MDEV-38147_gtid_off.cnf | 30 ++ .../galera_3nodes/t/MDEV-38147_gtid_off.test | 109 ++++++ .../suite/galera_3nodes/t/MDEV-40179.cnf | 37 ++ .../suite/galera_3nodes/t/MDEV-40179.inc | 347 ++++++++++++++++++ .../suite/galera_3nodes/t/MDEV-40179.test | 20 + .../galera_3nodes/t/MDEV-40179_nobinlog.cnf | 27 ++ .../galera_3nodes/t/MDEV-40179_nobinlog.test | 16 + scripts/wsrep_sst_mariabackup.sh | 52 ++- sql/handler.cc | 49 ++- sql/log.cc | 49 ++- sql/wsrep_sst.cc | 4 +- 21 files changed, 1199 insertions(+), 229 deletions(-) create mode 100644 mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result create mode 100644 mysql-test/suite/galera_3nodes/r/MDEV-40179.result create mode 100644 mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147_binlogdir.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147_binlogdir.test create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-40179.inc create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-40179.test create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc index ffca4a64ad7..35118758314 100644 --- a/extra/mariabackup/backup_mysql.cc +++ b/extra/mariabackup/backup_mysql.cc @@ -96,6 +96,17 @@ char *mysql_binlog_position = NULL; for is guaranteed to be the file that was actually sent (no rotation race). */ char *mysql_binlog_file = NULL; +/* + Position and GTID of that shipped binary log, captured together with + mysql_binlog_file at flush time (see write_current_binlog_file()). Used by + write_binlog_info() so that, if a rotation intervenes, the whole coordinate + tuple it records stays consistent with the file that was actually shipped - + which matters for a manual --galera-info backup used as a replication seed, + not only for the SST joiner (which reads just the file name). +*/ +static char *mysql_binlog_file_pos = NULL; +static char *mysql_binlog_file_gtid_executed = NULL; +static char *mysql_binlog_file_gtid_current_pos = NULL; char *buffer_pool_filename = NULL; /* History on server */ @@ -1588,7 +1599,10 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) { char *executed_gtid_set = NULL; char *gtid_binlog_state = NULL; + char *gtid_current_pos = NULL; char *log_bin_file = NULL; + char *log_bin_pos = NULL; + char *log_bin_gtid_executed = NULL; char *log_bin_dir = NULL; bool gtid_exists; bool result = true; @@ -1599,13 +1613,18 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) {NULL, NULL} }; + /* Capture the shipped file's coordinates together, right after the flush, + so they describe exactly the file that is copied below. */ mysql_variable status_after_flush[] = { {"File", &log_bin_file}, + {"Position", &log_bin_pos}, + {"Executed_Gtid_Set", &log_bin_gtid_executed}, {NULL, NULL} }; mysql_variable vars[] = { {"gtid_binlog_state", >id_binlog_state}, + {"gtid_current_pos", >id_current_pos}, {"log_bin_basename", &log_bin_dir}, {NULL, NULL} }; @@ -1644,12 +1663,22 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) /* Remember the file we just rotated to (before any further - rotation can happen) so that write_binlog_info() records this - very file in xtrabackup_binlog_info and the joiner looks for - exactly the file that is shipped below. + rotation can happen), together with its position and GTID, so + that write_binlog_info() records this very file - and a matching + position/GTID - in xtrabackup_binlog_info, and the joiner looks + for exactly the file that is shipped below. */ free(mysql_binlog_file); mysql_binlog_file = strdup(log_bin_file); + free(mysql_binlog_file_pos); + mysql_binlog_file_pos = + log_bin_pos ? strdup(log_bin_pos) : NULL; + free(mysql_binlog_file_gtid_executed); + mysql_binlog_file_gtid_executed = + log_bin_gtid_executed ? strdup(log_bin_gtid_executed) : NULL; + free(mysql_binlog_file_gtid_current_pos); + mysql_binlog_file_gtid_current_pos = + gtid_current_pos ? strdup(gtid_current_pos) : NULL; dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length); @@ -1681,11 +1710,14 @@ write_binlog_info(ds_ctxt *datasink, MYSQL *connection) { char *filename = NULL; const char *out_filename; + const char *out_position; + const char *out_gtid_executed; + const char *out_gtid_current_pos; char *position = NULL; char *gtid_mode = NULL; char *gtid_current_pos = NULL; char *gtid_executed = NULL; - char *gtid = NULL; + const char *gtid = NULL; bool result; bool mysql_gtid; bool mariadb_gtid; @@ -1724,32 +1756,42 @@ write_binlog_info(ds_ctxt *datasink, MYSQL *connection) string owned by the status[] array is still freed at cleanup. */ out_filename = filename; + out_position = position; + out_gtid_executed = gtid_executed; + out_gtid_current_pos = gtid_current_pos; if (mysql_binlog_file != NULL && strcmp(filename, mysql_binlog_file)) { - msg("Binary log rotated to '%s' after '%s' was shipped; " - "recording the shipped file in " XTRABACKUP_BINLOG_INFO, - filename, mysql_binlog_file); + msg("Binary log rotated to '%s' after '%s' was shipped; recording " + "the shipped file and its coordinates in " + XTRABACKUP_BINLOG_INFO, filename, mysql_binlog_file); + /* Record the whole tuple from the shipped file, not just its name, + so file/position/GTID stay consistent (the position and GTID from + the SHOW MASTER STATUS above belong to the current, newer file). */ out_filename = mysql_binlog_file; + out_position = mysql_binlog_file_pos; + out_gtid_executed = mysql_binlog_file_gtid_executed; + out_gtid_current_pos = mysql_binlog_file_gtid_current_pos; } mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0)); - mariadb_gtid = (gtid_current_pos != NULL); + mariadb_gtid = (out_gtid_current_pos != NULL); - gtid = (gtid_executed != NULL ? gtid_executed : gtid_current_pos); + gtid = (out_gtid_executed != NULL ? out_gtid_executed + : out_gtid_current_pos); if (mariadb_gtid || mysql_gtid) { ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s', " "GTID of the last change '%s'", - out_filename, position, gtid) != -1); + out_filename, out_position, gtid) != -1); result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO, - "%s\t%s\t%s\n", out_filename, position, + "%s\t%s\t%s\n", out_filename, out_position, gtid); } else { ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s'", - out_filename, position) != -1); + out_filename, out_position) != -1); result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO, - "%s\t%s\n", out_filename, position); + "%s\t%s\n", out_filename, out_position); } cleanup: @@ -2084,6 +2126,9 @@ backup_cleanup() free(mysql_slave_position); free(mysql_binlog_position); free(mysql_binlog_file); + free(mysql_binlog_file_pos); + free(mysql_binlog_file_gtid_executed); + free(mysql_binlog_file_gtid_current_pos); free(buffer_pool_filename); if (mysql_connection) { diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index 345624fcbf9..cecaaf5c28d 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -3750,6 +3750,19 @@ static void log_copying_thread() return; } + /* + This thread polls Innodb_lsn_flushed via SHOW STATUS on its own connection. + On a Galera donor wsrep_sync_wait may include SHOW, which would make that + poll wait until the node has applied the latest cluster transactions. During + a backup the donor's commit position can legitimately lag (e.g. a transaction + sitting between its binary log write and engine commit), so the poll could + block indefinitely and stall the redo log copier - failing the backup with a + misleading "Was only able to copy log ..." error. The main backup connection + already disables wsrep_sync_wait for the same reason, so do the same here. + */ + if (have_galera_enabled) + xb_mysql_query(limit_con, "SET SESSION wsrep_sync_wait=0", false); + mysql_mutex_lock(&recv_sys.mutex); for (;;) { diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result index 7e7199aa322..2150b39cdec 100644 --- a/mysql-test/suite/galera_3nodes/r/MDEV-38147.result +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result @@ -3,41 +3,50 @@ connection node_1; connection node_1; connection node_2; connection node_3; -connection node_1; # gtid_strict_mode must be enabled on all nodes SELECT @@global.gtid_strict_mode AS gtid_strict_mode; gtid_strict_mode 1 -connection node_2; -connection node_3; -connect node_1_load, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_2_load, 127.0.0.1, root, , test, $NODE_MYPORT_2; -connection node_1_load; -CALL p_load(); -connection node_2_load; -CALL p_load(); connection node_1; connection node_2; connection node_3; +connection node_3; connection node_1; -UPDATE ctrl SET stop = 1 WHERE id = 1; -connection node_1_load; -connection node_2_load; +connection node_1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL debug_dbug = '+d,sync.after_mdl_block_ddl'; +connection node_1; +SET DEBUG_SYNC = 'now WAIT_FOR sync.after_mdl_block_ddl_reached'; +connect node_1_freeze, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1_freeze; +SET DEBUG_SYNC = 'commit_before_get_LOCK_commit_ordered SIGNAL t_frozen WAIT_FOR t_go'; +INSERT INTO t1 (val) VALUES (1); +connection node_1; +SET DEBUG_SYNC = 'now WAIT_FOR t_frozen'; +SET DEBUG_SYNC = 'now SIGNAL signal.after_mdl_block_ddl_continue'; +SET DEBUG_SYNC = 'now SIGNAL t_go'; +connection node_1_freeze; +connection node_1; +SET DEBUG_SYNC = 'RESET'; +SET GLOBAL debug_dbug = ''; +connection node_1; +connection node_3; +connection node_1; +connection node_2; +connection node_3; connection node_1; SET SESSION wsrep_sync_wait = 15; SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; wsrep_cluster_size 3 -connection node_2; -SET SESSION wsrep_sync_wait = 15; -count_match checksum_match gtid_match -1 1 1 connection node_3; SET SESSION wsrep_sync_wait = 15; count_match checksum_match gtid_match 1 1 1 -connection node_1; connection node_2; -connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +DROP TABLE t1; disconnect node_2; disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result new file mode 100644 index 00000000000..e62da7e59db --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result @@ -0,0 +1,20 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); +connection node_3; +connection node_3; +connection node_1; +connection node_1; +connection node_3; +SELECT COUNT(*) AS rows_on_joiner FROM t1; +rows_on_joiner +3 +connection node_1; +DROP TABLE t1; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-40179.result b/mysql-test/suite/galera_3nodes/r/MDEV-40179.result new file mode 100644 index 00000000000..9d8d43bbd73 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-40179.result @@ -0,0 +1,47 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection n1_load_1; +CALL p_load('t1_1'); +connection n2_load_1; +CALL p_load('t1_5'); +connection n1_load_2; +CALL p_load('t1_2'); +connection n2_load_2; +CALL p_load('t1_6'); +connection n1_load_3; +CALL p_load('t1_3'); +connection n2_load_3; +CALL p_load('t1_7'); +connection n1_load_4; +CALL p_load('t1_4'); +connection n2_load_4; +CALL p_load('t1_8'); +connection node_1; +connection node_2; +connection node_3; +connection node_1; +UPDATE ctrl SET stop = 1 WHERE id = 1; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_1; +connection node_2; +connection node_3; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result b/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result new file mode 100644 index 00000000000..6ca4480ce03 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result @@ -0,0 +1,47 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection n1_load_1; +CALL p_load('t1_1'); +connection n2_load_1; +CALL p_load('t1_5'); +connection n1_load_2; +CALL p_load('t1_2'); +connection n2_load_2; +CALL p_load('t1_6'); +connection n1_load_3; +CALL p_load('t1_3'); +connection n2_load_3; +CALL p_load('t1_7'); +connection n1_load_4; +CALL p_load('t1_4'); +connection n2_load_4; +CALL p_load('t1_8'); +connection node_1; +connection node_2; +connection node_3; +connection node_1; +UPDATE ctrl SET stop = 1 WHERE id = 1; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match +1 1 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match +1 1 +connection node_1; +connection node_2; +connection node_3; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf index a8c4cfe8137..3393252e287 100644 --- a/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf @@ -9,7 +9,9 @@ wsrep_gtid_domain_id=100 gtid_domain_id=10 log_bin log_slave_updates=ON -wsrep_slave_threads=4 +innodb_flush_log_at_trx_commit=1 +sync_binlog=1 +wsrep_sync_wait=6 # allow SHOW to workaround MDEV-39468 and reproduce "error 1950" [mysqld.1] server_id=11 @@ -19,6 +21,10 @@ server_id=12 [mysqld.3] server_id=13 +# Force node_3 to always SST from node_1 (the node on which we freeze a +# transaction between binary log write and engine commit), so the snapshot +# node_3 receives is the one whose binary log is ahead of its engine checkpoint. +wsrep_sst_donor=node1 [sst] transferfmt=@ENV.MTR_GALERA_TFMT diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.test b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test index 8b7eb371529..fcfdb025a94 100644 --- a/mysql-test/suite/galera_3nodes/t/MDEV-38147.test +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test @@ -1,147 +1,149 @@ # -# MDEV-38147 +# MDEV-38147 - Mariadb error 1950 after SST # -# Three node cluster with gtid_strict_mode enabled and mariabackup SST. -# Two nodes (node_1 and node_2) run a continuous load, inserting batches -# of rows in a throttled loop, while the third node (node_3) is repeatedly -# stopped, has its data directory purged and is started again, forcing a -# full mariabackup SST on every rejoin (8 times). +# Here a single transaction is frozen on the donor in the 2PC window between +# the binary log write (step 2) and the engine commit (step 3), while a +# mariabackup SST to a joiner is paused just before it fixes its redo-log +# copy point. That makes the copied binary log carry a Gtid_list ahead of +# the copied engine snapshot - exactly the condition the bug is about. # -# At the end the cluster must reconverge to three nodes and all three -# nodes must hold identical data and GTID positions. +# The bug: +# +# BACKUP STAGE BLOCK_COMMIT fixes the InnoDB redo copy point but does not stop +# a wsrep transaction from having its GTID written to the binary log before it +# commits in the engine. So the copied binary log can carry a Gtid_list ahead +# of the copied engine snapshot. After the SST the joiner reports the +# (committed, behind) engine position, IST resends the missing transaction, and +# re-binlogging it under gtid_strict_mode=ON collides with the ahead Gtid_list +# -> ER_GTID_STRICT_OUT_OF_ORDER (error 1950), and the joiner never reaches +# synced state. (The same snapshot also captures the transaction in the InnoDB +# XA-prepared state, i.e. the MDEV-40179 condition.) +# +# The fix makes the joiner discard the received binary log and seed its GTID +# position from the engine checkpoint, so re-binlogging over IST stays in +# lockstep with the cluster and node_3 rejoins cleanly. # ---source include/big_test.inc --source include/galera_cluster.inc --source include/have_innodb.inc --source include/have_mariabackup.inc - -# Number of stop/purge/start cycles for node_3. ---let $restarts = 8 +--source include/have_debug_sync.inc --let $galera_connection_name = node_3 --let $galera_server_number = 3 --source include/galera_connect.inc # Save original auto_increment_offset values so that MTR's post-check is -# happy after node_3 has been restarted multiple times. +# happy after node_3 has been restarted. --let $node_1=node_1 --let $node_2=node_2 --let $node_3=node_3 --source ../galera/include/auto_increment_offset_save.inc -# -# Schema: t1 holds the load, ctrl carries the stop flag for the loaders. -# ---connection node_1 ---disable_query_log -CREATE TABLE t1 (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB; -CREATE TABLE ctrl (id INT PRIMARY KEY, stop INT) ENGINE=InnoDB; -INSERT INTO ctrl VALUES (1, 0); - -DELIMITER |; -CREATE PROCEDURE p_load() -BEGIN - DECLARE v_stop INT DEFAULT 0; - DECLARE v_i INT; - # Keep the loop alive across transient cluster errors (BF aborts, - # certification failures, donor desync timeouts, ...). - DECLARE CONTINUE HANDLER FOR SQLEXCEPTION - BEGIN - ROLLBACK; - END; - WHILE v_stop = 0 DO - START TRANSACTION; - SET v_i = 0; - WHILE v_i < 16 DO - INSERT INTO t1 (val) VALUES (v_i); - SET v_i = v_i + 1; - END WHILE; - COMMIT; - # Throttle slightly between transactions so that a freshly joined node can - # catch up its replication queue instead of being starved by the load. - DO SLEEP(0.01); - SELECT stop INTO v_stop FROM ctrl WHERE id = 1; - END WHILE; -END| -DELIMITER ;| ---enable_query_log - --echo # gtid_strict_mode must be enabled on all nodes SELECT @@global.gtid_strict_mode AS gtid_strict_mode; -# Make sure the schema reached the other nodes before starting the load. ---connection node_2 ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; ---source include/wait_condition.inc ---connection node_3 ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; ---source include/wait_condition.inc - -# -# Start the continuous load on node_1 and node_2. -# ---connect node_1_load, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---connect node_2_load, 127.0.0.1, root, , test, $NODE_MYPORT_2 - ---connection node_1_load ---send CALL p_load() - ---connection node_2_load ---send CALL p_load() - -# -# While the load is running, repeatedly stop node_3, purge its data -# directory and start it again. An empty data directory forces a full -# mariabackup SST on every rejoin. -# +--connection node_1 --disable_query_log ---let $i = $restarts -while ($i) -{ - --connection node_3 - --source include/shutdown_mysqld.inc - --disable_query_log - - # Wait until node_3 has actually left the cluster. - # (shutdown_mysqld.inc / wait_condition.inc / start_mysqld.inc / - # galera_wait_ready.inc each re-enable the query log, so re-disable it after - # every such include to keep the loop output out of the result file.) - --connection node_1 - --let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; - --source include/wait_condition.inc - --disable_query_log - - # Purge node_3's data directory. - --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test - --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql - --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema - --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr - --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data - - # Start node_3 again (rejoins via mariabackup SST). - --connection node_3 - --let $restart_noprint = 2 - --source include/start_mysqld.inc - --disable_query_log - --source include/galera_wait_ready.inc - --disable_query_log - - # Wait until the cluster is back to three nodes before the next cycle. - --connection node_1 - --let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; - --source include/wait_condition.inc - --disable_query_log - - --dec $i -} +CREATE TABLE t1 (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB; --enable_query_log +# Make sure the schema reached all nodes before we purge node_3. +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc + # -# Make sure the whole cluster is healthy before stopping the load, so that -# any donor that desynced during SST has resynced and the loaders can read -# the stop flag without blocking. +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST. # +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# +# Arm the donor-side backup sync point: mariabackup will pause after +# BACKUP STAGE BLOCK_DDL, before BLOCK_COMMIT (which fixes the redo copy point). +# +--connection node_1 +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL debug_dbug = '+d,sync.after_mdl_block_ddl'; + +# +# Start node_3 WITHOUT waiting for it to become ready: it rejoins via a +# mariabackup SST from node_1, whose donor backup pauses at the sync point armed +# above. We must not block on node_3 here, because releasing that pause (and +# everything that lets node_3 finish) happens below on the node_1 connection. +# So just trigger the restart via the expect file and continue. +# +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line restart $_expect_file_name + +--connection node_1 +SET DEBUG_SYNC = 'now WAIT_FOR sync.after_mdl_block_ddl_reached'; + +# +# Freeze one transaction between binary log write and engine commit. +# commit_before_get_LOCK_commit_ordered is reached after the GTID/Xid events +# have been written and fsync'd to the binary log but before the engine commit. +# +--connect node_1_freeze, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1_freeze +SET DEBUG_SYNC = 'commit_before_get_LOCK_commit_ordered SIGNAL t_frozen WAIT_FOR t_go'; +--send INSERT INTO t1 (val) VALUES (1) + +--connection node_1 +SET DEBUG_SYNC = 'now WAIT_FOR t_frozen'; + +# +# Let mariabackup continue. It fixes the redo copy point at BLOCK_COMMIT with +# the frozen transaction still in-doubt (so it is excluded from the engine +# snapshot), then issues "FLUSH BINARY LOGS" while shipping the binary log, +# which blocks on the in-doubt transaction's binary log checkpoint. +# +SET DEBUG_SYNC = 'now SIGNAL signal.after_mdl_block_ddl_continue'; + +--let $wait_condition = SELECT COUNT(*) >= 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE INFO LIKE 'FLUSH BINARY LOGS%' +--source include/wait_condition.inc + +# +# Release the frozen transaction. Its engine commit happens now - after the +# redo copy point was fixed - so it is absent from the copied engine snapshot +# while present in the shipped binary log's Gtid_list. +# +SET DEBUG_SYNC = 'now SIGNAL t_go'; + +--connection node_1_freeze +--reap +--connection node_1 +SET DEBUG_SYNC = 'RESET'; +SET GLOBAL debug_dbug = ''; + +# +# node_3 must rejoin and the cluster must reconverge to three nodes. +# +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect + --connection node_1 --source include/galera_wait_ready.inc --connection node_2 @@ -150,18 +152,8 @@ while ($i) --source include/galera_wait_ready.inc # -# Signal the loaders to stop and collect them. -# ---connection node_1 -UPDATE ctrl SET stop = 1 WHERE id = 1; - ---connection node_1_load ---reap ---connection node_2_load ---reap - -# -# Verify reconvergence and data / GTID consistency across all nodes. +# Verify data / GTID consistency across all nodes. node_1 is the origin of the +# highest GTID, so node_2 and node_3 are waited *up* to node_1's position. # --connection node_1 SET SESSION wsrep_sync_wait = 15; @@ -169,59 +161,36 @@ SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATU --let $expect_count = `SELECT COUNT(*) FROM t1` --let $expect_sum = `SELECT COALESCE(SUM(pk), 0) + COALESCE(SUM(val), 0) FROM t1` ---let $expect_gtid = `SELECT @@global.gtid_binlog_pos` +# Compare only the wsrep domain (wsrep_gtid_domain_id) of gtid_binlog_pos - that +# is the part the whole cluster shares; other domains are node-local. +--let $wsrep_dom = `SELECT @@global.wsrep_gtid_domain_id` +--let $expect_gtid = `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos, '(?.index' not found (Errcode: 2)". +# +# Here node_3 rejoins via a full mariabackup SST with log_bin relocated to a +# directory that does not exist yet (outside its datadir). The SST script must +# (re)create it so the joiner comes up and rejoins the cluster. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--connection node_1 +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); + +# Make sure the data reached node_3 before we purge it. +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 3 FROM t1 +--source include/wait_condition.inc + +# +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST. +# +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# A binary-log directory that does not exist yet, outside node_3's datadir. +--let $binlog_reloc = $MYSQLTEST_VARDIR/mysqld.3/reloc_binlog +--perl +use File::Path qw(rmtree); +rmtree("$ENV{MYSQLTEST_VARDIR}/mysqld.3/reloc_binlog"); +EOF + +# +# Restart node_3 with the binary log relocated to the (missing) directory. +# Do not block on it: it rejoins via a mariabackup SST which, with the fix, +# creates the directory before mysqld reopens the binary log. +# +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line "restart:--log-bin=$binlog_reloc/mdev-bin --log-bin-index=$binlog_reloc/mdev-bin.index" $_expect_file_name + +--connection node_1 +--let $wait_timeout = 60 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect +--source include/galera_wait_ready.inc + +# The joiner (re)created the relocated binlog directory and opened a fresh +# binary log there. +--file_exists $binlog_reloc/mdev-bin.index +--file_exists $binlog_reloc/mdev-bin.000001 + +# The SST data made it across. +--let $wait_condition = SELECT COUNT(*) = 3 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS rows_on_joiner FROM t1; + +# +# Cleanup. +# +--connection node_1 +DROP TABLE t1; + +# Restore original auto_increment_offset values. +--source ../galera/include/auto_increment_offset_restore.inc + +--source include/galera_end.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf new file mode 100644 index 00000000000..1fd5b6057f6 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf @@ -0,0 +1,30 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +log_bin +log_slave_updates=ON +# The case under test: binary logging ON, but Galera GTID mode OFF. Cluster +# writes are then binlogged under the node's own gtid_domain_id with a locally +# allocated seq_no, unrelated to the wsrep cluster seqno in the SE checkpoint. +wsrep_gtid_mode=OFF +gtid_domain_id=10 +# Distinct from gtid_domain_id so that any (incorrect) seeding from the wsrep +# checkpoint would be visible as a foreign domain/seqno. +wsrep_gtid_domain_id=100 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 +# Force node_3 to always SST from node_1. +wsrep_sst_donor=node1 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test new file mode 100644 index 00000000000..54cbe37754e --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test @@ -0,0 +1,109 @@ +# +# MDEV-38147 follow-up: binary-log GTID seeding must apply only with +# wsrep_gtid_mode=ON. +# +# After MDEV-38147 a mariabackup-SST joiner starts a fresh binary log and, in +# wsrep_gtid_mode=ON, seeds @@gtid_binlog_pos from the storage-engine checkpoint +# (cluster writes are re-tagged to wsrep_gtid_domain_id and binlogged with the +# cluster seqno, so the checkpoint position maps directly onto the binlog GTID +# state - see MDEV-38147.test for the ON-mode case). +# +# With wsrep_gtid_mode=OFF that mapping does not hold: cluster writes keep the +# node's own gtid_domain_id and are binlogged with a locally allocated seq_no, +# while the cluster seqno lives only in thd->wsrep_current_gtid_seqno and is +# never written to the binlog GTID. Seeding gtid_domain_id from the checkpoint +# seqno would therefore inject a wrong, node-unrelated position. So in OFF mode +# the joiner must seed nothing; a fresh binary log simply resumes its own local +# counter. +# +# This test drives a full mariabackup SST of node_3 in wsrep_gtid_mode=OFF and +# asserts the seeding code path did NOT run on the joiner. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--echo # Precondition: Galera GTID mode is OFF +SELECT @@global.wsrep_gtid_mode AS wsrep_gtid_mode; + +--connection node_1 +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); + +# Make sure the data reached node_3 before we purge it. +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 5 FROM t1 +--source include/wait_condition.inc + +# +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST (which restores the wsrep position into the SE checkpoint). +# +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# Restart node_3 (non-blocking): it rejoins via a mariabackup SST from node_1. +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line restart $_expect_file_name + +--connection node_1 +--let $wait_timeout = 60 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect +--source include/galera_wait_ready.inc + +# The SST data made it across. +--let $wait_condition = SELECT COUNT(*) = 5 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS rows_on_joiner FROM t1; + +# +# Regression assertion: in wsrep_gtid_mode=OFF the joiner must NOT seed its +# binary-log GTID state from the wsrep checkpoint. The seeding code path emits +# this exact line only when it runs (it does run in the ON-mode +# MDEV-38147.test); here it must be absent on the joiner. +# +--let $assert_text = joiner did not seed binlog GTID state in wsrep_gtid_mode=OFF +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.3.err +--let $assert_select = seeding binlog GTID state +--let $assert_count = 0 +--source include/assert_grep.inc + +# +# Cleanup. +# +--connection node_1 +DROP TABLE t1; + +# Restore original auto_increment_offset values. +--source ../galera/include/auto_increment_offset_restore.inc + +--source include/galera_end.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf new file mode 100644 index 00000000000..1ce02400920 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf @@ -0,0 +1,37 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +gtid_strict_mode=ON +wsrep_gtid_mode=ON +wsrep_gtid_domain_id=100 +gtid_domain_id=10 +log_bin +log_slave_updates=ON +# Parallel apply so that prepared transactions can be committed out of order, +# producing a non-contiguous prepared set on the donor. +wsrep_slave_threads=8 +# Slow, durable commits widen the window during which transactions sit in the +# prepared (XA) state of two-phase commit, so the backup's BLOCK_COMMIT +# snapshot is more likely to capture in-doubt transactions. sync_binlog adds an +# fsync inside the 2PC window without exploding the binlog file count, and a +# moderate max_binlog_size keeps the binlog rotating (further widening the +# window) while avoiding the tens of thousands of tiny files that a 4 KB limit +# would create under this load. +innodb_flush_log_at_trx_commit=1 +sync_binlog=1 +max_binlog_size=16384 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc b/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc new file mode 100644 index 00000000000..8e4bcc0b677 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc @@ -0,0 +1,347 @@ +# +# Shared body for the MDEV-40179 tests (sourced by MDEV-40179.test with +# log_bin=ON and MDEV-40179_nobinlog.test with log_bin=OFF). +# +# The bug (reproduced by the log_bin=ON variant): +# +# With log_bin=ON a transaction is committed via two-phase commit (the binary +# log is the second participant), so it passes through the InnoDB XA-prepare +# state. While a donor is held in BLOCK_COMMIT for a mariabackup backup, its +# parallel appliers (wsrep_slave_threads > 1) leave one or more such writesets +# prepared-but-not-yet-committed, and the snapshot captures them. On a freshly +# SST'd joiner nothing resolves these prepared transactions: binlog crash +# recovery does not run (the joiner has no in-use binlog to recover from), and +# the wsrep continuity-based commit is inactive because wsrep_emulate_bin_log +# is FALSE when log_bin is ON. The leftover prepared transactions then abort +# startup with "Found prepared transactions!". Note this does not depend on +# the prepared set being non-contiguous - even a contiguous run aborts, because +# nothing commits or rolls it back. +# +# The log_bin=OFF variant is coverage only: with a single (InnoDB) read-write +# engine and no binary log, commits use one-phase commit, so transactions never +# enter the XA-prepared state and the snapshot has nothing in doubt. It simply +# verifies that mariabackup SST and reconvergence keep working with log_bin=OFF. +# +# To maximize parallel apply on the donor (and thus the chance of catching +# prepared transactions in the snapshot) each client thread writes to its own +# table: there are no certification conflicts between writers, so all of them +# apply concurrently. $writers client threads load on each of node_1 and node_2 +# while node_3 is repeatedly stopped, has its data directory purged and is +# started again, forcing a full mariabackup SST on every rejoin. At the end the +# cluster must reconverge to three nodes and all three nodes must hold identical +# data (and, with log_bin, identical GTID positions). +# +# Parameters set by the including .test: +# $restarts - number of stop/purge/start cycles for node_3 +# $writers - number of concurrent loader threads per node (each gets its +# own table to avoid certification conflicts) +# $check_gtid - 1 to also compare @@global.gtid_binlog_pos across nodes +# (only meaningful with log_bin), 0 otherwise +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted multiple times. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +# Total number of data tables: one per writer thread across both nodes. +--let $ntables = `SELECT 2 * $writers` + +# +# Schema: t1_1 .. t1_$ntables hold the load (one table per writer thread), +# ctrl carries the stop flag for the loaders. +# +--connection node_1 +--disable_query_log +CREATE TABLE ctrl (id INT PRIMARY KEY, stop INT) ENGINE=InnoDB; +INSERT INTO ctrl VALUES (1, 0); + +--let $t = 1 +while ($t <= $ntables) +{ + --eval CREATE TABLE t1_$t (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB + --inc $t +} + +DELIMITER |; +CREATE PROCEDURE p_load(IN tname VARCHAR(64)) +BEGIN + DECLARE v_stop INT DEFAULT 0; + DECLARE v_i INT; + # Keep the loop alive across transient cluster errors (BF aborts, + # certification failures, donor desync timeouts, ...). + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + END; + SET @ins_sql = CONCAT('INSERT INTO ', tname, ' (pk, val) VALUES (DEFAULT, 1)'); + PREPARE ins FROM @ins_sql; + WHILE v_stop = 0 DO + START TRANSACTION; + SET v_i = 0; + WHILE v_i < 16 DO + EXECUTE ins; + SET v_i = v_i + 1; + END WHILE; + COMMIT; + # Throttle slightly between transactions so that a freshly joined node can + # catch up its replication queue instead of being starved by the load. + DO SLEEP(0.01); + SELECT stop INTO v_stop FROM ctrl WHERE id = 1; + END WHILE; + DEALLOCATE PREPARE ins; +END| +DELIMITER ;| +--enable_query_log + +# Make sure the schema reached the other nodes before starting the load. +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = $ntables FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't1\_%'; +--source include/wait_condition.inc +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = $ntables FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't1\_%'; +--source include/wait_condition.inc + +# +# Start the continuous load: $writers threads on node_1 (tables t1_1..t1_W) +# and $writers threads on node_2 (tables t1_(W+1)..t1_2W). +# +--disable_query_log +--let $w = 1 +while ($w <= $writers) +{ + --connect (n1_load_$w, 127.0.0.1, root, , test, $NODE_MYPORT_1) + --connect (n2_load_$w, 127.0.0.1, root, , test, $NODE_MYPORT_2) + --inc $w +} +--enable_query_log + +--let $w = 1 +--let $n2 = $writers +while ($w <= $writers) +{ + --connection n1_load_$w + --send_eval CALL p_load('t1_$w') + --inc $n2 + --connection n2_load_$w + --send_eval CALL p_load('t1_$n2') + --inc $w +} + +# +# While the load is running, repeatedly stop node_3, purge its data +# directory and start it again. An empty data directory forces a full +# mariabackup SST on every rejoin. +# +--disable_query_log +--let $i = $restarts +while ($i) +{ + --connection node_3 + --source include/shutdown_mysqld.inc + --disable_query_log + + # Wait until node_3 has actually left the cluster. + # (shutdown_mysqld.inc / wait_condition.inc / start_mysqld.inc / + # galera_wait_ready.inc each re-enable the query log, so re-disable it after + # every such include to keep the loop output out of the result file.) + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + # Purge node_3's data directory. + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + + # Start node_3 again (rejoins via mariabackup SST). + --connection node_3 + --let $restart_noprint = 2 + --source include/start_mysqld.inc + --disable_query_log + --source include/galera_wait_ready.inc + --disable_query_log + + # Wait until the cluster is back to three nodes before the next cycle. + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + --dec $i +} +--enable_query_log + +# +# Make sure the whole cluster is healthy before stopping the load, so that +# any donor that desynced during SST has resynced and the loaders can read +# the stop flag without blocking. +# +--connection node_1 +--source include/galera_wait_ready.inc +--connection node_2 +--source include/galera_wait_ready.inc +--connection node_3 +--source include/galera_wait_ready.inc + +# +# Signal the loaders to stop and collect them. +# +--connection node_1 +UPDATE ctrl SET stop = 1 WHERE id = 1; + +--disable_query_log +--let $w = 1 +while ($w <= $writers) +{ + --connection n1_load_$w + --reap + --connection n2_load_$w + --reap + --inc $w +} +--enable_query_log + +# +# Build the aggregate count / checksum expressions over all data tables. +# +--let $count_expr = 0 +--let $sum_expr = 0 +--let $t = 1 +while ($t <= $ntables) +{ + --let $count_expr = $count_expr + (SELECT COUNT(*) FROM t1_$t) + --let $sum_expr = $sum_expr + (SELECT COALESCE(SUM(pk),0)+COALESCE(SUM(val),0) FROM t1_$t) + --inc $t +} + +# +# Verify reconvergence and data / GTID consistency across all nodes. +# +--connection node_1 +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + +# The load has stopped; issue one final transaction from node_1 (sync_wait is +# on, so node_1 first applies everything else). This makes node_1 the origin of +# the cluster's highest GTID, so the checks below can wait for node_2/node_3 to +# converge *up* to node_1's position instead of comparing a single snapshot: +# @@gtid_binlog_pos is a system variable, so reading it is not covered by +# wsrep_sync_wait and a plain read can otherwise sample a position before the +# node has finished applying (a race that grows with accumulated load, e.g. +# under --repeat). +--disable_query_log +UPDATE ctrl SET stop = 2 WHERE id = 1; +--enable_query_log + +--let $expect_count = `SELECT $count_expr` +--let $expect_sum = `SELECT $sum_expr` +if ($check_gtid) +{ + # Compare only the wsrep domain (wsrep_gtid_domain_id) of gtid_binlog_pos. + # That is the part the whole cluster shares. Other domains in the position + # are node-local and legitimately differ: e.g. CALL mtr.add_suppression() + # below writes to the non-replicated 'mtr' database, which each node binlogs + # under its own gtid_domain_id/server_id - so those entries accumulate + # per-node across runs (visible under --repeat) and must not be compared. + --let $wsrep_dom = `SELECT @@global.wsrep_gtid_domain_id` + --let $expect_gtid = `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos, '(? prepared transactions!". (gtid_strict_mode is enabled so any +# binlog/engine position inconsistency would also be caught.) +# +# See MDEV-40179.inc for the shared test body. +# + +--let $restarts = 8 +--let $writers = 4 +--let $check_gtid = 1 +--source MDEV-40179.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf new file mode 100644 index 00000000000..38740c2ec87 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf @@ -0,0 +1,27 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +# No log_bin: Galera uses its emulated binlog (wsrep_emulate_bin_log), so the +# wsrep XID continuity check is what resolves prepared transactions on a joiner. +# Parallel apply so that prepared transactions can be committed out of order, +# producing a non-contiguous prepared set on the donor. +wsrep_slave_threads=8 +# Slow, durable commits widen the window during which transactions sit in the +# prepared state, so the backup's BLOCK_COMMIT snapshot is more likely to +# capture in-doubt transactions. +innodb_flush_log_at_trx_commit=1 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test new file mode 100644 index 00000000000..3eb96d3defd --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test @@ -0,0 +1,16 @@ +# +# MDEV-40179 - prepared transactions left behind by a mariabackup SST. +# +# log_bin=OFF variant: coverage only. With a single InnoDB read-write engine +# and no binary log, commits use one-phase commit, so transactions never enter +# the XA-prepared state and a mariabackup snapshot has nothing in doubt - the +# bug cannot occur here. This variant just exercises the same load and repeated +# mariabackup SST with log_bin=OFF and checks the cluster reconverges. +# +# See MDEV-40179.inc for the shared test body. +# + +--let $restarts = 8 +--let $writers = 4 +--let $check_gtid = 0 +--source MDEV-40179.inc diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 237f128a104..7515ee029b3 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -1462,8 +1462,8 @@ else # joiner # that an old joiner keeps working and a new joiner can identify # exactly which file was sent. That file, however, only carries a # Gtid_list, and its position can be ahead of the engine snapshot - # (BACKUP STAGE BLOCK_COMMIT blocks the engine commit but not the - # binary log write). With gtid_strict_mode=ON that ahead position + # (BACKUP STAGE BLOCK_COMMIT does not pause commits if mariabackup + # is used for SST). With gtid_strict_mode=ON that ahead position # makes the joiner raise error 1950 when it re-binlogs transactions # during IST, and keeping the file could also collide with the # joiner's own binary log numbering. @@ -1475,28 +1475,30 @@ else # joiner # from which IST resumes, which keeps the joiner's binary log in # lockstep with the rest of the cluster. # - binlogs="" + binlogs=() if [ -f 'xtrabackup_binlog_info' ]; then while read bin_string || [ -n "$bin_string" ]; do bin_file=$(echo "$bin_string" | cut -f1) if [ -n "$bin_file" -a -f "$bin_file" ]; then - binlogs="$binlogs${binlogs:+ }$bin_file" + binlogs+=("$bin_file") fi done < 'xtrabackup_binlog_info' else - binlogs=$(ls -d -1 "$binlog_base".[0-9]* 2>/dev/null || :) + for bin_file in "$binlog_base".[0-9]*; do + [ -f "$bin_file" ] && binlogs+=("$bin_file") + done fi - if [ -n "$binlogs" ]; then + if [ ${#binlogs[@]} -ne 0 ]; then wsrep_log_info "Removing received binary log(s) so the joiner" \ "starts a fresh binary log seeded from the" \ - "storage-engine checkpoint (MDEV-38147)" - for bin_file in $binlogs; do + "storage-engine checkpoint" + for bin_file in "${binlogs[@]}"; do rm -f "$DATA/$bin_file" done else wsrep_log_info "No binary log received from donor; the joiner" \ "will start a fresh binary log seeded from the" \ - "storage-engine checkpoint (MDEV-38147)" + "storage-engine checkpoint" fi cd "$OLD_PWD" fi @@ -1516,6 +1518,38 @@ else # joiner exit 22 fi + # + # The joiner starts a fresh binary log after SST (see the MDEV-38147 + # note above), but the server does not create the log-bin directory + # itself - it must exist before mysqld reopens the binary log. When the + # binary log lives outside the datadir it may not exist yet on a freshly + # provisioned joiner, and when it lives in a subdirectory of the datadir + # the cleanup above removed it and the move stage did not restore it + # (binary logs are not part of the backup). Recreate the binary log and + # index directories here, after the move so they are not wiped again. + # Relative paths are resolved against the datadir, matching the server. + # + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + cd "$DATA_DIR" + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ + "$binlog_dir" != "$DATA_DIR" -a ! -d "$binlog_dir" ] + then + wsrep_log_info "Creating the binlog directory '$binlog_dir'" + mkdir -p "$binlog_dir" + fi + if [ -n "$binlog_index" ]; then + index_dir=$(dirname "$binlog_index") + if [ -n "$index_dir" -a "$index_dir" != '.' -a \ + "$index_dir" != "$DATA_DIR" -a ! -d "$index_dir" ] + then + wsrep_log_info \ + "Creating the binlog index directory '$index_dir'" + mkdir -p "$index_dir" + fi + fi + cd "$OLD_PWD" + fi + else wsrep_log_info "'$IST_FILE' received from donor: Running IST" diff --git a/sql/handler.cc b/sql/handler.cc index 84e78b83740..27b3e668238 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2843,6 +2843,53 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, x <= wsrep_limit) && info->dry_run, info->dry_run)) { +#ifdef WITH_WSREP + /* + MDEV-40179: a wsrep transaction still in the prepared state at the + final recovery pass (the dry run, commit_list == 0) but they don't + have corresponding binlog events because they are no longer part of + SST. With log_bin=ON they can't be committed. + After recovering from SST without binlogs in place the joiner runs + no binlog XA recovery to commit or roll back such transactions, so + without binlog events they would abort startup with "Found N prepared + transactions!". Roll them back here; the node re-receives them + from the donor via IST. Non-wsrep (e.g. user XA) prepared transactions + are left untouched and still reported. + + Notice that in the wsrep_emulate_bin_log case below we don't need the + binlog events, so these prepared and wsrep-ordered transactions can be + safely committed. + + The guard is WSREP_PROVIDER_EXISTS ("a Galera provider is loaded"): + a node configured with a provider will rejoin and receive + these transactions; a standalone node (no provider) cannot, so + there we keep the conservative default and still report them. + */ + if (WSREP_PROVIDER_EXISTS && wsrep_is_wsrep_xid(info->list + i)) + { + int rc= hton->rollback_by_xid(hton, info->list + i); + if (rc == 0) + { + sql_print_warning("Rolled back orphan prepared wsrep " + "transaction %lld", (longlong) x); + continue; + } + /* + A failed rollback is critical: the storage engine is left with + a transaction in the prepared state, which blocks purge and will + re-surface at the next recovery. We cannot safely continue, so + flag the error and abort startup (ha_recover() returns non-zero, + which makes the caller unireg_abort()). + */ + sql_print_error("Failed to roll back orphan prepared wsrep " + "transaction %lld during recovery (error %d). " + "The storage engine is left with a transaction in " + "the prepared state; aborting startup.", + (longlong) x, rc); + info->error= true; + break; + } +#endif /* WITH_WSREP */ info->found_my_xids++; continue; } @@ -2892,7 +2939,7 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, } } } - if (got < info->len) + if (got < info->len || info->error) break; } } diff --git a/sql/log.cc b/sql/log.cc index 07c717a3c6f..00544f71b17 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -12029,28 +12029,37 @@ err1: (also restored from the engine) and are handled separately, so they are not seeded here. - The whole cluster binlogs cluster writes under one consistent stream (the - seqno stays in lockstep because every node applies in the same total order). - The domain of that stream depends on the mode: - - wsrep_gtid_mode=ON : wsrep_gtid_domain_id (cluster writes are re-tagged - to it, see [wsrep_mysqld.cc:2983]); this is the domain in the checkpoint. - - wsrep_gtid_mode=OFF: gtid_domain_id (cluster writes keep the node's - configured domain, no re-tag). - In both modes the committed cluster seqno is the SE checkpoint seqno, so we - seed that domain's binlog state to the checkpoint position. This is also the - exact position from which IST will resume re-binlogging, so the joiner stays - in lockstep with the rest of the cluster (and, in ON mode, avoids error 1950 - from re-binlogging over an ahead position). + This seeding applies only with wsrep_gtid_mode=ON. In that mode cluster + writes are re-tagged to wsrep_gtid_domain_id and binlogged with the cluster + seqno, so @@gtid_binlog_pos in that domain tracks the cluster seqno - which is + exactly the SE checkpoint position, and exactly the position from which IST + resumes re-binlogging. Seeding it keeps the joiner in lockstep and avoids + error 1950 from re-binlogging over an ahead position. + + With wsrep_gtid_mode=OFF cluster writes keep the node's configured + gtid_domain_id and are binlogged with a locally allocated seq_no; the cluster + seqno is not written to the binlog GTID (it lives only in + thd->wsrep_current_gtid_seqno - see the GTID assignment guarded by + wsrep_gtid_mode in wsrep_mysqld.cc). That binlog position is node-local and + unrelated to the checkpoint seqno, so there is nothing cluster-consistent to + seed: a fresh joiner just resumes its own local counter, which cannot produce + an ahead position. We therefore seed nothing in that mode. */ static void wsrep_seed_binlog_gtid_state() { + /* + Only wsrep_gtid_mode=ON has a cluster-consistent binlog position that maps + onto the SE checkpoint (see the block comment above). + */ + if (!wsrep_gtid_mode) + return; + wsrep_server_gtid_t const eng= wsrep_get_SE_checkpoint(); if (eng.seqno <= 0) return; /* not a wsrep node / no position */ rpl_gtid eng_gtid; - eng_gtid.domain_id= wsrep_gtid_mode ? eng.domain_id - : global_system_variables.gtid_domain_id; + eng_gtid.domain_id= eng.domain_id; /* == wsrep_gtid_domain_id */ eng_gtid.server_id= eng.server_id; eng_gtid.seq_no= eng.seqno; @@ -12062,7 +12071,17 @@ static void wsrep_seed_binlog_gtid_state() "from the storage-engine checkpoint", eng_gtid.domain_id, eng_gtid.server_id, (unsigned long long) eng_gtid.seq_no); - rpl_global_gtid_binlog_state.update_nolock(&eng_gtid, false); + /* + Use the locking update() for consistency with the find_most_recent() read + above. With strict=false the only failure is OOM; update() will have called + my_error(ER_OUT_OF_RESOURCES), but there is no current_thd this early in + startup, so report the failure explicitly here as well. + */ + if (rpl_global_gtid_binlog_state.update(&eng_gtid, false)) + sql_print_error("WSREP: failed to seed binlog GTID state to %u-%u-%llu " + "from the storage-engine checkpoint (out of memory)", + eng_gtid.domain_id, eng_gtid.server_id, + (unsigned long long) eng_gtid.seq_no); } #endif /* WITH_WSREP && HAVE_REPLICATION */ diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc index 71f0325967d..b0a0b2cf566 100644 --- a/sql/wsrep_sst.cc +++ b/sql/wsrep_sst.cc @@ -425,7 +425,7 @@ static bool wsrep_sst_complete (THD* thd, position actually adopted, not the script-reported one, to avoid confusion. */ - wsrep::gtid const received_gtid(wsrep_get_SE_checkpoint()); + wsrep::gtid const received_gtid= wsrep_get_SE_checkpoint(); char recv_pos_buf[FN_REFLEN]; ssize_t const recv_len= wsrep::print_to_c_str(received_gtid, recv_pos_buf, FN_REFLEN-1); @@ -437,7 +437,7 @@ static bool wsrep_sst_complete (THD* thd, { char start_pos_buf[FN_REFLEN]; ssize_t const len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN - 1); - start_pos_buf[len]= '\0'; + start_pos_buf[len > 0 ? len : 0]= '\0'; WSREP_ERROR("SST failed for position %s initialized %d server_state %s", start_pos_buf, server_state.is_initialized(), From a848493c6fe031f23606144420c1ca1e467cbd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 10 Aug 2026 12:18:36 +0300 Subject: [PATCH 096/150] MDEV-40683 SET GLOBAL innodb_log_archive=OFF may break recovery log_t::set_archive(false, thd): Ensure that the sequence bit value 0 will be expected on crash recovery and backup. log_t::circular_recovery_from_0(): Accessor for log_sys.circular_recovery_from_sequence_bit_0. buf_flush_wait(), log_checkpoint_low(): Ensure that a checkpoint will be written to reset log_sys.circular_recovery_from_sequence_bit_0. log_t::write_checkpoint(): Reset circular_recovery_from_sequence_bit_0 whenever applicable. We used to blindly reset it in log_t::set_archive(). --- .../mariabackup/huge_lsn,strict_crc32.rdiff | 9 ++++++--- mysql-test/suite/mariabackup/huge_lsn.result | 1 + mysql-test/suite/mariabackup/huge_lsn.test | 4 ++++ storage/innobase/buf/buf0flu.cc | 16 ++++++++++++++-- storage/innobase/include/log0log.h | 5 +++++ storage/innobase/log/log0log.cc | 17 +++++++++++++---- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/mysql-test/suite/mariabackup/huge_lsn,strict_crc32.rdiff b/mysql-test/suite/mariabackup/huge_lsn,strict_crc32.rdiff index 9ed08fcd26a..39d26a39dcc 100644 --- a/mysql-test/suite/mariabackup/huge_lsn,strict_crc32.rdiff +++ b/mysql-test/suite/mariabackup/huge_lsn,strict_crc32.rdiff @@ -1,6 +1,6 @@ ---- suite/mariabackup/huge_lsn.result -+++ suite/mariabackup/huge_lsn.reject -@@ -1,8 +1,8 @@ +--- huge_lsn.result ++++ huge_lsn,strict_crc32.result +@@ -1,11 +1,10 @@ # # MDEV-13416 mariabackup fails with EFAULT "Bad Address" # @@ -10,4 +10,7 @@ +FOUND 1 /redo log: [0-9.]*[KMGT]iB; LSN=17596481010687\b/ in mysqld.1.err CREATE TABLE t(i INT) ENGINE=INNODB ENCRYPTED=YES; INSERT INTO t VALUES(1); +-SET GLOBAL innodb_log_archive=ON, innodb_log_archive=OFF; # xtrabackup backup + SET GLOBAL innodb_flush_log_at_trx_commit=1; + INSERT INTO t VALUES(2); diff --git a/mysql-test/suite/mariabackup/huge_lsn.result b/mysql-test/suite/mariabackup/huge_lsn.result index 503d13fcac4..ce24a24edf4 100644 --- a/mysql-test/suite/mariabackup/huge_lsn.result +++ b/mysql-test/suite/mariabackup/huge_lsn.result @@ -5,6 +5,7 @@ FOUND 1 /InnoDB: log sequence number 17596481011216/ in mysqld.1.err CREATE TABLE t(i INT) ENGINE=INNODB ENCRYPTED=YES; INSERT INTO t VALUES(1); +SET GLOBAL innodb_log_archive=ON, innodb_log_archive=OFF; # xtrabackup backup SET GLOBAL innodb_flush_log_at_trx_commit=1; INSERT INTO t VALUES(2); diff --git a/mysql-test/suite/mariabackup/huge_lsn.test b/mysql-test/suite/mariabackup/huge_lsn.test index 0da67744457..a7c9b0f3f0d 100644 --- a/mysql-test/suite/mariabackup/huge_lsn.test +++ b/mysql-test/suite/mariabackup/huge_lsn.test @@ -76,6 +76,10 @@ let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; CREATE TABLE t(i INT) ENGINE=INNODB ENCRYPTED=YES; INSERT INTO t VALUES(1); +if (!$MTR_COMBINATION_STRICT_CRC32) { +SET GLOBAL innodb_log_archive=ON, innodb_log_archive=OFF; +} + echo # xtrabackup backup; let $targetdir=$MYSQLTEST_VARDIR/tmp/backup; --disable_result_log diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index be4b0e473b2..829b402d70f 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -1942,7 +1942,6 @@ inline lsn_t log_t::write_checkpoint(lsn_t checkpoint, lsn_t end_lsn) noexcept archive_header_was_reset= first_lsn + capacity(); ut_ad(current_lsn >= first_lsn); ut_ad(current_lsn < archive_header_was_reset); - circular_recovery_from_sequence_bit_0= false; next_checkpoint_no= uint16_t(4 * is_encrypted()); if (is_encrypted()) @@ -1951,8 +1950,18 @@ inline lsn_t log_t::write_checkpoint(lsn_t checkpoint, lsn_t end_lsn) noexcept #ifdef HAVE_PMEM c= checkpoint_buf; #endif + + goto all_sequence_bit_1; } } + else + all_sequence_bit_1: + /* + All log records starting with this checkpoint in the + innodb_log_archive=ON format will have been + written with the sequence bit 1. + */ + circular_recovery_from_sequence_bit_0= false; ut_ad(end_lsn >= first_lsn); offset= next_checkpoint_no * 8; @@ -2213,6 +2222,7 @@ static lsn_t log_checkpoint_low(lsn_t oldest_lsn, lsn_t end_lsn) noexcept if (oldest_lsn == log_sys.last_checkpoint_lsn || (oldest_lsn == end_lsn && !log_sys.resize_in_progress() && + (!log_sys.archive || !log_sys.circular_recovery_from_0()) && oldest_lsn == log_sys.last_checkpoint_lsn + log_sys.is_encrypted() * 8 + SIZE_OF_FILE_CHECKPOINT)) if (oldest_lsn != log_sys.get_first_lsn()) @@ -2327,6 +2337,7 @@ ATTRIBUTE_COLD void buf_flush_wait(lsn_t lsn, bool checkpoint) noexcept lsn_t oldest_lsn; if (!checkpoint); else if (lsn == log_sys.get_lsn() && + (!log_sys.archive || !log_sys.circular_recovery_from_0()) && lsn == log_sys.last_checkpoint_lsn + SIZE_OF_FILE_CHECKPOINT + 8 * log_sys.is_encrypted()); else if (buf_flush_sync_lsn < lsn) @@ -2361,7 +2372,8 @@ ATTRIBUTE_COLD void buf_flush_wait(lsn_t lsn, bool checkpoint) noexcept { lsn= log_sys.get_lsn(); if (lsn != log_sys.last_checkpoint_lsn + - SIZE_OF_FILE_CHECKPOINT + 8 * log_sys.is_encrypted()) + SIZE_OF_FILE_CHECKPOINT + 8 * log_sys.is_encrypted() || + (log_sys.archive && log_sys.circular_recovery_from_0())) { buf_flush_sync_lsn= lsn; log_sys.set_check_for_checkpoint(true); diff --git a/storage/innobase/include/log0log.h b/storage/innobase/include/log0log.h index 44a827dbf63..bb5456f056e 100644 --- a/storage/innobase/include/log0log.h +++ b/storage/innobase/include/log0log.h @@ -572,6 +572,11 @@ public: lsn_t checkpoint_age_max() const noexcept { return max_checkpoint_age + archive * file_size; } + /** @return whether !archive log records may have been written with + get_sequence_bit()==0 */ + bool circular_recovery_from_0() const noexcept + { ut_ad(latch_have_wr()); return circular_recovery_from_sequence_bit_0; } + /** Make previous write_buf() durable and update flushed_to_disk_lsn. */ bool flush(lsn_t lsn) noexcept; diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index c8aa06b02dc..be2e6a45eba 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -799,7 +799,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (wait_lsn) { mysql_mutex_lock(&buf_pool.flush_list_mutex); - buf_flush_wait(wait_lsn, false); + buf_flush_wait(wait_lsn, circular_recovery_from_sequence_bit_0); mysql_mutex_unlock(&buf_pool.flush_list_mutex); } latch.wr_unlock(); @@ -819,6 +819,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept allow any wrap-around. */ if (checkpoint < limit) goto retry_after_checkpoint; + circular_recovery_from_sequence_bit_0= limit != wait_lsn; wait_lsn= limit; } else if (circular_recovery_from_sequence_bit_0) @@ -830,7 +831,6 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept the completion of set_archive(false) and the next write_checkpoint(), recovery will only encounter get_sequence_bit() == 1, consistent with our first_lsn. */ - circular_recovery_from_sequence_bit_0= false; goto retry_after_checkpoint; } else if (checkpoint < first_lsn) @@ -879,8 +879,17 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (!archive) { std::swap(old_name, new_name); - header_rewrite(archive); std::string spare{get_archive_path(first_lsn + capacity())}; + if (!get_sequence_bit(last_checkpoint_lsn)) + /* + In the innodb_log_archive=ON format, mtr_t::finish_writer() + always writes the sequence bit as 1. Ensure that the + innodb_log_archive=OFF recovery will expect this value. + */ + first_lsn-= capacity(); + ut_ad(get_sequence_bit(last_checkpoint_lsn)); + ut_ad(get_sequence_bit(get_lsn())); + header_rewrite(false); IF_WIN(DeleteFile(spare.c_str()), unlink(spare.c_str())); } #if defined HAVE_PMEM && !defined _WIN32 @@ -925,7 +934,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (archive) { - header_rewrite(archive); + header_rewrite(true); archive_set_size(); wait_lsn= 0; } From 97d0d0b4ff67b0e0a67d5a073e8c25322ffc28a2 Mon Sep 17 00:00:00 2001 From: Razvan-Liviu Varzaru Date: Wed, 4 Feb 2026 17:22:29 +0200 Subject: [PATCH 097/150] MDEV-38754 s3.clone run in high concurrency environments s3.clone needs to source create_database.inc so that it can run in a high-concurrency environment where multiple MTR invocations are happening at the same time, while only a single S3 bucket (the S3 DB) is shared among all instances. --- mysql-test/suite/s3/clone.result | 4 +--- mysql-test/suite/s3/clone.test | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mysql-test/suite/s3/clone.result b/mysql-test/suite/s3/clone.result index a33c8c4259a..308b0a80923 100644 --- a/mysql-test/suite/s3/clone.result +++ b/mysql-test/suite/s3/clone.result @@ -1,12 +1,10 @@ # # SELECT using ror_merged scan fails with s3 tables # -DROP TABLE IF EXISTS t1; -Warnings: -Note 1051 Unknown table 'test.t1' CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; INSERT INTO t1 VALUES (0,0),(0,10),(3,10); ALTER TABLE t1 ENGINE=S3; SELECT * FROM t1 WHERE a = 99 OR b = 2; a b DROP TABLE t1; +End of 10.11 tests diff --git a/mysql-test/suite/s3/clone.test b/mysql-test/suite/s3/clone.test index 5695257c404..304671654e6 100644 --- a/mysql-test/suite/s3/clone.test +++ b/mysql-test/suite/s3/clone.test @@ -2,13 +2,21 @@ --source include/have_sequence.inc --source include/have_innodb.inc +# +# Create unique database for running the tests +# +--source create_database.inc + --echo # --echo # SELECT using ror_merged scan fails with s3 tables --echo # -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; INSERT INTO t1 VALUES (0,0),(0,10),(3,10); ALTER TABLE t1 ENGINE=S3; SELECT * FROM t1 WHERE a = 99 OR b = 2; DROP TABLE t1; + +--source drop_database.inc + +--echo End of 10.11 tests From 27dad5cc0c815315fd45623147feaa4f20721b4e Mon Sep 17 00:00:00 2001 From: Prathamesh Hukkeri Date: Wed, 5 Aug 2026 16:04:48 +0530 Subject: [PATCH 098/150] MDEV-40122: `+DEFAULT` is not a valid value for master_heartbeat_period MDEV-28302 changed the grammar for master_heartbeat_period to accept DEFAULT (via num_or_default), while MDEV-38454 added an opt_plus to allow numeric values with an explicit `+` sign. The combination made the rule `opt_plus num_or_default`, which also accepted `+DEFAULT`, equivalent to `= DEFAULT`. Move the opt_plus under num_or_default's definition, so that `+` may only precede a numeric literal, and DEFAULT is a separate alternative. Now `master_heartbeat_period= +DEFAULT` is a syntax error again, while `= +45` and `= DEFAULT` are both accepted. --- mysql-test/main/change_master_default.result | 5 +++++ mysql-test/main/change_master_default.test | 9 +++++++++ sql/sql_yacc.yy | 8 ++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/change_master_default.result b/mysql-test/main/change_master_default.result index 71d76cca818..865361e670c 100644 --- a/mysql-test/main/change_master_default.result +++ b/mysql-test/main/change_master_default.result @@ -258,6 +258,11 @@ master_ssl_crlpath using_gtid Slave_Pos master_retry_count 100000 slave_heartbeat_period 60.000 +# +# MDEV-40122: `+DEFAULT` is not a valid value for master_heartbeat_period +# +CHANGE MASTER TO master_heartbeat_period= +DEFAULT; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'DEFAULT' at line 1 # Clean-up DROP PROCEDURE show_defaultable_fields; RESET SLAVE 'unset' ALL; diff --git a/mysql-test/main/change_master_default.test b/mysql-test/main/change_master_default.test index 971013dc3fe..37c937533f4 100644 --- a/mysql-test/main/change_master_default.test +++ b/mysql-test/main/change_master_default.test @@ -138,6 +138,15 @@ FROM information_schema.slave_status ORDER BY connection_name; --query_vertical CALL show_defaultable_fields() +--echo # +--echo # MDEV-40122: `+DEFAULT` is not a valid value for master_heartbeat_period +--echo # + +# The `+` prefix may only precede a numeric literal, not `DEFAULT` +--error ER_PARSE_ERROR +CHANGE MASTER TO master_heartbeat_period= +DEFAULT; + + --echo # Clean-up DROP PROCEDURE show_defaultable_fields; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 83f8edd7263..1fdad927ca1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2423,14 +2423,14 @@ master_def: { mi->master_ssl_crlpath= path; }; } - | MASTER_HEARTBEAT_PERIOD_SYM '=' opt_plus num_or_default + | MASTER_HEARTBEAT_PERIOD_SYM '=' num_or_default { - if ($4) + if ($3) { uint32_t milliseconds; bool overprecise; auto decimal_buf= my_decimal(), - *decimal= $4->val_decimal(&decimal_buf); + *decimal= $3->val_decimal(&decimal_buf); DBUG_ASSERT(decimal); if (Master_info_file::Heartbeat_period_value::from_decimal( milliseconds, *decimal, overprecise @@ -2490,7 +2490,7 @@ master_use_gtid_enum: | DEFAULT { $$= enum_master_use_gtid::DEFAULT; } ; num_or_default: - NUM_literal { DBUG_ASSERT($$); } + opt_plus NUM_literal { DBUG_ASSERT($2); $$= $2; } | DEFAULT { $$= nullptr; } ; From 3064caf84c692152d3b3a402c011d5772d64c2ff Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 5 Aug 2026 12:07:10 +1000 Subject: [PATCH 099/150] MDEV-40630: stunnel as Deb/RPM recommends for rsync sst stunnel has been a component used by the rsync SST mechanism of galera but has never been listed as a package dependency. Since MDEV-28233 corrected a unencrypted fallback in the the case of stunnel being absent, we add stunnel as a recommendation for Debian and RPM packages. --- cmake/cpack_rpm.cmake | 2 +- debian/control | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/cpack_rpm.cmake b/cmake/cpack_rpm.cmake index 46abfea68c1..25c5ab1be2d 100644 --- a/cmake/cpack_rpm.cmake +++ b/cmake/cpack_rpm.cmake @@ -229,7 +229,7 @@ IF(WITH_WSREP) SETA(CPACK_RPM_server_PACKAGE_REQUIRES "galera-4" "rsync" "grep" "gawk" "iproute" "coreutils" "findutils" "tar") - SETA(CPACK_RPM_server_PACKAGE_RECOMMENDS "lsof" "socat") + SETA(CPACK_RPM_server_PACKAGE_RECOMMENDS "lsof" "socat" "stunnel") SETA(CPACK_RPM_test_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES}" "socat") ENDIF() diff --git a/debian/control b/debian/control index 3b239f5221a..4de45f2fb2e 100644 --- a/debian/control +++ b/debian/control @@ -612,7 +612,8 @@ Package: mariadb-server-10.6 Architecture: any Suggests: mariadb-test, netcat-openbsd -Recommends: libhtml-template-perl +Recommends: libhtml-template-perl, + stunnel Pre-Depends: adduser (>= 3.40), debconf, mariadb-common (>= ${source:Version}) From 7d73a695267b09c9025b629ee7b12ee5f5d49b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Sat, 8 Aug 2026 13:19:27 +0300 Subject: [PATCH 100/150] MDEV-40645 : Slave Crash on Malformed User_var_log_event A crafted DECIMAL user variable event makes bin2decimal() allocate and copy a size taken entirely from two attacker-supplied bytes. bin2decimal() has no parameter telling it how many bytes are available (strings/decimal.c:1424): int bin_size= decimal_bin_size(precision, scale); sanity(to); d_copy= (uchar*) my_alloca(bin_size); memcpy(d_copy, from, bin_size); decimal_bin_size() guards its inputs with DBUG_ASSERT(precision > 0) and DBUG_ASSERT(scale <= precision) only, so release builds proceed with anything. my_alloca is real alloca in this build. Precision and scale arrive as val[0] and val[1]. Three call sites pass them through: * the applier, User_var_log_event::do_apply_event (sql/log_event_server.cc:4563), which checks only val_len < 3 * pack_info (sql/log_event_server.cc:4356), which checks nothing, not even val_len >= 3 * mysqlbinlog (sql/log_event_client.cc:2489), which checks nothing The constructor does bound val_len against the event (sql/log_event.cc:3064), so val_len is honest, but nothing ties decimal_bin_size(val[0], val[1]) to it. val_len of 3 with a precision of 65 already asks for 30 bytes where 1 is available. Fixed by adding guards against malformed precision, scale and val_len. Co-Authored-By: Claude Opus 5 (1M context) --- .../r/rpl_corrupted_decimal_precision.result | 70 ++++++++++++++++ .../t/rpl_corrupted_decimal_precision.test | 82 +++++++++++++++++++ sql/log_event_client.cc | 9 +- sql/log_event_server.cc | 23 +++++- 4 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result create mode 100644 mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test diff --git a/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result b/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result new file mode 100644 index 00000000000..9070bef9dc0 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result @@ -0,0 +1,70 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Slave SQL: Invalid variable length at User var event"); +connection master; +create table t1 (a decimal(10,5)); +insert into t1 values (0.5); +connection slave; +# +# Write a User_var_log_event whose DECIMAL precision and scale do not +# match the length of the packed value +# +connection master; +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +set @a= 1.5; +insert into t1 values (@a); +set @@session.debug_dbug= ""; +# +# SHOW BINLOG EVENTS must not crash in User_var_log_event::pack_info() +# +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a decimal(10,5)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0.5) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # User var # # +master-bin.000001 # Query # # use `test`; insert into t1 values (@a) +master-bin.000001 # Query # # COMMIT +set @@session.debug_dbug= ""; +# +# mysqlbinlog must not crash in User_var_log_event::print() +# +# +# The slave rejects the event instead of crashing on it +# +connection slave; +include/wait_for_slave_sql_error.inc [errno=1593] +# +# Replication continues once the corrupted event group is skipped +# +set global sql_slave_skip_counter= 1; +include/start_slave.inc +connection master; +insert into t1 values (3.5); +connection slave; +# Only the skipped row (1.50000) is missing on the slave, every later +# event is applied normally +connection master; +select * from t1 order by a; +a +0.50000 +1.50000 +3.50000 +connection slave; +select * from t1 order by a; +a +0.50000 +3.50000 +# +# Cleanup +# +connection master; +drop table t1; +connection slave; +include/rpl_end.inc +# End of rpl_corrupted_decimal_precision.test diff --git a/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test b/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test new file mode 100644 index 00000000000..36058770ff3 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test @@ -0,0 +1,82 @@ +# +# MDEV-40645 Slave crash on malformed User_var_log_event +# +# A User_var_log_event of type DECIMAL_RESULT carries the precision and the +# scale of the value in the first two bytes of the packed value. Nothing ties +# them to the length of the event, so a crafted event makes bin2decimal() +# alloca() and copy a size taken entirely from those two bytes. +# +--source include/have_debug.inc +# A User_var_log_event is only written when the statement itself is binlogged, +# i.e. never for row based replication. +--source include/have_binlog_format_mixed_or_statement.inc +--source include/master-slave.inc + +# The master intentionally writes a corrupted event, the slave has to reject it +--connection slave +call mtr.add_suppression("Slave SQL: Invalid variable length at User var event"); + +--connection master +create table t1 (a decimal(10,5)); +insert into t1 values (0.5); +--sync_slave_with_master + +--echo # +--echo # Write a User_var_log_event whose DECIMAL precision and scale do not +--echo # match the length of the packed value +--echo # +--connection master +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +set @a= 1.5; +insert into t1 values (@a); +set @@session.debug_dbug= ""; +--let $corrupt_file= query_get_value(SHOW MASTER STATUS, File, 1) + +--echo # +--echo # SHOW BINLOG EVENTS must not crash in User_var_log_event::pack_info() +--echo # +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +--source include/show_binlog_events.inc +set @@session.debug_dbug= ""; + +--echo # +--echo # mysqlbinlog must not crash in User_var_log_event::print() +--echo # +--let $datadir= `select @@datadir` +# The corrupted event is rejected, only the exit code matters here +--error 1 +--exec $MYSQL_BINLOG $datadir/$corrupt_file >/dev/null 2>&1 + +--echo # +--echo # The slave rejects the event instead of crashing on it +--echo # +--connection slave +--let $slave_sql_errno= 1593 +--source include/wait_for_slave_sql_error.inc + +--echo # +--echo # Replication continues once the corrupted event group is skipped +--echo # +set global sql_slave_skip_counter= 1; +--source include/start_slave.inc + +--connection master +insert into t1 values (3.5); +--sync_slave_with_master + +--echo # Only the skipped row (1.50000) is missing on the slave, every later +--echo # event is applied normally +--connection master +select * from t1 order by a; +--connection slave +select * from t1 order by a; + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--sync_slave_with_master + +--source include/rpl_end.inc +--echo # End of rpl_corrupted_decimal_precision.test diff --git a/sql/log_event_client.cc b/sql/log_event_client.cc index 3171eaa2fab..294fc201ee1 100644 --- a/sql/log_event_client.cc +++ b/sql/log_event_client.cc @@ -2531,13 +2531,18 @@ bool User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) { char str_buf[200]; int str_len= sizeof(str_buf) - 1; - int precision= (int)val[0]; - int scale= (int)val[1]; + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; decimal_digit_t dec_buf[10]; decimal_t dec; dec.len= 10; dec.buf= dec_buf; + /* Verify decimal metadata as well as the actual length */ + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) + goto err; + bin2decimal((uchar*) val+2, &dec, precision, scale); decimal2string(&dec, str_buf, &str_len, 0, 0, 0); str_buf[str_len]= 0; diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index e6c798cef26..d0291f7733a 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -4705,6 +4705,15 @@ void User_var_log_event::pack_info(Protocol* protocol) char buf2[DECIMAL_MAX_STR_LENGTH+1]; String str(buf2, sizeof(buf2), &my_charset_bin); buf.length(0); + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; + /* Values were intentionally corrupted in User_var_log_event::write */ + DBUG_EXECUTE_IF("corrupt_user_var_decimal_precision", + precision = 2; scale= 1;); + + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) + return; my_decimal((const uchar *) (val + 2), val[0], val[1]).to_string(&str); if (user_var_append_name_part(protocol->thd, &buf, name, name_len, m_data_type_name) || @@ -4800,6 +4809,9 @@ bool User_var_log_event::write() buf2[1]= (char)dec->frac; decimal2bin((decimal_t*)val, buf2+2, buf2[0], buf2[1]); val_len= decimal_bin_size(buf2[0], buf2[1]) + 2; + /* Leave val_len honest and lie about the metadata. */ + DBUG_EXECUTE_IF("corrupt_user_var_decimal_precision", + buf2[0]= 65; buf2[1]= 0;); break; } case STRING_RESULT: @@ -4885,7 +4897,7 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } float8get(real_val, val); it= new (thd->mem_root) Item_float(thd, real_val, 0); @@ -4898,7 +4910,7 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } int_val= (longlong) uint8korr(val); it= new (thd->mem_root) Item_int(thd, int_val); @@ -4907,12 +4919,15 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) break; case DECIMAL_RESULT: { - if (val_len < 3) + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) { rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } Item_decimal *dec= new (thd->mem_root) Item_decimal(thd, (uchar*) val+2, val[0], val[1]); it= dec; From c38073eb6c63295a36df16bdf276d97bc8e4bc81 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 1 Aug 2026 15:42:01 +1000 Subject: [PATCH 101/150] MDEV-40572 SIGSEGV in JSON_OVERLAPS() on a truncated nested JSON object with different character sets/collations. The value->s.c_str and js->s.c_str became invalid once the function returned which caused the crash. --- mysql-test/main/func_json.result | 19 +++++++++++++++++++ mysql-test/main/func_json.test | 15 +++++++++++++++ sql/item_jsonfunc.cc | 6 +++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index 15da9211752..4030637ad17 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -3022,4 +3022,23 @@ x NULL SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); 1 +# +# MDEV-40572 SIGSEGV in JSON_OVERLAPS() on a truncated nested JSON object with different character sets/collations +# +SET @col=@@collation_connection; +SET @char=@@character_set_connection; +SET collation_connection=ucs2_general_ci; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_ucs2; +exp_ucs2 +0 +Warnings: +Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_overlaps' +SET character_set_connection=utf16; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_utf16; +exp_utf16 +0 +Warnings: +Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_overlaps' +SET @@collation_connection=@col; +SET @@character_set_connection=@char; # End of 10.11 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index 58b806c78bd..c05506b1bb7 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -2150,4 +2150,19 @@ SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPL SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); +--echo # +--echo # MDEV-40572 SIGSEGV in JSON_OVERLAPS() on a truncated nested JSON object with different character sets/collations +--echo # + +SET @col=@@collation_connection; +SET @char=@@character_set_connection; + +SET collation_connection=ucs2_general_ci; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_ucs2; +SET character_set_connection=utf16; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_utf16; + +SET @@collation_connection=@col; +SET @@character_set_connection=@char; + --echo # End of 10.11 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 1681b555ca5..6acf31a8f67 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -4877,24 +4877,24 @@ int compare_nested_object(json_engine_t *js, json_engine_t *value) if (json_normalize_engine(&je, &a_res, a.ptr(), a.length(), value->s.cs)) { value->s.error= je.s.error; - value->s.c_str= je.s.c_str; goto error; } if (json_normalize_engine(&je, &b_res, b.ptr(), b.length(), value->s.cs)) { js->s.error= je.s.error; - js->s.c_str= je.s.c_str; goto error; } result= strcmp(a_res.str, b_res.str) ? 0 : 1; - error: +error: dynstr_free(&a_res); dynstr_free(&b_res); return MY_TEST(result); } + + int json_find_overlap_with_object(json_engine_t *js, json_engine_t *value, bool compare_whole) { From 0cb7c2f61d7cbfd535630938316cc30210518138 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 1 Aug 2026 16:06:51 +1000 Subject: [PATCH 102/150] MDEV-40165 JSON_EQUAL/JSON_NORMALIZE/JSON_CONTAINS error handling Adds test cases that validate the incorrect null handling in the 2894e90b6545d3c7fa13ff00fb50c9e8631003e7 commit and other JSON_EQUALS,CONTAINS/OVERLAPS behaviour. --- mysql-test/main/func_json.result | 45 ++++++++++++++++++++++++++++++++ mysql-test/main/func_json.test | 20 ++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index 4030637ad17..a5b21ad2029 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -3041,4 +3041,49 @@ Warnings: Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_overlaps' SET @@collation_connection=@col; SET @@character_set_connection=@char; +# +# MDEV-40165 JSON_EQUAL/JSON_NORMALIZE/JSON_CONTAINS error handling +# +CREATE TABLE t(c1 JSON, c2 JSON); +INSERT INTO t VALUES ('{"a":1,"b":2}', '{"b":2E0, "a":1E0}'), +(NULL, '["a", "b"]'), +('{"r": 1}', NULL), +('[{"a": 3}, {"d": [1, 2, 3]}]', '[{"d": [1E0, 2E0, 3E0]}, {"a":3E0}]'); +SELECT c1, JSON_EQUALS(c1, '{"r": 1E0}') AS je_c1_const FROM t; +c1 je_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_EQUALS(c1, c2) AS je_c1_c2 FROM t; +c1 c2 je_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 0 +SELECT c1, JSON_CONTAINS(c1, '{"r": 1E0}') AS jc_c1_const FROM t; +c1 jc_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_CONTAINS(c1, c2) AS jc_c1_c2 FROM t; +c1 c2 jc_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 1 +SELECT c1, JSON_OVERLAPS(c1, '{"r": 1E0}') AS jo_c1_const FROM t; +c1 jo_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_OVERLAPS(c1, c2) AS jo_c1_c2 FROM t; +c1 c2 jo_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 1 +DROP TABLE t; # End of 10.11 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index c05506b1bb7..6c181ce40f0 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -2165,4 +2165,24 @@ SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_utf16; SET @@collation_connection=@col; SET @@character_set_connection=@char; +--echo # +--echo # MDEV-40165 JSON_EQUAL/JSON_NORMALIZE/JSON_CONTAINS error handling +--echo # + +CREATE TABLE t(c1 JSON, c2 JSON); +INSERT INTO t VALUES ('{"a":1,"b":2}', '{"b":2E0, "a":1E0}'), + (NULL, '["a", "b"]'), + ('{"r": 1}', NULL), + ('[{"a": 3}, {"d": [1, 2, 3]}]', '[{"d": [1E0, 2E0, 3E0]}, {"a":3E0}]'); + +SELECT c1, JSON_EQUALS(c1, '{"r": 1E0}') AS je_c1_const FROM t; +SELECT c1, c2, JSON_EQUALS(c1, c2) AS je_c1_c2 FROM t; + +SELECT c1, JSON_CONTAINS(c1, '{"r": 1E0}') AS jc_c1_const FROM t; +SELECT c1, c2, JSON_CONTAINS(c1, c2) AS jc_c1_c2 FROM t; + +SELECT c1, JSON_OVERLAPS(c1, '{"r": 1E0}') AS jo_c1_const FROM t; +SELECT c1, c2, JSON_OVERLAPS(c1, c2) AS jo_c1_c2 FROM t; +DROP TABLE t; + --echo # End of 10.11 Test From 210ab6edf47fa73f3b77e974ed2e9357ccdd051c Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Tue, 11 Aug 2026 10:13:16 +0400 Subject: [PATCH 103/150] MDEV-40722 DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv DROP PACKAGE now removes both PACKAGE BODY and PACKAGE privileges for the given package. --- .../oracle/r/sp-package-security.result | 23 +++++ .../compat/oracle/t/sp-package-security.test | 25 ++++++ sql/sql_acl.cc | 88 +++++++++++++------ 3 files changed, 109 insertions(+), 27 deletions(-) diff --git a/mysql-test/suite/compat/oracle/r/sp-package-security.result b/mysql-test/suite/compat/oracle/r/sp-package-security.result index c08b78cb8cf..c11589a0ea4 100644 --- a/mysql-test/suite/compat/oracle/r/sp-package-security.result +++ b/mysql-test/suite/compat/oracle/r/sp-package-security.result @@ -320,3 +320,26 @@ SESSION_USER() CURRENT_USER() msg root@localhost root@localhost p1.p1 DROP PACKAGE p1; DROP USER xxx@localhost; +# +# MDEV-40722 DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv +# +CREATE PACKAGE pkg1 AS +FUNCTION f RETURN VARCHAR2(64); +END +$$ +CREATE PACKAGE BODY pkg1 AS +FUNCTION f RETURN VARCHAR2(64) AS +BEGIN RETURN 'f'; +END; +END$$ +CREATE USER reader@localhost; +GRANT EXECUTE ON PACKAGE test.pkg1 TO reader@localhost; +GRANT EXECUTE ON PACKAGE BODY test.pkg1 TO reader@localhost; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +host db user routine_name routine_type proc_priv +localhost test reader pkg1 PACKAGE Execute +localhost test reader pkg1 PACKAGE BODY Execute +DROP PACKAGE pkg1; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +host db user routine_name routine_type proc_priv +DROP USER reader@localhost; diff --git a/mysql-test/suite/compat/oracle/t/sp-package-security.test b/mysql-test/suite/compat/oracle/t/sp-package-security.test index 583f70afe14..20a4fddb5b4 100644 --- a/mysql-test/suite/compat/oracle/t/sp-package-security.test +++ b/mysql-test/suite/compat/oracle/t/sp-package-security.test @@ -330,3 +330,28 @@ DELIMITER ;$$ CALL p1.p1; DROP PACKAGE p1; DROP USER xxx@localhost; + + +--echo # +--echo # MDEV-40722 DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv +--echo # + +DELIMITER $$; +CREATE PACKAGE pkg1 AS + FUNCTION f RETURN VARCHAR2(64); +END +$$ +CREATE PACKAGE BODY pkg1 AS + FUNCTION f RETURN VARCHAR2(64) AS + BEGIN RETURN 'f'; + END; +END$$ +DELIMITER ;$$ + +CREATE USER reader@localhost; +GRANT EXECUTE ON PACKAGE test.pkg1 TO reader@localhost; +GRANT EXECUTE ON PACKAGE BODY test.pkg1 TO reader@localhost; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +DROP PACKAGE pkg1; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +DROP USER reader@localhost; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 3dc5f391f9d..94d81d749d9 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -11761,6 +11761,61 @@ Silence_routine_definer_errors::handle_condition( } +/* + The low level function to revoke routine privileges for the given sp handler + @param thd the thd + @param proc_privs the table mysql.proc_privs + @param sp_db the routine database + @param sp_name the routine name + @param sph the sp handler +*/ +static void sp_revoke_privileges_for_handler(THD *thd, TABLE *proc_privs, +#if MYSQL_VERSION_ID < 110501 + const char *sp_db, + const char *sp_name, +#else +#error Remove the above conditional code + const Lex_ident_db &sp_db, + const Lex_ident_routine &sp_name, +#endif + const Sp_handler *sph) +{ + uint counter, revoked; + HASH *hash= sph->get_priv_hash(); + do + { + for (counter= 0, revoked= 0 ; counter < hash->records ; ) + { + GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); +#if MYSQL_VERSION_ID < 110501 + if (!my_strcasecmp(&my_charset_utf8mb3_bin, grant_proc->db, sp_db) && + !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) +#else +#error Remove the above conditional code + if (sp_db.streq(Lex_cstring_strlen(grant_proc->db)) && + sp_name.streq(Lex_cstring_strlen(grant_proc->tname))) +#endif + { + LEX_USER lex_user; + lex_user.user.str= grant_proc->user; + lex_user.user.length= strlen(grant_proc->user); + lex_user.host.str= safe_str(grant_proc->host.hostname); + lex_user.host.length= strlen(lex_user.host.str); + if (replace_routine_table(thd, grant_proc, + proc_privs, lex_user, + grant_proc->db, grant_proc->tname, + sph, ALL_KNOWN_ACL, 1) == 0) + { + revoked= 1; + continue; + } + } + counter++; + } + } while (revoked); +} + + /** Revoke privileges for all users on a stored procedure. Use an error handler that converts errors about missing grants into warnings. @@ -11781,9 +11836,7 @@ Silence_routine_definer_errors::handle_condition( bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, const Sp_handler *sph) { - uint counter, revoked; int result; - HASH *hash= sph->get_priv_hash(); Silence_routine_definer_errors error_handler; DBUG_ENTER("sp_revoke_privileges"); @@ -11803,31 +11856,12 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, mysql_mutex_lock(&acl_cache->lock); /* Remove procedure access */ - do - { - for (counter= 0, revoked= 0 ; counter < hash->records ; ) - { - GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); - if (!my_strcasecmp(&my_charset_utf8mb3_bin, grant_proc->db, sp_db) && - !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) - { - LEX_USER lex_user; - lex_user.user.str= grant_proc->user; - lex_user.user.length= strlen(grant_proc->user); - lex_user.host.str= safe_str(grant_proc->host.hostname); - lex_user.host.length= strlen(lex_user.host.str); - if (replace_routine_table(thd, grant_proc, - tables.procs_priv_table().table(), lex_user, - grant_proc->db, grant_proc->tname, - sph, ALL_KNOWN_ACL, 1) == 0) - { - revoked= 1; - continue; - } - } - counter++; - } - } while (revoked); + if (sph == &sp_handler_package_spec) + sp_revoke_privileges_for_handler(thd, tables.procs_priv_table().table(), + sp_db, sp_name, &sp_handler_package_body); + + sp_revoke_privileges_for_handler(thd, tables.procs_priv_table().table(), + sp_db, sp_name, sph); mysql_mutex_unlock(&acl_cache->lock); mysql_rwlock_unlock(&LOCK_grant); From b608e108d850c3955b4fdde306d2e7f7b128c66d Mon Sep 17 00:00:00 2001 From: forkfun Date: Wed, 5 Aug 2026 16:14:26 +0200 Subject: [PATCH 104/150] MDEV-40584 ST_CROSSES always returns 0 for different-dimension geometries MDEV-36058 added a dimension-equality check ("Both geometries must have the same number of dimensions") for SP_OVERLAPS_FUNC, but a stray fall-through from SP_CROSSES_FUNC into that same case made CROSSES share it too. CROSSES is defined precisely for geometries of different dimensions, so any such pair now hit "if (g1_dim != g2_dim) DBUG_RETURN(0)" and always returned 0. Give SP_CROSSES_FUNC its own case again, calling handle_sp_crosses_func_case() directly without the dimension check. SP_OVERLAPS_FUNC keeps the check. --- mysql-test/main/gis-precise.result | 60 ++++++++++++++++++++++++++++++ mysql-test/main/gis-precise.test | 39 +++++++++++++++++++ sql/item_geofunc.cc | 4 +- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/gis-precise.result b/mysql-test/main/gis-precise.result index 7173d1d0c54..d127d016114 100644 --- a/mysql-test/main/gis-precise.result +++ b/mysql-test/main/gis-precise.result @@ -881,6 +881,66 @@ SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) ') a NULL # +# MDEV-40584 ST_CROSSES always returns 0 for geometries of different dimensions +# +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 3, 2 2, 3 2)'), +ST_GeomFromText('LINESTRING(0 3, 1 1, 2 2, 2 0)')) a; +a +1 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 1, 1 3, 2 3)'), +ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +a +1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)'), +ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))')) a; +a +1 +# line x line, crossing (dims 1,1) -> 1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), +ST_GeomFromText('LINESTRING(0 2, 2 0)')) a; +a +1 +# line x line, not crossing (dims 1,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 0)'), +ST_GeomFromText('LINESTRING(0 1, 2 1)')) a; +a +0 +# point x line, disjoint (dims 0,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(10 10)'), +ST_GeomFromText('LINESTRING(0 0, 1 1)')) a; +a +0 +# point x polygon, disjoint (dims 0,2) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(100 100)'), +ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +a +0 +# line x point (g2 is a point) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 1 1)'), +ST_GeomFromText('POINT(5 5)')) a; +a +NULL +# line x multipoint (g2 is a multipoint) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 5 5)'), +ST_GeomFromText('MULTIPOINT(1 1, 2 2)')) a; +a +NULL +# multipolygon x line (g1 is a multipolygon) -> NULL +SELECT ST_Crosses(ST_GeomFromText('MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))'), +ST_GeomFromText('LINESTRING(1 1, 2 2)')) a; +a +NULL +# polygon x line, reversed order of a valid crossing case -> NULL +SELECT ST_Crosses(ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))'), +ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)')) a; +a +NULL +# line x same line, equal (not lower-dim intersection) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), +ST_GeomFromText('LINESTRING(0 0, 2 2)')) a; +a +0 +# # MDEV-31267 Server crash or assertion failure in get_n_sincos with nested ST_BUFFER. # set @geom=ST_BUFFER(ST_BUFFER(ST_POLYGONFROMTEXT('POLYGON((0 0, 5 5, 6 6, 0 0))'),1),2); diff --git a/mysql-test/main/gis-precise.test b/mysql-test/main/gis-precise.test index 533ef8f40a7..341b5de0257 100644 --- a/mysql-test/main/gis-precise.test +++ b/mysql-test/main/gis-precise.test @@ -494,6 +494,45 @@ SELECT ST_CROSSES( ST_GEOMFROMTEXT('point(1 1)'), st_geomfromtext('point(1 1)') SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) '), st_geomfromtext('polygon((2 2,2 4, 4 2,2 2))') ) a; SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) '), st_geomfromtext('point(1 1)') ) a; +--echo # +--echo # MDEV-40584 ST_CROSSES always returns 0 for geometries of different dimensions +--echo # + +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 3, 2 2, 3 2)'), + ST_GeomFromText('LINESTRING(0 3, 1 1, 2 2, 2 0)')) a; +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 1, 1 3, 2 3)'), + ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)'), + ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))')) a; + +--echo # line x line, crossing (dims 1,1) -> 1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), + ST_GeomFromText('LINESTRING(0 2, 2 0)')) a; +--echo # line x line, not crossing (dims 1,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 0)'), + ST_GeomFromText('LINESTRING(0 1, 2 1)')) a; +--echo # point x line, disjoint (dims 0,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(10 10)'), + ST_GeomFromText('LINESTRING(0 0, 1 1)')) a; +--echo # point x polygon, disjoint (dims 0,2) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(100 100)'), + ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +--echo # line x point (g2 is a point) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 1 1)'), + ST_GeomFromText('POINT(5 5)')) a; +--echo # line x multipoint (g2 is a multipoint) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 5 5)'), + ST_GeomFromText('MULTIPOINT(1 1, 2 2)')) a; +--echo # multipolygon x line (g1 is a multipolygon) -> NULL +SELECT ST_Crosses(ST_GeomFromText('MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))'), + ST_GeomFromText('LINESTRING(1 1, 2 2)')) a; +--echo # polygon x line, reversed order of a valid crossing case -> NULL +SELECT ST_Crosses(ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))'), + ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)')) a; +--echo # line x same line, equal (not lower-dim intersection) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), + ST_GeomFromText('LINESTRING(0 0, 2 2)')) a; + --echo # --echo # MDEV-31267 Server crash or assertion failure in get_n_sincos with nested ST_BUFFER. --echo # diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index e332ec22c9a..17122f8469a 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -1509,7 +1509,9 @@ bool Item_func_spatial_precise_rel::val_bool() null_value= true; goto exit; } - /* fall through */ + handle_sp_crosses_func_case(func, trn, g1, g2, + shape_a, shape_b, null_value); + break; case SP_OVERLAPS_FUNC: { // Both geometries must have the same number of dimensions. From 30ca9a481cf99632e9c5ab71ec9336fb9f97cfd7 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Tue, 11 Aug 2026 15:39:36 +0300 Subject: [PATCH 105/150] Add comments about KEY_PART_INFO::type, KEY_PART_INFO::key_type, ... and Column_definition_attributes::pack_flag. Approved-by: Dave Gosselin (dave.gosselin@mariadb.com) --- sql/field.h | 2 +- sql/structs.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/field.h b/sql/field.h index d6b61d235cb..cc10f766262 100644 --- a/sql/field.h +++ b/sql/field.h @@ -5215,7 +5215,7 @@ public: const TYPELIB *interval; CHARSET_INFO *charset; uint32 srid; - uint32 pack_flag; + uint32 pack_flag; /* Bitmap of FIELDFLAG_* flags */ decimal_digits_t decimals; Field::utype unireg_check; Column_definition_attributes() diff --git a/sql/structs.h b/sql/structs.h index 2dff01f007c..13b462393ed 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -86,10 +86,10 @@ typedef struct st_key_part_info { /* Info about a key part */ - possible HA_KEY_BLOB_LENGTH bytes needed to store actual value length. */ uint store_length; - uint16 key_type; + uint16 key_type; /* Bitmap of FIELDFLAG_* flags */ field_index_t fieldnr; /* Fieldnr begins counting from 1 */ uint16 key_part_flag; /* 0 or HA_REVERSE_SORT */ - uint8 type; + uint8 type; /* One of HA_KEYTYPE_* values */ uint8 null_bit; /* Position to null_bit */ } KEY_PART_INFO ; From e4fa7ac8a27ac2daedb5e9b6b188f01e4d2a56fb Mon Sep 17 00:00:00 2001 From: forkfun Date: Fri, 7 Aug 2026 12:30:07 +0200 Subject: [PATCH 106/150] MDEV-39846 UNIQUE BLOB contains duplicate records after ALTER IGNORE with READ COMMITTED Force REPEATABLE READ isolation for the duration of mysql_alter_table, so HA_CHECK_UNIQUE_AFTER_WRITE never triggers during ALTER's exclusive table copy. (cherry-pick of 46ee272a10d7 - online ALTER TABLE (MDEV-32100)) --- mysql-test/main/long_unique_innodb.result | 11 +++++++++++ mysql-test/main/long_unique_innodb.test | 10 ++++++++++ sql/sql_table.cc | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/mysql-test/main/long_unique_innodb.result b/mysql-test/main/long_unique_innodb.result index fd2b3daf8e5..8c23d61f2bc 100644 --- a/mysql-test/main/long_unique_innodb.result +++ b/mysql-test/main/long_unique_innodb.result @@ -169,4 +169,15 @@ id f 1 foo 2 foo drop table t1; +# +# MDEV-39846 UNIQUE BLOB contains duplicate records after ALTER IGNORE with READ COMMITTED +# +create table t1 (pk int primary key, b blob) engine=innodb; +insert into t1 values (1,'foo'),(2,'foo'); +set transaction isolation level read committed; +alter ignore table t1 add unique (b); +select * from t1; +pk b +1 foo +drop table t1; # End of 10.6 tests diff --git a/mysql-test/main/long_unique_innodb.test b/mysql-test/main/long_unique_innodb.test index cbe2d7431fe..fc51817e45a 100644 --- a/mysql-test/main/long_unique_innodb.test +++ b/mysql-test/main/long_unique_innodb.test @@ -176,4 +176,14 @@ update ignore t1 set id = 2; select * from t1; drop table t1; +--echo # +--echo # MDEV-39846 UNIQUE BLOB contains duplicate records after ALTER IGNORE with READ COMMITTED +--echo # +create table t1 (pk int primary key, b blob) engine=innodb; +insert into t1 values (1,'foo'),(2,'foo'); +set transaction isolation level read committed; +alter ignore table t1 add unique (b); +select * from t1; +drop table t1; + --echo # End of 10.6 tests diff --git a/sql/sql_table.cc b/sql/sql_table.cc index bb4993bec96..533c9d720ff 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -94,6 +94,7 @@ class Enable_wsrep_ctas_guard #endif /* WITH_WSREP */ #include "sql_debug.h" +#include "scope.h" #ifdef _WIN32 #include @@ -10647,6 +10648,12 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, */ table_list->required_type= TABLE_TYPE_NORMAL; + enum_tx_isolation iso_level_initial= thd->tx_isolation; + SCOPE_EXIT([thd, iso_level_initial](){ + thd->tx_isolation= iso_level_initial; + }); + thd->tx_isolation= ISO_REPEATABLE_READ; + DEBUG_SYNC(thd, "alter_table_before_open_tables"); thd->open_options|= HA_OPEN_FOR_ALTER; From af04faee2b0b8625b51cbf72a79a5da3d5c28a34 Mon Sep 17 00:00:00 2001 From: forkfun Date: Fri, 7 Aug 2026 16:02:17 +0200 Subject: [PATCH 107/150] MDEV-38472 Assertion in Diagnostics_area::set_error_status on UPDATE...LIMIT DEFAULT Binding DEFAULT to a LIMIT ? placeholder via EXECUTE ... USING is never rejected at bind time. unit->set_limit() later evaluates it and sets ER_INVALID_DEFAULT_PARAM in the Diagnostics_area, but SQLCOM_UPDATE (and SQLCOM_UPDATE_MULTI, SQLCOM_INSERT_SELECT/REPLACE_SELECT) proceeded into open_tables() regardless. A concurrent metadata change (e.g. ALTER TABLE FORCE) then triggers Reprepare_observer::report_error(), which tries to set ER_NEED_REPREPARE on an already-set DA and hits the assert. Add the same thd->is_error() check after unit->set_limit() (as in SQLCOM_DELETE), aborting before open_tables() is reached. --- mysql-test/main/ps_ddl1.result | 45 ++++++++++++++++++++++++++++++ mysql-test/main/ps_ddl1.test | 51 ++++++++++++++++++++++++++++++++++ sql/sql_parse.cc | 6 ++++ 3 files changed, 102 insertions(+) diff --git a/mysql-test/main/ps_ddl1.result b/mysql-test/main/ps_ddl1.result index 16776325934..4c794505397 100644 --- a/mysql-test/main/ps_ddl1.result +++ b/mysql-test/main/ps_ddl1.result @@ -482,3 +482,48 @@ drop procedure if exists p1; drop function if exists f1; drop view if exists t1; drop schema if exists mysqltest; +# +# MDEV-38472 Assertion `! is_set() || m_can_overwrite_status' +# failed in Diagnostics_area::set_error_status +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +PREPARE stmt FROM "UPDATE t1 SET a = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING 1, DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1; +CREATE TABLE t1 (a INT, b INT); +CREATE TABLE t2 (a INT, c INT); +INSERT INTO t1 VALUES (1,1),(2,2); +INSERT INTO t2 VALUES (1,1),(2,2); +CREATE VIEW v1 AS SELECT t1.a, t1.b, t2.c FROM t1 JOIN t2 ON t1.a = t2.a; +PREPARE stmt FROM "UPDATE v1 SET b = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING 1, DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP VIEW v1; +DROP TABLE t1, t2; +CREATE TABLE t1 (a INT); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "INSERT INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "REPLACE INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/ps_ddl1.test b/mysql-test/main/ps_ddl1.test index e0441cb0ab8..0c7d3e9f843 100644 --- a/mysql-test/main/ps_ddl1.test +++ b/mysql-test/main/ps_ddl1.test @@ -398,3 +398,54 @@ drop function if exists f1; drop view if exists t1; drop schema if exists mysqltest; --enable_warnings + +--echo # +--echo # MDEV-38472 Assertion `! is_set() || m_can_overwrite_status' +--echo # failed in Diagnostics_area::set_error_status +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +PREPARE stmt FROM "UPDATE t1 SET a = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING 1, DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1; + +CREATE TABLE t1 (a INT, b INT); +CREATE TABLE t2 (a INT, c INT); +INSERT INTO t1 VALUES (1,1),(2,2); +INSERT INTO t2 VALUES (1,1),(2,2); +CREATE VIEW v1 AS SELECT t1.a, t1.b, t2.c FROM t1 JOIN t2 ON t1.a = t2.a; +PREPARE stmt FROM "UPDATE v1 SET b = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING 1, DEFAULT; +DEALLOCATE PREPARE stmt; +DROP VIEW v1; +DROP TABLE t1, t2; + +CREATE TABLE t1 (a INT); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "INSERT INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; + +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "REPLACE INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 8be73d4ba48..6a2dc575b6e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4491,6 +4491,8 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) DBUG_ASSERT(select_lex->limit_params.offset_limit == 0); unit->set_limit(select_lex); + if (thd->is_error()) + goto error; MYSQL_UPDATE_START(thd->query()); res= up_result= mysql_update(thd, all_tables, select_lex->item_list, @@ -4525,6 +4527,8 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) res= 0; unit->set_limit(select_lex); + if (thd->is_error()) + goto error; /* We can not use mysql_explain_union() because of parameters of mysql_select in mysql_multi_update so just set the option if needed @@ -4740,6 +4744,8 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) select_lex->options|= SELECT_NO_UNLOCK; unit->set_limit(select_lex); + if (thd->is_error()) + goto error; if (!(res=open_and_lock_tables(thd, all_tables, TRUE, 0))) { From ebb48286fb933d4dcbd5f295ab187f6f77332ec3 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Wed, 5 Aug 2026 13:10:28 -0600 Subject: [PATCH 108/150] MDEV-40643: Corrupt Heartbeat Log Event can Crash Slave A slave can crash when its master sends an event shorter than the event's own header. A truncated heartbeat makes debug builds fail an assertion in Binary_string::realloc_raw(). On 64-bit release builds it stops the slave IO thread with an error message that omits the log file name. If the master also declares an oversized common header in its format description event, the slave allocates and fills nearly 4GB instead. On 32-bit release builds it writes that error message past the end of a stack buffer. An event under four bytes crashes every build, because the slave checksums roughly 16 EiB and reads far past the end of the packet. Neither queue_event() nor the Heartbeat_log_event constructor bounded the length the master sent. queue_event() handed that length to event_checksum_test(), which subtracted the checksum length from it, and the result wrapped on an event under four bytes. The constructor computed the log file name length as event_len minus the header lengths, and on a truncated heartbeat that subtraction wrapped to a value near 4GB. queue_event() rejected the heartbeat as invalid, so its error path appended the log file name to the error message using the wrapped length. Add the missing bound in both places. queue_event() now rejects an event shorter than the common header before anything reads that header, and the slave IO thread stops with an error. The Heartbeat_log_event constructor now compares event_len against the combined header lengths before the subtraction. A heartbeat that clears the first check but still stops inside its headers keeps ident_len at 0 and log_ident at NULL, so the error path appends nothing. That constructor check also precedes the read of the extended log position, which previously ran on a short event before any validation. A short event from the master now stops the slave IO thread with an error instead of crashing the server. Reviewed-by: Kristian Nielsen Signed-off-by: Brandon Nesterenko --- .../rpl_heartbeat_log_event_underflow.result | 76 +++++++++ ...l_heartbeat_log_event_underflow-master.opt | 1 + .../t/rpl_heartbeat_log_event_underflow.test | 147 ++++++++++++++++++ sql/log_event_server.cc | 38 +++-- sql/slave.cc | 22 +++ sql/sql_repl.cc | 22 ++- 6 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt create mode 100644 mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test diff --git a/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result b/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result new file mode 100644 index 00000000000..9fc0267cc11 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result @@ -0,0 +1,76 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Unexpected master's heartbeat data"); +call mtr.add_suppression("Event from master is shorter than its common header"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +CHANGE MASTER TO MASTER_HEARTBEAT_PERIOD= 2; +include/start_slave.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case 1: a heartbeat that leaves no room for a file name +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_truncated_heartbeat"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +# Waiting for the IO thread to reject the truncated heartbeat +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure heartbeat validation rejected the event +include/assert_grep.inc [The IO thread reported an inconsistent heartbeat event] +include/assert_grep.inc [The heartbeat error should report an empty log_file_name] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 2: a heartbeat shorter than the common header +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_undersized_heartbeat"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +# Waiting for the IO thread to reject the undersized heartbeat +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before its header was read +include/assert_grep.inc [The IO thread reported an event shorter than the common header] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Ensure replication works after the invalid heartbeat events +# +connection master; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_heartbeat_log_event_underflow.test diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt new file mode 100644 index 00000000000..a6e99a9fd5a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt @@ -0,0 +1 @@ +--binlog-checksum=CRC32 diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test new file mode 100644 index 00000000000..2dc188c22c3 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test @@ -0,0 +1,147 @@ +# +# This test verifies that a replica rejects a heartbeat event that is +# too short to carry the log file name it must hold. Two lengths are +# covered. An event that reaches the heartbeat parser but leaves no room +# for a file name must stop the IO thread with an error reporting an +# empty file name. An event shorter than the common header must be +# rejected before any parser reads that header. Replication must be able +# to resume after each. +# +# The debug injections send heartbeats of 17 and 13 bytes. MDEV-40643 +# reported that the 17-byte case underflowed the replica's computed name +# length and crashed the replica. +# +# Note rpl_heartbeat_log_event_underflow-master.opt sets +# binlog_checksum=CRC32 on the master for Test Case 1. That is, those four +# checksum bytes carry the 17-byte heartbeat over queue_event()'s 19-byte +# minimum length. +# +# References: +# * MDEV-40643: Corrupt Heartbeat Log Event can Crash Slave +# * Bug#39377010: Improve malformed heartbeat handling +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Unexpected master's heartbeat data"); +call mtr.add_suppression("Event from master is shorter than its common header"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +# Both cases need a heartbeat to arrive promptly. The default period is +# slave_net_timeout/2. The CHANGE MASTER that rpl_end.inc runs on every +# server restores it. +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_HEARTBEAT_PERIOD= 2; +--source include/start_slave.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case 1: a heartbeat that leaves no room for a file name +--echo # + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_truncated_heartbeat"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--echo # Waiting for the IO thread to reject the truncated heartbeat +# queue_event() reports 1623, then handle_slave_io() overwrites it with 1595 +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure heartbeat validation rejected the event +--let $assert_text= The IO thread reported an inconsistent heartbeat event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Unexpected master.s heartbeat data: inconsistent heartbeat event content +--let $assert_count= 1 +--source include/assert_grep.inc + +# An empty log_file_name shows ident_len was bounded. Reaching this assertion +# at all is the other half of the evidence. +--let $assert_text= The heartbeat error should report an empty log_file_name +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= heartbeat event content;the event.s data: log_file_name log_pos [0-9]+ +--let $assert_count= 1 +--source include/assert_grep.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 2: a heartbeat shorter than the common header +--echo # +--connection master +set @@global.debug_dbug= "+d,binlog_sender_undersized_heartbeat"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--echo # Waiting for the IO thread to reject the undersized heartbeat +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the event was rejected before its header was read +--let $assert_text= The IO thread reported an event shorter than the common header +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master is shorter than its common header; the event.s length: [0-9]+ +--let $assert_count= 1 +--source include/assert_grep.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the invalid heartbeat events +--echo # +--connection master +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_heartbeat_log_event_underflow.test diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index d0291f7733a..03d9d879930 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -9006,20 +9006,34 @@ void Ignorable_log_event::pack_info(Protocol *protocol) #if defined(HAVE_REPLICATION) Heartbeat_log_event::Heartbeat_log_event(const uchar *buf, uint event_len, const Format_description_log_event* description_event) - :Log_event(buf, description_event) + :Log_event(buf, description_event), ident_len(0), log_ident(NULL) { - uint8 header_size= description_event->common_header_len; + uint sub_header_len= (log_pos == 0) ? HB_SUB_HEADER_LEN : 0; + uint all_headers_len= description_event->common_header_len + sub_header_len; + + /* + The comparison is <= rather than <, so an event whose length stops exactly + at the headers is rejected along with a shorter one. + + * A shorter event must be rejected out of necessity. ident_len is + unsigned, so the subtraction below would wrap and the caller would + read the log file name from far past the event. + * An event of exactly the header length must be rejected as policy. It + carries no log file name, and a heartbeat that names no binary log + file tells the replica nothing it can compare its own coordinates + against. + + Leaving log_ident at NULL is what reports both to the caller, through + is_valid(). + */ + if (event_len <= all_headers_len) + return; + if (log_pos == 0) - { - log_pos= uint8korr(buf + header_size); - log_ident= buf + header_size + HB_SUB_HEADER_LEN; - ident_len= event_len - (header_size + HB_SUB_HEADER_LEN); - } - else - { - log_ident= buf + header_size; - ident_len = event_len - header_size; - } + log_pos= uint8korr(buf + description_event->common_header_len); + + log_ident= buf + all_headers_len; + ident_len= event_len - all_headers_len; } #endif diff --git a/sql/slave.cc b/sql/slave.cc index 4215a3c1923..96d7eab6ca1 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -6686,12 +6686,34 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) bool dbg_crash_after_enqueue_gtid_flag= false; #endif + /* The shortest event a master of this binlog version can legally send. */ + ulong min_event_len= + mi->rli.relay_log.description_event_for_queue->binlog_version < 4 ? + OLD_HEADER_LEN : LOG_EVENT_MINIMAL_HEADER_LEN; DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_OFF || checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); DBUG_ENTER("queue_event"); + + /* + Reject an event that is shorter than the common header before anything + reads that header. The code below indexes into the header unconditionally, + and several of the per-type parsers derive a body length by subtracting + the header length, which wraps on a shorter event. + event_checksum_test() subtracts BINLOG_CHECKSUM_LEN in the same way. + */ + if (unlikely(event_len < min_event_len)) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Event from master is shorter than its " + "common header; the event's length: ")); + error_msg.append_ulonglong(event_len); + unlock_data_lock= FALSE; + goto err; + } + /* FD_queue checksum alg description does not apply in a case of FD itself. The one carries both parts of the checksum data. diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index d1d396370c0..464acfc899b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -863,10 +863,28 @@ static int send_heartbeat_event(binlog_send_info *info, event_len+= HB_SUB_HEADER_LEN; } + size_t hb_header_len= sizeof(header); +#ifndef DBUG_OFF + /* + Corrupt the heartbeat event to test that a replica can correctly identify + malformed events. 17 bytes keeps LOG_POS_OFFSET on the wire and clears + queue_event()'s minimum length, so the heartbeat parser rejects it. + 13 bytes falls under that minimum, so queue_event() rejects it first. + */ + DBUG_EXECUTE_IF("binlog_sender_truncated_heartbeat", hb_header_len= 17;); + DBUG_EXECUTE_IF("binlog_sender_undersized_heartbeat", hb_header_len= 13;); + if (hb_header_len != sizeof(header)) + { + ident_len= 0; + event_len= hb_header_len + (sub_header_in_use ? HB_SUB_HEADER_LEN : 0) + + (do_checksum ? BINLOG_CHECKSUM_LEN : 0); + } +#endif + int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, 0); - packet->append(header, sizeof(header)); + packet->append(header, hb_header_len); if (sub_header_in_use) packet->append(sub_header_buf, sizeof(sub_header_buf)); packet->append(p, ident_len); // log_file_name @@ -874,7 +892,7 @@ static int send_heartbeat_event(binlog_send_info *info, if (do_checksum) { char b[BINLOG_CHECKSUM_LEN]; - ha_checksum crc= my_checksum(0, (uchar*) header, sizeof(header)); + ha_checksum crc= my_checksum(0, (uchar*) header, hb_header_len); if (sub_header_in_use) crc= my_checksum(crc, (uchar*) sub_header_buf, sizeof(sub_header_buf)); crc= my_checksum(crc, (uchar*) p, ident_len); From 10313100b97a8d2f036e51e50ba112017e60d984 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Thu, 6 Aug 2026 13:39:51 -0600 Subject: [PATCH 109/150] MDEV-40492: Oversized Fake Rotate Event can Crash Slave A slave can crash when the master's binlog_checksum setting differs from the checksum the slave's relay log carries. On the first Rotate event of such a connection, the slave IO thread copies the event onto a stack buffer sized for the longest binary log file name a master can send. A master that names a longer file makes the copy run off the end of that buffer, and what it writes past the end is the file name itself. A name that overruns the buffer by a few hundred bytes reaches only the other buffers of the same stack frame, which the slave does not read on this path, so it keeps running. A name that overruns the whole frame takes the return address with it, and the slave crashes. Builds with AddressSanitizer stop at the copy and report a stack-buffer-overflow. The fake Rotate event that opens a connection is rewritten when the master's checksum policy and the relay log's disagree. One case adds a checksum to the event, and the other strips one. Both copy the event into rot_buf using the length the master sent, and neither compared that length against the size of rot_buf. The Rotate_log_event constructed just above bounds its own copy of the file name at FN_REFLEN-1, which leaves the length the master sent untouched. rot_buf was itself one checksum shorter than the event the first case produces from the longest file name. Bound the event before either case copies it. queue_event() now compares the event's length against the longest Rotate a master can send: the two headers, a file name of at most FN_REFLEN bytes, and the checksum the master's own policy puts on the event. rot_buf now reserves that checksum length as well. A Rotate event naming a longer file stops the slave IO thread with an error reporting the length the master sent, instead of running off the end of the buffer. Reviewed-by: Kristian Nielsen Signed-off-by: Brandon Nesterenko --- .../r/rpl_rotate_log_event_overflow.result | 92 +++++++++ .../rpl/t/rpl_rotate_log_event_overflow.test | 188 ++++++++++++++++++ sql/slave.cc | 51 ++++- sql/sql_repl.cc | 18 ++ 4 files changed, 343 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test diff --git a/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result b/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result new file mode 100644 index 00000000000..b8c921980a8 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result @@ -0,0 +1,92 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Rotate event from master names a binary log file longer"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +change master to master_use_gtid=no; +include/start_slave.inc +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +set @saved_checksum= @@global.binlog_checksum; +# +# Test Case 1: the master adds no checksum and the relay log carries one +# +connection master; +set @@global.binlog_checksum= CRC32; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +connection master; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; +connection slave; +START SLAVE; +# Waiting for the IO thread to reject the oversized Rotate event +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before the replica copied it +include/assert_grep.inc [The IO thread reported an oversized Rotate event] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 2: the master adds a checksum and the relay log carries none +# +connection master; +set @@global.binlog_checksum= NONE; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +connection master; +set @@global.binlog_checksum= CRC32; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; +connection slave; +START SLAVE; +# Waiting for the IO thread to reject the oversized Rotate event +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before the replica copied it +include/assert_grep.inc [The IO thread reported an oversized Rotate event] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Ensure replication works after the oversized Rotate events +# +connection master; +insert into t1 values (4); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_rotate_log_event_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test b/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test new file mode 100644 index 00000000000..d55e4aac81b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test @@ -0,0 +1,188 @@ +# +# This test verifies that a replica rejects a fake Rotate event that names +# a binary log file longer than the longest file name a replica can hold. The +# replica rewrites the checksum of that event through a buffer of its own +# whenever the master's binlog_checksum setting differs from the checksum its +# relay log carries. Both directions of that difference are covered, and +# replication must be able to resume after each. The replica connects by +# binlog file position, so each resume also verifies the rejected event left +# those coordinates untouched. +# +# MDEV-40492 reported that the replica sized the copy into that buffer from +# the length the master sent, and overran it. +# +# Note the relay log takes its checksum from the last format description +# event the replica queued, so each case changes binlog_checksum on the master +# while the IO thread is stopped. That is, the fake Rotate of the next +# connection arrives ahead of the format description event that settles the +# difference again. +# +# References: +# * MDEV-40492: queue_event: fake ROTATE checksum adjust copies into fixed +# rot_buf without bounding event_len (stack OOB) +# * MDEV-39689: Slave Overflow on Malformed Table_map_log_event +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Rotate event from master names a binary log file longer"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +--source include/stop_slave.inc +change master to master_use_gtid=no; +--source include/start_slave.inc + +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; +set @saved_checksum= @@global.binlog_checksum; + +--echo # +--echo # Test Case 1: the master adds no checksum and the relay log carries one +--echo # + +# Replicating from a master that adds a checksum gives the relay log one. +--connection master +set @@global.binlog_checksum= CRC32; +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--source include/stop_slave.inc + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; + +# The IO thread can reject the event before include/start_slave.inc observes +# the thread running, and the include reports a nonzero Last_IO_Errno as a +# failure of its own. +--connection slave +START SLAVE; + +--echo # Waiting for the IO thread to reject the oversized Rotate event +# queue_event() reports 1593, then handle_slave_io() overwrites it with 1595 +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +# A setup that leaves the master and the relay log agreeing shows up here as +# a timeout. Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The reported maximum bounds a name of FN_REFLEN bytes behind the same headers +--echo # Ensure the event was rejected before the replica copied it +--let $assert_text= The IO thread reported an oversized Rotate event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Rotate event from master names a binary log file longer than the maximum file name length;.*the maximum: 539 +--let $assert_count= 1 +--source include/assert_grep.inc + +# Restore the master's checksum before the IO thread reconnects. The relay log +# takes its checksum from the next format description event, so the reconnect +# leaves the master and the relay log agreeing again. +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 2: the master adds a checksum and the relay log carries none +--echo # + +# Replicating from a master that adds no checksum leaves the relay log +# without one. +--connection master +set @@global.binlog_checksum= NONE; +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--source include/stop_slave.inc + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.binlog_checksum= CRC32; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; + +# The IO thread can reject the event before include/start_slave.inc observes +# the thread running, and the include reports a nonzero Last_IO_Errno as a +# failure of its own. +--connection slave +START SLAVE; + +--echo # Waiting for the IO thread to reject the oversized Rotate event +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +# A setup that leaves the master and the relay log agreeing shows up here as +# a timeout. Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The reported maximum bounds a name of FN_REFLEN bytes behind the same +# headers, plus that checksum. +--echo # Ensure the event was rejected before the replica copied it +--let $assert_text= The IO thread reported an oversized Rotate event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Rotate event from master names a binary log file longer than the maximum file name length;.*the maximum: 543 +--let $assert_count= 1 +--source include/assert_grep.inc + +# Restore the master's checksum before the IO thread reconnects. The relay log +# takes its checksum from the next format description event, so the reconnect +# leaves the master and the relay log agreeing again. +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the oversized Rotate events +--echo # +--connection master +insert into t1 values (4); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_rotate_log_event_overflow.test diff --git a/sql/slave.cc b/sql/slave.cc index 96d7eab6ca1..95ba2753794 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -6680,7 +6680,8 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) mi->checksum_alg_before_fd : mi->rli.relay_log.relay_log_checksum_alg; const uchar *save_buf= NULL; // needed for checksumming the fake Rotate event - uchar rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN]; + uchar rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN + + BINLOG_CHECKSUM_LEN]; #ifndef DBUG_OFF bool dbg_crash_after_enqueue_gtid_flag= false; @@ -6806,7 +6807,47 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) goto err; case ROTATE_EVENT: { - Rotate_log_event rev(buf, checksum_alg != BINLOG_CHECKSUM_ALG_OFF ? + /* + RSC_1 and RSC_2 below rewrite the fake Rotate (the one that opens a + connection, naming the binary log file the dump starts in; the file + switches later in the connection send more, but by then the first + format description event has left the relay log carrying the master's + algorithm) through rot_buf, and the first two conditions here are the + pair that selects them: a fake Rotate that carries a checksum the + relay log does not, or the reverse. Both cases memcpy() into rot_buf + a length taken from event_len, so event_len has to be bounded before + they run. The Rotate_log_event constructed below does not bound it. + That constructor caps only new_log_ident, the copy of the file name + it keeps for itself, so event_len remains the length the master sent. + This check is placed at the start of the case block so a rejected event + will not change replication state. + */ + bool is_fake_rotate= uint4korr(&buf[0]) == 0; + bool event_has_checksum= checksum_alg != BINLOG_CHECKSUM_ALG_OFF; + bool relay_log_has_checksum= + mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF; + + /* The longest Rotate a master can send under its own checksum policy. */ + ulong max_rotate_len= + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN + + (event_has_checksum ? BINLOG_CHECKSUM_LEN : 0); + + if (unlikely(is_fake_rotate && + event_has_checksum != relay_log_has_checksum && + event_len > max_rotate_len)) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Rotate event from master names a " + "binary log file longer than the " + "maximum file name length; the event's " + "length: ")); + error_msg.append_ulonglong(event_len); + error_msg.append(STRING_WITH_LEN(", the maximum: ")); + error_msg.append_ulonglong(max_rotate_len); + goto err; + } + + Rotate_log_event rev(buf, event_has_checksum ? event_len - BINLOG_CHECKSUM_LEN : event_len, mi->rli.relay_log.description_event_for_queue); bool master_changed= false; @@ -6926,8 +6967,7 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) to compute checksum for its first FD event for RL the fake Rotate gets checksummed here. */ - if (uint4korr(&buf[0]) == 0 && checksum_alg == BINLOG_CHECKSUM_ALG_OFF && - mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF) + if (is_fake_rotate && !event_has_checksum && relay_log_has_checksum) { ha_checksum rot_crc= 0; event_len += BINLOG_CHECKSUM_LEN; @@ -6950,8 +6990,7 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) RSC_2: If NM \and fake Rotate \and slave does not compute checksum the fake Rotate's checksum is stripped off before relay-logging. */ - if (uint4korr(&buf[0]) == 0 && checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - mi->rli.relay_log.relay_log_checksum_alg == BINLOG_CHECKSUM_ALG_OFF) + if (is_fake_rotate && event_has_checksum && !relay_log_has_checksum) { event_len -= BINLOG_CHECKSUM_LEN; memcpy(rot_buf, buf, event_len); diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 464acfc899b..05dcf69659e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -223,6 +223,24 @@ static int fake_rotate_event(binlog_send_info *info, ulonglong position, String *packet= info->packet; ha_checksum crc= 0; +#ifndef DBUG_OFF + /* + Name a binary log file longer than any file name a replica can hold, to + test that a replica bounds the event before copying it into a buffer of + its own. The length is a large multiple of FN_REFLEN rather than one byte + over it, so that the copy of a replica missing the bound runs past its + whole stack frame. An overrun of a few hundred bytes only reaches the + buffers next to the target, which that replica never reads on this path. + The name is replaced here, ahead of the header, so that both the event + length the header carries and the checksum below cover it. + */ + static char oversized_ident[32 * FN_REFLEN]; + DBUG_EXECUTE_IF("binlog_sender_oversized_fake_rotate", + memset(oversized_ident, 'a', sizeof(oversized_ident)); + p= oversized_ident; + ident_len= (uint) sizeof(oversized_ident);); +#endif + /* reset transmit packet for the fake rotate event below */ if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) DBUG_RETURN(1); From 9e4948c2cf9dcc84d4b3f7267150ef6368acb44b Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Fri, 7 Aug 2026 10:55:47 -0600 Subject: [PATCH 110/150] MDEV-40648: Replication Undefined Behavior on Malformed Rotate Log Event A slave stops with a relay log read failure when its master sends an event whose header declares a length other than the number of bytes the event arrived in. The file and position the slave reports for that failure are not where the problem is. A master that logs no checksum does not stop the slave at all. Such a master can make the slave run one statement twice, leaving the slave's data holding a row the master's binary log never carried. A "malicious" master can already send whatever events it likes, so what this defeats is comparing a slave's applied stream against the master's binary log. The slave IO thread reads each event from the master as one network packet, and writes that packet into the relay log unchanged, using the packet's own length. Every later reader of that relay log frames the events by a different length: the one each event's header declares at EVENT_LEN_OFFSET. A master writing an event sets the two to the same value. Log_event::read_log_event(), which parses the events that queue_event() does not construct itself, checks only that the packet reaches EVENT_LEN_OFFSET, and never compares the declared length with the length of the packet. queue_event() never compared the two lengths either. An event declaring fewer bytes than the packet held reached the relay log with the extra bytes behind the event. The SQL thread framed its next read from inside the previous event. Where the master had placed a complete event in those extra bytes, and no checksum covered the packet, the SQL thread applied that second event. This patch adds validation to ensure the lengths are equal. An event whose lengths disagree will stop the IO thread with ER_SLAVE_FATAL_ERROR, and the relay log will never receive the event. Reviewed-by: Kristian Nielsen Signed-off-by: Brandon Nesterenko --- .../r/rpl_queue_event_length_mismatch.result | 120 ++++++++ ...rpl_queue_event_length_mismatch-master.opt | 1 + .../t/rpl_queue_event_length_mismatch.test | 257 ++++++++++++++++++ sql/slave.cc | 23 ++ sql/sql_repl.cc | 69 +++++ 5 files changed, 470 insertions(+) create mode 100644 mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result create mode 100644 mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt create mode 100644 mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test diff --git a/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result new file mode 100644 index 00000000000..4f3c89fc900 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result @@ -0,0 +1,120 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Event from master declares a length that does not match"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +change master to master_use_gtid=no; +include/start_slave.inc +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case 1: a Rotate event declaring fewer bytes than its packet +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_short_event_len"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +FLUSH LOGS; +# Waiting for the IO thread to reject the mismatched Rotate event +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the event was rejected before the replica relay logged the event +include/assert_grep.inc [The IO thread reported a Rotate event declaring 8 bytes fewer than its packet] +# Ensure the SQL thread read everything the IO thread relay logged +include/sync_slave_sql_with_io.inc +include/assert.inc [The SQL thread reported no relay log read failure] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 2: a Rotate event declaring more bytes than its packet +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_long_event_len"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +FLUSH LOGS; +# Waiting for the IO thread to reject the mismatched Rotate event +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the event was rejected before the replica relay logged the event +include/assert_grep.inc [The IO thread reported a Rotate event declaring 8 bytes more than its packet] +# Ensure the SQL thread read everything the IO thread relay logged +include/sync_slave_sql_with_io.inc +include/assert.inc [The SQL thread reported no relay log read failure] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 3: a packet carrying a second event behind the first +# +connection slave; +include/stop_slave.inc +connection master; +set @saved_checksum= @@global.binlog_checksum; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_append_extra_event"; +connection slave; +include/start_slave.inc +connection master; +set @saved_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +insert into t1 values (2); +set @@session.binlog_format= @saved_format; +# Waiting for the IO thread to reject the doubled packet +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the rejected packet delivered nothing to the replica +include/assert.inc [The replica has not applied the insert the packet carried] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +# Ensure old relay logs are purged, use GTID +change master to master_use_gtid=slave_pos; +include/start_slave.inc +# Ensure the replica holds the row once the master resends the insert +connection master; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Ensure replication works after the mismatched events +# +connection master; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_queue_event_length_mismatch.test diff --git a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt new file mode 100644 index 00000000000..a6e99a9fd5a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt @@ -0,0 +1 @@ +--binlog-checksum=CRC32 diff --git a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test new file mode 100644 index 00000000000..2d9132fae0f --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test @@ -0,0 +1,257 @@ +# +# This test verifies that a replica rejects an event whose header +# declares a length other than the number of bytes the packet holds. +# Three shapes are covered: a header declaring fewer bytes than the +# packet holds, a header declaring more, and a packet holding a second +# complete event behind the first. Each must stop the IO thread with an +# error, and replication must be able to resume after each. +# +# A replica relay logs the packet as received, while every reader of +# that relay log frames each event by the length the event's own header +# declares. An accepted event of any of these shapes therefore leaves +# the SQL thread beginning its next read at the wrong offset. Where the +# bytes at that offset are themselves an event, the replica applies that +# event, and the replica's data ends up holding what the master's binary +# log does not. MDEV-40648 reported that a replica never compared the +# two lengths. +# +# Note rpl_queue_event_length_mismatch-master.opt sets +# binlog_checksum=CRC32 on the master, so the event lengths Test Cases 1 +# and 2 pin include those four checksum bytes. +# +# References: +# * MDEV-40648: Replication Undefined Behavior on Malformed Rotate Log Event +# * Bug#39254914: Improve directly queued event validation +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Event from master declares a length that does not match"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +--source include/stop_slave.inc +change master to master_use_gtid=no; +--source include/start_slave.inc + +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case 1: a Rotate event declaring fewer bytes than its packet +--echo # + +# The dump thread reads the injection when the replica reconnects below. +# The injection reaches real Rotate events only, so FLUSH LOGS is what +# produces the event. The fake Rotate that opens a connection is written +# by fake_rotate_event(), which the injection never runs in. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_short_event_len"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +FLUSH LOGS; + +--echo # Waiting for the IO thread to reject the mismatched Rotate event +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The Rotate event names master-bin.000002, so the packet holds 48 bytes: +# the two headers, the 17 byte file name, and the checksum. +--echo # Ensure the event was rejected before the replica relay logged the event +--let $assert_text= The IO thread reported a Rotate event declaring 8 bytes fewer than its packet +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master declares a length that does not match the packet the event arrived in; the declared length: 40, the packet.s length: 48 +--let $assert_count= 1 +--source include/assert_grep.inc + +# A relay logged mismatch stops the SQL thread, which frames the relay log +# by the lengths the event headers declare. The sample below must wait for +# the SQL thread to read up to the IO thread's stop position: an earlier +# sample reads 0 whether or not a mismatch reached the relay log. +--echo # Ensure the SQL thread read everything the IO thread relay logged +--source include/sync_slave_sql_with_io.inc +--let $sql_errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1) +--let $assert_text= The SQL thread reported no relay log read failure +--let $assert_cond= "$sql_errno" = "0" +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 2: a Rotate event declaring more bytes than its packet +--echo # + +--connection master +set @@global.debug_dbug= "+d,binlog_sender_long_event_len"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +FLUSH LOGS; + +--echo # Waiting for the IO thread to reject the mismatched Rotate event +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +--echo # Ensure the event was rejected before the replica relay logged the event +--let $assert_text= The IO thread reported a Rotate event declaring 8 bytes more than its packet +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master declares a length that does not match the packet the event arrived in; the declared length: 56, the packet.s length: 48 +--let $assert_count= 1 +--source include/assert_grep.inc + +# A relay logged mismatch stops the SQL thread, which frames the relay log +# by the lengths the event headers declare. The sample below must wait for +# the SQL thread to read up to the IO thread's stop position: an earlier +# sample reads 0 whether or not a mismatch reached the relay log. +--echo # Ensure the SQL thread read everything the IO thread relay logged +--source include/sync_slave_sql_with_io.inc +--let $sql_errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1) +--let $assert_text= The SQL thread reported no relay log read failure +--let $assert_cond= "$sql_errno" = "0" +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 3: a packet carrying a second event behind the first +--echo # + +# Note that this case does not assert whether the appended event executes. +# Execution state is not tracked the same way before and after the fix: the +# fix rejects the packet before either copy is queued, so the assertions +# below are never reached without the fix. Testing the regression before +# the fix means observing the replica's t1 by hand, for example after a +# sleep. + +# The master logs no checksum for this case. The packet ends in the checksum +# bytes the trailing copy carries, and those bytes cover the copy rather +# than the packet, so a replica checking the packet's own checksum would +# reject the event as corrupt before reaching the length comparison. +--connection slave +--source include/stop_slave.inc + +--connection master +set @saved_checksum= @@global.binlog_checksum; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_append_extra_event"; + +--connection slave +--source include/start_slave.inc + +# The statement's own Query event carries the injection, so the insert is +# logged as a statement. Note that we switch to STATEMENT mode temporarily +# because a row event cannot carry the injection: the trailing copy reaches +# the applier after the leading copy's STMT_END_F has cleared the table map, +# and the applier skips a row event whose table id is no longer mapped. +--connection master +set @saved_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +insert into t1 values (2); +set @@session.binlog_format= @saved_format; + +--echo # Waiting for the IO thread to reject the doubled packet +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The packet held the insert twice. A replica reading its relay log by the +# event headers applies the leading copy and then the trailing one, landing +# the row twice. Rejecting the packet keeps both copies out, so the table +# still holds only the row the initialization above inserted. +--echo # Ensure the rejected packet delivered nothing to the replica +--let $slave_row_count= query_get_value(SELECT COUNT(*) AS c FROM t1, c, 1) +--let $assert_text= The replica has not applied the insert the packet carried +--let $assert_cond= $slave_row_count = 1 +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc + +--echo # Ensure old relay logs are purged, use GTID +change master to master_use_gtid=slave_pos; +--source include/start_slave.inc + +--echo # Ensure the replica holds the row once the master resends the insert +--connection master +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Ensure replication works after the mismatched events +--echo # +--connection master +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_queue_event_length_mismatch.test diff --git a/sql/slave.cc b/sql/slave.cc index 95ba2753794..8f038abb55b 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -6715,6 +6715,29 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) goto err; } + /* + An event states its length twice: once as the length of the packet the + IO thread read the event from, and once in the EVENT_LEN_OFFSET field + of the event's own header. The relay log write below takes the packet's + length, and every reader of the relay log frames each event by the + length that header declares. An event whose two lengths disagree + therefore leaves the SQL thread beginning its next read at the wrong + offset. + */ + if (unlikely(event_len != uint4korr(buf + EVENT_LEN_OFFSET))) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Event from master declares a length " + "that does not match the packet the " + "event arrived in; the declared " + "length: ")); + error_msg.append_ulonglong(uint4korr(buf + EVENT_LEN_OFFSET)); + error_msg.append(STRING_WITH_LEN(", the packet's length: ")); + error_msg.append_ulonglong(event_len); + unlock_data_lock= FALSE; + goto err; + } + /* FD_queue checksum alg description does not apply in a case of FD itself. The one carries both parts of the checksum data. diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 05dcf69659e..d2ad9e8cc1b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2071,6 +2071,75 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type, return "run 'before_send_event' hook failed"; } +#ifndef DBUG_OFF + /* + Declare a length in the event's header that differs from the number of + bytes the packet carries, to test that a replica reconciles the two + lengths before relay logging the event. Real Rotate events carry this + injection, which keeps the injection on one event of a known length + instead of on every event of the connection. Eight bytes moves the + declared end of the event off the packet's own end in either + direction: short of that end, into the file name the event closes + with, or past that end entirely. + + fix_checksum() cannot serve here. That helper checksums the range + EVENT_LEN_OFFSET declares, the field this injection falsifies, and a + replica checksums the event over the length of the packet the event + arrived in. Recomputing over the packet is what carries the + event past the replica's checksum test and on to the length + comparison. + */ + if (event_type == ROTATE_EVENT) + { + long len_delta= 0; + DBUG_EXECUTE_IF("binlog_sender_short_event_len", len_delta= -8;); + DBUG_EXECUTE_IF("binlog_sender_long_event_len", len_delta= 8;); + if (len_delta) + { + uchar *ev= (uchar*) packet->ptr() + ev_offset; + ulong ev_len= (ulong) (len - ev_offset); + + int4store(ev + EVENT_LEN_OFFSET, (ulong) ((long) ev_len + len_delta)); + if (current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + int4store(ev + ev_len - BINLOG_CHECKSUM_LEN, + my_checksum(0, ev, ev_len - BINLOG_CHECKSUM_LEN)); + } + } + + /* + Put a second copy of the event in the packet behind the first, the + other shape the same disagreement takes on the wire. This injection + leaves the header alone and grows the packet, so the bytes behind the + length the header declares form a complete event of their own, and a + replica that frames its relay log by those headers goes on to apply + that second event. + + Query events carry this injection, because a statement that runs a + second time inserts a row the master never sent, which the replica's + own data then shows. A row event cannot carry the injection. The + trailing copy reaches the applier after the leading copy's STMT_END_F + has closed the statement's tables and cleared the table map, so + Rows_log_event::do_apply_event() finds no table for the copy's table + id and returns without applying or reporting anything. + + The master must be logging no checksum for the trailing copy to + survive: the packet ends in the checksum bytes the trailing copy + carries, and those bytes cover that copy alone, not the packet. + */ + DBUG_EXECUTE_IF("binlog_sender_append_extra_event", + { + if (event_type == QUERY_EVENT) + { + String extra; + if (!extra.copy(packet->ptr() + ev_offset, len - ev_offset, + &my_charset_bin) && + !packet->append(extra)) + len= packet->length(); + } + }); +#endif + if (my_net_write(info->net, (uchar*) packet->ptr(), len)) { info->error= ER_UNKNOWN_ERROR; From f17a3ea6009d3a583334d3f974ea64d5150da2cc Mon Sep 17 00:00:00 2001 From: ARaveala Date: Fri, 7 Aug 2026 11:29:16 -0600 Subject: [PATCH 111/150] MDEV-39762: Slave Overflow on Malformed Query_compressed_log_event A replica can crash when a compressed event from its master carries a corrupted uncompressed length. A compressed Query event whose length is near 4GB overwrites the IO thread's stack with the event's content. A compressed rows event of the same shape makes the replica ask for an allocation of 4GB. A debug build that gets that memory then fails an assertion in binlog_buf_uncompress(). On a build where a ulong is 32 bits wide, both events overwrite the IO thread's stack. The IO thread uncompresses a compressed event before writing the event to the relay log, and offers the uncompress function 4096 bytes of its own stack to hold the result. The function computes how large the event will be once uncompressed, and that size decides where the uncompressed event goes. An event that fits in the stack buffer is uncompressed there. A larger event is uncompressed into an allocation the function makes at that size. With either buffer, the function tells zlib that the room available is the uncompressed length the event declared. query_event_uncompress() never bounded the length read out of the event. The function added the header length to that value, rounded the sum up for alignment, and cast the result to uint32. The cast dropped the high bits of a sum above 4GB, so a length near 4GB produced a size of a few dozen bytes. That size fit the stack buffer, so content of any size went onto the IO thread's stack. row_log_event_uncompress() computes that sum in a ulong, which wraps where a ulong is 32 bits wide. The Query_compressed_log_event constructor and Rows_log_event::uncompress_buf() size their allocations from the same unbounded length. Reject an uncompressed length above MAX_MAX_ALLOWED_PACKET at every point the length is read, before any size is computed from it. No master writes a larger length, because max_allowed_packet is capped at 1GB. query_event_uncompress() also keeps that size in a size_t, so the allocation is made from the whole computed value. Both functions return the error code for the IO thread to report, so a length over the limit stops the IO thread with ER_TOO_BIG_FOR_UNCOMPRESS rather than the generic uncompress error. A compressed event with a corrupted uncompressed length now stops the replica's IO thread with an error instead of crashing the replica. Alexandra Raveala wrote the original patch and its first regression test, contributed through PR 5413. Brandon Nesterenko saw the work through for the 10.6 release, replacing the test with one that makes the master write the corrupt length and reworking how the IO thread reports the error. Co-authored-by: Brandon Nesterenko Reviewed-by: Kristian Nielsen --- .../rpl_compressed_log_event_overflow.result | 78 +++++++++ ...l_compressed_log_event_overflow-master.opt | 1 + .../t/rpl_compressed_log_event_overflow.test | 160 ++++++++++++++++++ sql/log_event.cc | 89 ++++++---- sql/slave.cc | 33 ++-- 5 files changed, 318 insertions(+), 43 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt create mode 100644 mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test diff --git a/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result b/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result new file mode 100644 index 00000000000..6107508c9fc --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result @@ -0,0 +1,78 @@ +include/master-slave.inc +[connection master] +connection slave; +include/stop_slave.inc +CHANGE MASTER TO master_use_gtid= slave_pos; +include/start_slave.inc +call mtr.add_suppression("Uncompressed data size too large"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +create table t2 (a longtext); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +# +# Test Case 1: a compressed Query event +# +connection master; +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +connection slave; +# Waiting for the IO thread to reject the compressed Query event +include/wait_for_slave_io_error.inc [errno=1256, 1595] +# Ensure the replica rejected the event on its recorded length +include/assert_grep.inc [The IO thread reported an oversized uncompressed length] +# Move the replica past the corrupt event +include/stop_slave_sql.inc +include/start_slave.inc +# Ensure replication resumes +connection master; +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Test Case 2: a compressed Write_rows event +# +connection master; +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= ROW; +insert into t2 values (repeat('a', 65536)); +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +connection slave; +# Waiting for the IO thread to reject the compressed Write_rows event +include/wait_for_slave_io_error.inc [errno=1256, 1595] +# Ensure the replica rejected the event on its recorded length +include/assert_grep.inc [The IO thread reported an oversized uncompressed length] +# Move the replica past the corrupt event +include/stop_slave_sql.inc +include/start_slave.inc +# Ensure replication resumes +connection master; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1, t2; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_compressed_log_event_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt new file mode 100644 index 00000000000..c949694b21b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt @@ -0,0 +1 @@ +--log-bin-compress=1 diff --git a/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test new file mode 100644 index 00000000000..29d51a823a7 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test @@ -0,0 +1,160 @@ +# +# This test verifies that a replica rejects a compressed event whose +# recorded uncompressed length is larger than the biggest packet the +# server accepts. A compressed Query event and a compressed Write_rows +# event are both covered, because the IO thread uncompresses each with +# its own function. MDEV-39762 reported that the compressed Query event +# crashed the replica. +# +# A debug injection makes the master write each of those events with +# a corrupted length field. The event stays in the master's binary log +# and is resent as it is, so each case moves gtid_slave_pos past it +# before checking that replication resumes. The sibling .opt file turns +# on log_bin_compress, without which the master writes no compressed +# events at all. +# +# References: +# +# * MDEV-39762: Slave Overflow on Malformed Query_compressed_log_event +# + +--source include/have_debug.inc +# Each test case sets the binlog format that its event type needs +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +# Test requires slave_pos, as each case recovers by moving gtid_slave_pos +# past the corrupt event. +--connection slave +--source include/stop_slave.inc +CHANGE MASTER TO master_use_gtid= slave_pos; +--source include/start_slave.inc + +call mtr.add_suppression("Uncompressed data size too large"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +# t1 tracks what replicates. t2 takes the corrupt events, and the replica +# never receives its rows. +create table t1 (a int); +create table t2 (a longtext); + +# The content must uncompress to far more than the 4096 byte stack buffer +# that queue_event() passes to the uncompress functions. +--let $long_value= `SELECT REPEAT('a', 65536)` +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--echo # +--echo # Test Case 1: a compressed Query event +--echo # +--connection master +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +# In statement format the compressed content is the query text itself, so +# the value has to appear in the statement rather than come from repeat(). +--disable_query_log +--eval insert into t2 values ('$long_value') +--enable_query_log +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +--let $corrupt_gtid_pos= `SELECT @@gtid_binlog_pos` + +--connection slave +--echo # Waiting for the IO thread to reject the compressed Query event +# The IO thread reports ER_TOO_BIG_FOR_UNCOMPRESS (1256) and then +# overwrites it with ER_SLAVE_RELAY_LOG_WRITE_FAILURE (1595), so the +# wait can sample either errno. +--let $slave_io_errno= 1256, 1595 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the replica rejected the event on its recorded length +--let $assert_text= The IO thread reported an oversized uncompressed length +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Uncompressed data size too large +--let $assert_match= Uncompressed data size too large; the maximum size is 1073741824 +--source include/assert_grep.inc + +--echo # Move the replica past the corrupt event +--source include/stop_slave_sql.inc +--disable_query_log +--eval SET GLOBAL gtid_slave_pos= '$corrupt_gtid_pos' +--enable_query_log +--source include/start_slave.inc + +--echo # Ensure replication resumes +--connection master +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables= master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Test Case 2: a compressed Write_rows event +--echo # +--connection master +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= ROW; +# In row format the compressed content is the record, so the statement +# that writes it can stay short. +insert into t2 values (repeat('a', 65536)); +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +--let $corrupt_gtid_pos= `SELECT @@gtid_binlog_pos` + +--connection slave +--echo # Waiting for the IO thread to reject the compressed Write_rows event +# The IO thread reports ER_TOO_BIG_FOR_UNCOMPRESS (1256) and then +# overwrites it with ER_SLAVE_RELAY_LOG_WRITE_FAILURE (1595), so the +# wait can sample either errno. +--let $slave_io_errno= 1256, 1595 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the replica rejected the event on its recorded length +--let $assert_text= The IO thread reported an oversized uncompressed length +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Uncompressed data size too large +--let $assert_match= Uncompressed data size too large; the maximum size is 1073741824 +--source include/assert_grep.inc + +--echo # Move the replica past the corrupt event +--source include/stop_slave_sql.inc +--disable_query_log +--eval SET GLOBAL gtid_slave_pos= '$corrupt_gtid_pos' +--enable_query_log +--source include/start_slave.inc + +--echo # Ensure replication resumes +--connection master +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables= master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1, t2; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_compressed_log_event_overflow.test diff --git a/sql/log_event.cc b/sql/log_event.cc index 3ff4abbace1..c49c22e8b3e 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -338,6 +338,25 @@ int binlog_buf_compress(const uchar *src, uchar *dst, uint32 len, uint32 *comlen dst[1]= uchar(len); lenlen= 1; } + + /* + Write the length of an event no replica can uncompress, so that a + replica's handling of one can be tested. Reaching a value this large + takes four length bytes, and the encoding above spends four only on + content of 16MB or more, so the injection sets the encoding as well + as the value. The compressed content still goes where those four + bytes place it, leaving the event well formed apart from the length + it claims. + */ + DBUG_EXECUTE_IF("binlog_compress_corrupt_len", + { + dst[1]= 0xFF; + dst[2]= 0xFF; + dst[3]= 0xFF; + dst[4]= 0xFC; + lenlen= 4; + }); + dst[0]= 0x80 | (lenlen & 0x07); uLongf tmplen= (uLongf)*comlen - BINLOG_COMPRESSED_HEADER_LEN - lenlen - 1; @@ -360,7 +379,7 @@ int binlog_buf_compress(const uchar *src, uchar *dst, uint32 len, uint32 *comlen 2) If *is_malloc is retuened as false, then 'dst' reuses the passed-in 'buf'. - return zero if successful, non-zero otherwise. + return zero if successful, otherwise the error code the caller reports. */ int @@ -375,8 +394,8 @@ query_event_uncompress(const Format_description_log_event *description_event, uchar *new_dst; // bad event - if (src_len < len) - return 1; + if (unlikely(src_len < len)) + return ER_BINLOG_UNCOMPRESS_ERROR; DBUG_ASSERT((uchar)src[EVENT_TYPE_OFFSET] == QUERY_COMPRESSED_EVENT); @@ -388,30 +407,33 @@ query_event_uncompress(const Format_description_log_event *description_event, tmp+= common_header_len; // bad event - if (end <= tmp) - return 1; + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint db_len= (uint)tmp[Q_DB_LEN_OFFSET]; uint16 status_vars_len= uint2korr(tmp + Q_STATUS_VARS_LEN_OFFSET); tmp+= post_header_len + status_vars_len + db_len + 1; // bad event - if (end <= tmp) - return 1; + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; int32 comp_len= (int32)(len - (tmp - src) - (contain_checksum ? BINLOG_CHECKSUM_LEN : 0)); uint32 un_len= binlog_get_uncompress_len(tmp); // bad event - if (comp_len < 0 || un_len == 0) - return 1; + if (unlikely(comp_len < 0 || un_len == 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; + + if (unlikely(un_len > MAX_MAX_ALLOWED_PACKET)) + return ER_TOO_BIG_FOR_UNCOMPRESS; *newlen= (ulong)(tmp - src) + un_len; if (contain_checksum) *newlen+= BINLOG_CHECKSUM_LEN; - - uint32 alloc_size= (uint32)ALIGN_SIZE(*newlen); + + size_t alloc_size= ALIGN_SIZE(*newlen); if (alloc_size <= buf_size) new_dst= buf; @@ -419,7 +441,7 @@ query_event_uncompress(const Format_description_log_event *description_event, { new_dst= (uchar *) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME)); if (!new_dst) - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; *is_malloc= true; } @@ -432,7 +454,7 @@ query_event_uncompress(const Format_description_log_event *description_event, *is_malloc= false; my_free(new_dst); } - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; } new_dst[EVENT_TYPE_OFFSET]= QUERY_EVENT; @@ -459,8 +481,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, uchar *new_dst= NULL; const uchar *end= tmp + len; - if (src_len < len) - return 1; // bad event + if (unlikely(src_len < len)) + return ER_BINLOG_UNCOMPRESS_ERROR; DBUG_ASSERT(LOG_EVENT_IS_ROW_COMPRESSED(type)); @@ -475,8 +497,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, which includes length bytes */ - if (end - tmp <= 2) - return 1; // bad event + if (unlikely(end - tmp <= 2)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint16 var_header_len= uint2korr(tmp); DBUG_ASSERT(var_header_len >= 2); @@ -495,8 +517,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, (type - WRITE_ROWS_COMPRESSED_EVENT_V1 + WRITE_ROWS_EVENT_V1); } - if (end <= tmp) - return 1; //bad event + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; ulong m_width= net_field_length((uchar **)&tmp); tmp+= (m_width + 7) / 8; @@ -506,17 +528,20 @@ row_log_event_uncompress(const Format_description_log_event *description_event, tmp+= (m_width + 7) / 8; } - if (end <= tmp) - return 1; //bad event + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint32 un_len= binlog_get_uncompress_len(tmp); - if (un_len == 0) - return 1; //bad event + if (unlikely(un_len == 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; + + if (unlikely(un_len > MAX_MAX_ALLOWED_PACKET)) + return ER_TOO_BIG_FOR_UNCOMPRESS; int32 comp_len= (int32)(len - (tmp - src) - (contain_checksum ? BINLOG_CHECKSUM_LEN : 0)); - if (comp_len <=0) - return 1; //bad event + if (unlikely(comp_len <= 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; *newlen= ulong(tmp - src) + un_len; if (contain_checksum) @@ -533,7 +558,7 @@ row_log_event_uncompress(const Format_description_log_event *description_event, { new_dst= (uchar*) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME)); if (!new_dst) - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; *is_malloc= true; } @@ -545,7 +570,7 @@ row_log_event_uncompress(const Format_description_log_event *description_event, { if (*is_malloc) my_free(new_dst); - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; } new_dst[EVENT_TYPE_OFFSET]= type; @@ -1838,7 +1863,7 @@ Query_compressed_log_event::Query_compressed_log_event(const uchar *buf, if (query) { uint32 un_len= binlog_get_uncompress_len((uchar*) query); - if (!un_len) + if (unlikely(!un_len || un_len > MAX_MAX_ALLOWED_PACKET)) { query= 0; return; @@ -3539,8 +3564,12 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len, void Rows_log_event::uncompress_buf() { uint32 un_len= binlog_get_uncompress_len(m_rows_buf); - if (!un_len) + if (unlikely(!un_len || un_len > MAX_MAX_ALLOWED_PACKET)) + { + /* my_bitmap_free() nulls m_cols.bitmap, which is_valid() rejects. */ + my_bitmap_free(&m_cols); return; + } uchar *new_buf= (uchar*) my_malloc(PSI_INSTRUMENT_ME, ALIGN_SIZE(un_len), MYF(MY_WME)); @@ -3563,7 +3592,7 @@ void Rows_log_event::uncompress_buf() my_free(new_buf); } } - m_cols.bitmap= 0; // catch it in is_valid + my_bitmap_free(&m_cols); // catch it in is_valid } Rows_log_event::~Rows_log_event() diff --git a/sql/slave.cc b/sql/slave.cc index 8f038abb55b..845fb38759e 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -7405,13 +7405,14 @@ dbug_gtid_accept: */ case QUERY_COMPRESSED_EVENT: inc_pos= event_len; - if (query_event_uncompress(rli->relay_log.description_event_for_queue, - checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, - buf, event_len, new_buf_arr, sizeof(new_buf_arr), - &is_malloc, &new_buf, &event_len)) + if ((error= + query_event_uncompress(rli->relay_log.description_event_for_queue, + checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, + buf, event_len, new_buf_arr, + sizeof(new_buf_arr), + &is_malloc, &new_buf, &event_len))) { char llbuf[22]; - error = ER_BINLOG_UNCOMPRESS_ERROR; error_msg.append(STRING_WITH_LEN("binlog uncompress error, master log_pos: ")); llstr(mi->master_log_pos, llbuf); error_msg.append(llbuf, strlen(llbuf)); @@ -7429,14 +7430,14 @@ dbug_gtid_accept: case DELETE_ROWS_COMPRESSED_EVENT_V1: inc_pos = event_len; { - if (row_log_event_uncompress(rli->relay_log.description_event_for_queue, - checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, - buf, event_len, new_buf_arr, - sizeof(new_buf_arr), - &is_malloc, &new_buf, &event_len)) + if ((error= + row_log_event_uncompress(rli->relay_log.description_event_for_queue, + checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, + buf, event_len, new_buf_arr, + sizeof(new_buf_arr), + &is_malloc, &new_buf, &event_len))) { char llbuf[22]; - error = ER_BINLOG_UNCOMPRESS_ERROR; error_msg.append(STRING_WITH_LEN("binlog uncompress error, master log_pos: ")); llstr(mi->master_log_pos, llbuf); error_msg.append(llbuf, strlen(llbuf)); @@ -7818,8 +7819,14 @@ err: handle_slave_io() prints it on return. */ if (unlikely(error) && error != ER_SLAVE_RELAY_LOG_WRITE_FAILURE) - mi->report(ERROR_LEVEL, error, NULL, ER_DEFAULT(error), - error_msg.ptr()); + { + if (error == ER_TOO_BIG_FOR_UNCOMPRESS) + mi->report(ERROR_LEVEL, error, error_msg.c_ptr(), ER_DEFAULT(error), + MAX_MAX_ALLOWED_PACKET); + else + mi->report(ERROR_LEVEL, error, NULL, ER_DEFAULT(error), + error_msg.ptr()); + } if (unlikely(is_malloc)) my_free((void *)new_buf); From 8216ea97598ef7804a852d543614ec8beff09555 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Mon, 10 Aug 2026 15:33:35 -0600 Subject: [PATCH 112/150] MDEV-40644: Slave SQL Thread Overflow on Malformed Table_map_log_event A slave can crash when its master sends a Table_map event whose table name is longer than an identifier can be. The SQL thread applying the event copies the name into a fixed buffer and overruns it, and the SQL thread crashes. A Table_map event carries its database and table names each behind a one-byte length. The constructor that parses the event sizes a buffer for each name from its declared length, and do_apply_event() copies both names out of the event into NAME_LEN+1 byte buffers with strmov(). strmov() copies up to the terminating null. A real identifier is at most NAME_LEN bytes, so the name fits the buffer and carries that null. The constructor never bounded the two lengths. Each length is read as a byte, so each can reach 255. The constructor copied each name with strncpy() over the declared length, which writes no terminating null when the source holds none. do_apply_event() then copied the name into its NAME_LEN+1 byte buffer with strmov(), which ran past the buffer until it reached a null elsewhere in the heap. pack_info() read the same names with %s for SHOW RELAYLOG EVENTS, past the buffer in the same way. Bound the lengths in the constructor. It now rejects a Table_map event whose declared database name, or whose declared table name, exceeds NAME_LEN, before it sizes the name buffers, so is_valid() returns false. It also terminates each name it copies, so a source that holds no null does not leave the name unterminated. A master naming a table with an oversized name now stops the slave's SQL thread with ER_SLAVE_RELAY_LOG_READ_FAILURE instead of crashing it. Reviewed-by: Kristian Nielsen Signed-off-by: Brandon Nesterenko --- .../rpl/r/rpl_table_map_name_overflow.result | 68 ++++++++++ .../t/rpl_table_map_name_overflow-master.opt | 1 + .../rpl/t/rpl_table_map_name_overflow.test | 127 ++++++++++++++++++ sql/log_event.cc | 15 +++ sql/sql_repl.cc | 38 ++++++ 5 files changed, 249 insertions(+) create mode 100644 mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt create mode 100644 mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test diff --git a/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result b/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result new file mode 100644 index 00000000000..5b8c22ff498 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result @@ -0,0 +1,68 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Error in Log_event::read_log_event.*Found invalid event"); +call mtr.add_suppression("Relay log read failure"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +include/start_slave.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case: a Table_map event naming a table with an oversized name +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_oversized_table_name"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +insert into t1 values (2); +# Waiting for the SQL thread to reject the oversized Table_map event +connection slave; +include/wait_for_slave_sql_error.inc [errno=1594] +# Ensure the SQL thread rejected the oversized Table_map event +include/assert_grep.inc [The SQL thread reported the oversized Table_map event as invalid] +# Ensure the rejected event delivered nothing to the replica +include/assert.inc [The replica has not applied the insert the event carried] +connection master; +set @@global.debug_dbug= @saved_dbug; +# +# Ensure the replica holds the row once it refetches the relay log +# +connection slave; +include/stop_slave_io.inc +include/start_slave.inc +connection master; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Ensure replication works after the rejected event +# +connection master; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_table_map_name_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt new file mode 100644 index 00000000000..dc66bb4c46a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt @@ -0,0 +1 @@ +--binlog-checksum=NONE diff --git a/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test new file mode 100644 index 00000000000..b92f31123fb --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test @@ -0,0 +1,127 @@ +# +# This test verifies that a replica rejects a Table_map event whose +# database or table name is longer than an identifier can be, when the SQL +# thread reads the event from the relay log and constructs it. +# +# A replica copies each name into a NAME_LEN+1 byte buffer with strmov(), +# which stops only at a terminating null, so without the bound a 255 byte +# name overruns that buffer as the SQL thread applies the event. The event's +# constructor rejects the oversized name before that copy, so the SQL thread +# stops with ER_SLAVE_RELAY_LOG_READ_FAILURE instead. +# +# Note rpl_table_map_name_overflow-master.opt sets binlog_checksum=NONE on +# the master to simplify creating a corrupted event to send to the slave. +# +# References: +# * MDEV-40644: Slave SQL Thread Overflow on Malformed Table_map_log_event +# + +--source include/have_debug.inc +# A Table_map event is only produced in row format. +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Error in Log_event::read_log_event.*Found invalid event"); +call mtr.add_suppression("Relay log read failure"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +# Use slave_pos so we can easily reset the relay log after it receives a +# corrupted event +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +--source include/start_slave.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case: a Table_map event naming a table with an oversized name +--echo # +--connection master +set @@global.debug_dbug= "+d,binlog_sender_oversized_table_name"; + +# The dump thread reads the global setting when the replica reconnects, so +# restart the replica to open a fresh dump thread that carries the injection. +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +insert into t1 values (2); + +--echo # Waiting for the SQL thread to reject the oversized Table_map event +--connection slave +--let $slave_sql_errno= 1594 +--source include/wait_for_slave_sql_error.inc + +# The injection grows the table name to 255 bytes and leaves the database +# name "test" at its four bytes. The constructor rejects the oversized name, +# so read_log_event() reports the event as invalid. +--echo # Ensure the SQL thread rejected the oversized Table_map event +--let $assert_text= The SQL thread reported the oversized Table_map event as invalid +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Found invalid event in binary log +--let $assert_count= 1 +--source include/assert_grep.inc + +# The constructor rejects the event before do_apply_event() copies the name, +# so the SQL thread stops without applying the row the event carried. +--echo # Ensure the rejected event delivered nothing to the replica +--let $slave_row_count= query_get_value(SELECT COUNT(*) AS c FROM t1, c, 1) +--let $assert_text= The replica has not applied the insert the event carried +--let $assert_cond= $slave_row_count = 1 +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--echo # +--echo # Ensure the replica holds the row once it refetches the relay log +--echo # +--connection slave +--source include/stop_slave_io.inc +--source include/start_slave.inc + +--connection master +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Ensure replication works after the rejected event +--echo # +--connection master +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_table_map_name_overflow.test diff --git a/sql/log_event.cc b/sql/log_event.cc index c49c22e8b3e..3e160ce57be 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -3795,6 +3795,14 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, VALIDATE_BYTES_READ(ptr_tbllen, buf, event_len); m_tbllen= *(uchar*) ptr_tbllen; + /* + If either the database or table name is too long, return before allocating + memory for the event, so future validity checks on this event (i.e. + is_valid()) return false. + */ + if (unlikely(m_dblen > NAME_LEN || m_tbllen > NAME_LEN)) + DBUG_VOID_RETURN; + /* Length of table name + counter + terminating null */ uchar const *const ptr_colcnt= ptr_tbllen + m_tbllen + 2; uchar *ptr_after_colcnt= (uchar*) ptr_colcnt; @@ -3820,6 +3828,13 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, /* Copy the different parts into their memory */ strncpy(const_cast(m_dbnam), (const char*)ptr_dblen + 1, m_dblen + 1); strncpy(const_cast(m_tblnam), (const char*)ptr_tbllen + 1, m_tbllen + 1); + /* + Future uses of the database and table name require a null-terminating + string, but the source string from the above copy may not have one, and + strncpy will not add one. So always add it to ensure it exists. + */ + const_cast(m_dbnam)[m_dblen]= '\0'; + const_cast(m_tblnam)[m_tbllen]= '\0'; if (unlikely(ptr_after_colcnt + m_colcnt > buf + event_len)) { my_free(m_memory); diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index d2ad9e8cc1b..58de3a3fc25 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2107,6 +2107,44 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type, } } + /* + For a Table_map event, rewrite the copy sent to the replica so its table + name is 255 bytes, the largest a one-byte length can express, with no + terminating null. Growing the name shifts the column count and metadata + after it to the right and lengthens the event, so the event's declared + length is rewritten to match. Note that, for simplicity, the test that + invokes this is configured without checksumming. + */ + DBUG_EXECUTE_IF("binlog_sender_oversized_table_name", + { + if (event_type == TABLE_MAP_EVENT) + { + uchar *base= (uchar*) packet->ptr(); + uchar *ev= base + ev_offset; + uchar *dbnam_len_ptr= ev + LOG_EVENT_HEADER_LEN + TABLE_MAP_HEADER_LEN; + uint dblen= *dbnam_len_ptr; + uchar *tblnam_len_ptr= dbnam_len_ptr + dblen + 2; + uint tbllen= *tblnam_len_ptr; + size_t tblnam_len_off= (size_t) (tblnam_len_ptr - base); + size_t tblnam_off= tblnam_len_off + 1; + size_t tail_off= tblnam_off + tbllen + 1; + size_t tail_len= len - tail_off; + long delta= 256 - (long) (tbllen + 1); + + if (!packet->realloc((size_t) ((long) len + delta))) + { + base= (uchar*) packet->ptr(); + memmove(base + tail_off + delta, base + tail_off, tail_len); + base[tblnam_len_off]= 255; + memset(base + tblnam_off, 'a', 256); + len= (size_t) ((long) len + delta); + packet->length(len); + int4store(base + ev_offset + EVENT_LEN_OFFSET, + (uint32) (len - ev_offset)); + } + } + }); + /* Put a second copy of the event in the packet behind the first, the other shape the same disagreement takes on the wire. This injection From 080480c52d1cca022a1e0161aa76e40b9d12f178 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Mon, 10 Aug 2026 11:03:35 -0600 Subject: [PATCH 113/150] MDEV-40646: Undersized Table_map Metadata can Crash Slave A slave can crash or leak heap content when its master sends a Table_map event that declares columns whose types need more field metadata than the event carries. Building its description of the table, the slave reads past the end of the field metadata the event holds. The bytes beyond the buffer are decoded as column metadata. A Table_map event carries a column count, one type byte per column, and a block of field metadata. A column's type fixes how many metadata bytes it consumes: two for the string, enum, set, bit, varchar, and decimal types, one for the blob, float, and high precision temporal types, none for the rest. The master writes exactly that many bytes per column, so the metadata block a well formed event carries is the sum over its columns. The slave sizes the metadata block from the event and, while building its table_def, walks the columns in order and reads each type's metadata bytes from that block. The slave checked only that the metadata block was no larger than two bytes per column. It never checked the block against the column types themselves. table_def() then walked every declared column and indexed the metadata block for each, so a column whose type needed metadata the block did not hold read past the end of it. A column count large enough carried that read far past the allocation. The Table_map reader now sums the metadata the declared column types consume and rejects the event when the block is smaller than that sum, so is_valid() reports the event as invalid and the SQL thread stops with a relay log read failure before any column is decoded. A block larger than the sum is still accepted, because every consumer reads only the bytes the types call for. A column type this slave does not recognize counts as zero in the sum, so Table_map events from a newer master using such a type still parse. A Table_map whose metadata cannot cover its column types now stops the slave with an error instead of reading past the buffer. Note that the regression test does not crash a regular debug build; an ASAN build is required to show the invalid memory access. The 24 column table map it injects overruns the metadata allocation by a bounded amount that lands in mapped heap, which a debug build reads without faulting and then stops the SQL thread with ER_SLAVE_CONVERSION_FAILED on the column type mismatch. Reviewed-by: Kristian Nielsen Signed-off-by: Brandon Nesterenko --- .../r/rpl_table_map_metadata_underflow.result | 62 ++++++++++ .../t/rpl_table_map_metadata_underflow.test | 114 ++++++++++++++++++ sql/log_event.cc | 16 +++ sql/rpl_utility.cc | 28 +++++ sql/rpl_utility.h | 6 + sql/sql_repl.cc | 48 ++++++++ 6 files changed, 274 insertions(+) create mode 100644 mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test diff --git a/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result b/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result new file mode 100644 index 00000000000..0023b572b7e --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result @@ -0,0 +1,62 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Found invalid event in binary log"); +call mtr.add_suppression("Relay log read failure: Could not parse relay log event entry"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +include/start_slave.inc +connection master; +create table t1 (c1 int,c2 int,c3 int,c4 int,c5 int,c6 int,c7 int,c8 int,c9 int,c10 int,c11 int,c12 int,c13 int,c14 int,c15 int,c16 int,c17 int,c18 int,c19 int,c20 int,c21 int,c22 int,c23 int,c24 int); +insert into t1 (c1) values (0); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case: a Table_map declaring more metadata than it carries +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_undersized_table_map_metadata"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +insert into t1 (c1) values (1); +connection slave; +# Waiting for the SQL thread to reject the malformed Table_map +include/wait_for_slave_sql_error.inc [errno=1594] +# Ensure the event was rejected before its metadata was decoded +include/assert_grep.inc [The SQL thread reported an invalid event] +# +# Recover: discard the poisoned relay log and re-fetch a clean copy +# +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_io.inc +include/start_slave.inc +# +# Ensure replication works after the invalid event +# +connection master; +insert into t1 (c1) values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_table_map_metadata_underflow.test diff --git a/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test b/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test new file mode 100644 index 00000000000..2f081f062d2 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test @@ -0,0 +1,114 @@ +# +# This test verifies that a replica rejects a Table_map event whose field +# metadata is shorter than the metadata the column types it declares require. +# The replica must stop the SQL thread with an error rather than decode the +# metadata past the end of what the event carries, and replication must resume +# once a well formed copy of the event arrives. +# +# MDEV-40646 reported that such an event read past the metadata buffer. A +# build with AddressSanitizer is the reliable witness of that read, aborting +# at the first byte past the allocation. A regular debug build does not fault +# on the read: it decodes adjacent heap and stops the SQL thread with a +# conversion error whose reported type is rendered from those bytes. This test +# asserts the rejection the fix adds ahead of the read, which holds on every +# build. +# +# References: +# * MDEV-40646: Slave Crashes in table_def::table_def() on Malformed +# Table_map +# + +--source include/have_debug.inc +# A Table_map event is only written under row based binary logging. +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Found invalid event in binary log"); +call mtr.add_suppression("Relay log read failure: Could not parse relay log event entry"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +# Use slave_pos so we can easily reset the relay log after it receives a +# corrupted event +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +--source include/start_slave.inc + +--connection master +# With 24 columns, the metadata reads run well past the end of the replica's +# allocation once the event declares a single metadata byte. +create table t1 (c1 int,c2 int,c3 int,c4 int,c5 int,c6 int,c7 int,c8 int,c9 int,c10 int,c11 int,c12 int,c13 int,c14 int,c15 int,c16 int,c17 int,c18 int,c19 int,c20 int,c21 int,c22 int,c23 int,c24 int); +insert into t1 (c1) values (0); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case: a Table_map declaring more metadata than it carries +--echo # + +# The reconnect below starts a dump thread that inherits the injection. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_undersized_table_map_metadata"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +insert into t1 (c1) values (1); + +--connection slave +--echo # Waiting for the SQL thread to reject the malformed Table_map +--let $slave_sql_errno= 1594 +--source include/wait_for_slave_sql_error.inc + +--echo # Ensure the event was rejected before its metadata was decoded +--let $assert_text= The SQL thread reported an invalid event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Found invalid event in binary log +--let $assert_count= 1 +--source include/assert_grep.inc + +--echo # +--echo # Recover: discard the poisoned relay log and re-fetch a clean copy +--echo # +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_io.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the invalid event +--echo # +--connection master +insert into t1 (c1) values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_table_map_metadata_underflow.test diff --git a/sql/log_event.cc b/sql/log_event.cc index 3e160ce57be..87ef5b96364 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -3848,6 +3848,22 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, m_field_metadata_size= net_field_length(&ptr_after_colcnt); DBUG_EXECUTE_IF("corrupt_table_map_field_metadata_size_read", m_field_metadata_size= (1 << 20);); + + /* + Ensure the field metadata block covers the metadata the declared + column types consume. + */ + size_t metadata_needed= 0; + for (ulong i= 0; i < m_colcnt; i++) + metadata_needed+= table_def::field_metadata_length(m_coltype[i]); + if (unlikely(m_field_metadata_size < metadata_needed)) + { + m_coltype= NULL; + my_free(m_memory); + m_memory= NULL; + DBUG_VOID_RETURN; + } + if (m_field_metadata_size <= (m_colcnt * 2)) { uint num_null_bytes= (m_colcnt + 7) / 8; diff --git a/sql/rpl_utility.cc b/sql/rpl_utility.cc index 04a2efb3750..2a27972c9f8 100644 --- a/sql/rpl_utility.cc +++ b/sql/rpl_utility.cc @@ -173,6 +173,34 @@ uint32 table_def::calc_field_size(uint col, uchar *master_data) const PSI_memory_key key_memory_table_def_memory; +uint table_def::field_metadata_length(uint binlog_type) +{ + switch (static_cast(binlog_type)) { + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_BLOB_COMPRESSED: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + return 1; + case MYSQL_TYPE_SET: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VARCHAR_COMPRESSED: + case MYSQL_TYPE_NEWDECIMAL: + return 2; + default: + return 0; + } +} + table_def::table_def(unsigned char *types, ulong size, uchar *field_metadata, int metadata_size, uchar *null_bitmap, uint16 flags) diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index c28e8aa10eb..712855cb5c3 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -74,6 +74,12 @@ public: { return static_cast(m_type[index]); } + + /* + Number of field-metadata bytes a column of the given binlog type occupies + in a Table_map event. + */ + static uint field_metadata_length(uint binlog_type); /* Return a representation of the type data for one field. diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 58de3a3fc25..3525c297fb3 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2176,6 +2176,54 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type, len= packet->length(); } }); + /* + Rewrite the body of a Table_map event so it declares columns whose types + need more field metadata than the event carries, to test that a replica + bounds the metadata before decoding it. Every column becomes + MYSQL_TYPE_STRING, which reads two metadata bytes, and the event is left + with a single metadata byte, so a replica missing the bound reads past the + metadata while decoding the columns. The rewrite keeps the column count the + master already wrote, which the test fixes at 24 columns, enough to carry + the metadata reads well past the end of the replica's allocation. + + The event is rewritten here, on the wire, rather than in the master's own + binary log, so the injection lands on the copy the dump thread sends and + the master's binary log stays well formed. + */ + DBUG_EXECUTE_IF("binlog_sender_undersized_table_map_metadata", + { + if (event_type == TABLE_MAP_EVENT) + { + /* The rewritten event can be one byte longer than the original. */ + packet->realloc(len + 1); + uchar *ev= (uchar*) packet->ptr() + ev_offset; + uchar *p= ev + LOG_EVENT_HEADER_LEN + TABLE_MAP_HEADER_LEN; + uint db_len= *p; + p+= 1 + db_len + 1; // db name length byte, name, null + uint tbl_len= *p; + p+= 1 + tbl_len + 1; // table name length byte, name, null + uint colcnt= *p++; // column count (one packed byte) + uchar *coltype= p; + + memset(coltype, MYSQL_TYPE_STRING, colcnt); + uchar *w= coltype + colcnt; + *w++= 1; // field metadata size (one byte) + *w++= 0; // the single metadata byte + uint null_bytes= (colcnt + 7) / 8; + memset(w, 0, null_bytes); + w+= null_bytes; + + ulong new_ev_len= (ulong) (w - ev); + if (current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + new_ev_len+= BINLOG_CHECKSUM_LEN; + int4store(ev + EVENT_LEN_OFFSET, new_ev_len); + if (current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + int4store(ev + new_ev_len - BINLOG_CHECKSUM_LEN, + my_checksum(0, ev, new_ev_len - BINLOG_CHECKSUM_LEN)); + len= ev_offset + new_ev_len; + packet->length(len); + } + }); #endif if (my_net_write(info->net, (uchar*) packet->ptr(), len)) From edef9f2fd35e208af63a399353f2c46b82c35cb8 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Tue, 11 Aug 2026 09:57:49 -0600 Subject: [PATCH 114/150] MDEV-40648 Fix rpl.rpl_corruption The test would induce a corrupt binlog event on the master and send it to the slave, and with the new event validation logic, this could trigger the slave to output the new error message added in MDEV_40648. Add this error message as an allowed error during the executin of this test Signed-off-by: Brandon Nesterenko --- mysql-test/suite/binlog_encryption/rpl_corruption.result | 1 + mysql-test/suite/rpl/r/rpl_corruption.result | 1 + mysql-test/suite/rpl/t/rpl_corruption.test | 1 + 3 files changed, 3 insertions(+) diff --git a/mysql-test/suite/binlog_encryption/rpl_corruption.result b/mysql-test/suite/binlog_encryption/rpl_corruption.result index 7c60e15a567..e3501d29a57 100644 --- a/mysql-test/suite/binlog_encryption/rpl_corruption.result +++ b/mysql-test/suite/binlog_encryption/rpl_corruption.result @@ -11,6 +11,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; # 1. Creating test table/data and set corruption position for testing connection master; diff --git a/mysql-test/suite/rpl/r/rpl_corruption.result b/mysql-test/suite/rpl/r/rpl_corruption.result index 7c60e15a567..e3501d29a57 100644 --- a/mysql-test/suite/rpl/r/rpl_corruption.result +++ b/mysql-test/suite/rpl/r/rpl_corruption.result @@ -11,6 +11,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; # 1. Creating test table/data and set corruption position for testing connection master; diff --git a/mysql-test/suite/rpl/t/rpl_corruption.test b/mysql-test/suite/rpl/t/rpl_corruption.test index cd2a1cc7ef2..2311565e17c 100644 --- a/mysql-test/suite/rpl/t/rpl_corruption.test +++ b/mysql-test/suite/rpl/t/rpl_corruption.test @@ -37,6 +37,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; From e491a1e8804940c23dccb1b513cdd439886a2423 Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Tue, 11 Aug 2026 14:07:52 -0600 Subject: [PATCH 115/150] MDEV-40365 OOB read on malformed `Format_description_log_event` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binary-parsing Format Description Event constructor did not validate content length beyond the superclass `is_valid()` call. When parsing a malformed FDE, to load the content fields added in FDE v4 that are missing in this not-FDE, the parser constructor would read from erroneous memory locations beyond the buffer. If this did not outright crash the program, this would corrupt the FDE. With the Format Description playing a critical role in determining how to parse the events to follow, a corrupted FDE would also corrupt (or trigger a crash in) the parsing of subsequent non-FDE events as well. This commit fills in the validation with a FDE-specific guard. It adds the FDE constant `ST_POST_HEADER_LEN_OFFSET` to assist with comparing to the correct minimum size in the future. (Both of these points are designed to merge with the superclass’s guard as part of the MDEV-30128 merger). Reviewed-by: Kristian Nielsen --- .../suite/binlog/r/fdle_overflow.result | 6 +++++ mysql-test/suite/binlog/t/fdle_overflow.test | 24 +++++++++++++++++++ sql/log_event.cc | 11 +++++---- sql/log_event.h | 3 ++- sql/log_event_server.cc | 16 ++++++++----- 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/binlog/r/fdle_overflow.result create mode 100644 mysql-test/suite/binlog/t/fdle_overflow.test diff --git a/mysql-test/suite/binlog/r/fdle_overflow.result b/mysql-test/suite/binlog/r/fdle_overflow.result new file mode 100644 index 00000000000..d692f4845c2 --- /dev/null +++ b/mysql-test/suite/binlog/r/fdle_overflow.result @@ -0,0 +1,6 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_common_header_len'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/mysql-test/suite/binlog/t/fdle_overflow.test b/mysql-test/suite/binlog/t/fdle_overflow.test new file mode 100644 index 00000000000..5c328834959 --- /dev/null +++ b/mysql-test/suite/binlog/t/fdle_overflow.test @@ -0,0 +1,24 @@ +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # format-agnostic + +# MDEV-40365 OOB read on malformed `Format_description_log_event` +# +# Verify that Format Description Events with truncated contents fails gracefully +# rather than underflow the post-header count and lead to buffer over-read. + +SET @saved_dbug= @@GLOBAL.debug_dbug; + + +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_common_header_len'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + + +# Clean-up +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/sql/log_event.cc b/sql/log_event.cc index 87ef5b96364..feaa94dad7d 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -2309,19 +2309,22 @@ Format_description_log_event(const uchar *buf, uint event_len, common_header_len(0), post_header_len(NULL), event_type_permutation(0) { DBUG_ENTER("Format_description_log_event::Format_description_log_event(char*,...)"); - if (!Start_log_event_v3::is_valid()) + if (unlikely( + event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET || + !Start_log_event_v3::is_valid())) DBUG_VOID_RETURN; /* sanity check */ buf+= LOG_EVENT_MINIMAL_HEADER_LEN; - if ((common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < OLD_HEADER_LEN) + if (unlikely( + (common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < OLD_HEADER_LEN)) DBUG_VOID_RETURN; /* sanity check */ number_of_event_types= - event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET + 1); + event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET); DBUG_PRINT("info", ("common_header_len=%d number_of_event_types=%d", common_header_len, number_of_event_types)); /* If alloc fails, we'll detect it in is_valid() */ post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, - buf+ST_COMMON_HEADER_LEN_OFFSET+1, + buf + ST_POST_HEADER_LEN_OFFSET, number_of_event_types* sizeof(*post_header_len), MYF(0)); diff --git a/sql/log_event.h b/sql/log_event.h index f7aa99572ba..d93bdf39148 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -205,7 +205,7 @@ class String; #define STOP_HEADER_LEN 0 #define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4) #define SLAVE_HEADER_LEN 0 -#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4) +#define START_V3_HEADER_LEN ST_COMMON_HEADER_LEN_OFFSET #define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-header is frozen) #define INTVAR_HEADER_LEN 0 #define CREATE_FILE_HEADER_LEN 4 @@ -282,6 +282,7 @@ class String; #define ST_SERVER_VER_OFFSET 2 #define ST_CREATED_OFFSET (ST_SERVER_VER_OFFSET + ST_SERVER_VER_LEN) #define ST_COMMON_HEADER_LEN_OFFSET (ST_CREATED_OFFSET + 4) +#define ST_POST_HEADER_LEN_OFFSET (ST_COMMON_HEADER_LEN_OFFSET + 1) /* slave event post-header (this event is never written) */ diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 03d9d879930..20724813bc6 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2649,9 +2649,12 @@ bool Format_description_log_event::write() We don't call Start_log_event_v3::write() because this would make 2 my_b_safe_write(). */ - uchar buff[START_V3_HEADER_LEN+1]; - size_t rec_size= sizeof(buff) + BINLOG_CHECKSUM_ALG_DESC_LEN + - number_of_event_types; + uchar buff[ST_POST_HEADER_LEN_OFFSET]; + const size_t buff_size= DBUG_IF("truncate_fde_at_common_header_len") ? + ST_COMMON_HEADER_LEN_OFFSET : sizeof(buff); + size_t rec_size= buff_size; + if (!DBUG_IF("truncate_fde_at_common_header_len")) + rec_size += number_of_event_types + BINLOG_CHECKSUM_ALG_DESC_LEN; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN); if (!dont_set_created) @@ -2690,9 +2693,10 @@ bool Format_description_log_event::write() checksum_alg= BINLOG_CHECKSUM_ALG_CRC32; // Forcing (V) room to fill anyway } ret= write_header(rec_size) || - write_data(buff, sizeof(buff)) || - write_data(post_header_len, number_of_event_types) || - write_data(&checksum_byte, sizeof(checksum_byte)) || + write_data(buff, buff_size) || + (!DBUG_IF("truncate_fde_at_common_header_len") && ( + write_data(post_header_len, number_of_event_types) || + write_data(&checksum_byte, sizeof(checksum_byte)))) || write_footer(); if (no_checksum) checksum_alg= BINLOG_CHECKSUM_ALG_OFF; From c61b1a2ef7c3c879e68be80b82f3456807c63241 Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Tue, 11 Aug 2026 14:21:52 -0600 Subject: [PATCH 116/150] MDEV-40366 OOB read on malformed `Format_description_log_event` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neither the binary-parsing Format Description Event constructor nor the constructor-bypassing `get_checksum_alg()` function validated the content length of the passed event buffer. If they receive an FDE with undersized contents, they would obtain corrupt results from an erronous memory location, if not outright crash the program with that memory error. This commit fixes both sites by adding content length checks. Because the goal is not to solve the existence of two binary parsers, `get_checksum_alg()` receives a check duplicated from the Format Description constructor and is no longer a function that never errors. Note, it is implementation detail that `get_checksum_alg()`’s fix catches the invalidity before any code reaches the parser-contructor’s fix, though the latter would come to effect if we refactor `get_checksum_alg()` away. Reviewed-by: Kristian Nielsen --- .../suite/binlog/r/fdle_overflow.result | 5 +- mysql-test/suite/binlog/t/fdle_overflow.test | 17 ++++- sql/log_event.cc | 76 ++++++++++++------- sql/log_event.h | 3 +- sql/log_event_server.cc | 6 +- sql/slave.cc | 7 +- sql/sql_repl.cc | 22 ++++-- 7 files changed, 94 insertions(+), 42 deletions(-) diff --git a/mysql-test/suite/binlog/r/fdle_overflow.result b/mysql-test/suite/binlog/r/fdle_overflow.result index d692f4845c2..d88d0305ca7 100644 --- a/mysql-test/suite/binlog/r/fdle_overflow.result +++ b/mysql-test/suite/binlog/r/fdle_overflow.result @@ -1,5 +1,8 @@ SET @saved_dbug= @@GLOBAL.debug_dbug; -SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_common_header_len'; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_post_header_len'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; FLUSH BINARY LOGS; ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error SET @@GLOBAL.debug_dbug= @saved_dbug; diff --git a/mysql-test/suite/binlog/t/fdle_overflow.test b/mysql-test/suite/binlog/t/fdle_overflow.test index 5c328834959..ebba9bed2b2 100644 --- a/mysql-test/suite/binlog/t/fdle_overflow.test +++ b/mysql-test/suite/binlog/t/fdle_overflow.test @@ -1,7 +1,7 @@ --source include/have_debug.inc --source include/have_binlog_format_mixed.inc # format-agnostic -# MDEV-40365 OOB read on malformed `Format_description_log_event` +# OOB read on malformed `Format_description_log_event` # # Verify that Format Description Events with truncated contents fails gracefully # rather than underflow the post-header count and lead to buffer over-read. @@ -9,7 +9,9 @@ SET @saved_dbug= @@GLOBAL.debug_dbug; -SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_common_header_len'; +# MDEV-40366: `used_checksum_alg` + +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_post_header_len'; FLUSH BINARY LOGS; --let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) @@ -19,6 +21,17 @@ FLUSH BINARY LOGS; --enable_query_log +# MDEV-40365: `common_header_len` & `post_header_len` + +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + # Clean-up SET @@GLOBAL.debug_dbug= @saved_dbug; RESET MASTER; diff --git a/sql/log_event.cc b/sql/log_event.cc index feaa94dad7d..bd34c93e0eb 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1047,7 +1047,7 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, my_bool crc_check, my_bool print_errors) { - Log_event* ev; + Log_event* ev= nullptr; enum enum_binlog_checksum_alg alg; DBUG_ENTER("Log_event::read_log_event(char*,...)"); DBUG_ASSERT(fdle != 0); @@ -1066,14 +1066,21 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, } uint event_type= buf[EVENT_TYPE_OFFSET]; + switch (event_type) { + case FORMAT_DESCRIPTION_EVENT: + // If event is FD the descriptor is in it. + if (unlikely(get_checksum_alg(buf, event_len, &alg))) + goto exit; + break; + case START_EVENT_V3: // all following START events in the current file are without checksum - if (event_type == START_EVENT_V3) (const_cast< Format_description_log_event *>(fdle))->checksum_alg= BINLOG_CHECKSUM_ALG_OFF; + // fall-through + default: /* CRC verification by SQL and Show-Binlog-Events master side. The caller has to provide @fdle->checksum_alg to be the last seen FD's (A) descriptor. - If event is FD the descriptor is in it. Notice, FD of the binlog can be only in one instance and therefore Show-Binlog-Events executing master side thread needs just to know the only FD's (A) value - whereas RL can contain more. @@ -1088,11 +1095,9 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF. */ - alg= (event_type != FORMAT_DESCRIPTION_EVENT) ? - fdle->checksum_alg : get_checksum_alg(buf, event_len); + alg= fdle->checksum_alg; // Emulate the corruption during reading an event DBUG_EXECUTE_IF("corrupt_read_log_event_char", - if (event_type != FORMAT_DESCRIPTION_EVENT) { uchar *debug_event_buf_c= const_cast(buf); int debug_cor_pos= rand() % (event_len - BINLOG_CHECKSUM_LEN); @@ -1101,6 +1106,7 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, DBUG_SET("-d,corrupt_read_log_event_char"); } ); + } if (crc_check && event_checksum_test(const_cast(buf), event_len, alg)) { #ifdef MYSQL_CLIENT @@ -2323,22 +2329,25 @@ Format_description_log_event(const uchar *buf, uint event_len, common_header_len, number_of_event_types)); /* If alloc fails, we'll detect it in is_valid() */ - post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, - buf + ST_POST_HEADER_LEN_OFFSET, - number_of_event_types* - sizeof(*post_header_len), - MYF(0)); calc_server_version_split(); + buf+= ST_POST_HEADER_LEN_OFFSET; if (!is_version_before_checksum(&server_version_split)) { - /* the last bytes are the checksum alg desc and value (or value's room) */ + /* the last bytes are the checksum alg desc */ + if (unlikely(number_of_event_types < BINLOG_CHECKSUM_ALG_DESC_LEN)) + DBUG_VOID_RETURN; /* sanity check: But there are no last bytes. */ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN; - checksum_alg= (enum_binlog_checksum_alg)post_header_len[number_of_event_types]; + checksum_alg= (enum_binlog_checksum_alg)buf[number_of_event_types]; } else { checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; } + post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, + buf, + number_of_event_types* + sizeof(*post_header_len), + MYF(0)); deduct_options_written_to_bin_log(); reset_crypto(); @@ -2452,35 +2461,50 @@ Format_description_log_event::is_version_before_checksum(const master_version_sp } /** - @param buf buffer holding serialized FD event - @param len netto (possible checksum is stripped off) length of the event buf - - @return the version-safe checksum alg descriptor where zero + @param buf buffer holding serialized FD event including the 4-byte checksum + @param len length of the event buf + @param alg output the version-safe checksum alg descriptor where zero designates no checksum, 255 - the orginator is checksum-unaware (effectively no checksum) and the actuall [1-254] range alg descriptor. + @return whether this is an invalid FD event */ -enum enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, ulong len) +bool get_checksum_alg(const uchar *buf, ulong len, + enum enum_binlog_checksum_alg *alg) { - enum enum_binlog_checksum_alg ret; + constexpr ptrdiff_t POST_HEADER_LEN_OFFSET= + LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET; char version[ST_SERVER_VER_LEN]; DBUG_ENTER("get_checksum_alg"); DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); + if (unlikely(len < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); memcpy(version, buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET, ST_SERVER_VER_LEN); version[ST_SERVER_VER_LEN - 1]= 0; Format_description_log_event::master_version_split version_split(version); - ret= Format_description_log_event::is_version_before_checksum(&version_split) - ? BINLOG_CHECKSUM_ALG_UNDEF - : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN]; - DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF || - ret == BINLOG_CHECKSUM_ALG_UNDEF || - ret == BINLOG_CHECKSUM_ALG_CRC32); - DBUG_RETURN(ret); + if (Format_description_log_event::is_version_before_checksum(&version_split)) + *alg= BINLOG_CHECKSUM_ALG_UNDEF; + else + { + /* + len >= POST_HEADER_LEN_OFFSET > + BINLOG_CHECKSUM_LEN + BINLOG_CHECKSUM_ALG_DESC_LEN + */ + ulong checksum_alg_offset= + len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN; + if (unlikely(checksum_alg_offset < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); + *alg= static_cast(buf[checksum_alg_offset]); + } + DBUG_ASSERT(*alg == BINLOG_CHECKSUM_ALG_OFF || + *alg == BINLOG_CHECKSUM_ALG_UNDEF || + *alg == BINLOG_CHECKSUM_ALG_CRC32); + DBUG_RETURN(false); } Start_encryption_log_event:: diff --git a/sql/log_event.h b/sql/log_event.h index d93bdf39148..412e348505c 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -5948,7 +5948,8 @@ bool slave_execute_deferred_events(THD *thd); bool event_that_should_be_ignored(const uchar *buf); bool event_checksum_test(uchar *buf, ulong event_len, enum_binlog_checksum_alg alg); -enum enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, ulong len); +bool get_checksum_alg(const uchar *buf, ulong len, + enum enum_binlog_checksum_alg *alg); extern TYPELIB binlog_checksum_typelib; #ifdef WITH_WSREP enum Log_event_type wsrep_peak_event(rpl_group_info *rgi, ulonglong* event_size); diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 20724813bc6..113ec2c3702 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2650,10 +2650,10 @@ bool Format_description_log_event::write() my_b_safe_write(). */ uchar buff[ST_POST_HEADER_LEN_OFFSET]; - const size_t buff_size= DBUG_IF("truncate_fde_at_common_header_len") ? + const size_t buff_size= DBUG_IF("truncate_fde_common_header_len") ? ST_COMMON_HEADER_LEN_OFFSET : sizeof(buff); size_t rec_size= buff_size; - if (!DBUG_IF("truncate_fde_at_common_header_len")) + if (!DBUG_IF("truncate_fde_at_post_header_len")) rec_size += number_of_event_types + BINLOG_CHECKSUM_ALG_DESC_LEN; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN); @@ -2694,7 +2694,7 @@ bool Format_description_log_event::write() } ret= write_header(rec_size) || write_data(buff, buff_size) || - (!DBUG_IF("truncate_fde_at_common_header_len") && ( + (!DBUG_IF("truncate_fde_at_post_header_len") && ( write_data(post_header_len, number_of_event_types) || write_data(&checksum_byte, sizeof(checksum_byte)))) || write_footer(); diff --git a/sql/slave.cc b/sql/slave.cc index 845fb38759e..d600cb89317 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -6744,7 +6744,12 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) */ if (buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) { - checksum_alg= get_checksum_alg(buf, event_len); + if (unlikely(get_checksum_alg(buf, event_len, &checksum_alg))) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + unlock_data_lock= FALSE; + goto err; + } } else if (buf[EVENT_TYPE_OFFSET] == START_EVENT_V3) { diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 3525c297fb3..34f84a0c32e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1547,10 +1547,10 @@ gtid_state_from_pos(const char *name, uint32 offset, goto end; } - current_checksum_alg= get_checksum_alg((uchar*) packet.ptr(), - packet.length()); found_format_description_event= true; - if (unlikely(!(tmp= new Format_description_log_event((uchar*) packet.ptr(), + if (unlikely(get_checksum_alg((uchar*) packet.ptr(), packet.length(), + ¤t_checksum_alg) || + !(tmp= new Format_description_log_event((uchar*)packet.ptr(), packet.length(), fdev)))) { @@ -2439,6 +2439,8 @@ static int init_binlog_sender(binlog_send_info *info, static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, LOG_INFO *linfo, my_off_t start_pos) { + static constexpr const char* CORRUPT_FDE= + "Corrupt Format_description event found or out-of-memory"; int error; ulong ev_offset; THD *thd= info->thd; @@ -2505,9 +2507,14 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, DBUG_RETURN(1); } - info->current_checksum_alg= get_checksum_alg((uchar*) packet->ptr() + - ev_offset, - packet->length() - ev_offset); + if (unlikely(get_checksum_alg((uchar*) packet->ptr() + ev_offset, + packet->length() - ev_offset, + &(info->current_checksum_alg)))) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= CORRUPT_FDE; + DBUG_RETURN(1); + } DBUG_ASSERT(info->current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || info->current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || @@ -2535,8 +2542,7 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, ev_len, info->fdev))) { info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; - info->errmsg= "Corrupt Format_description event found " - "or out-of-memory"; + info->errmsg= CORRUPT_FDE; DBUG_RETURN(1); } delete info->fdev; From cd51639e4a52464e82550486cecd6b15d6307d9a Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Tue, 21 Jul 2026 00:16:15 -0600 Subject: [PATCH 117/150] MDEV-39485 Heap-buffer-overflow upon read in `Rows_log_event` constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MariaDB recognizes Version 2 Rows Events from MySQL, including the format of the “extra data” field added in this version. (MDEV-5115) When parsing this extra data according to the format, whether this data has sufficient length was only checked by assertions in the `Rows_log_event` constructor and the `mariadb-binlog --verbose` printer. When parsing an event with malformed extra data, these assertions * would straight up terminate the program in debug builds. * were stripped in non-debug (release) builds. This would render the parser defenseless to reading from erroneous memory locations outside of the containing event, which will either crash the program or, for `mariadb-binlog --verbose`, snapshot the running memory to be exposed when outputting the event. This commit replaces those assertions with an actual validity check. Since MariaDB does not generate v2 Rows Events, the included test uses a handcrafted binlog file. Reviewed-by: Kristian Nielsen --- .../suite/binlog/r/binlog_old_versions.result | 1 + .../suite/binlog/std_data/invalid_row_v2_tag.001 | Bin 0 -> 353 bytes .../suite/binlog/t/binlog_old_versions.test | 12 ++++++++++++ sql/log_event.cc | 12 ++++++++++-- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 diff --git a/mysql-test/suite/binlog/r/binlog_old_versions.result b/mysql-test/suite/binlog/r/binlog_old_versions.result index 30b64535eb4..de8e270a3b9 100644 --- a/mysql-test/suite/binlog/r/binlog_old_versions.result +++ b/mysql-test/suite/binlog/r/binlog_old_versions.result @@ -13,6 +13,7 @@ SELECT COUNT(*) FROM t3; COUNT(*) 17920 DROP TABLE t1, t2, t3; +FOUND 3 /[Uu]nknown [Ee]vent/ in invalid_row_v2_tag.sql ==== Read modern binlog (version 5.1.23) ==== SELECT * FROM t1 ORDER BY a; a b diff --git a/mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 b/mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 new file mode 100644 index 0000000000000000000000000000000000000000..4b558372ea550f6aace9cefa6992b199efbb243a GIT binary patch literal 353 zcmeyDl$m!YJvNJy*KV8@?!YmBDKefEmPMkB3Mr0@)5I(yR;&jQ>#;B%&(-$^!sP>lvQ_ literal 0 HcmV?d00001 diff --git a/mysql-test/suite/binlog/t/binlog_old_versions.test b/mysql-test/suite/binlog/t/binlog_old_versions.test index 130101541e3..2517c72e78f 100644 --- a/mysql-test/suite/binlog/t/binlog_old_versions.test +++ b/mysql-test/suite/binlog/t/binlog_old_versions.test @@ -35,6 +35,18 @@ SELECT COUNT(*) FROM t3; # Reset. DROP TABLE t1, t2, t3; +# MDEV-39485 Heap-buffer-overflow upon read in `Rows_log_event` constructor +# +# This binlog file contains a normal Format Description +# Event followed by 3 malformed v2 Write Rows Events: +# 1. A tag followed by no data +# 2. An undersized tagged data followed by an unrecognized tag +# 3. A tag followed by a length longer than the entire event +--let SEARCH_PATTERN= [Uu]nknown [Ee]vent +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/invalid_row_v2_tag.sql +--exec $MYSQL_BINLOG --force-read --verbose suite/binlog/std_data/invalid_row_v2_tag.001 > $SEARCH_FILE +--source include/search_pattern_in_file.inc + --echo ==== Read modern binlog (version 5.1.23) ==== diff --git a/sql/log_event.cc b/sql/log_event.cc index bd34c93e0eb..38b92017c57 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -3488,9 +3488,17 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len, case RW_V_EXTRAINFO_TAG: { /* Have an 'extra info' section, read it in */ - assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES); + if (unlikely((end - pos) <= EXTRA_ROW_INFO_LEN_OFFSET)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET]; - assert((end - pos) >= infoLen); + if (unlikely(infoLen < EXTRA_ROW_INFO_HDR_BYTES || (end-pos) < infoLen)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } /* Just store/use the first tag of this type, skip others */ if (likely(!m_extra_row_data)) { From 52a2bc27cccaf27632c8c7f92d52604b3bb38b0a Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Thu, 6 Aug 2026 20:46:58 -0600 Subject: [PATCH 118/150] MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the replication IO Thread receives a Rotate event following a Format Description event (FDE) with no post-header length for Rotate events, the Rotate event’s parser constructor indexes the FDE’s post-header lengths array out of bounds. This commit defends against this situation by checking before the constructor that the FDE describes Rotate events as recognized at all. In practice, because the Binlog Dump thread generates a Fake `ROTATE_EVENT` **before** sending the FDE, it has pinned Rotate events’ post-header length to 8 regardless of FDEs. This fix solution considers that the FDE’s description should still be respected, matching the constructor. Reviewed-by: Kristian Nielsen --- .../suite/binlog/r/fdle_overflow.result | 3 +- mysql-test/suite/binlog/t/fdle_overflow.test | 3 +- .../suite/rpl/r/rpl_rotate_ev_overflow.result | 19 +++++++ .../suite/rpl/t/rpl_rotate_ev_overflow.test | 51 +++++++++++++++++++ sql/log_event_server.cc | 13 +++-- sql/slave.cc | 11 ++++ 6 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result create mode 100644 mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test diff --git a/mysql-test/suite/binlog/r/fdle_overflow.result b/mysql-test/suite/binlog/r/fdle_overflow.result index d88d0305ca7..3fde805de82 100644 --- a/mysql-test/suite/binlog/r/fdle_overflow.result +++ b/mysql-test/suite/binlog/r/fdle_overflow.result @@ -1,5 +1,6 @@ SET @saved_dbug= @@GLOBAL.debug_dbug; -SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_post_header_len'; +SET @@GLOBAL.debug_dbug= +'+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; FLUSH BINARY LOGS; ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; diff --git a/mysql-test/suite/binlog/t/fdle_overflow.test b/mysql-test/suite/binlog/t/fdle_overflow.test index ebba9bed2b2..abaf143d515 100644 --- a/mysql-test/suite/binlog/t/fdle_overflow.test +++ b/mysql-test/suite/binlog/t/fdle_overflow.test @@ -11,7 +11,8 @@ SET @saved_dbug= @@GLOBAL.debug_dbug; # MDEV-40366: `used_checksum_alg` -SET @@GLOBAL.debug_dbug= '+d,truncate_fde_at_post_header_len'; +SET @@GLOBAL.debug_dbug= + '+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; FLUSH BINARY LOGS; --let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) diff --git a/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result new file mode 100644 index 00000000000..7c218054950 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result @@ -0,0 +1,19 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +include/master-slave.inc +[connection master] +FLUSH BINARY LOGS; +CALL mtr.add_suppression('Found invalid event in binary log'); +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +connection slave; +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +include/wait_for_slave_io_error.inc [errno=1595] +connection master; +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; +connection slave; +CHANGE MASTER TO master_use_gtid=SLAVE_POS; +SET @@GLOBAL.gtid_slave_pos=''; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test new file mode 100644 index 00000000000..579d7b4c420 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test @@ -0,0 +1,51 @@ +# MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events + +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # should be format-agnostic + +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +--let $rpl_skip_start_slave= 1 +# This setup will also RESET MASTER ... +--source include/master-slave.inc +# ... which means the binlog file is consistent. +--let $binlog_file= master-bin.000001 + +# Use a specific binlog position to avoid the +# test failing early from other metadata events +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) +# Generate the Rotate Event +FLUSH BINARY LOGS; + + +# Control: SHOW BINLOG EVENTS fails as expected. +CALL mtr.add_suppression('Found invalid event in binary log'); +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' FROM $binlog_start +--enable_query_log + +--connection slave +--disable_query_log + eval CHANGE MASTER TO master_use_gtid=NO, + master_log_file='$binlog_file', master_log_pos=$binlog_start; +--enable_query_log +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +# ER_SLAVE_RELAY_LOG_WRITE_FAILURE +--let $slave_io_errno= 1595 +# Experiment: The IO thread should fail. +--source include/wait_for_slave_io_error.inc + +# Clean-up +--connection master +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; + +--connection slave +CHANGE MASTER TO master_use_gtid=SLAVE_POS; # restore the default +--disable_warnings + SET @@GLOBAL.gtid_slave_pos=''; # for good measure +--enable_warnings +--source include/start_slave.inc +--source include/rpl_end.inc diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 113ec2c3702..85bec1cc77c 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2653,8 +2653,10 @@ bool Format_description_log_event::write() const size_t buff_size= DBUG_IF("truncate_fde_common_header_len") ? ST_COMMON_HEADER_LEN_OFFSET : sizeof(buff); size_t rec_size= buff_size; - if (!DBUG_IF("truncate_fde_at_post_header_len")) - rec_size += number_of_event_types + BINLOG_CHECKSUM_ALG_DESC_LEN; + if (!DBUG_IF("truncate_fde_post_header_len")) + rec_size += number_of_event_types; + if (!DBUG_IF("truncate_fde_used_checksum_alg")) + rec_size += BINLOG_CHECKSUM_ALG_DESC_LEN; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN); if (!dont_set_created) @@ -2694,9 +2696,10 @@ bool Format_description_log_event::write() } ret= write_header(rec_size) || write_data(buff, buff_size) || - (!DBUG_IF("truncate_fde_at_post_header_len") && ( - write_data(post_header_len, number_of_event_types) || - write_data(&checksum_byte, sizeof(checksum_byte)))) || + (!DBUG_IF("truncate_fde_post_header_len") && + write_data(post_header_len, number_of_event_types)) || + (!DBUG_IF("truncate_fde_used_checksum_alg") && + write_data(&checksum_byte, sizeof(checksum_byte))) || write_footer(); if (no_checksum) checksum_alg= BINLOG_CHECKSUM_ALG_OFF; diff --git a/sql/slave.cc b/sql/slave.cc index d600cb89317..1d3573980ec 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -6835,6 +6835,17 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) goto err; case ROTATE_EVENT: { + /* + This is normally done in Log_event::read_log_event(), + but we bypass it here because it's expensive and costs dynamic memory. + */ + if (unlikely(ROTATE_EVENT > + mi->rli.relay_log.description_event_for_queue->number_of_event_types)) + { + // The current FDE does not support `ROTATE_EVENT`. + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } /* RSC_1 and RSC_2 below rewrite the fake Rotate (the one that opens a connection, naming the binary log file the dump starts in; the file From e383fb0f4ed8eb7ca9b9bfee42798e06adb6ea20 Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Fri, 7 Aug 2026 20:43:51 -0600 Subject: [PATCH 119/150] MDEV-40674 Include the checksum for non-corrupted Unknown events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit reörders code so the checksum is populated after `mariadb-binlog --force` generates `Unknown_log_event` substitutes. Previously, `mariadb-binlog --force` inconsistently omitted those checksums from the output even if the checksum is presumably usable. After merging to 11.4 (MDEV-31273), this commit will also fix « MDEV-40542 MSAN use-of-uninitialized-value on Unknown_log_event::read_checksum_alg », which was exposed by MDEV-31273’s removal of the base `Log_event::checksum_alg` field. Reviewed-by: Kristian Nielsen --- .../suite/binlog/r/unknown_log_event.result | 6 ++++ .../suite/binlog/t/unknown_log_event.test | 29 +++++++++++++++++++ sql/log_event.cc | 23 ++++++++------- 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/binlog/r/unknown_log_event.result create mode 100644 mysql-test/suite/binlog/t/unknown_log_event.test diff --git a/mysql-test/suite/binlog/r/unknown_log_event.result b/mysql-test/suite/binlog/r/unknown_log_event.result new file mode 100644 index 00000000000..88ce76acc4d --- /dev/null +++ b/mysql-test/suite/binlog/r/unknown_log_event.result @@ -0,0 +1,6 @@ +CREATE TABLE t (a INT); +INSERT INTO t VALUES (0); +DROP TABLE t; +FLUSH BINARY LOGS; +#mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql diff --git a/mysql-test/suite/binlog/t/unknown_log_event.test b/mysql-test/suite/binlog/t/unknown_log_event.test new file mode 100644 index 00000000000..0c687ad155c --- /dev/null +++ b/mysql-test/suite/binlog/t/unknown_log_event.test @@ -0,0 +1,29 @@ +# MDEV-40674: Test `mariadb-binlog --force`'s Unknown event output +# +# Also: MDEV-40542 +# MSAN use-of-uninitialized-value on Unknown_log_event::read_checksum_alg + +--source include/have_debug.inc +--source include/have_binlog_format_row.inc # Testing with Table Map event + +# Setup +CREATE TABLE t (a INT); +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) + +# Generate an invalid Table Map event followed by an otherwise-valid Rows Event +INSERT INTO t VALUES (0); + +--let $binlog_stop= query_get_value(SHOW BINLOG STATUS, Position, 1) +DROP TABLE t; +FLUSH BINARY LOGS; + + +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/unknown_log_event.sql +--let SEARCH_PATTERN= CRC32 0x[[:xdigit:]]{8}.*\\n# [Uu]nknown [Ee]vent +--let $mariadb_binlog= $MYSQL_BINLOG --result-file=$SEARCH_FILE --verify-binlog-checksum --force-read +--let $binlog_file= `SELECT CONCAT(@@datadir, '/$binlog_file')` + +--echo #mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=$binlog_start --stop-position=$binlog_stop $binlog_file 2>&1 +--source include/search_pattern_in_file.inc diff --git a/sql/log_event.cc b/sql/log_event.cc index 38b92017c57..5039c21c4cb 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1113,8 +1113,9 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, *error= "Event crc check failed! Most likely there is event corruption."; if (force_opt) { + event_len-= BINLOG_CHECKSUM_LEN; ev= new Unknown_log_event(buf, fdle); - DBUG_RETURN(ev); + goto exit; } else DBUG_RETURN(NULL); @@ -1330,16 +1331,6 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, } exit: - if (ev) - { - ev->checksum_alg= alg; -#ifdef MYSQL_CLIENT - if (ev->checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - ev->checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) - ev->crc= uint4korr(buf + (event_len)); -#endif - } - DBUG_PRINT("read_event", ("%s(type_code: %u; event_len: %u)", ev ? ev->get_type_str() : "", (uchar)buf[EVENT_TYPE_OFFSET], @@ -1374,6 +1365,16 @@ exit: DBUG_RETURN(0); #endif } + + if (ev) + { + ev->checksum_alg= alg; +#ifdef MYSQL_CLIENT + if (ev->checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + ev->checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + ev->crc= uint4korr(buf + (event_len)); +#endif + } DBUG_RETURN(ev); } From 387e9af8462114215ee8499636e873522c3704c1 Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Sun, 9 Aug 2026 16:31:00 -0600 Subject: [PATCH 120/150] MDEV-40544 Assertion fail / Memory leak in `mariadb-binlog --force-read` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A checksum error in `mariadb-binlog --verify-binlog-checksum --force-read` previously resulted in both an error message and an Unknown event substitute, the latter of which failed an assertion in debug builds or became forgotten (memory leak) in non-debug (release) builds. Since `mariadb-binlog --force-read` outputs “Unknown event”s rather than errors in other invalid event cases, this commit removes the error status from this situation to match. Reviewed-by: Kristian Nielsen --- mysql-test/suite/binlog/r/unknown_log_event.result | 2 ++ mysql-test/suite/binlog/t/unknown_log_event.test | 8 ++++++++ sql/log_event.cc | 6 ++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mysql-test/suite/binlog/r/unknown_log_event.result b/mysql-test/suite/binlog/r/unknown_log_event.result index 88ce76acc4d..b3bbc14a6de 100644 --- a/mysql-test/suite/binlog/r/unknown_log_event.result +++ b/mysql-test/suite/binlog/r/unknown_log_event.result @@ -4,3 +4,5 @@ DROP TABLE t; FLUSH BINARY LOGS; #mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql +#mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql diff --git a/mysql-test/suite/binlog/t/unknown_log_event.test b/mysql-test/suite/binlog/t/unknown_log_event.test index 0c687ad155c..267d107816b 100644 --- a/mysql-test/suite/binlog/t/unknown_log_event.test +++ b/mysql-test/suite/binlog/t/unknown_log_event.test @@ -27,3 +27,11 @@ FLUSH BINARY LOGS; --echo #mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 --exec $mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=$binlog_start --stop-position=$binlog_stop $binlog_file 2>&1 --source include/search_pattern_in_file.inc + +# Test checksum failure +# +# Only test the first event since the rest of the binlog is unreadable +# without a valid Format Description, which is typically the first event. +--echo #mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 $binlog_file 2>&1 +--source include/search_pattern_in_file.inc diff --git a/sql/log_event.cc b/sql/log_event.cc index 5039c21c4cb..84dfc8f1681 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1110,21 +1110,19 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, if (crc_check && event_checksum_test(const_cast(buf), event_len, alg)) { #ifdef MYSQL_CLIENT - *error= "Event crc check failed! Most likely there is event corruption."; if (force_opt) { event_len-= BINLOG_CHECKSUM_LEN; ev= new Unknown_log_event(buf, fdle); goto exit; } - else - DBUG_RETURN(NULL); + *error= "Event crc check failed! Most likely there is event corruption."; #else *error= ER_THD_OR_DEFAULT(current_thd, ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE); if (print_errors) sql_print_error("%s", *error); - DBUG_RETURN(NULL); #endif + DBUG_RETURN(NULL); } if (event_type > fdle->number_of_event_types && From d646e24f7f7b24dec695d8ebc5471f9b939dff8a Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Tue, 16 Jun 2026 13:04:53 +0400 Subject: [PATCH 121/150] MDEV-40009 SIGSEGV in Sql_path::from_text Problem: - The string passed to Sql_path::from_text() could be in various character sets, returned by Item::val_str_ascii(), which is not necessarily utf8mb3. - While the code in Sql_path::from_text() was written in the way that "str" was considered to be in utf8mb3. - As as result, cs->charset() in this line: auto len = cs->charlen(curr, end); could return a negative value and the whole loop got stuck. Changes: - Fixing Sys_var_path::from_item() to use val_str() instead of val_str_ascii(), to get the original value from "item", without any conversion. - Moving the conversion code inside Sql_path::from_text(). --- mysql-test/main/path_ctype.result | 22 ++++++++++++++++ mysql-test/main/path_ctype.test | 32 ++++++++++++++++++++++ sql/sql_path.cc | 44 ++++++++++++++++++++++++++----- sql/sys_vars.inl | 2 +- 4 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 mysql-test/main/path_ctype.result create mode 100644 mysql-test/main/path_ctype.test diff --git a/mysql-test/main/path_ctype.result b/mysql-test/main/path_ctype.result new file mode 100644 index 00000000000..5ddc36748fe --- /dev/null +++ b/mysql-test/main/path_ctype.result @@ -0,0 +1,22 @@ +# +# MDEV-40009 SIGSEGV in Sql_path::from_text +# +SET NAMES utf8mb4; +SET PATH _latin1 0xE5; +SELECT @@path; +@@path +`å` +SET NAMES swe7; +SET PATH '`'; +SET NAMES utf8mb4; +SELECT @@path; +@@path +`é` +SET PATH CONVERT(_latin1 0xE4 USING utf32); +SELECT @@path; +@@path +`ä` +SET PATH '😎'; +ERROR 42000: Variable 'PATH' can't be set to the value of '?' +SET PATH 0xE5; +ERROR 42000: Variable 'PATH' can't be set to the value of '\xE5' diff --git a/mysql-test/main/path_ctype.test b/mysql-test/main/path_ctype.test new file mode 100644 index 00000000000..13f5f232d06 --- /dev/null +++ b/mysql-test/main/path_ctype.test @@ -0,0 +1,32 @@ +--source include/have_utf32.inc + +--echo # +--echo # MDEV-40009 SIGSEGV in Sql_path::from_text +--echo # + +# Conversion from latin1 +SET NAMES utf8mb4; +SET PATH _latin1 0xE5; +SELECT @@path; + + +# Conversion from a tricky character set swe7 +SET NAMES swe7; +SET PATH '`'; +SET NAMES utf8mb4; +SELECT @@path; + + +# Conversion from a tricky character set utf32 +SET PATH CONVERT(_latin1 0xE4 USING utf32); +SELECT @@path; + + +# Supplementary characters are not allowed +--error ER_WRONG_VALUE_FOR_VAR +SET PATH '😎'; + + +# Binary input: 0xE5 does represent well-formed utf8mb3 +--error ER_WRONG_VALUE_FOR_VAR +SET PATH 0xE5; diff --git a/sql/sql_path.cc b/sql/sql_path.cc index 982fbffaf98..0b50be0ad13 100644 --- a/sql/sql_path.cc +++ b/sql/sql_path.cc @@ -252,7 +252,20 @@ void Sql_path::set(Sql_path &&rhs) } -bool Sql_path::from_text(const system_variables &sv, String *str) +/* + Set the PATH value from a string str0. + The string can contain: + - A character string in an arbitrary character set, + including tricky ones such as swe7, ucs2, utf32. + It is expected to convert to utf8mb3 without data loss. + - A binary string. + It is expected to re-enterpret to utf8mb3 without errors + (i.e. be a well-formed utf8mb3) + + If the string does not convert or re-enterpret to utf8mb3 without + errors or data loss, then an SQL error is raised. +*/ +bool Sql_path::from_text(const system_variables &sv, String *str0) { enum tokenize_state { @@ -263,26 +276,43 @@ bool Sql_path::from_text(const system_variables &sv, String *str) CHARSET_INFO *cs= &my_charset_utf8mb3_general_ci; // as in make_ident_casedn() DBUG_ASSERT(cs->cset->casedn_multiply(cs) == 1); - DBUG_ASSERT(str->ptr()); + DBUG_ASSERT(str0->ptr()); + + // Convert convert/re-intepret str0 to utf8mb3 + String_copier copier; + StringBuffer<128> str; + if (str.copy(cs, str0->charset(), str0->ptr(), str0->length(), + str0->length()/*nchars*/, &copier) || + copier.most_important_error_pos()) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "PATH", ErrConvString(str0).ptr()); + return true; + } + + /* + Now we have a well-formed utf8mb3 string in `str`. + Remember and parse it. + */ char *buf= (char*)my_malloc(key_memory_Sys_var_charptr_value, - str->length() + 1, MYF(MY_WME)); + str.length() + 1, MYF(MY_WME)); if (!buf) return true; char *curr= buf, *to= buf, *end; if (lower_case_table_names > 0) - end= buf + cs->cset->casedn(cs, str->ptr(), str->length(), buf, str->length()); + end= buf + cs->cset->casedn(cs, str.ptr(), str.length(), buf, str.length()); else { - memcpy(buf, str->ptr(), str->length()); - end= buf + str->length(); + memcpy(buf, str.ptr(), str.length()); + end= buf + str.length(); } free(); while (curr < end) { auto len = cs->charlen(curr, end); + DBUG_ASSERT(len > 0); // We have a well-formed utf8mb3 switch (state) { @@ -367,7 +397,7 @@ bool Sql_path::from_text(const system_variables &sv, String *str) return false; err_bad_val: - my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "PATH", str->c_ptr()); + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "PATH", ErrConvString(&str).ptr()); err: m_count= 0; my_free(buf); diff --git a/sql/sys_vars.inl b/sql/sys_vars.inl index 0f8aa1eb63b..1d24cdd823a 100644 --- a/sql/sys_vars.inl +++ b/sql/sys_vars.inl @@ -3100,7 +3100,7 @@ private: static bool from_item(THD *thd, Sql_path *path, Item *item) { String *value, buffer; - if (!(value= item->val_str_ascii(&buffer))) + if (!(value= item->val_str(&buffer))) return true; return path->from_text(thd->variables, value); } From 790a084aca40a3700a14cf3f98de7f3159957e76 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Mon, 10 Aug 2026 21:51:44 +0100 Subject: [PATCH 122/150] feat: update DuckDB submodule to 1.5.5. --- storage/duckdb/README.md | 2 +- storage/duckdb/cmake/duckdb.cmake | 12 ++++++++++-- storage/duckdb/docs/architecture.md | 2 +- .../duckdb/docs/mariadb-duckdb-incompatibilities.md | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/storage/duckdb/README.md b/storage/duckdb/README.md index f72cd9aa0f6..a65283eb501 100644 --- a/storage/duckdb/README.md +++ b/storage/duckdb/README.md @@ -62,7 +62,7 @@ Tables created with `ENGINE=DuckDB` store data in DuckDB's native format. Querie ## Building -The engine is built as part of the MariaDB server tree. It lives under `storage/duckdb/` and uses `ExternalProject_Add` to build DuckDB v1.5.4 from source. +The engine is built as part of the MariaDB server tree. It lives under `storage/duckdb/` and uses `ExternalProject_Add` to build DuckDB v1.5.5 from source. Clone the branch matching your target MariaDB version. The engine is already part of the tree under `storage/duckdb/`: diff --git a/storage/duckdb/cmake/duckdb.cmake b/storage/duckdb/cmake/duckdb.cmake index 1eb9924924d..6b9e5809ffa 100644 --- a/storage/duckdb/cmake/duckdb.cmake +++ b/storage/duckdb/cmake/duckdb.cmake @@ -60,6 +60,14 @@ ADD_SUBMODULE(third_parties/duckdb) # reconciliation). Drop when the submodule is bumped to a release containing # the PR: the apply below will fail loudly on an already-present setval. SET(_DUCKDB_PATCH "${CMAKE_CURRENT_SOURCE_DIR}/patches/duckdb-pr24061-setval.diff") +EXECUTE_PROCESS( + COMMAND git -C "${DUCKDB_SUBMODULE_DIR}" rev-parse HEAD + OUTPUT_VARIABLE _DUCKDB_SOURCE_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _DUCKDB_GIT_RESULT) +IF(NOT _DUCKDB_GIT_RESULT EQUAL 0) + MESSAGE(FATAL_ERROR "Failed to determine the DuckDB submodule revision") +ENDIF() # Upstream sets DUCKDB_EXTENSION_JEMALLOC_LINKED via add_extension_definitions(), # which runs in extension/ but NOT in src/, so allocator.cpp (in duckdb_static) @@ -90,7 +98,7 @@ ExternalProject_Add(duckdb_build SOURCE_DIR "${DUCKDB_SUBMODULE_DIR}" BINARY_DIR "${_DUCKDB_BUILD_DIR}" # Idempotent: skip when the working tree already carries the patch. - PATCH_COMMAND bash -c "git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}'" + PATCH_COMMAND bash -c "test \"$(git rev-parse HEAD)\" = '${_DUCKDB_SOURCE_REVISION}' && (git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}')" CMAKE_ARGS -DCMAKE_BUILD_TYPE=${_DUCKDB_BUILD_TYPE} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} @@ -107,7 +115,7 @@ ExternalProject_Add(duckdb_build "-DCMAKE_CXX_FLAGS=${_DUCKDB_EXTRA_CXX_FLAGS}" -DENABLE_SANITIZER=${ADDRESS_SANITIZER} -DENABLE_UBSAN=${UB_SANITIZER} - -DOVERRIDE_GIT_DESCRIBE=v1.5.4-0-g0000000000 + -DOVERRIDE_GIT_DESCRIBE=v1.5.5-0-g0000000000 INSTALL_COMMAND "" BUILD_BYPRODUCTS ${_DUCKDB_STATIC_LIBS} USES_TERMINAL_BUILD ON diff --git a/storage/duckdb/docs/architecture.md b/storage/duckdb/docs/architecture.md index b87a027b984..3b5a2709133 100644 --- a/storage/duckdb/docs/architecture.md +++ b/storage/duckdb/docs/architecture.md @@ -326,5 +326,5 @@ storage/duckdb/ │ └── row_helpers.h — byte-level row reading utilities │ ├── cmake/ — DuckDB build integration (ExternalProject_Add) -└── third_parties/duckdb/ — upstream DuckDB submodule (v1.5.4) +└── third_parties/duckdb/ — upstream DuckDB submodule (v1.5.5) ``` diff --git a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md index 512488a5b7d..636bc2e90c3 100644 --- a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md +++ b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md @@ -1,7 +1,7 @@ # MariaDB - DuckDB Incompatibilities Discovered during porting the DuckDB storage engine plugin to MariaDB 12. -DuckDB upstream version: **v1.5.4** (submodule at `third_parties/duckdb/`). +DuckDB upstream version: **v1.5.5** (submodule at `third_parties/duckdb/`). --- From 81bb68ba63b59ec5999afea7b1182350761d4de9 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Tue, 11 Aug 2026 21:17:14 +0100 Subject: [PATCH 123/150] chore: add DuckDB version info function. --- .../docs/mariadb-duckdb-incompatibilities.md | 21 +++++++++++++++---- storage/duckdb/ha_duckdb.cc | 9 ++++++++ .../mysql-test/duckdb/r/duckdb_monitor.result | 3 +++ .../mysql-test/duckdb/t/duckdb_monitor.test | 2 ++ storage/duckdb/third_parties/duckdb | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md index 636bc2e90c3..0a1511b7b83 100644 --- a/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md +++ b/storage/duckdb/docs/mariadb-duckdb-incompatibilities.md @@ -132,7 +132,20 @@ MariaDB `MEDIUMTEXT`, `LONGTEXT`, `TEXT`, `TINYTEXT` all map to DuckDB `VARCHAR` --- -## 5. DuckDB API Differences (AliSQL fork vs upstream v1.5.2) +## 5. Table Partitioning + +MariaDB table partitioning is not supported for `ENGINE=DuckDB`. The handlerton declares `HTON_NO_PARTITION`, so the server rejects both partitioned table creation and conversion of an existing DuckDB table: + +```sql +CREATE TABLE t (id INT) ENGINE=DuckDB PARTITION BY HASH(id); +ALTER TABLE t PARTITION BY HASH(id); +``` + +Both statements fail with `ER_PARTITION_MERGE_ERROR` (`Engine cannot be used in partitioned tables`) before MariaDB creates partition child handlers or starts the `ALGORITHM=COPY` protocol. + +--- + +## 6. DuckDB API Differences (AliSQL fork vs upstream v1.5.2) The AliSQL fork of DuckDB has custom extensions to the API that do not exist in upstream DuckDB: @@ -145,7 +158,7 @@ The AliSQL fork of DuckDB has custom extensions to the API that do not exist in --- -## 6. DuckDB Extensions +## 7. DuckDB Extensions Loaded via `cmake/duckdb_extensions.cmake`: @@ -159,7 +172,7 @@ Build flags in `cmake/duckdb.cmake`: `EXTENSION_STATIC_BUILD=1`, `DUCKDB_EXTENSI --- -## 7. ABI Compatibility +## 8. ABI Compatibility DuckDB is built as a static library (`libduckdb_bundle.a`) via `ExternalProject_Add` in `cmake/duckdb.cmake`. @@ -171,7 +184,7 @@ DuckDB is built as a static library (`libduckdb_bundle.a`) via `ExternalProject_ --- -## 8. Potential Future Incompatibilities +## 9. Potential Future Incompatibilities These have not been triggered yet but are likely to cause issues when more complex queries are pushed down: diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 62d08c8a075..c1e39473998 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -1578,7 +1578,16 @@ static struct st_mysql_sys_var *duckdb_system_variables[]= { MYSQL_SYSVAR(explain_output), MYSQL_SYSVAR(disabled_optimizers), MYSQL_SYSVAR(cross_engine_ryow), NULL}; +static int show_duckdb_version(MYSQL_THD, struct st_mysql_show_var *var, + void *, system_status_var *, enum_var_type) +{ + var->type= SHOW_CHAR; + var->value= const_cast(duckdb::DuckDB::LibraryVersion()); + return 0; +} + static struct st_mysql_show_var duckdb_status_variables[]= { + {"Duckdb_version", (char *) show_duckdb_version, SHOW_SIMPLE_FUNC}, {"Duckdb_rows_insert", (char *) &srv_duckdb_status.duckdb_rows_insert, SHOW_LONGLONG}, {"Duckdb_rows_update", (char *) &srv_duckdb_status.duckdb_rows_update, diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_monitor.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_monitor.result index 247910ab169..f99eb3c94e0 100644 --- a/storage/duckdb/mysql-test/duckdb/r/duckdb_monitor.result +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_monitor.result @@ -2,6 +2,9 @@ CREATE TABLE t1(id INT PRIMARY KEY, v VARCHAR(255)) ENGINE=duckdb; CREATE TABLE t2(id INT PRIMARY KEY, v VARCHAR(255)) ENGINE=duckdb; CREATE TABLE t3(id INT PRIMARY KEY, v VARCHAR(255)) ENGINE=innodb; +SHOW STATUS LIKE 'Duckdb_version'; +Variable_name Value +Duckdb_version v1.5.5 # # 1) INSERT: rows_insert increments per row (dml_in_batch=OFF) # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test index 9480f9a588d..f8fad1d37b1 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test @@ -24,6 +24,8 @@ CREATE TABLE t3(id INT PRIMARY KEY, v VARCHAR(255)) ENGINE=innodb; # Save baseline commit count (CREATE TABLEs cause autocommits) --let $base_commit = query_get_value(SHOW STATUS LIKE 'Duckdb_commit', Value, 1) +SHOW STATUS LIKE 'Duckdb_version'; + --echo # --echo # 1) INSERT: rows_insert increments per row (dml_in_batch=OFF) --echo # diff --git a/storage/duckdb/third_parties/duckdb b/storage/duckdb/third_parties/duckdb index 8a5851971fa..d8cdaa33fda 160000 --- a/storage/duckdb/third_parties/duckdb +++ b/storage/duckdb/third_parties/duckdb @@ -1 +1 @@ -Subproject commit 8a5851971fae891f292c2714d86046ee018e9737 +Subproject commit d8cdaa33fda8df955cc76ef58a280f68f4cd43fa From 970492062d91cd606dd3f6f8a52bd51cf746ab4f Mon Sep 17 00:00:00 2001 From: drrtuy Date: Tue, 11 Aug 2026 21:50:04 +0100 Subject: [PATCH 124/150] chore: remove jemalloc extension from DuckDB CMake b/c since 1.5.4 is is a part of DuckDB core. --- storage/duckdb/cmake/duckdb.cmake | 16 ++++------------ storage/duckdb/cmake/duckdb_extensions.cmake | 1 - 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/storage/duckdb/cmake/duckdb.cmake b/storage/duckdb/cmake/duckdb.cmake index 6b9e5809ffa..3987960b4a2 100644 --- a/storage/duckdb/cmake/duckdb.cmake +++ b/storage/duckdb/cmake/duckdb.cmake @@ -43,7 +43,6 @@ SET(_DUCKDB_STATIC_LIBS "${_DUCKDB_BUILD_DIR}/extension/libduckdb_generated_extension_loader.a" "${_DUCKDB_BUILD_DIR}/extension/core_functions/libcore_functions_extension.a" "${_DUCKDB_BUILD_DIR}/extension/icu/libicu_extension.a" - "${_DUCKDB_BUILD_DIR}/extension/jemalloc/libjemalloc_extension.a" "${_DUCKDB_BUILD_DIR}/extension/parquet/libparquet_extension.a" "${_DUCKDB_BUILD_DIR}/extension/json/libjson_extension.a" ) @@ -60,20 +59,13 @@ ADD_SUBMODULE(third_parties/duckdb) # reconciliation). Drop when the submodule is bumped to a release containing # the PR: the apply below will fail loudly on an already-present setval. SET(_DUCKDB_PATCH "${CMAKE_CURRENT_SOURCE_DIR}/patches/duckdb-pr24061-setval.diff") -EXECUTE_PROCESS( - COMMAND git -C "${DUCKDB_SUBMODULE_DIR}" rev-parse HEAD - OUTPUT_VARIABLE _DUCKDB_SOURCE_REVISION - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _DUCKDB_GIT_RESULT) -IF(NOT _DUCKDB_GIT_RESULT EQUAL 0) - MESSAGE(FATAL_ERROR "Failed to determine the DuckDB submodule revision") -ENDIF() +SET(_DUCKDB_GIT_DESCRIBE "v1.5.5-0-g0000000000") # Upstream sets DUCKDB_EXTENSION_JEMALLOC_LINKED via add_extension_definitions(), # which runs in extension/ but NOT in src/, so allocator.cpp (in duckdb_static) # compiles the glibc malloc() path even though libjemalloc_extension.a is linked. # Define it globally + add the jemalloc header dir so the USE_JEMALLOC branch is active. -SET(_DUCKDB_EXTRA_CXX_FLAGS "-DDUCKDB_EXTENSION_JEMALLOC_LINKED=1 -I${DUCKDB_SUBMODULE_DIR}/extension/jemalloc/include") +SET(_DUCKDB_EXTRA_CXX_FLAGS "") # GCC 16+ warns about DuckDB classes in SFINAE contexts (upstream issue, not ours). # Silence it for DuckDB's own ExternalProject build too. MY_CHECK_CXX_COMPILER_FLAG("-Wsfinae-incomplete") @@ -98,7 +90,7 @@ ExternalProject_Add(duckdb_build SOURCE_DIR "${DUCKDB_SUBMODULE_DIR}" BINARY_DIR "${_DUCKDB_BUILD_DIR}" # Idempotent: skip when the working tree already carries the patch. - PATCH_COMMAND bash -c "test \"$(git rev-parse HEAD)\" = '${_DUCKDB_SOURCE_REVISION}' && (git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}')" + PATCH_COMMAND bash -c "echo '${_DUCKDB_GIT_DESCRIBE}' >/dev/null && (git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}')" CMAKE_ARGS -DCMAKE_BUILD_TYPE=${_DUCKDB_BUILD_TYPE} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} @@ -115,7 +107,7 @@ ExternalProject_Add(duckdb_build "-DCMAKE_CXX_FLAGS=${_DUCKDB_EXTRA_CXX_FLAGS}" -DENABLE_SANITIZER=${ADDRESS_SANITIZER} -DENABLE_UBSAN=${UB_SANITIZER} - -DOVERRIDE_GIT_DESCRIBE=v1.5.5-0-g0000000000 + -DOVERRIDE_GIT_DESCRIBE=${_DUCKDB_GIT_DESCRIBE} INSTALL_COMMAND "" BUILD_BYPRODUCTS ${_DUCKDB_STATIC_LIBS} USES_TERMINAL_BUILD ON diff --git a/storage/duckdb/cmake/duckdb_extensions.cmake b/storage/duckdb/cmake/duckdb_extensions.cmake index ff6134187b9..b831f89cc0a 100644 --- a/storage/duckdb/cmake/duckdb_extensions.cmake +++ b/storage/duckdb/cmake/duckdb_extensions.cmake @@ -1,7 +1,6 @@ # Extensions required by the DuckDB storage engine plugin for MariaDB. # This config is passed to DuckDB via DUCKDB_EXTENSION_CONFIGS. -duckdb_extension_load(jemalloc) duckdb_extension_load(core_functions) duckdb_extension_load(icu) duckdb_extension_load(json) From 282a10a4a7958fd11dcf98a6575664a3aa57ea21 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 25 May 2026 11:22:07 +1000 Subject: [PATCH 125/150] MDEV-38405 Assertion `tbl->trn == 0' failed in _ma_set_trn_for_table Aria bulk insert operations disables share->now_transaction meaning a concurrent open of the stable table will have trn == &dummy_transaction_object for its MARIA_HA object during opening. As the _ma_set_trn_for_table is setting the trn, its harmless if the current trn is the dummy_transaction_object. Relax the assert to allow for this state. --- storage/maria/ma_trnman.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/maria/ma_trnman.h b/storage/maria/ma_trnman.h index 4a73dcda680..a0cbd5ccb6c 100644 --- a/storage/maria/ma_trnman.h +++ b/storage/maria/ma_trnman.h @@ -32,7 +32,7 @@ static inline void _ma_set_trn_for_table(MARIA_HA *tbl, TRN *newtrn) /* check that we are not calling this twice in a row */ DBUG_ASSERT(newtrn->used_instances != (void*) tbl); DBUG_ASSERT(newtrn != &dummy_transaction_object); - DBUG_ASSERT(tbl->trn == 0); + DBUG_ASSERT(tbl->trn == 0 || tbl->trn == &dummy_transaction_object); DBUG_ASSERT(tbl->trn_next == 0); DBUG_ASSERT(tbl->trn_prev == 0); From 79520ba4e5b17ac69f69a980615ad81f26f6b975 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 12 Aug 2026 11:56:57 +1000 Subject: [PATCH 126/150] RocksDB: compile fix std::replace requires algorithm header Otherwise it compile fails. Found in clang-24. --- storage/rocksdb/rdb_mariadb_server_port.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/storage/rocksdb/rdb_mariadb_server_port.cc b/storage/rocksdb/rdb_mariadb_server_port.cc index e74c792c75b..9d0c3476a54 100644 --- a/storage/rocksdb/rdb_mariadb_server_port.cc +++ b/storage/rocksdb/rdb_mariadb_server_port.cc @@ -12,6 +12,7 @@ //#include #include +#include /* MyRocks includes */ #include "./rdb_threads.h" From c62d7895c35cafe00a0d516778ddfbd2ab66028e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 12 Aug 2026 12:44:03 +1000 Subject: [PATCH 127/150] sql_test: mallinfo2 msan exclusion no longer needed MSAN interceptor was added in clang-18.1. --- sql/sql_test.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sql/sql_test.cc b/sql/sql_test.cc index c96e5c8fe65..995edd6fc43 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -633,10 +633,6 @@ Next alarm time: %lu\n", #elif defined(HAVE_MALLINFO) struct mallinfo info= mallinfo(); #endif -#if __has_feature(memory_sanitizer) - /* Work around missing MSAN instrumentation */ - MEM_MAKE_DEFINED(&info, sizeof info); -#endif #if defined(HAVE_MALLINFO) || defined(HAVE_MALLINFO2) char llbuff[10][22]; printf("\nMemory status:\n\ From 84695e9c0b762db7ce7e23fc4ac51717b3a77efc Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 21 Jul 2026 10:29:29 +1000 Subject: [PATCH 128/150] MDEV-40243 Fix MEMORY_LEAK_C leaks in mariadb-dump (rockdb tests) With memory leaks fixed in the rocksdb.mysqldump/mysqldump2 no longer need to run with leak detection disabled. There tests are still disabled as there's no --rockdb arg to mariadb-dump, but removing so there's no precidence to ignoring leaks. ref: 2217477fb8fc82f4921d9d13afcd24b1d86b34d6 --- storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test | 10 +++++----- storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test index 473bebdda89..081096d8077 100644 --- a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test +++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test @@ -33,10 +33,10 @@ insert into r1 values (5,5,5,5,5,5,5,5); update r1 set value1=value1+100 where id1=1 and id2=1 and id3='1'; --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load test --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load --rocksdb_bulk_load_allow_sk test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load --rocksdb_bulk_load_allow_sk test rollback; @@ -49,17 +49,17 @@ source include/search_pattern_in_file.inc; set @save_default_storage_engine=@@global.default_storage_engine; SET GLOBAL default_storage_engine=rocksdb; --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test source include/search_pattern_in_file.inc; # Sanity test mysqldump when the --innodb-stats-on-metadata is specified (no effect) --echo ==== mysqldump with --innodb-stats-on-metadata ==== --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --innodb-stats-on-metadata test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --innodb-stats-on-metadata test # testing mysqldump work with statement based binary logging SET GLOBAL binlog_format=statement; ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test > /dev/null +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test > /dev/null SET GLOBAL binlog_format=row; drop table r1; diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test index 4f4f5ed092b..d0cf34725b9 100644 --- a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test +++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test @@ -29,7 +29,7 @@ let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; select variable_value into @a from information_schema.global_status where variable_name='rocksdb_block_cache_add'; ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb test > /dev/null +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb test > /dev/null # verifying block cache was not filled select case when variable_value - @a > 20 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add'; From bc3939b84b71baa816cf9011fd04f7bbbf43dffd Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 13 Aug 2026 11:26:50 +1000 Subject: [PATCH 129/150] MDEV-17846 Wrong result with grouping select (fix) Prevent unused variable 'ref_type' warnings on non-debug builds. --- sql/item.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sql/item.cc b/sql/item.cc index a6b5af2ba3e..2fc2647b42c 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5924,7 +5924,9 @@ static inline bool inner_refs_check(THD *thd, enum_parsing_place place, Item **reference) { +#ifndef DBUG_OFF Item::Type ref_type= (*reference)->type(); +#endif if (!last_checked_context->select_lex->having_fix_field && select->group_list.elements && (place == SELECT_LIST || place == IN_HAVING)) From 53b1cc0149328c7d3857d9d0d6efbbbc779ac2c2 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Mon, 4 Dec 2023 06:21:19 +0400 Subject: [PATCH 130/150] MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE Applying HEX encoding write writting an ENUM/SET TYPELIB to FRM if the TYPELIB has 0x00 bytes in the value. This HEX encoding was earlier used only to write UCS2/UTF16/UTF32 TYPELIBs. A new flag FIELDFLAG_FRM_HEX_ENCODED_TYPELIB was added to indicate that the TYPELIB is hex encoded. It's used only inside FRM. Note, it's mangled with FIELDFLAG_TREAT_BIT_AS_CHAR. This should not be harmful: - BIT and ENUM/SET columns are handled by two separate code branches when opening an FRM - The flag is unset immediately after decoding TYPELIB, so the rest of the code does not se an unexpected flag combination. --- mysql-test/main/ctype_binary.result | 16 +++++++++++++-- mysql-test/main/ctype_binary.test | 9 ++++++--- mysql-test/main/type_enum.result | 29 +++++++++++++++++++++++++++ mysql-test/main/type_enum.test | 17 ++++++++++++++++ mysql-test/main/type_set.result | 31 +++++++++++++++++++++++++++++ mysql-test/main/type_set.test | 18 +++++++++++++++++ sql/field.h | 1 + sql/sql_type.cc | 8 ++++++-- sql/strfunc.h | 14 +++++++++++++ sql/table.cc | 24 ++++++++++++++-------- sql/unireg.cc | 7 ++++++- 11 files changed, 158 insertions(+), 16 deletions(-) diff --git a/mysql-test/main/ctype_binary.result b/mysql-test/main/ctype_binary.result index 41e16f94289..3b416253bec 100644 --- a/mysql-test/main/ctype_binary.result +++ b/mysql-test/main/ctype_binary.result @@ -3254,9 +3254,21 @@ HEX(a) HEX(b) 61 62 61 0061 DROP TABLE t1; -0x00 in the beginning of a value of a non-last ENUM/SET causes an error: +0x00 in the beginning of a value of a non-last ENUM/SET (fixed by MDEV-28498): CREATE TABLE t1 (a ENUM('a',0x0061), b ENUM('b')); -ERROR HY000: Incorrect information in file: 'DIR/t1.frm' +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('a','\0a') DEFAULT NULL, + `b` enum('b') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (1,1); +INSERT INTO t1 VALUES (2,1); +SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; +HEX(a) HEX(b) +61 62 +0061 62 +DROP TABLE t1; # # End of 10.1 tests # diff --git a/mysql-test/main/ctype_binary.test b/mysql-test/main/ctype_binary.test index 9537f09e9b1..a3d97289573 100644 --- a/mysql-test/main/ctype_binary.test +++ b/mysql-test/main/ctype_binary.test @@ -116,10 +116,13 @@ INSERT INTO t1 VALUES (1,2); SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; DROP TABLE t1; ---echo 0x00 in the beginning of a value of a non-last ENUM/SET causes an error: ---replace_regex /'.*t1.frm'/'DIR\/t1.frm'/ ---error ER_NOT_FORM_FILE +--echo 0x00 in the beginning of a value of a non-last ENUM/SET (fixed by MDEV-28498): CREATE TABLE t1 (a ENUM('a',0x0061), b ENUM('b')); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (1,1); +INSERT INTO t1 VALUES (2,1); +SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; +DROP TABLE t1; --echo # diff --git a/mysql-test/main/type_enum.result b/mysql-test/main/type_enum.result index 2b9cbb1d03a..44823eab790 100644 --- a/mysql-test/main/type_enum.result +++ b/mysql-test/main/type_enum.result @@ -2629,4 +2629,33 @@ select * from t1; a drop table t1; set sql_mode=default; +# +# MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +# +CREATE TABLE t1 (a ENUM ('',0x0000),b ENUM ('')); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('','\0\0') DEFAULT NULL, + `b` enum('') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +SELECT HEX(a) FROM t1; +HEX(a) +0000 +0000 +DROP TABLE t1; +CREATE TABLE t1 (a ENUM ('',0x0000) AS (0x0000) VIRTUAL,b ENUM ('') DEFAULT ''); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('','\0\0') GENERATED ALWAYS AS (0x0000) VIRTUAL, + `b` enum('') DEFAULT '' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (); +SELECT HEX(a) FROM t1; +HEX(a) +0000 +DROP TABLE t1; # End of 10.11 tests diff --git a/mysql-test/main/type_enum.test b/mysql-test/main/type_enum.test index 2c685b5594c..6c2ce9f1e7e 100644 --- a/mysql-test/main/type_enum.test +++ b/mysql-test/main/type_enum.test @@ -630,4 +630,21 @@ select * from t1; drop table t1; set sql_mode=default; +--echo # +--echo # MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +--echo # + +CREATE TABLE t1 (a ENUM ('',0x0000),b ENUM ('')); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +SELECT HEX(a) FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (a ENUM ('',0x0000) AS (0x0000) VIRTUAL,b ENUM ('') DEFAULT ''); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (); +SELECT HEX(a) FROM t1; +DROP TABLE t1; + --echo # End of 10.11 tests diff --git a/mysql-test/main/type_set.result b/mysql-test/main/type_set.result index e35aa66f9b6..80ca38f98aa 100644 --- a/mysql-test/main/type_set.result +++ b/mysql-test/main/type_set.result @@ -704,4 +704,35 @@ Catalog Database Table Table_alias Column Column_alias Type Length Max length Is def COALESCE(c_int, c_set) 253 33 0 Y 0 39 33 COALESCE(c_int, c_set) DROP TABLE t1; +# +# MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +# +CREATE TABLE t1 (a SET ('',0x0000),b SET ('')) CHARACTER SET latin1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` set('','\0\0') DEFAULT NULL, + `b` set('') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +INSERT INTO t1 VALUES ('',''); +SELECT a+0, HEX(a) FROM t1; +a+0 HEX(a) +2 0000 +2 0000 +0 +DROP TABLE t1; +CREATE TABLE t1 (a SET ('',0x0000) AS (0x0000) VIRTUAL,b SET ('') DEFAULT '') CHARACTER SET latin1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` set('','\0\0') GENERATED ALWAYS AS (0x0000) VIRTUAL, + `b` set('') DEFAULT '' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (); +SELECT a+0, HEX(a) FROM t1; +a+0 HEX(a) +2 0000 +DROP TABLE t1; # End of 10.11 tests diff --git a/mysql-test/main/type_set.test b/mysql-test/main/type_set.test index 1873a54897a..87ad00b094d 100644 --- a/mysql-test/main/type_set.test +++ b/mysql-test/main/type_set.test @@ -334,4 +334,22 @@ SELECT COALESCE(c_int, c_set) FROM t1; DROP TABLE t1; +--echo # +--echo # MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +--echo # + +CREATE TABLE t1 (a SET ('',0x0000),b SET ('')) CHARACTER SET latin1; +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +INSERT INTO t1 VALUES ('',''); +SELECT a+0, HEX(a) FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (a SET ('',0x0000) AS (0x0000) VIRTUAL,b SET ('') DEFAULT '') CHARACTER SET latin1; +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (); +SELECT a+0, HEX(a) FROM t1; +DROP TABLE t1; + --echo # End of 10.11 tests diff --git a/sql/field.h b/sql/field.h index c31b38d78e6..8ff4d90f38a 100644 --- a/sql/field.h +++ b/sql/field.h @@ -5939,6 +5939,7 @@ bool check_expression(Virtual_column_info *vcol, const LEX_CSTRING *name, #define FIELDFLAG_GEOM 2048U // mangled with decimals! #define FIELDFLAG_TREAT_BIT_AS_CHAR 4096U /* use Field_bit_as_char */ +#define FIELDFLAG_FRM_HEX_ENCODED_TYPELIB 4096U // mangled with *_BIT_AS_CHAR #define FIELDFLAG_LONG_DECIMAL 8192U #define FIELDFLAG_NO_DEFAULT 16384U /* sql */ #define FIELDFLAG_MAYBE_NULL 32768U // sql diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 95e18672c3d..74917bbcf57 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -8791,7 +8791,9 @@ void Type_handler_enum:: Column_definition_attributes_frm_pack(const Column_definition_attributes *def, uchar *buff) const { - DBUG_ASSERT(f_decimals(def->pack_flag & ~FIELDFLAG_INTERVAL) == 0); + DBUG_ASSERT(f_decimals(def->pack_flag & + ~(FIELDFLAG_INTERVAL| + FIELDFLAG_FRM_HEX_ENCODED_TYPELIB)) == 0); DBUG_ASSERT(def->decimals == 0); Type_handler::Column_definition_attributes_frm_pack(def, buff); } @@ -8801,7 +8803,9 @@ void Type_handler_set:: Column_definition_attributes_frm_pack(const Column_definition_attributes *def, uchar *buff) const { - DBUG_ASSERT(f_decimals(def->pack_flag & ~FIELDFLAG_BITFIELD) == 0); + DBUG_ASSERT(f_decimals(def->pack_flag & + ~(FIELDFLAG_BITFIELD| + FIELDFLAG_FRM_HEX_ENCODED_TYPELIB)) == 0); DBUG_ASSERT(def->decimals == 0); Type_handler::Column_definition_attributes_frm_pack(def, buff); } diff --git a/sql/strfunc.h b/sql/strfunc.h index b2b293e153f..c48c5161298 100644 --- a/sql/strfunc.h +++ b/sql/strfunc.h @@ -30,6 +30,20 @@ uint find_type(const TYPELIB *lib, const char *find, size_t length, uint find_type2(const TYPELIB *lib, const char *find, size_t length, CHARSET_INFO *cs); void unhex_type2(TYPELIB *lib); + +static inline bool typelib_with_0x00(const TYPELIB &typelib) +{ + for (uint pos= 0; pos < typelib.count; pos++) + { + for (uint i= 0; i < typelib.type_lengths[pos]; i++) + { + if (typelib.type_names[pos][i] == 0x00) + return true; + } + } + return false; +} + uint check_word(TYPELIB *lib, const char *val, const char *end, const char **end_of_word); int find_string_in_array(LEX_CSTRING * const haystack, diff --git a/sql/table.cc b/sql/table.cc index a2a01f6f600..f52f0590dea 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2634,17 +2634,25 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, attr.pack_flag&= ~FIELDFLAG_LONG_DECIMAL; } - if (interval_nr && attr.charset->mbminlen > 1 && - !interval_unescaped[interval_nr - 1]) + if (interval_nr && (attr.charset->mbminlen > 1 || + (attr.pack_flag & FIELDFLAG_FRM_HEX_ENCODED_TYPELIB))) { /* - Unescape UCS2/UTF16/UTF32 intervals from HEX notation. - Note, ENUM/SET columns with equal value list share a single - copy of TYPELIB. Unescape every TYPELIB only once. + This flag is used only in FRM and is mangled with other flags. + Unset it to avoid unexpected bit combinations in the rest of the code. */ - TYPELIB *interval= share->intervals + interval_nr - 1; - unhex_type2(interval); - interval_unescaped[interval_nr - 1]= true; + attr.pack_flag&= ~FIELDFLAG_FRM_HEX_ENCODED_TYPELIB; + if (!interval_unescaped[interval_nr - 1]) + { + /* + Decode the interval from the HEX notation. + Note, ENUM/SET columns with equal value lists share a single + copy of TYPELIB. Unescape every TYPELIB only once. + */ + TYPELIB *interval= share->intervals + interval_nr - 1; + unhex_type2(interval); + interval_unescaped[interval_nr - 1]= true; + } } #ifndef TO_BE_DELETED_ON_PRODUCTION diff --git a/sql/unireg.cc b/sql/unireg.cc index 54424d3fd0a..5b99a9f98da 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -33,6 +33,7 @@ #include "create_options.h" #include "discover.h" #include +#include "strfunc.h" // typelib_with_0x00 #define FCOMP 17 /* Bytes for a packed field */ @@ -894,13 +895,17 @@ static bool pack_header(THD *thd, uchar *forminfo, if (field->interval) { uint old_int_count=int_count; + bool typelib_with_z= typelib_with_0x00(*field->interval); - if (field->charset->mbminlen > 1) + if (field->charset->mbminlen > 1 || typelib_with_z) { TYPELIB *tmpint; + if (typelib_with_z && field->charset->mbminlen == 1) + field->pack_flag|= FIELDFLAG_FRM_HEX_ENCODED_TYPELIB; /* Escape UCS2 intervals using HEX notation to avoid problems with delimiters between enum elements. + Do the same for single byte intervals with 0x00 characters. As the original representation is still needed in the function make_empty_rec to create a record of filled with default values it is saved in save_interval From 12ceb4d6aac1c287f4e2bf8d0412795f4e539020 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 13 Aug 2026 14:17:07 +1000 Subject: [PATCH 131/150] MDEV-34482 main.events_processlist test fix As the test result is dependent of SHOW PROCESSLIST output, adjust the wait condition to ensure the state is in sleeping rather than "init" or another state. --- mysql-test/main/events_processlist.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/events_processlist.test b/mysql-test/main/events_processlist.test index 4b562de5b0f..93acf4c6c9d 100644 --- a/mysql-test/main/events_processlist.test +++ b/mysql-test/main/events_processlist.test @@ -23,7 +23,8 @@ CREATE EVENT e # Wait until the event worker appears in PROCESSLIST for the current user let $wait_condition= SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO LIKE '%SLEEP(10000)%' AND ID <> CONNECTION_ID(); + WHERE INFO LIKE '%SLEEP(10000)%' AND ID <> CONNECTION_ID() + AND STATE='User sleep'; --source include/wait_condition.inc # Check that show processlist works From bb0ac437015dec04fbee226745a8eb2bb4825917 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Thu, 13 Aug 2026 16:40:00 +1000 Subject: [PATCH 132/150] MDEV-40486 Length check for vector fields in CREATE TABLE ... SELECT The changes of MDEV-39558 2b6529426a7e7c65d286e093d84138be9dcc34a3 added length check assertion in Field_varstring constructors, and length check in type inference for SELECT set operations, to emit errors before reaching the assertions. That change caused an error to turn into an assertion failure in a separate path, when the length limit violation is not detected before tripping the assertion. So in this patch we fix it by adding an earlier length check in that path. The reason that we place this check inside Item_func_vec_fromtext::fix_length_and_dec rather than say `create_field_for_create_select is for consistency: If create table t1 as select vec_fromtext(concat('[',group_concat(1),']')) as c1 from seq_1_to_64; fails due to length limit violation, then so should create table t1 (v vector(64) not null); insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; Also use max_char_length() instead of max_length. This is a more accurate length of characters. And add handling of empty string edge case. Added testcases accordingly. The change that uses max_char_length() causes side effects where creating a table using a VEC_FROMTEXT(CHAR(1)) would result in a 0-dimensional vector field. This is accurate but 0-dim vector table fields should not be allowed. So we make cases like this result in a one-dimensional field. Also fixed the underflow in (args[0]->max_length - 1) * 2 when the arg's max length is 0. Previously this underflow would cause create table t1 select vec_fromtext(NULL) to fail with ER_TOO_BIG_FIELDLENGTH. Now it will be a VECTOR(1) field --- mysql-test/main/vector2.result | 131 ++++++++++++++++++++++++++++++++- mysql-test/main/vector2.test | 107 ++++++++++++++++++++++++++- sql/item_vectorfunc.cc | 21 +++++- 3 files changed, 254 insertions(+), 5 deletions(-) diff --git a/mysql-test/main/vector2.result b/mysql-test/main/vector2.result index 5dbe71c8adf..c4ff5eb7793 100644 --- a/mysql-test/main/vector2.result +++ b/mysql-test/main/vector2.result @@ -160,7 +160,7 @@ drop table t; # MDEV-35141 Server crashes in Field_vector::report_wrong_value upon statistic collection # create table t1 (v vector(64) not null); -insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; +insert into t1 select vec_fromtext(cast(concat('[',group_concat(1),']') as char(130))) from seq_1_to_64; analyze table t1 persistent for all; Table Op Msg_type Msg_text test.t1 analyze status Engine-independent statistics collected @@ -565,4 +565,133 @@ tmp CREATE TABLE `tmp` ( c drop table tmp, t1, t2; set sql_mode=@old_sql_mode; +# +# MDEV-40486 ER_TOO_BIG_FIELDLENGTH or assertion failure upon creating vector from blob +# +## Original testcase +CREATE TABLE t (a TEXT) AS SELECT '[1]' AS a; +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +DROP TABLE t; +CREATE TABLE t (a LONGBLOB) AS SELECT '[1]' AS a; +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +DROP TABLE t; +## Another case, which would have failed with ERROR 1292 +## without the fix +CREATE TABLE t SELECT VEC_FROMTEXT('[1]'); +DROP TABLE t; +## Conversion from max varchar length +CREATE TABLE t (a VARCHAR(16383)) AS +SELECT concat('[1', repeat(',1', 8190), ']') AS a; +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +DROP TABLE t,tt; +## Conversion to max vector dimension +CREATE TABLE tt AS +SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16382), ']')) AS f; +DROP TABLE tt; +CREATE TABLE tt AS +SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; +ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +## "Zero-dimensional" argument, no change in behaviour after fix +SELECT VEC_FROMTEXT('[]') as f; +f + +CREATE TABLE t1 SELECT VEC_FROMTEXT('[]'); +ERROR 22007: Incorrect vector value: '' for column `test`.`t1`.`VEC_FROMTEXT('[]')` at row 1 +## Zero length: no underflow +SELECT VEC_FROMTEXT('') AS f; +f +NULL +Warnings: +Warning 4037 Unexpected end of JSON text in argument 1 to function 'VEC_FromText' +CREATE TABLE tt AS SELECT VEC_FROMTEXT('') AS f; +ERROR HY000: Unexpected end of JSON text in argument 1 to function 'VEC_FromText' +CREATE TABLE t1 (a CHAR(1)); +CREATE TABLE t2 AS SELECT VEC_FROMTEXT(a) AS f FROM t1; +show create table t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `f` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1, t2; +CREATE TABLE t1 (a CHAR(2)); +CREATE TABLE t2 AS SELECT VEC_FROMTEXT(a) AS f FROM t1; +show create table t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `f` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1, t2; +CREATE TABLE t3 (f VECTOR(0)); +ERROR 42000: Incorrect column specifier for column 'f' +## Fails because concat('[',group_concat(1),']') is mediumblob +create view v1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; +ERROR 42000: Column length too big for column 'vec_fromtext(concat('[',group_concat(1),']'))' (max = 16383); use BLOB or TEXT instead +## NULLs +select vec_fromtext(NULL); +vec_fromtext(NULL) +NULL +select vec_fromtext(NULL + NULL); +vec_fromtext(NULL + NULL) +NULL +create table t1 select vec_fromtext(NULL) as c; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1; +create table t1 select vec_fromtext(NULL + NULL) as c; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` vector(8) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +drop table t1; +create table t1 select vec_fromtext(c) from (select '[1,2]' as c) d; +DROP TABLE t1; +create table t1 select vec_fromtext(c) from (select '' as c) d; +ERROR HY000: Unexpected end of JSON text in argument 1 to function 'VEC_FromText' +create table t1 select vec_fromtext(c) from (select NULL as c) d; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `vec_fromtext(c)` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1; +create table t2 (c varchar(5)); +insert into t2 values (NULL); +create table t1 select vec_fromtext(c) from t2; +drop table t1, t2; +## Prepared statements with placeholders +prepare p1 from 'select vec_fromtext(?)'; +execute p1 using '[1,2]'; +execute p1 using NULL; +vec_fromtext(?) +NULL +execute p1 using '[]'; +vec_fromtext(?) + +deallocate prepare p1; +prepare p1 from 'create table t1 select vec_fromtext(?)'; +execute p1 using '[1,2]'; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `vec_fromtext(?)` vector(2) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1; +execute p1 using NULL; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `vec_fromtext(?)` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t1; +execute p1 using '[]'; +ERROR 22007: Incorrect vector value: '' for column `test`.`t1`.`vec_fromtext(?)` at row 1 +execute p1 using ''; +ERROR HY000: Unexpected end of JSON text in argument 1 to function 'VEC_FromText' +deallocate prepare p1; # End of 11.8 tests diff --git a/mysql-test/main/vector2.test b/mysql-test/main/vector2.test index be2493011aa..f187559365d 100644 --- a/mysql-test/main/vector2.test +++ b/mysql-test/main/vector2.test @@ -125,7 +125,7 @@ drop table t; --echo # MDEV-35141 Server crashes in Field_vector::report_wrong_value upon statistic collection --echo # create table t1 (v vector(64) not null); -insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; +insert into t1 select vec_fromtext(cast(concat('[',group_concat(1),']') as char(130))) from seq_1_to_64; analyze table t1 persistent for all; drop table t1; @@ -447,4 +447,109 @@ drop table tmp, t1, t2; set sql_mode=@old_sql_mode; +--echo # +--echo # MDEV-40486 ER_TOO_BIG_FIELDLENGTH or assertion failure upon creating vector from blob +--echo # + +--echo ## Original testcase +CREATE TABLE t (a TEXT) AS SELECT '[1]' AS a; +--error ER_TOO_BIG_FIELDLENGTH +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +DROP TABLE t; + +CREATE TABLE t (a LONGBLOB) AS SELECT '[1]' AS a; +--error ER_TOO_BIG_FIELDLENGTH +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +DROP TABLE t; + +--echo ## Another case, which would have failed with ERROR 1292 +--echo ## without the fix +CREATE TABLE t SELECT VEC_FROMTEXT('[1]'); +DROP TABLE t; + +--echo ## Conversion from max varchar length +CREATE TABLE t (a VARCHAR(16383)) AS + SELECT concat('[1', repeat(',1', 8190), ']') AS a; +CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; +DROP TABLE t,tt; + +--echo ## Conversion to max vector dimension +CREATE TABLE tt AS + SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16382), ']')) AS f; +DROP TABLE tt; + +--error ER_TOO_BIG_FIELDLENGTH +CREATE TABLE tt AS + SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; + +--echo ## "Zero-dimensional" argument, no change in behaviour after fix +SELECT VEC_FROMTEXT('[]') as f; +--error ER_TRUNCATED_WRONG_VALUE +CREATE TABLE t1 SELECT VEC_FROMTEXT('[]'); +--echo ## Zero length: no underflow +SELECT VEC_FROMTEXT('') AS f; +--error ER_JSON_EOS +CREATE TABLE tt AS SELECT VEC_FROMTEXT('') AS f; + +CREATE TABLE t1 (a CHAR(1)); +CREATE TABLE t2 AS SELECT VEC_FROMTEXT(a) AS f FROM t1; +show create table t2; +DROP TABLE t1, t2; +CREATE TABLE t1 (a CHAR(2)); +CREATE TABLE t2 AS SELECT VEC_FROMTEXT(a) AS f FROM t1; +show create table t2; +DROP TABLE t1, t2; + +--error ER_WRONG_FIELD_SPEC +CREATE TABLE t3 (f VECTOR(0)); + +--echo ## Fails because concat('[',group_concat(1),']') is mediumblob +--error ER_TOO_BIG_FIELDLENGTH +create view v1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; + +--echo ## NULLs +select vec_fromtext(NULL); +select vec_fromtext(NULL + NULL); +create table t1 select vec_fromtext(NULL) as c; +show create table t1; +DROP TABLE t1; +create table t1 select vec_fromtext(NULL + NULL) as c; +show create table t1; +drop table t1; + +create table t1 select vec_fromtext(c) from (select '[1,2]' as c) d; +DROP TABLE t1; +--error ER_JSON_EOS +create table t1 select vec_fromtext(c) from (select '' as c) d; +create table t1 select vec_fromtext(c) from (select NULL as c) d; +show create table t1; +DROP TABLE t1; + +create table t2 (c varchar(5)); +insert into t2 values (NULL); +create table t1 select vec_fromtext(c) from t2; +drop table t1, t2; + +--echo ## Prepared statements with placeholders +prepare p1 from 'select vec_fromtext(?)'; +--disable_result_log +execute p1 using '[1,2]'; +--enable_result_log +execute p1 using NULL; +execute p1 using '[]'; +deallocate prepare p1; + +prepare p1 from 'create table t1 select vec_fromtext(?)'; +execute p1 using '[1,2]'; +show create table t1; +DROP TABLE t1; +execute p1 using NULL; +show create table t1; +DROP TABLE t1; +--error ER_TRUNCATED_WRONG_VALUE +execute p1 using '[]'; +--error ER_JSON_EOS +execute p1 using ''; +deallocate prepare p1; + --echo # End of 11.8 tests diff --git a/sql/item_vectorfunc.cc b/sql/item_vectorfunc.cc index 13e458d7675..12bd63340be 100644 --- a/sql/item_vectorfunc.cc +++ b/sql/item_vectorfunc.cc @@ -181,12 +181,27 @@ Item_func_vec_fromtext::Item_func_vec_fromtext(THD *thd, Item *a) bool Item_func_vec_fromtext::fix_length_and_dec(THD *thd) { + uint32 maxlen= args[0]->max_char_length(); decimals= 0; /* Worst case scenario, for a valid input we have a string of the form: [1,2,3,4,5,...] single digit numbers. - This means we can have (max_length - 1) / 2 floats. - Each float takes 4 bytes, so we do (max_length - 1) * 2. */ - fix_length_and_charset((args[0]->max_length - 1) * 2, &my_charset_bin); + This means we can have (maxlen - 1) / 2 floats. + Each float takes 4 bytes, so we do (maxlen - 1) * 2. Clamp at 4 + (so that result is at least VECTOR(1)) and UINT_MAX32 - 1 (to + avoid overflow) */ + if (maxlen <= 2) + maxlen= 4; + else if (maxlen > INT_MAX32) + maxlen= UINT_MAX32 - 1; + else + maxlen= (maxlen - 1) * 2; + fix_length_and_charset(maxlen, &my_charset_bin); + if (max_length > MAX_FIELD_VARCHARLENGTH) + { + my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), name.str, + static_cast(MAX_FIELD_VARCHARLENGTH / sizeof(float))); + return true; + } set_maybe_null(); return false; } From 18fce8e1e76a5798ac9bf0a9a615b97175e8351a Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Wed, 12 Aug 2026 16:02:15 +0300 Subject: [PATCH 133/150] MDEV-40738: main.cte_update_delete missing DROP VIEW v1, refers to wrong MDEVs. Fix the testcase. --- mysql-test/main/cte_update_delete.result | 3 ++- mysql-test/main/cte_update_delete.test | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/cte_update_delete.result b/mysql-test/main/cte_update_delete.result index 6cda90fb9e2..4a2ace910e0 100644 --- a/mysql-test/main/cte_update_delete.result +++ b/mysql-test/main/cte_update_delete.result @@ -1354,7 +1354,7 @@ with cte as (select * from t1) delete from t1,cte where t1.a=cte.a; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'where t1.a=cte.a' at line 1 with cte as (select * from t1) delete from t1 using t1,cte where t1.a=cte.a; # -# MDEV-38272 Recursive CTE usage leaks memory when not used in a top level select +# MDEV-38295 Recursive CTE usage leaks memory when not used in a top level select # with recursive cte as (select a from t1 union select a from cte) update t1, cte set t1.o = 1; @@ -1374,5 +1374,6 @@ a 15 20 25 +drop view v1; drop table t1; # End of 12.2 tests diff --git a/mysql-test/main/cte_update_delete.test b/mysql-test/main/cte_update_delete.test index 8ec964153c4..ce053ba80e3 100644 --- a/mysql-test/main/cte_update_delete.test +++ b/mysql-test/main/cte_update_delete.test @@ -549,7 +549,7 @@ with cte as (select * from t1) delete from t1,cte where t1.a=cte.a; with cte as (select * from t1) delete from t1 using t1,cte where t1.a=cte.a; --echo # ---echo # MDEV-38272 Recursive CTE usage leaks memory when not used in a top level select +--echo # MDEV-38295 Recursive CTE usage leaks memory when not used in a top level select --echo # --error ER_BAD_FIELD_ERROR @@ -593,6 +593,7 @@ with recursive cte as (select a from t1 union select a from cte where a < 2) delete from v1 using v1, cte where v1.a = cte.a; select * from t1 order by 1; +drop view v1; drop table t1; --echo # End of 12.2 tests From f51edd8933e3b7c9e796864c7e6c61e25828b4b4 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Fri, 12 Jun 2026 19:32:09 +0000 Subject: [PATCH 134/150] MDEV-39153 Fix sporadic main.change_master_default mismatch The test used "restart_abort:" in the expect file, which MTR never recognized. It fell through to the else branch, deleted restart_opts, and started the server with defaults (heartbeat_period=60 instead of 0). Replace restart_abort with direct --exec $MYSQLD calls. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/change_master_default.result | 4 --- mysql-test/main/change_master_default.test | 27 ++++++++++---------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/mysql-test/main/change_master_default.result b/mysql-test/main/change_master_default.result index 865361e670c..dfcc641d1b6 100644 --- a/mysql-test/main/change_master_default.result +++ b/mysql-test/main/change_master_default.result @@ -201,10 +201,6 @@ slave_heartbeat_period 15.000 RESET SLAVE 'unset' ALL; CHANGE MASTER 'unset' TO master_host='127.0.1.3'; # Validate command line options -# restart_abort: --master-heartbeat-period='' -# restart_abort: --master-heartbeat-period=123abc -# restart_abort: --master-heartbeat-period=-1 -# restart_abort: --master-heartbeat-period=4294967.296 # restart: --skip-slave-start --master-heartbeat-period=0.000499 SELECT connection_name, slave_heartbeat_period FROM information_schema.slave_status ORDER BY connection_name; diff --git a/mysql-test/main/change_master_default.test b/mysql-test/main/change_master_default.test index 37c937533f4..2b1b808a2d4 100644 --- a/mysql-test/main/change_master_default.test +++ b/mysql-test/main/change_master_default.test @@ -10,6 +10,10 @@ # only need CHANGE MASTER and IS.slave_status --source include/have_binlog_format_mixed.inc +# This test uses --exec $MYSQLD directly, which bypasses MTR's automatic +# --user=root injection. mysqld refuses to start as root without --user=root, +# so skip the test when running as root. +--source include/not_as_root.inc CREATE PROCEDURE show_defaultable_fields() SELECT connection_name, @@ -104,21 +108,16 @@ CHANGE MASTER 'unset' TO master_host='127.0.1.3'; --echo # Validate command line options # Invalid `--master-heartbeat-period` values should abort the server -# (`restart_abort` includes a wait for the server to exit on its own, -# e.g., due to a startup error.) --source include/shutdown_mysqld.inc ---let $restart_parameters= restart_abort: --master-heartbeat-period='' ---echo # $restart_parameters ---write_line "$restart_parameters" $_expect_file_name ---let $restart_parameters= restart_abort: --master-heartbeat-period=123abc ---echo # $restart_parameters ---write_line "$restart_parameters" $_expect_file_name ---let $restart_parameters= restart_abort: --master-heartbeat-period=-1 ---echo # $restart_parameters ---write_line "$restart_parameters" $_expect_file_name ---let $restart_parameters= restart_abort: --master-heartbeat-period=4294967.296 ---echo # $restart_parameters ---write_line "$restart_parameters" $_expect_file_name + +--disable_result_log +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --master-heartbeat-period=123abc +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --master-heartbeat-period=-1 +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --master-heartbeat-period=4294967.296 +--enable_result_log # Numbers between 0 and 0.5 exclusive should warn about rounding to 0 (disabled) --let $restart_parameters= --skip-slave-start --master-heartbeat-period=0.000499 From 77c8f791618136002e5724ca81f365134b583a29 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 13 Aug 2026 16:48:08 +1000 Subject: [PATCH 135/150] MDEV-40749 period.create test leaks/faults in asan Selecting from the information_schema.plugins causes the loading of all plugins. Because rockdb leaks, and duckdb triggers an address sanitizer warning on shutdown avoid this table. Use the information_schema.ENGINES to validate that InnoDB is disabled per the original request in the review of MDEV-32205. --- mysql-test/suite/period/r/create.result | 6 +++--- mysql-test/suite/period/t/create.test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql-test/suite/period/r/create.result b/mysql-test/suite/period/r/create.result index 865861e97a0..93e98577f31 100644 --- a/mysql-test/suite/period/r/create.result +++ b/mysql-test/suite/period/r/create.result @@ -132,9 +132,9 @@ drop table t2; select "yes" from information_schema.tables where engine="innodb" limit 1; yes yes -select plugin_status from information_schema.all_plugins where plugin_name = "innodb"; -plugin_status -DISABLED +select SUPPORT from information_schema.engines where ENGINE='InnoDB'; +SUPPORT +NO select * from information_schema.periods; TABLE_CATALOG TABLE_SCHEMA TABLE_NAME PERIOD START_COLUMN_NAME END_COLUMN_NAME select * from information_schema.key_period_usage; diff --git a/mysql-test/suite/period/t/create.test b/mysql-test/suite/period/t/create.test index 537a1dc05c3..072befa44f5 100644 --- a/mysql-test/suite/period/t/create.test +++ b/mysql-test/suite/period/t/create.test @@ -113,7 +113,7 @@ drop table t2; --echo # Make sure innodb id disabled, but there's at least one innodb table --disable_warnings select "yes" from information_schema.tables where engine="innodb" limit 1; -select plugin_status from information_schema.all_plugins where plugin_name = "innodb"; +select SUPPORT from information_schema.engines where ENGINE='InnoDB'; select * from information_schema.periods; select * from information_schema.key_period_usage; --enable_warnings From 91156c3de58762196605150ab3f756fe952592d1 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Fri, 14 Aug 2026 11:04:12 +0400 Subject: [PATCH 136/150] MDEV-40639 Open SYS_REFCURSOR crash, if many cursors inside a function Problem: If: - A routine A() opened a SYS_REFCURSOR with a function B() in the SELECT list - The function B() also opened some SYS_REFCURSORs Then reallocation of the cursor array THD::m_statement_cursors could happen during the execution of B(), so all sp_cursor_array_element pointers inside sp_instr_copen_by_ref::exec_core() of routine A() became invalid. Fix: Chaging the data type of sp_cursor_array: - from Dynamic_array - to Dynamic_array So now only reallocations of the array of cursor pointers happen, while sp_cursor_array_element instances always stay on their originally allocated memory positions. Note: sp_cursor_array_element instances are allocated using the standart C++ "new" and deleted using the standard C++ "delete". Using a MEM_ROOT does not seem to be relevant here. --- mysql-test/main/sp-sys_refcursor.result | 53 +++++++++++++++++++++ mysql-test/main/sp-sys_refcursor.test | 61 +++++++++++++++++++++++++ sql/sp_cursor.cc | 11 +++-- sql/sp_cursor.h | 19 +++++--- sql/sp_rcontext.cc | 2 +- 5 files changed, 134 insertions(+), 12 deletions(-) diff --git a/mysql-test/main/sp-sys_refcursor.result b/mysql-test/main/sp-sys_refcursor.result index 0ef4eb4a3c2..77d82f3d085 100644 --- a/mysql-test/main/sp-sys_refcursor.result +++ b/mysql-test/main/sp-sys_refcursor.result @@ -442,3 +442,56 @@ END$ a 116 SET @@max_open_cursors = DEFAULT; +# +# MDEV-40639 Open SYS_REFCURSOR crash, if many cursors inside a function +# +CREATE FUNCTION trigger_realloc() RETURNS INT +BEGIN +DECLARE c1 SYS_REFCURSOR; +DECLARE c2 SYS_REFCURSOR; +DECLARE c3 SYS_REFCURSOR; +DECLARE c4 SYS_REFCURSOR; +DECLARE c5 SYS_REFCURSOR; +DECLARE c6 SYS_REFCURSOR; +DECLARE c7 SYS_REFCURSOR; +DECLARE c8 SYS_REFCURSOR; +DECLARE c9 SYS_REFCURSOR; +DECLARE c10 SYS_REFCURSOR; +DECLARE c11 SYS_REFCURSOR; +DECLARE c12 SYS_REFCURSOR; +DECLARE c13 SYS_REFCURSOR; +DECLARE c14 SYS_REFCURSOR; +DECLARE c15 SYS_REFCURSOR; +DECLARE c16 SYS_REFCURSOR; +DECLARE c17 SYS_REFCURSOR; +OPEN c1 FOR SELECT 1; +OPEN c2 FOR SELECT 1; +OPEN c3 FOR SELECT 1; +OPEN c4 FOR SELECT 1; +OPEN c5 FOR SELECT 1; +OPEN c6 FOR SELECT 1; +OPEN c7 FOR SELECT 1; +OPEN c8 FOR SELECT 1; +OPEN c9 FOR SELECT 1; +OPEN c10 FOR SELECT 1; +OPEN c11 FOR SELECT 1; +OPEN c12 FOR SELECT 1; +OPEN c13 FOR SELECT 1; +OPEN c14 FOR SELECT 1; +OPEN c15 FOR SELECT 1; +OPEN c16 FOR SELECT 1; +OPEN c17 FOR SELECT 1; +RETURN 1; +END; +$$ +CREATE PROCEDURE trigger_uaf() +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR SELECT trigger_realloc(); +END; +$$ +SET SESSION max_open_cursors= 100; +CALL trigger_uaf(); +SET SESSION max_open_cursors= DEFAULT; +DROP PROCEDURE trigger_uaf; +DROP FUNCTION trigger_realloc; diff --git a/mysql-test/main/sp-sys_refcursor.test b/mysql-test/main/sp-sys_refcursor.test index ed576c9a1cb..73dab262077 100644 --- a/mysql-test/main/sp-sys_refcursor.test +++ b/mysql-test/main/sp-sys_refcursor.test @@ -466,3 +466,64 @@ BEGIN NOT ATOMIC END$ delimiter ;$ SET @@max_open_cursors = DEFAULT; + + +--echo # +--echo # MDEV-40639 Open SYS_REFCURSOR crash, if many cursors inside a function +--echo # + +DELIMITER $$; +# Function that forces cursor array reallocation (17 > default 16 slots) +CREATE FUNCTION trigger_realloc() RETURNS INT +BEGIN + DECLARE c1 SYS_REFCURSOR; + DECLARE c2 SYS_REFCURSOR; + DECLARE c3 SYS_REFCURSOR; + DECLARE c4 SYS_REFCURSOR; + DECLARE c5 SYS_REFCURSOR; + DECLARE c6 SYS_REFCURSOR; + DECLARE c7 SYS_REFCURSOR; + DECLARE c8 SYS_REFCURSOR; + DECLARE c9 SYS_REFCURSOR; + DECLARE c10 SYS_REFCURSOR; + DECLARE c11 SYS_REFCURSOR; + DECLARE c12 SYS_REFCURSOR; + DECLARE c13 SYS_REFCURSOR; + DECLARE c14 SYS_REFCURSOR; + DECLARE c15 SYS_REFCURSOR; + DECLARE c16 SYS_REFCURSOR; + DECLARE c17 SYS_REFCURSOR; + OPEN c1 FOR SELECT 1; + OPEN c2 FOR SELECT 1; + OPEN c3 FOR SELECT 1; + OPEN c4 FOR SELECT 1; + OPEN c5 FOR SELECT 1; + OPEN c6 FOR SELECT 1; + OPEN c7 FOR SELECT 1; + OPEN c8 FOR SELECT 1; + OPEN c9 FOR SELECT 1; + OPEN c10 FOR SELECT 1; + OPEN c11 FOR SELECT 1; + OPEN c12 FOR SELECT 1; + OPEN c13 FOR SELECT 1; + OPEN c14 FOR SELECT 1; + OPEN c15 FOR SELECT 1; + OPEN c16 FOR SELECT 1; + OPEN c17 FOR SELECT 1; + RETURN 1; +END; +$$ + +CREATE PROCEDURE trigger_uaf() +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR SELECT trigger_realloc(); +END; +$$ +DELIMITER ;$$ + +SET SESSION max_open_cursors= 100; +CALL trigger_uaf(); +SET SESSION max_open_cursors= DEFAULT; +DROP PROCEDURE trigger_uaf; +DROP FUNCTION trigger_realloc; diff --git a/sql/sp_cursor.cc b/sql/sp_cursor.cc index f020f770e47..d983682b9bd 100644 --- a/sql/sp_cursor.cc +++ b/sql/sp_cursor.cc @@ -48,8 +48,11 @@ bool sp_cursor::check_for_open(THD *thd, bool check_open_cursor_counter) const */ Type_ref_null sp_cursor_array::append(THD *thd) { - if (Dynamic_array::append(sp_cursor_array_element())) + sp_cursor_array_element *elem= new sp_cursor_array_element(); + if (!elem || Dynamic_array::append(elem)) { + if (!elem) + my_error(ER_OUTOFMEMORY, MYF(0), (int) sizeof(sp_cursor_array_element)); DBUG_ASSERT(thd->is_error()); return Type_ref_null(); } @@ -70,7 +73,7 @@ sp_cursor_array_element *sp_cursor_array::get_cursor_by_ref(THD *thd, { Type_ref_null ref= ref_field->val_ref(thd); if (ref < (ulonglong) elements()) - return &at((size_t) ref.value());// "ref" points to an initialized sp_cursor + return at((size_t) ref.value());// "ref" points to an initialized sp_cursor if (!for_open) return nullptr; @@ -88,8 +91,8 @@ sp_cursor_array_element *sp_cursor_array::get_cursor_by_ref(THD *thd, ref_field->store_ref(ref, true/*no_conversions*/)) return nullptr; - at((size_t) ref.value()).reset(thd, 1/*ref count*/); - return &at((size_t) ref.value()); + at((size_t) ref.value())->reset(thd, 1/*ref count*/); + return at((size_t) ref.value()); } #endif // MYSQL_SERVER diff --git a/sql/sp_cursor.h b/sql/sp_cursor.h index 6f4479b7291..21c5ff8a4ff 100644 --- a/sql/sp_cursor.h +++ b/sql/sp_cursor.h @@ -193,14 +193,14 @@ public: }; -class sp_cursor_array: public Dynamic_array +class sp_cursor_array: public Dynamic_array { protected: Type_ref_null find_unused() { for (size_t i= 0 ; i < size(); i++) { - if (!at(i).is_open() && !at(i).ref_count()) + if (!at(i)->is_open() && !at(i)->ref_count()) return Type_ref_null((ulonglong) i); } return Type_ref_null(); @@ -220,20 +220,20 @@ public: ULonglong_null ref_count(ulonglong offset) const { return offset < elements() ? - ULonglong_null((ulonglong) at((size_t) offset).ref_count()) : + ULonglong_null((ulonglong) at((size_t) offset)->ref_count()) : ULonglong_null(); } void ref_count_inc(ulonglong offset) { if (offset < elements()) - at((size_t) offset).ref_count_inc(); + at((size_t) offset)->ref_count_inc(); } void ref_count_dec(THD *thd, ulonglong offset) { if (offset < elements()) - at((size_t) offset).ref_count_dec(thd); + at((size_t) offset)->ref_count_dec(thd); } void ref_count_update(THD *thd, const Type_ref_null &old_value, @@ -281,13 +281,18 @@ public: { for (uint i= 0; i < (uint) size(); i++) { - if (at(i).is_open()) - at(i).close(thd); + if (at(i)->is_open()) + at(i)->close(thd); } } void free(THD *thd) { close(thd); + for (uint i= 0; i < (uint) size(); i++) + { + DBUG_ASSERT(at(i)); + delete at(i); + } free_memory(); } }; diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index aae0340595f..472f3ff7de6 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -109,7 +109,7 @@ sp_cursor *Sp_rcontext_handler_local::get_cursor(THD *thd, uint offset) const sp_cursor *Sp_rcontext_handler_statement::get_cursor(THD *thd, uint offset) const { - return &thd->statement_cursors()->at(offset); + return thd->statement_cursors()->at(offset); } sp_cursor *Sp_rcontext_handler_statement::get_cursor_by_ref(THD *thd, From 3ad0455a2651a993b3cd8dfceeb893bfb1218a0e Mon Sep 17 00:00:00 2001 From: "Georgi (Joro) Kodinov" Date: Fri, 7 Aug 2026 12:59:55 +0300 Subject: [PATCH 137/150] MDEV-40661: mysql_upgrade.test not stable on a busy server The cleanup of the old connection goes on in the background. It can take longer on a busy server and this triggers the active sessions warning in DROP user. Stablizied the test by disabling the warnings. --- mysql-test/main/mysql_upgrade.test | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mysql-test/main/mysql_upgrade.test b/mysql-test/main/mysql_upgrade.test index 86d237438c8..7aacc37e3a9 100644 --- a/mysql-test/main/mysql_upgrade.test +++ b/mysql-test/main/mysql_upgrade.test @@ -65,7 +65,9 @@ GRANT ALL ON *.* TO mysqltest1@'%'; --echo Run mysql_upgrade with password protected account --exec $MYSQL_UPGRADE --force --user=mysqltest1 --password=sakila 2>&1 +--disable_warnings DROP USER mysqltest1@'%'; +--enable_warnings # # check that we get proper error messages if wrong user @@ -128,7 +130,9 @@ GRANT ALL PRIVILEGES ON `roelt`.`test2` TO 'user3'@'%'; --exec $MYSQL_UPGRADE --force 2>&1 SHOW GRANTS FOR 'user3'@'%'; +--disable_warnings DROP USER 'user3'@'%'; +--enable_warnings --echo # End of 5.1 tests @@ -192,7 +196,9 @@ CREATE PROCEDURE test.pr() BEGIN END; SELECT definer FROM mysql.proc WHERE db = 'test' AND name = 'pr'; SELECT grantor FROM mysql.tables_priv WHERE db = 'mysql' AND table_name = 'user'; +--disable_warnings DROP USER very_long_user_name_number_1, very_long_user_name_number_2, even_longer_user_name_number_3_to_test_the_grantor_and_definer_field_length@localhost; +--enable_warnings DROP PROCEDURE test.pr; --enable_service_connection # @@ -406,7 +412,9 @@ alter table mysql.db drop column Delete_history_priv; --remove_file $MYSQLD_DATADIR/mariadb_upgrade_info flush privileges; SHOW GRANTS FOR 'user3'@'%'; +--disable_warnings DROP USER 'user3'@'%'; +--enable_warnings update mysql.db set Delete_history_priv='Y' where db like 'test%'; drop table mysql.global_priv; rename table mysql.global_priv_bak to mysql.global_priv; @@ -433,7 +441,9 @@ connect con1,localhost,user3,a_password; select current_user(); disconnect con1; connection default; +--disable_warnings drop user user3@localhost; +--enable_warnings drop table mysql.global_priv; rename table mysql.global_priv_bak to mysql.global_priv; @@ -574,7 +584,9 @@ show grants for foo@bar; select show_create_routine_priv from mysql.db where user='foo'; show grants for foo@bar; +--disable_warnings drop user foo@bar; +--enable_warnings --echo # End of 11.3 tests From add63991988734383c5e942de2a19c6c45f511f7 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Mon, 17 Aug 2026 16:27:38 +0400 Subject: [PATCH 138/150] MDEV-40790 SELECT INTO row_type_of.field crashes the server The server crashed on DBUG_ASSERT on a SELECT into: - a `ROW TYPE OF table1` field variable - a `ROW TYPE OF cursor1` field variable Fix: - Adding a class my_var_sp_row_field_by_name - Adding a method sp_rcontext::set_variable_row_field_by_name() - Fixing the DBUG_ASSERT --- mysql-test/main/select_into_row.result | 140 ++++++++++++++++++++ mysql-test/main/select_into_row.test | 174 +++++++++++++++++++++++++ sql/sp_rcontext.cc | 14 ++ sql/sp_rcontext.h | 3 + sql/sql_type_row.cc | 38 +++++- 5 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/select_into_row.result create mode 100644 mysql-test/main/select_into_row.test diff --git a/mysql-test/main/select_into_row.result b/mysql-test/main/select_into_row.result new file mode 100644 index 00000000000..98315c114d1 --- /dev/null +++ b/mysql-test/main/select_into_row.result @@ -0,0 +1,140 @@ +# +# MDEV-40790 SELECT INTO row_type_of.field crashes the server +# +# Into an explicit ROW variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into an explicit ROW field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into an explicit ROW field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW (a INT, b VARCHAR(10)); +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP TABLE t1; +# Into a `ROW TYPE OF table1` variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF table1` field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF table1` field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE r0 ROW TYPE OF t1; +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +CALL p1; +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0 FROM t1; +SELECT r0.a, r0.b; +END; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` field variable +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0.a,r0.b FROM t1; +SELECT r0.a, r0.b; +END; +END; +$$ +CALL p1; +r0.a r0.b +10 b10 +DROP PROCEDURE p1; +DROP TABLE t1; +# Into a `ROW TYPE OF cursor1` field variable - non-existing field +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +CREATE PROCEDURE p1() +BEGIN +DECLARE c1 CURSOR FOR SELECT * FROM t1; +BEGIN +DECLARE r0 ROW TYPE OF c1; +SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +END; +$$ +CALL p1; +ERROR HY000: Row variable 'r0' does not have a field 'b1_non_existing' +DROP PROCEDURE p1; +DROP TABLE t1; +# End of 12.3 tests diff --git a/mysql-test/main/select_into_row.test b/mysql-test/main/select_into_row.test new file mode 100644 index 00000000000..79127eca21b --- /dev/null +++ b/mysql-test/main/select_into_row.test @@ -0,0 +1,174 @@ +--echo # +--echo # MDEV-40790 SELECT INTO row_type_of.field crashes the server +--echo # + + +--echo # Into an explicit ROW variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into an explicit ROW field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into an explicit ROW field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW (a INT, b VARCHAR(10)); + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +DELIMITER ;$$ +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF table1` field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE r0 ROW TYPE OF t1; + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0 FROM t1; + SELECT r0.a, r0.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` field variable + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0.a,r0.b FROM t1; + SELECT r0.a, r0.b; + END; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # Into a `ROW TYPE OF cursor1` field variable - non-existing field + +CREATE TABLE t1 (a INT,b VARCHAR(10)); +INSERT INTO t1 VALUES (10,'b10'); +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE c1 CURSOR FOR SELECT * FROM t1; + BEGIN + DECLARE r0 ROW TYPE OF c1; + SELECT a,b INTO r0.a,r0.b1_non_existing FROM t1; + END; +END; +$$ +DELIMITER ;$$ +--error ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD +CALL p1; +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # End of 12.3 tests diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 472f3ff7de6..2ae352b83ea 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -694,6 +694,20 @@ int sp_rcontext::set_variable_row_field(THD *thd, uint var_idx, uint field_idx, } +int sp_rcontext::set_variable_row_field_by_name(THD *thd, uint var_idx, + const Lex_ident_sys_st &field_name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_row_field"); + DBUG_ASSERT(value); + uint field_idx= 0; + if (find_row_field_by_name_or_error(&field_idx, var_idx, field_name)) + DBUG_RETURN(true); + Virtual_tmp_table *vtable= virtual_tmp_table_for_row(var_idx); + DBUG_RETURN(thd->sp_eval_expr(vtable->field[field_idx], value)); +} + + int sp_rcontext::set_variable_row(THD *thd, uint var_idx, List &items) { DBUG_ENTER("sp_rcontext::set_variable_row"); diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 484d0e000ff..4d55f475e6b 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -209,6 +209,9 @@ public: int set_variable(THD *thd, uint var_idx, Item **value); int set_variable_row_field(THD *thd, uint var_idx, uint field_idx, Item **value); + int set_variable_row_field_by_name(THD *thd, uint var_idx, + const Lex_ident_sys_st &field_name, + Item **value); int set_variable_row(THD *thd, uint var_idx, List &items); int set_variable_composite_field_by_key(THD *thd, diff --git a/sql/sql_type_row.cc b/sql/sql_type_row.cc index c72cab62075..c81e13d0636 100644 --- a/sql/sql_type_row.cc +++ b/sql/sql_type_row.cc @@ -135,6 +135,32 @@ public: }; +class my_var_sp_row_field_by_name: public my_var_sp +{ + const Lex_ident_sys_st m_field_name; +public: + my_var_sp_row_field_by_name(const Lex_ident_sys_st &varname, + const sp_rcontext_addr &varaddr, + const Lex_ident_sys_st &field_name, + sp_head *s) + :my_var_sp(varname, varaddr, + &type_handler_double/*Not really used*/, s), + m_field_name(field_name) + { } + bool check_assignability(THD *thd, const List &select_list, + bool *assign_as_row) const override + { + *assign_as_row= false; + return select_list.elements == 1; + } + bool set(THD *thd, Item *item) override + { + return get_rcontext(thd->spcont)-> + set_variable_row_field_by_name(thd, offset(), m_field_name, &item); + } +}; + + my_var *Type_handler_row::make_outvar(THD *thd, const Lex_ident_sys_st &name, const sp_rcontext_addr &addr, @@ -159,11 +185,19 @@ my_var *Type_handler_row::make_outvar_field(THD *thd, DBUG_ASSERT(t); DBUG_ASSERT(t->type_handler() == this); + if (t->field_def.is_table_rowtype_ref() || + t->field_def.is_cursor_rowtype_ref()) + { + if (validate_only) + return nullptr; + return new (thd->mem_root) my_var_sp_row_field_by_name(name, addr, + field, sphead); + + } uint row_field_offset; if (!t->find_row_field(&name, &field, &row_field_offset)) { - DBUG_ASSERT(0); - my_error(ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD, MYF(0), name.str, field.str); + DBUG_ASSERT(thd->is_error()); return NULL; } if (validate_only) // e.g. EXPLAIN SELECT .. INTO spvar_row.field; From 1a052f27e374fc9b4cb5b1fd098cf10f0e1381cc Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 18 Aug 2026 11:18:13 +1000 Subject: [PATCH 139/150] rocksdb: don't abort early in submodule update Fix for 1fb075512a7aeab8646a163cbb6f265c49f4c075 to allow the ADD_SUBMODULE to perform updates. --- storage/rocksdb/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/storage/rocksdb/CMakeLists.txt b/storage/rocksdb/CMakeLists.txt index 5be48c507ea..ddca7d156f3 100644 --- a/storage/rocksdb/CMakeLists.txt +++ b/storage/rocksdb/CMakeLists.txt @@ -19,10 +19,6 @@ MACRO(SKIP_ROCKSDB_PLUGIN msg) RETURN() ENDMACRO() -IF (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/CMakeLists.txt") - SKIP_ROCKSDB_PLUGIN("Missing CMakeLists.txt in rocksdb directory. Try \"git submodule update\".") -ENDIF() - CHECK_LIBRARY_EXISTS(rt timer_delete "" HAVE_TIMER_DELETE) IF (HAVE_TIMER_DELETE) ADD_DEFINITIONS(-DHAVE_TIMER_DELETE) From 93e051860a9c7e87ee8cee6ed38b640d491f7170 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 18 Aug 2026 14:15:49 +1000 Subject: [PATCH 140/150] MDEV-40801 ppc64le ro_after_init isn't pagesize aligned Align ro_after_init using MAXPAGESIZE instead of COMMONPAGESIZE. COMMONPAGESIZE may be smaller than the actual page size supported by the target ABI. This can leave ro_after_init sharing an OS page with adjacent sections, causing mprotect() to change permissions on data outside ro_after_init. Use MAXPAGESIZE so the section boundaries are aligned to the maximum page size required by the target linker/ABI. This is particularly important on architectures such as ppc64le and aarch64, where the runtime page size can differ from COMMONPAGESIZE. Before: .data 0x...1b80000 ro_after_init 0x...1c70000 .bss 0x...1c72000 After: ro_after_init starts and ends on MAXPAGESIZE boundaries, ensuring mprotect() only affects pages belonging to ro_after_init. Co-authored-by: ChatGPT GPT-5.6 Luna --- sql/mysqld_ro.lds | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/mysqld_ro.lds b/sql/mysqld_ro.lds index 5777feeeabc..4698c1daa55 100644 --- a/sql/mysqld_ro.lds +++ b/sql/mysqld_ro.lds @@ -1,5 +1,5 @@ /* - Copyright (c) 2026, MariaDB plc. + Copyright (c) 2026, MariaDB plc, MariaDB Foundation 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 @@ -21,11 +21,11 @@ */ SECTIONS { - ro_after_init ALIGN(CONSTANT(COMMONPAGESIZE)) : + ro_after_init ALIGN(CONSTANT(MAXPAGESIZE)) : { ro_after_init_start = .; *(ro_after_init) - . = ALIGN(CONSTANT(COMMONPAGESIZE)); + . = ALIGN(CONSTANT(MAXPAGESIZE)); ro_after_init_end = .; } } INSERT AFTER .data; From 9d916c463cd695034d603fb74329df6db4abc3b7 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Mon, 17 Aug 2026 13:48:21 +1000 Subject: [PATCH 141/150] MDEV-40486 [fixup] Clamp max_length at MAX_FIELD_VARCHARLENGTH in Item_func_vec_fromtext::fix_length_and_dec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows create table t1 (v vector(64) not null); insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; which was banned in the previous fix bb0ac437015dec04fbee226745a8eb2bb4825917, though this also introduces the inconsistency(?) where create table t1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; still fails ER_TRUNCATED_WRONG_VALUE see updated tests TODO: - Changes in vector_utf16.result does not look right - the following tests crashes: CREATE TABLE t1 (v VECTOR(2)); --error ER_TOO_BIG_FIELDLENGTH INSERT INTO t1 VALUES (VEC_FROMTEXT(CONCAT('[1.', REPEAT('0',70000), ',2]'))); DROP TABLE t1; SELECT VEC_FROMTEXT('😀😀😀'); --- mysql-test/main/vector2.result | 13 +++++++------ mysql-test/main/vector2.test | 14 ++++++++------ mysql-test/main/vector_utf16.result | Bin 469 -> 425 bytes sql/item_vectorfunc.cc | 10 ++++------ 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/mysql-test/main/vector2.result b/mysql-test/main/vector2.result index c4ff5eb7793..3974af1814e 100644 --- a/mysql-test/main/vector2.result +++ b/mysql-test/main/vector2.result @@ -160,7 +160,7 @@ drop table t; # MDEV-35141 Server crashes in Field_vector::report_wrong_value upon statistic collection # create table t1 (v vector(64) not null); -insert into t1 select vec_fromtext(cast(concat('[',group_concat(1),']') as char(130))) from seq_1_to_64; +insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; analyze table t1 persistent for all; Table Op Msg_type Msg_text test.t1 analyze status Engine-independent statistics collected @@ -571,11 +571,11 @@ set sql_mode=@old_sql_mode; ## Original testcase CREATE TABLE t (a TEXT) AS SELECT '[1]' AS a; CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; -ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +ERROR 22007: Incorrect vector value: '\x00\x00\x80?' for column `test`.`tt`.`f` at row 1 DROP TABLE t; CREATE TABLE t (a LONGBLOB) AS SELECT '[1]' AS a; CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; -ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +ERROR 22007: Incorrect vector value: '\x00\x00\x80?' for column `test`.`tt`.`f` at row 1 DROP TABLE t; ## Another case, which would have failed with ERROR 1292 ## without the fix @@ -592,7 +592,7 @@ SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16382), ']')) AS f; DROP TABLE tt; CREATE TABLE tt AS SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; -ERROR 42000: Column length too big for column 'f' (max = 16383); use BLOB or TEXT instead +ERROR 22007: Incorrect vector value: '\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00...' for column `test`.`tt`.`f` at row 1 ## "Zero-dimensional" argument, no change in behaviour after fix SELECT VEC_FROMTEXT('[]') as f; f @@ -625,9 +625,10 @@ t2 CREATE TABLE `t2` ( DROP TABLE t1, t2; CREATE TABLE t3 (f VECTOR(0)); ERROR 42000: Incorrect column specifier for column 'f' -## Fails because concat('[',group_concat(1),']') is mediumblob create view v1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; -ERROR 42000: Column length too big for column 'vec_fromtext(concat('[',group_concat(1),']'))' (max = 16383); use BLOB or TEXT instead +DROP view v1; +create table t1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; +ERROR 22007: Incorrect vector value: '\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00...' for column `test`.`t1`.`vec_fromtext(concat('[',group_concat(1),']'))` at row 65 ## NULLs select vec_fromtext(NULL); vec_fromtext(NULL) diff --git a/mysql-test/main/vector2.test b/mysql-test/main/vector2.test index f187559365d..1cbb377a6bd 100644 --- a/mysql-test/main/vector2.test +++ b/mysql-test/main/vector2.test @@ -125,7 +125,7 @@ drop table t; --echo # MDEV-35141 Server crashes in Field_vector::report_wrong_value upon statistic collection --echo # create table t1 (v vector(64) not null); -insert into t1 select vec_fromtext(cast(concat('[',group_concat(1),']') as char(130))) from seq_1_to_64; +insert into t1 select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; analyze table t1 persistent for all; drop table t1; @@ -453,12 +453,12 @@ set sql_mode=@old_sql_mode; --echo ## Original testcase CREATE TABLE t (a TEXT) AS SELECT '[1]' AS a; ---error ER_TOO_BIG_FIELDLENGTH +--error ER_TRUNCATED_WRONG_VALUE CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; DROP TABLE t; CREATE TABLE t (a LONGBLOB) AS SELECT '[1]' AS a; ---error ER_TOO_BIG_FIELDLENGTH +--error ER_TRUNCATED_WRONG_VALUE CREATE TABLE tt AS SELECT VEC_FROMTEXT(a) AS f FROM t; DROP TABLE t; @@ -478,7 +478,7 @@ CREATE TABLE tt AS SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16382), ']')) AS f; DROP TABLE tt; ---error ER_TOO_BIG_FIELDLENGTH +--error ER_TRUNCATED_WRONG_VALUE CREATE TABLE tt AS SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; @@ -503,9 +503,11 @@ DROP TABLE t1, t2; --error ER_WRONG_FIELD_SPEC CREATE TABLE t3 (f VECTOR(0)); ---echo ## Fails because concat('[',group_concat(1),']') is mediumblob ---error ER_TOO_BIG_FIELDLENGTH create view v1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; +DROP view v1; + +--error ER_TRUNCATED_WRONG_VALUE +create table t1 as select vec_fromtext(concat('[',group_concat(1),']')) from seq_1_to_64; --echo ## NULLs select vec_fromtext(NULL); diff --git a/mysql-test/main/vector_utf16.result b/mysql-test/main/vector_utf16.result index 24d6977beb0ad0a3eb721be4a33f5943dc8ba8e0..2e493dc30051fb19012c4f8aef306a6f448d50ca 100644 GIT binary patch delta 26 icmcc0ypnlB5szP}j}KRIYEEi$iNfRtM*E2`-v9u8FA6aL delta 62 ocmZ3!iThsx06anph5!Hn diff --git a/sql/item_vectorfunc.cc b/sql/item_vectorfunc.cc index 12bd63340be..108dd1414f6 100644 --- a/sql/item_vectorfunc.cc +++ b/sql/item_vectorfunc.cc @@ -196,12 +196,7 @@ bool Item_func_vec_fromtext::fix_length_and_dec(THD *thd) else maxlen= (maxlen - 1) * 2; fix_length_and_charset(maxlen, &my_charset_bin); - if (max_length > MAX_FIELD_VARCHARLENGTH) - { - my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), name.str, - static_cast(MAX_FIELD_VARCHARLENGTH / sizeof(float))); - return true; - } + set_if_smaller(max_length, MAX_FIELD_VARCHARLENGTH); set_maybe_null(); return false; } @@ -215,6 +210,9 @@ String *Item_func_vec_fromtext::val_str(String *buf) if ((null_value= !value)) return nullptr; + if (value->length() > max_length) + return nullptr; + buf->length(0); buf->set_charset(&my_charset_bin); CHARSET_INFO *cs= value->charset(); From 3436bd69ec0194229a26b6ff8df13abc5c152319 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Mon, 17 Aug 2026 17:24:31 +1000 Subject: [PATCH 142/150] MDEV-40486 [to-squash] Move the new lneght check in Item_func_vec_fromtext::val_str to later This fixes the crashes mentioned in the parent commit --- mysql-test/main/vector2.result | 10 +++++++++- mysql-test/main/vector2.test | 6 ++++++ mysql-test/main/vector_utf16.result | Bin 425 -> 469 bytes sql/item_vectorfunc.cc | 6 ++---- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/mysql-test/main/vector2.result b/mysql-test/main/vector2.result index 3974af1814e..789dc3a8dae 100644 --- a/mysql-test/main/vector2.result +++ b/mysql-test/main/vector2.result @@ -592,7 +592,15 @@ SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16382), ']')) AS f; DROP TABLE tt; CREATE TABLE tt AS SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; -ERROR 22007: Incorrect vector value: '\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00...' for column `test`.`tt`.`f` at row 1 +ERROR 22007: Truncated incorrect vector value: '[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,...' +CREATE TABLE t1 (v VECTOR(2)); +INSERT INTO t1 VALUES (VEC_FROMTEXT(CONCAT('[1.', REPEAT('0',70000), ',2]'))); +DROP TABLE t1; +SELECT VEC_FROMTEXT('😀😀😀'); +VEC_FROMTEXT('😀😀😀') +NULL +Warnings: +Warning 4038 Syntax error in JSON text in argument 1 to function 'VEC_FromText' at position 1 ## "Zero-dimensional" argument, no change in behaviour after fix SELECT VEC_FROMTEXT('[]') as f; f diff --git a/mysql-test/main/vector2.test b/mysql-test/main/vector2.test index 1cbb377a6bd..7ef042d49df 100644 --- a/mysql-test/main/vector2.test +++ b/mysql-test/main/vector2.test @@ -482,6 +482,12 @@ DROP TABLE tt; CREATE TABLE tt AS SELECT VEC_FROMTEXT(concat('[1', repeat(',1', 16383), ']')) AS f; +CREATE TABLE t1 (v VECTOR(2)); +INSERT INTO t1 VALUES (VEC_FROMTEXT(CONCAT('[1.', REPEAT('0',70000), ',2]'))); +DROP TABLE t1; + +SELECT VEC_FROMTEXT('😀😀😀'); + --echo ## "Zero-dimensional" argument, no change in behaviour after fix SELECT VEC_FROMTEXT('[]') as f; --error ER_TRUNCATED_WRONG_VALUE diff --git a/mysql-test/main/vector_utf16.result b/mysql-test/main/vector_utf16.result index 2e493dc30051fb19012c4f8aef306a6f448d50ca..24d6977beb0ad0a3eb721be4a33f5943dc8ba8e0 100644 GIT binary patch delta 62 ocmZ3!iThsx06anph5!Hn delta 26 icmcc0ypnlB5szP}j}KRIYEEi$iNfRtM*E2`-v9u8FA6aL diff --git a/sql/item_vectorfunc.cc b/sql/item_vectorfunc.cc index 108dd1414f6..a71ee25379b 100644 --- a/sql/item_vectorfunc.cc +++ b/sql/item_vectorfunc.cc @@ -210,9 +210,6 @@ String *Item_func_vec_fromtext::val_str(String *buf) if ((null_value= !value)) return nullptr; - if (value->length() > max_length) - return nullptr; - buf->length(0); buf->set_charset(&my_charset_bin); CHARSET_INFO *cs= value->charset(); @@ -262,7 +259,8 @@ String *Item_func_vec_fromtext::val_str(String *buf) if (!end_ok) goto error_format; - if (Type_handler_vector::is_valid(buf->ptr(), buf->length())) + if (buf->length() <= MAX_FIELD_VARCHARLENGTH && + Type_handler_vector::is_valid(buf->ptr(), buf->length())) return buf; null_value= true; From bf9193a939f515e95dd8def1a5468088c91cede6 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Mon, 17 Aug 2026 18:02:04 +1000 Subject: [PATCH 143/150] MDEV-40751 Make sure that VEC_FROMTEXT results in a length of multiple of 4 --- mysql-test/main/vector2.result | 12 ++++++++++++ mysql-test/main/vector2.test | 10 ++++++++++ sql/item_vectorfunc.cc | 19 ++++++++----------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/mysql-test/main/vector2.result b/mysql-test/main/vector2.result index 789dc3a8dae..5e9d073240c 100644 --- a/mysql-test/main/vector2.result +++ b/mysql-test/main/vector2.result @@ -703,4 +703,16 @@ ERROR 22007: Incorrect vector value: '' for column `test`.`t1`.`vec_fromtext(?)` execute p1 using ''; ERROR HY000: Unexpected end of JSON text in argument 1 to function 'VEC_FromText' deallocate prepare p1; +# +# MDEV-40751 CREATE ... SELECT VEC_FROMTEXT(...) may create VECTOR columns that reject every value +# +CREATE TABLE t1 (a CHAR(4)); +create table t2 select vec_fromtext(a) from t1; +show create table t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `vec_fromtext(a)` vector(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +insert into t2 values (vec_fromtext('[1]')); +DROP TABLE t1, t2; # End of 11.8 tests diff --git a/mysql-test/main/vector2.test b/mysql-test/main/vector2.test index 7ef042d49df..3bf9de064cb 100644 --- a/mysql-test/main/vector2.test +++ b/mysql-test/main/vector2.test @@ -560,4 +560,14 @@ execute p1 using '[]'; execute p1 using ''; deallocate prepare p1; +--echo # +--echo # MDEV-40751 CREATE ... SELECT VEC_FROMTEXT(...) may create VECTOR columns that reject every value +--echo # + +CREATE TABLE t1 (a CHAR(4)); +create table t2 select vec_fromtext(a) from t1; +show create table t2; +insert into t2 values (vec_fromtext('[1]')); +DROP TABLE t1, t2; + --echo # End of 11.8 tests diff --git a/sql/item_vectorfunc.cc b/sql/item_vectorfunc.cc index a71ee25379b..89e8f82e112 100644 --- a/sql/item_vectorfunc.cc +++ b/sql/item_vectorfunc.cc @@ -181,22 +181,19 @@ Item_func_vec_fromtext::Item_func_vec_fromtext(THD *thd, Item *a) bool Item_func_vec_fromtext::fix_length_and_dec(THD *thd) { - uint32 maxlen= args[0]->max_char_length(); + ulonglong maxlen= args[0]->max_char_length(); decimals= 0; /* Worst case scenario, for a valid input we have a string of the form: [1,2,3,4,5,...] single digit numbers. This means we can have (maxlen - 1) / 2 floats. - Each float takes 4 bytes, so we do (maxlen - 1) * 2. Clamp at 4 - (so that result is at least VECTOR(1)) and UINT_MAX32 - 1 (to - avoid overflow) */ - if (maxlen <= 2) - maxlen= 4; - else if (maxlen > INT_MAX32) - maxlen= UINT_MAX32 - 1; - else + Each float takes 4 bytes, so we do (maxlen - 1) * 2. */ + if (maxlen > 0) maxlen= (maxlen - 1) * 2; - fix_length_and_charset(maxlen, &my_charset_bin); - set_if_smaller(max_length, MAX_FIELD_VARCHARLENGTH); + set_if_smaller(maxlen, MAX_FIELD_VARCHARLENGTH); + /* VECTOR(0) is not allowed */ + set_if_bigger(maxlen, 4); + /* Field_vector length must be a whole number of floats */ + fix_length_and_charset((uint32) (maxlen & ~3ULL), &my_charset_bin); set_maybe_null(); return false; } From 83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Wed, 19 Aug 2026 21:18:35 -0600 Subject: [PATCH 144/150] MDEV-40768: Slave cannot apply a fragmented row event written with log_bin_compress=ON MDEV-32570 added fragmentation of large row events. When binlog compression is enabled (log_bin_compress=ON), the compression is bypassed, and these binary log events remain as regular row events, when they should be compressed row events. This is because the compression happens during the Log_event::write() function, whereas row data fragmentation happens before Log_event::write() is called. The Rows_log_event super-class stores the row data buffers to be written to disk. The regular Rows_log_event sub-classes's implementations of Log_event::write() write this data as-is. The compressed sub-classes's implementation of ::write() over-write these buffers with the compressed rows data before writing to disk. To fragment a large row event, the server fragments the Rows_log_event's row data buffers, to be written by multiple Partial_rows_log_event's, and each Partial_rows_log_event::write() call writes its portion of the row data buffer to disk. However, this happens before compression ever has a chance to take place, and thereby, an event that should be compressed, never is. MDEV-39762 added event structure validation for compressed events, and discovered that these events carry the compressed event type, but are not actually compressed. Event validation thereby fails. This patch adds a workaround to override the event type of the fragmented row events to be regular row events, to be consistent with the actual on-disk content. The underlying problem still needs to be addressed though, and is tracked by MDEV-40851. Signed-off-by: Brandon Nesterenko --- sql/log_event_server.cc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 69a9aae852d..411b9c6dc99 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -6117,6 +6117,26 @@ Rows_log_event_fragmenter::fragment() net_store_length(width_tmp_buf, (size_t) rows_event->m_width); uint32_t width_size= static_cast(width_tmp_buf_end - width_tmp_buf); + /* + TODO: MDEV-40851 + + A bug in the fragmentation logic bypasses compression when + log_bin_compress=ON, so events cannot currently be both fragmented and + compressed. Where the server expects the event to be compressed, we must + revert that decision because of this bug. So revert the type of the event + back to a regular row event. + + Remove the following if conditional when MDEV-40851 is fixed. + rpl_fragment_row_event_(main/mysqlbinlog) tests should fail without this + workaround. + */ + if (LOG_EVENT_IS_ROW_COMPRESSED(rows_event->m_type)) + { + DBUG_ASSERT(!LOG_EVENT_IS_ROW_V2(rows_event->m_type)); + rows_event->m_type= (Log_event_type) + (rows_event->m_type - WRITE_ROWS_COMPRESSED_EVENT_V1 + + WRITE_ROWS_EVENT_V1); + } /* Update row events write an extra bitmap From 68683b7cfb3e978a43e17855716bad0ae32c9cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 20 Aug 2026 14:07:35 +0300 Subject: [PATCH 145/150] MDEV-40791 SET GLOBAL innodb_log_archive=OFF triggers full flush log_t::set_archive(): When a checkpoint needs to be forced, set a minimum target that guarantees progress, instead of flushing the entire buffer pool. Remove some duplicated reads of last_checkpoint_lsn. When switching innodb_log_archive from OFF to ON, accurately remember whether any log records may have been written with get_sequence_bit() == 0, to ensure that a checkpoint will be waited for on a subsequent switch from ON to OFF. log_t::set_recovered(): If innodb_log_archive=ON, only make a future set_archive(false) trigger a checkpoint if we may have recovered a server that was killed between set_archive(true) and write_checkpoint(). --- storage/innobase/log/log0log.cc | 66 ++++++++++++++++++++------------ storage/innobase/log/log0recv.cc | 22 ++++++++--- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index be2e6a45eba..10821294c79 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -812,33 +812,49 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (archive) { const lsn_t limit{wait_lsn - (wait_lsn - first_lsn) % capacity()}; - /* We are in innodb_log_archive=OFF. If the file has wrapped - around between the checkpoint and the current position, we must - wait for a log checkpoint not before the desired first_lsn of - our innodb_log_archive=ON log file, because that format does not - allow any wrap-around. */ + ut_ad(get_sequence_bit(limit) == get_sequence_bit(wait_lsn)); if (checkpoint < limit) + /* + The circular ib_logfile0 is currently wrapped around between + the checkpoint and the current position. Wait for a checkpoint, + because innodb_log_archive=ON does not allow any wrap-around. + */ goto retry_after_checkpoint; - circular_recovery_from_sequence_bit_0= limit != wait_lsn; + ut_ad(get_sequence_bit(checkpoint) == get_sequence_bit(wait_lsn)); wait_lsn= limit; + /* + The innodb_log_archive=ON recovery ignores the sequence bit, + so this transition is clear. + + However, a future set_archive(false) must ensure that recovery + will not observe any get_sequence_bit() == 0 residue from the + previous time this log file was in innodb_log_archive=OFF + format. + + The following assignment controls the condition in the "else if" + below, in a subsequent invocation of set_archive(false). + */ + circular_recovery_from_sequence_bit_0= !get_sequence_bit(wait_lsn); } - else if (circular_recovery_from_sequence_bit_0) + else if (circular_recovery_from_sequence_bit_0 || checkpoint < first_lsn) { - /* When switching innodb_log_archive back and forth, it could be - the case that some records since the latest checkpoint were - written with get_sequence_bit() == 0. Let us wait for another - checkpoint. This guarantees that if the server crashes between - the completion of set_archive(false) and the next - write_checkpoint(), recovery will only encounter - get_sequence_bit() == 1, consistent with our first_lsn. */ - goto retry_after_checkpoint; - } - else if (checkpoint < first_lsn) - { - /* write_checkpoint() switched files, but the latest FILE_CHECKPOINT - record points to a previous log file. In the innodb_log_archive=OFF - format the checkpoint must be within the only log file. */ - wait_lsn= first_lsn; + /* + Some records after the latest checkpoint may have been written + with get_sequence_bit() == 0 in innodb_log_archive=OFF + mode. Since then we may have switched to innodb_log_archive=ON + without advancing the checkpoint (see the above "if" body), + and now back to innodb_log_archive=OFF again. + + Wait for write_checkpoint() to ensure that recovery will only + find get_sequence_bit() == 1, consistent with our first_lsn. + + Alternatively, before we can switch to innodb_log_archive=OFF, + we must ensure that the checkpoint resides within the last file. + + Progress is guaranteed by setting a checkpoint target right + after the latest written FILE_CHECKPOINT record. + */ + wait_lsn= end_lsn; goto retry_after_checkpoint; } @@ -880,14 +896,14 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept { std::swap(old_name, new_name); std::string spare{get_archive_path(first_lsn + capacity())}; - if (!get_sequence_bit(last_checkpoint_lsn)) + if (!get_sequence_bit(checkpoint)) /* In the innodb_log_archive=ON format, mtr_t::finish_writer() always writes the sequence bit as 1. Ensure that the innodb_log_archive=OFF recovery will expect this value. */ first_lsn-= capacity(); - ut_ad(get_sequence_bit(last_checkpoint_lsn)); + ut_ad(get_sequence_bit(checkpoint)); ut_ad(get_sequence_bit(get_lsn())); header_rewrite(false); IF_WIN(DeleteFile(spare.c_str()), unlink(spare.c_str())); @@ -942,7 +958,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept { ut_ad(wait_lsn == get_lsn()); /* apply similar logic as log_close() */ - const lsn_t checkpoint_age{wait_lsn - log_sys.last_checkpoint_lsn}; + const lsn_t checkpoint_age{wait_lsn - checkpoint}; if (checkpoint_age < max_modified_age_async) wait_lsn= 0; else diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 39b17bb30c6..92a47bceb8c 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -5565,12 +5565,22 @@ inline void log_t::set_recovered() noexcept ut_ad(!resize_log.is_opened()); ut_ad(!resize_buf); ut_ad(!resize_flush_buf); - /* If innodb_log_archive=ON, we always write the sequence bit as 0. - A subsequent log_t::set_archive(archive=false, ...) must wait for - a checkpoint, to guarantee that recovery with innodb_log_archive=OFF - will observe all sequence bits as 1. */ - circular_recovery_from_sequence_bit_0= archive || - !get_sequence_bit(last_checkpoint_lsn); + ut_ad(!archive || !recv_sys.log_archive.empty()); + /* + Remember if a subsequent set_archive(false) must wait for a + checkpoint, to guarantee that recovery with innodb_log_archive=OFF + will observe all sequence bits as 1. + + While innodb_log_archive=ON always writes the sequence bit as 1, + some earlier records may have been written in the + innodb_log_archive=OFF format as 0 at the time set_archive(true) + rewrote the header. It is only possible that the server had been + killed between SET GLOBAL innodb_log_archive=ON and a checkpoint + if we recovered from a single log file. + */ + circular_recovery_from_sequence_bit_0= archive + ? recv_sys.log_archive.size() < 2 + : !get_sequence_bit(last_checkpoint_lsn); ut_ad(write_size >= 512); ut_ad(ut_is_2pow(write_size)); From be111d2cd60d035d555b71da019765cfb42ea0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 20 Aug 2026 17:46:34 +0300 Subject: [PATCH 146/150] MDEV-40080: innodb_log_archive=ON corruption log_t::write_checkpoint(): Extend the the correct file. log_t::archived_mmap_switch_prepare(): Tolerate a near-concurrent buf_flush_archive_create() from the buf_flush_page_cleaner() thread. --- storage/innobase/buf/buf0flu.cc | 2 +- storage/innobase/log/log0log.cc | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 829b402d70f..299da45582c 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -1933,7 +1933,7 @@ inline lsn_t log_t::write_checkpoint(lsn_t checkpoint, lsn_t end_lsn) noexcept resize_target= (offset + 4095) & ~off_t{4095}; sql_print_warning("InnoDB: Adjusting innodb_log_file_size=" LSN_PF "k", resize_target >> 10); - ut_a(os_file_set_size(get_archive_path().c_str(), resize_log.m_file, + ut_a(os_file_set_size(get_archive_path().c_str(), log.m_file, resize_target, false)); } } diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index 10821294c79..f1834bd8f67 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -1538,14 +1538,16 @@ void log_t::archived_mmap_switch_prepare(bool late, bool ex) noexcept (l & (WRITE_BACKOFF - 1)); waits++; ut_ad(archive); - ut_ad(!resize_buf); ut_ad(!resize_in_progress()); ut_ad(resize_target >= 4U << 20); ut_ad(is_latest()); ut_ad(log.is_opened()); - ut_ad(!!checkpoint_buf == resize_log.is_opened()); + ut_ad((checkpoint_buf || resize_buf) == resize_log.is_opened()); - if (UNIV_LIKELY_NULL(checkpoint_buf)) + if (resize_buf) + /* archive_create(..., false) had recently succeeded in another thread */ + ut_ad(!checkpoint_buf); + else if (UNIV_LIKELY_NULL(checkpoint_buf)) /* Both archived_mmap_switch_prepare() and archived_mmap_switch_complete() were invoked by another thread, but write_checkpoint() did not clean up yet. */ From b2c4e675049e23ce8c7346985f40840fcd4e583f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 24 Aug 2026 10:44:48 +0300 Subject: [PATCH 147/150] MDEV-40728 Recovery wrongly fails if FILE_CREATE is followed by FILE_RENAME deferred_spaces.deferred_dblwr(): Skip newly created tablespaces to avoid a bogus invocation of fil_space_free(). fil_name_process(): Simplify the logic. If no matching tablespace is found but file_name_t::create_lsn had been set in response to parsing a FILE_CREATE record, try to apply FILE_RENAME to deferred_spaces. log_parse_file(): Parse each FILE_ record only once. In multi-batch recovery, there may be redundant calls that would break the logic of fil_name_process(). fil_delete_apply(): Wrappers for fil_space_free(). When recovering a log in innodb_log_archive=ON format, we must apply FILE_DELETE records in order to avoid a future clash with FILE_CREATE or FILE_RENAME. --- storage/innobase/log/log0recv.cc | 121 +++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 29 deletions(-) diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 92a47bceb8c..736c226cac4 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -929,6 +929,18 @@ processed: d++; continue; } + else + { + recv_spaces_t::iterator it{recv_spaces.find(d->first)}; + if (UNIV_UNLIKELY(it == recv_spaces.end())) + ut_ad("inconsistent data structures" == 0); + else if (it->second.create_lsn) + /* + Because a FILE_CREATE record exists, the entire file must + be recoverable via log records. + */ + goto next_item; + } const page_id_t page_id{d->first, 0}; const byte *page= recv_sys.dblwr.find_page(page_id, max_lsn); if (!page) @@ -1208,6 +1220,50 @@ inline size_t recv_sys_t::files_size() return files.size(); } +/** Apply a FILE_DELETE record. */ +ATTRIBUTE_COLD static void fil_delete_apply(fil_space_t *space) noexcept +{ + if (log_sys.archive) + { + /* + In recovery with innodb_log_archive=ON there may be a situation like + the following: + + FILE_RENAME(123, t1.ibd, #sql-ib123.ibd) + FILE_CREATE(124, #sql-alter-.ibd) + FILE_RENAME(124, #sql-alter-.ibd, t1.ibd) + FILE_DELETE(123, #sql-ib123.ibd) + + In such a scenario, we must delete the old file in order to + avoid a name clash in recv_rename_files(). + + In normal crash recovery, at most one file operation can be + pending, and it will be handled by rollback or purge. + */ + if (FSP_FLAGS_HAS_DATA_DIR(space->flags)) + RemoteDatafile::delete_link_file(space->name()); + os_file_delete(innodb_data_file_key, space->chain.start->name); + } + fil_space_free(space->id, false); +} + +/** Apply a FILE_DELETE record to a tablespace that has not been loaded yet. */ +ATTRIBUTE_COLD static void fil_delete_apply(uint32_t id, const char *name) + noexcept +{ + if (log_sys.archive) + { + fil_space_t *space{nullptr}; + if (fil_ibd_load(id, name, space) == FIL_LOAD_OK) + { + ut_ad(space); + fil_delete_apply(space); + return; + } + ut_ad(!space); + } +} + /** Process a file name from a FILE_* record. @param[in] name file name @param[in] len length of the file name @@ -1234,32 +1290,32 @@ static void fil_name_process(const char *name, ulint len, uint32_t space_id, ut_ad(p.first->first == space_id); file_name_t& f = p.first->second; - + ut_ad(!f.space || f.space->id == space_id); auto d = deferred_spaces.find(space_id); - if (d) { - if (deleted) { - d->deleted = true; - goto got_deleted; - } - goto reload; - } + /* deferred_spaces must be a proper subset of recv_spaces. + Both here and in recv_validate_tablespace(), deferred_spaces.add() + may only be invoked for entries that exist in recv_spaces. */ + ut_ad(!d || !p.second); if (deleted) { -got_deleted: /* Got FILE_DELETE */ - if (!p.second && f.status != file_name_t::DELETED) { + if (d) { + d->deleted = true; + } + if (p.second) { + fil_delete_apply(space_id, f.name.c_str()); + } else if (f.status != file_name_t::DELETED) { f.status = file_name_t::DELETED; if (f.space != NULL) { - fil_space_free(space_id, false); + fil_delete_apply(f.space); f.space = NULL; } } ut_ad(f.space == NULL); - goto reset_create; - } else if (p.second // the first FILE_MODIFY or FILE_RENAME + f.create_lsn = 0; + } else if (d || p.second /* the first FILE_MODIFY or FILE_RENAME */ || f.name != fname.name) { -reload: if (f.name.size() == 0) { /* Augment the recv_spaces.emplace_hint() for the FILE_MODIFY record that had been added by @@ -1273,7 +1329,8 @@ reload: the space_id. If not, ignore the file after displaying a note. Abort if there are multiple files with the same space_id. */ - switch (fil_ibd_load(space_id, fname.name.c_str(), space)) { + switch (fil_load_status s + = fil_ibd_load(space_id, fname.name.c_str(), space)) { case FIL_LOAD_OK: ut_ad(space != NULL); @@ -1308,25 +1365,28 @@ same_space: break; case FIL_LOAD_ID_CHANGED: - ut_ad(space == NULL); - break; - case FIL_LOAD_NOT_FOUND: /* No matching tablespace was found; maybe it was renamed, and we will find a subsequent FILE_* record. */ ut_ad(space == NULL); - if (srv_operation == SRV_OPERATION_RESTORE && d - && ftype == FILE_RENAME) { + if (f.create_lsn) { + if (d && ftype == FILE_RENAME) { rename: - d->file_name = fname.name; - f.name = fname.name; + d->file_name = fname.name; + f.name = fname.name; + } break; } - if (f.create_lsn) { - return; + if (ftype == FILE_CREATE) { + f.create_lsn = lsn; + break; + } + + if (s == FIL_LOAD_ID_CHANGED) { + break; } if (srv_force_recovery @@ -1346,11 +1406,10 @@ rename: int(fname.name.size()), fname.name.data(), space_id); } - return; + break; case FIL_LOAD_DEFER: - if (d && ftype == FILE_RENAME - && srv_operation == SRV_OPERATION_RESTORE) { + if (d && ftype == FILE_RENAME && f.create_lsn) { goto rename; } /* Skip the deferred spaces @@ -1382,8 +1441,6 @@ rename: " due to innodb_force_recovery", int(len), name, space_id); } -reset_create: - f.create_lsn = 0; } else if (ftype == FILE_CREATE && !f.space) { f.create_lsn = lsn; } @@ -3187,6 +3244,12 @@ static recv_sys_t::parse_mtr_result log_parse_file(const page_id_t id, bool if_exists, const byte b, const byte *l, uint32_t rlen) { + if (recv_sys.scanned_lsn > recv_sys.lsn) + /* + We must already have parsed this record, in the skip_the_rest: + loop in recv_scan_log() before starting multi-batch recovery. + */ + return recv_sys_t::OK; if (UNIV_LIKELY(rlen)); else if (!id.raw() && b == FILE_CHECKPOINT + 2) /* FILE_CHECKPOINT followed by any number of NUL bytes could be From ea69ff8a3b294f5f5f52396218675eafd4c125f5 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Wed, 26 Aug 2026 19:51:04 +0200 Subject: [PATCH 148/150] fixed maturity --- VERSION | 2 +- mysql-test/main/mysql-interactive.result | 8 -------- mysql-test/suite/sys_vars/r/sysvars_star.result | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/VERSION b/VERSION index 51f2704d067..405e4522d58 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=13 MYSQL_VERSION_MINOR=0 MYSQL_VERSION_PATCH=2 -SERVER_MATURITY=gamma +SERVER_MATURITY=stable diff --git a/mysql-test/main/mysql-interactive.result b/mysql-test/main/mysql-interactive.result index e73b8618c78..f9ee4fd7ea2 100644 --- a/mysql-test/main/mysql-interactive.result +++ b/mysql-test/main/mysql-interactive.result @@ -10,8 +10,6 @@ Your MariaDB connection id is X Server version: Y Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. -Help others discover MariaDB. Star it on GitHub: https://github.com/MariaDB/server - Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> delimiter $ @@ -42,8 +40,6 @@ Your MariaDB connection id is X Server version: Y Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. -Help others discover MariaDB. Star it on GitHub: https://github.com/MariaDB/server - Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> create database db1; @@ -78,8 +74,6 @@ Your MariaDB connection id is X Server version: Y Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. -Help others discover MariaDB. Star it on GitHub: https://github.com/MariaDB/server - Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [test]> CREATE TABLE t (c INT) ENGINE=InnoDB; @@ -111,8 +105,6 @@ Your MariaDB connection id is X Server version: Y Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. -Help others discover MariaDB. Star it on GitHub: https://github.com/MariaDB/server - Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [test]> CREATE TABLE t (c INT) ENGINE=InnoDB; diff --git a/mysql-test/suite/sys_vars/r/sysvars_star.result b/mysql-test/suite/sys_vars/r/sysvars_star.result index 34aae495657..1339d6d13a3 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_star.result +++ b/mysql-test/suite/sys_vars/r/sysvars_star.result @@ -66,7 +66,7 @@ VARIABLE_NAME PLUGIN_MATURITY SESSION_VALUE NULL GLOBAL_VALUE alpha GLOBAL_VALUE_ORIGIN CONFIG -DEFAULT_VALUE beta +DEFAULT_VALUE gamma VARIABLE_SCOPE GLOBAL VARIABLE_TYPE ENUM VARIABLE_COMMENT The lowest desirable plugin maturity. Plugins less mature than that will not be installed or loaded From 93e76eccca3af403ff5c7b2424287e1bb3c4a64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 31 Aug 2026 13:32:58 +0300 Subject: [PATCH 149/150] MDEV-40932 SET GLOBAL innodb_log_archive=OFF may still break recovery log_t::set_archive(archive=false): Ensure that both the latest checkpoint and the latest log record (which has possibly not been written out yet) will carry the log_sys.get_sequence_bit(lsn)==1, to guarantee a successful recovery after the switch to the innodb_log_archive=OFF format. Tested by: Matthias Leich Reviewed by: Thirunarayanan Balathandayuthapani --- storage/innobase/log/log0log.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index f1834bd8f67..ae236359328 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -836,7 +836,9 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept */ circular_recovery_from_sequence_bit_0= !get_sequence_bit(wait_lsn); } - else if (circular_recovery_from_sequence_bit_0 || checkpoint < first_lsn) + else if (circular_recovery_from_sequence_bit_0 || + checkpoint < first_lsn || + get_sequence_bit(checkpoint) != get_sequence_bit(wait_lsn)) { /* Some records after the latest checkpoint may have been written From ba0d134749e68e17d1e75e04e929704c29b44250 Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Sun, 26 Jul 2026 19:54:38 -0600 Subject: [PATCH 150/150] =?UTF-8?q?MDEV-39880:=20Re=C3=AFmplement=20MDEV-3?= =?UTF-8?q?7146=20to=20include=20MDEV-39519?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MDEV-39519 added MySQL 8 compatibility to `mariadb-dump --dump-slave` by attempting `SHOW REPLICA STATUS` first and then `SHOW SLAVE STATUS`. This conflicted with MDEV-37146, where `mariadb-dump --dump-slave` queries either `SELECT … FROM information_schema.SLAVE_STATUS` or `SHOW ALL SLAVES STATUS` depending on the server version. This commit merges MDEV-37146 and MDEV-39519: * Use MDEV-39519’s strategy based on syntax error handling. * Use MDEV-37146’s preference order: 1. `SELECT … FROM information_schema.SLAVE_STATUS` 2. `SHOW ALL SLAVES STATUS` 3. `SHOW REPLICA STATUS` (for MySQL compatibility _only_) * Send `STOP`/`START REPLICA SQL_THREAD FOR CHANNEL '…'` commands for both MariaDB 10.7+ and MySQL. * Refactor column indices to variables set when a query succeeds. * Partially revert MDEV-37146’s removal of `--dump-slave`’s support for pre-GTID & pre-multi-source, but tailored for MySQL compatibility; coverage for MariaDB pre-10.0 is not fully restored. Reviewed-by: Brandon Nesterenko --- client/mysqldump.cc | 141 ++++++++++++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 45 deletions(-) diff --git a/client/mysqldump.cc b/client/mysqldump.cc index 6be3c447252..68df60a2ec4 100644 --- a/client/mysqldump.cc +++ b/client/mysqldump.cc @@ -176,17 +176,22 @@ static uint opt_use_gtid; static uint my_end_arg; static char * opt_mysql_unix_port=0; static int first_error=0; -/** - `true` for 11.6 or above servers, `false` otherwise. - @deprecated This and its `false` branches are to be removed after 11.4's EOL. -*/ static bool have_info_schema_slave_status; +/// @deprecated For pre-10.7 compatibility, all but 10.6-ES are EOL. +static bool have_for_channel= true; static DYNAMIC_STRING extended_row; static DYNAMIC_STRING dynamic_where; static MYSQL_RES *get_table_name_result= NULL; static MEM_ROOT glob_root; static MYSQL_RES *routine_res, *routine_list_res, *slave_status_res= NULL; +static struct +{ + ptrdiff_t connection_name, slave_sql_running, + master_host, master_port, + relay_master_log_file, exec_master_log_pos; +} slave_status_indices; + #include FILE *md_result_file= 0; @@ -1515,7 +1520,7 @@ static void maybe_die(int error_num, const char* fmt_reason, ...) RETURN VALUES 0 query sending and (if res!=0) result reading went ok 1 error - -1 Syntax/parse error (for retry code) + -1 Syntax/parse or unknown table error (for retry code) */ static int mysql_query_with_error_report(MYSQL *mysql_con, MYSQL_RES **res, @@ -1526,8 +1531,13 @@ static int mysql_query_with_error_report(MYSQL *mysql_con, MYSQL_RES **res, if (mysql_query(mysql_con, query) || (res && !((*res)= mysql_store_result(mysql_con)))) { - if (no_parse_error && mysql_errno(mysql_con) == ER_PARSE_ERROR) - return -1; + if (no_parse_error) + switch (mysql_errno(mysql_con)) { + case ER_PARSE_ERROR: + case ER_UNKNOWN_TABLE: + return -1; + default: break; + } maybe_die(EX_MYSQLERR, "Couldn't execute '%s': %s (%d)", query, mysql_error(mysql_con), mysql_errno(mysql_con)); return 1; @@ -6448,23 +6458,66 @@ static int do_stop_slave_sql(MYSQL *mysql_con) // do_stop_slave_sql() should only be called once DBUG_ASSERT(!slave_status_res); - if (mysql_query_with_error_report(mysql_con, &slave_status_res, - have_info_schema_slave_status ? + // 11.6 and above + switch (mysql_query_with_error_report(mysql_con, &slave_status_res, "SELECT Connection_name FROM information_schema.SLAVE_STATUS" // If the slave's SQL thread is not running, we don't stop (or start) it. - " WHERE Slave_SQL_Running <> 'No'" : - "SHOW ALL SLAVES STATUS" - )) - return(1); + " WHERE Slave_SQL_Running <> 'No'", true + )) { + default: // placing `default` at top and `-1` at bottom for readable chaining + return 1; + case 0: + have_info_schema_slave_status= true; + slave_status_indices= {0, /* unused */ -1, + // these are actually for the separate query in do_show_slave_status() + 1, 2, 3, 4}; + break; + case -1: + have_info_schema_slave_status= false; + // Between 10.0 and 11.5 inclusive + switch (mysql_query_with_error_report(mysql_con, &slave_status_res, + "SHOW ALL SLAVES STATUS", true + )) { + default: + return 1; + case 0: + slave_status_indices= {0, 13, 3, 5, 11, 23}; + break; + case -1: + // MySQL 8.0.22 and above + if (mysql_query_with_error_report(mysql_con, &slave_status_res, + "SHOW REPLICA STATUS" + )) + return 1; + slave_status_indices= {55, 11, 1, 3, 9, 21}; + } + } + // Loop over all slaves while ((row= mysql_fetch_row(slave_status_res))) { if ((have_info_schema_slave_status || - strcmp(row[/* Slave_SQL_Running */ 13], "No"))) + strcmp(row[slave_status_indices.slave_sql_running], "No"))) { - char query[25 + NAME_CHAR_LEN]; // sizeof(snprintf) - snprintf(query, sizeof(query), "STOP SLAVE '%.*s' SQL_THREAD", - NAME_CHAR_LEN, row[/* Connection_name */ 0]); + char query[39 + NAME_CHAR_LEN]; // sizeof(snprintf) + if (likely(have_for_channel)) + { + // 10.7+ or MySQL + snprintf(query, sizeof(query), + "STOP REPLICA SQL_THREAD FOR CHANNEL '%.*s'", + NAME_CHAR_LEN, row[slave_status_indices.connection_name]); + switch (mysql_query_with_error_report(mysql_con, nullptr, query, true)) { + default: + return 1; + case 0: + continue; + case -1:; // fall-through to `!have_for_channel` + } + } + // Between 10.0 and 10.6 inclusive + have_for_channel= false; + snprintf(query, sizeof(query), "STOP SLAVE '%.*s' SQL_THREAD", + NAME_CHAR_LEN, row[slave_status_indices.connection_name]); if (mysql_query_with_error_report(mysql_con, nullptr, query)) return 1; } @@ -6490,7 +6543,7 @@ static int add_slave_statements(void) return(0); } -static int do_show_slave_status(MYSQL *mysql_con, +static int do_show_slave_status(MYSQL *mysql_con, int have_mariadb_gtid, int use_gtid, char *set_gtid_pos, size_t set_gtid_pos_size) { @@ -6500,7 +6553,6 @@ static int do_show_slave_status(MYSQL *mysql_con, (opt_slave_data == MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL) ? "-- " : ""; const char *gtid_comment_prefix= (use_gtid ? comment_prefix : "-- "); const char *nogtid_comment_prefix= (!use_gtid ? comment_prefix : "-- "); - char gtid_pos[MAX_GTID_LENGTH]; char name_buff[FN_REFLEN*2+3]; if (have_info_schema_slave_status) @@ -6524,13 +6576,18 @@ static int do_show_slave_status(MYSQL *mysql_con, slave= slave_status_res; } - if (get_gtid_pos(gtid_pos, false)) + if (have_mariadb_gtid) { - mysql_free_result(slave); - return 1; + char gtid_pos[MAX_GTID_LENGTH]; + if (get_gtid_pos(gtid_pos, false)) + { + if (have_info_schema_slave_status) + mysql_free_result(slave); + return 1; + } + snprintf(set_gtid_pos, set_gtid_pos_size, + fmt_gtid_pos, gtid_comment_prefix, gtid_pos); } - snprintf(set_gtid_pos, set_gtid_pos_size, - fmt_gtid_pos, gtid_comment_prefix, gtid_pos); print_comment(md_result_file, 0, "\n-- The following is the replication SQL position " @@ -6553,22 +6610,19 @@ static int do_show_slave_status(MYSQL *mysql_con, if (use_gtid) fprintf(md_result_file, "%sCHANGE MASTER %s TO MASTER_USE_GTID=slave_pos;\n", - gtid_comment_prefix, quote_for_equal(row[/* Connection_name */ 0], - name_buff)); + gtid_comment_prefix, + quote_for_equal(row[slave_status_indices.connection_name], name_buff)); fprintf(md_result_file, "%sCHANGE MASTER %s TO ", nogtid_comment_prefix, - quote_for_equal(row[/* Connection_name */ 0], name_buff)); + quote_for_equal(row[slave_status_indices.connection_name], name_buff)); if (opt_include_master_host_port) fprintf(md_result_file, "MASTER_HOST=%s, MASTER_PORT=%llu, ", - quote_for_equal(row[have_info_schema_slave_status ? 1 - : /* Master_Host */ 3], name_buff), - atoll(row[have_info_schema_slave_status ? 2 - : /* Master_Port */ 5])); + quote_for_equal(row[slave_status_indices.master_host], name_buff), + atoll(row[slave_status_indices.master_port])); fprintf(md_result_file, "MASTER_LOG_FILE=%s, MASTER_LOG_POS=%llu;\n", - quote_for_equal(row[have_info_schema_slave_status ? 3 - : /* Relay_Master_Log_File */ 11], name_buff), - atoll(row[have_info_schema_slave_status ? 4 - : /* Exec_Master_Log_Pos */ 23])); + quote_for_equal(row[slave_status_indices.relay_master_log_file], + name_buff), + atoll(row[slave_status_indices.exec_master_log_pos])); check_io(md_result_file); } @@ -6597,12 +6651,13 @@ static int do_start_slave_sql(MYSQL *mysql_con) */ while ((row= mysql_fetch_row(slave_status_res))) { - char query[26 + NAME_CHAR_LEN]; // sizeof(snprintf) + char query[40 + NAME_CHAR_LEN]; // sizeof(snprintf) snprintf(query, sizeof(query), - "START SLAVE '%.*s' SQL_THREAD", - NAME_CHAR_LEN, row[/* Connection_name */ 0]); + likely(have_for_channel) ? "START REPLICA SQL_THREAD FOR CHANNEL '%.*s'" : + "START SLAVE '%.*s' SQL_THREAD", + NAME_CHAR_LEN, row[slave_status_indices.connection_name]); if ((have_info_schema_slave_status || - strcmp(row[/* Slave_SQL_Running */ 13], "No") + strcmp(row[slave_status_indices.slave_sql_running], "No") ) && mysql_query_with_error_report(mysql_con, nullptr, query)) { fprintf(stderr, "%s: Error: Unable to start slave '%s'\n", @@ -7622,12 +7677,7 @@ int main(int argc, char **argv) init_connection_pool(opt_parallel); /* Check if the server support multi source */ - unsigned long server_version= mysql_get_server_version(mysql); - if (server_version >= 100000) - { - have_info_schema_slave_status= server_version >= 110600; - have_mariadb_gtid= 1; - } + have_mariadb_gtid= mysql_get_server_version(mysql) >= 100000; if (opt_slave_data && do_stop_slave_sql(mysql)) goto err; @@ -7694,6 +7744,7 @@ int main(int argc, char **argv) sizeof(master_set_gtid_pos))) goto err; if (opt_slave_data && do_show_slave_status(mysql, + have_mariadb_gtid, opt_use_gtid, slave_set_gtid_pos, sizeof(slave_set_gtid_pos)))