From e3e401333066a764bee99ad5321c2ba9696ce8b7 Mon Sep 17 00:00:00 2001 From: Michael Widenius Date: Sun, 14 Sep 2025 14:10:05 +0300 Subject: [PATCH 01/17] Added memory allocated by my_once_malloc() to global_memory_used --- mysys/my_once.c | 4 ++++ mysys/my_static.c | 1 + mysys/my_static.h | 1 + 3 files changed, 6 insertions(+) diff --git a/mysys/my_once.c b/mysys/my_once.c index e2bea0a4d1e..9497392859c 100644 --- a/mysys/my_once.c +++ b/mysys/my_once.c @@ -63,6 +63,8 @@ void* my_once_alloc(size_t Size, myf MyFlags) return((uchar*) 0); } DBUG_PRINT("test",("my_once_malloc %lu byte malloced", (ulong) get_size)); + my_once_allocated+= get_size; + update_malloc_size(get_size, 0); next->next= 0; next->size= get_size; next->left= get_size-ALIGN_SIZE(sizeof(USED_MEM)); @@ -113,7 +115,9 @@ void my_once_free(void) old=next; next= next->next ; free((uchar*) old); } + update_malloc_size(- (longlong) my_once_allocated, 0); my_once_root_block=0; + my_once_allocated= 0; DBUG_VOID_RETURN; } /* my_once_free */ diff --git a/mysys/my_static.c b/mysys/my_static.c index 74522d35829..8df2deb2786 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -94,6 +94,7 @@ const char *soundex_map= "01230120022455012623010202"; /* from my_malloc */ USED_MEM* my_once_root_block=0; /* pointer to first block */ uint my_once_extra=ONCE_ALLOC_INIT; /* Memory to alloc / block */ +size_t my_once_allocated= 0; /* from my_alarm */ int volatile my_have_got_alarm=0; /* declare variable to reset */ diff --git a/mysys/my_static.h b/mysys/my_static.h index b30540b1ce4..80fc3e90e51 100644 --- a/mysys/my_static.h +++ b/mysys/my_static.h @@ -40,6 +40,7 @@ extern const char *soundex_map; extern USED_MEM* my_once_root_block; extern uint my_once_extra; +extern size_t my_once_allocated; extern struct st_my_file_info my_file_info_default[MY_NFILE]; From 6058e0273230adcf71019dcde0c4d390f49aefa5 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 14 Sep 2025 16:44:50 +0300 Subject: [PATCH 02/17] MDEV-37172 Server crashes in Item_func_nextval::update_table after INSERT to the table, that uses expression with nextval() as default The issue was that unpack_vcol_info_from_frm() wrongly linked the used sequence tables into tables->internal_tables when more than one sequence table was used. Other things: - Fixed internal_table_exists() to take db into account. (This is making the code easier to read. As we where comparing pointers the old code also worked). --- mysql-test/suite/sql_sequence/default.result | 33 ++++++++++++++++++++ mysql-test/suite/sql_sequence/default.test | 23 ++++++++++++++ sql/sql_base.cc | 8 ++--- sql/sql_parse.cc | 4 +-- sql/table.cc | 10 +++--- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/sql_sequence/default.result b/mysql-test/suite/sql_sequence/default.result index fe9c6af1795..a6f6d1270f8 100644 --- a/mysql-test/suite/sql_sequence/default.result +++ b/mysql-test/suite/sql_sequence/default.result @@ -313,4 +313,37 @@ insert v1 values (default); ERROR HY000: The target table v1 of the INSERT is not insertable-into drop view v1; drop table t1; +# +# MDEV-37172 Server crashes in Item_func_nextval::update_table after +# INSERT to the table, that uses expression with nextval() as default +# +create sequence s1; +create sequence s2; +create table t1 (id int default(nextval(s1))); +create table t2 (id int default(nextval(s1)+nextval(s1))); +create table t3 (id1 int default(nextval(s1)+nextval(s1)), +id2 int default(nextval(s2))); +insert t1 values (); +select lastval(s1); +lastval(s1) +1 +select * from t1; +id +1 +insert t2 values (); +select lastval(s1); +lastval(s1) +3 +select * from t2; +id +5 +insert t3 values (); +select lastval(s1), lastval(s2); +lastval(s1) lastval(s2) +5 1 +select * from t3; +id1 id2 +9 1 +drop table t1,t2,t3; +drop sequence s1,s2; # End of 10.6 tests diff --git a/mysql-test/suite/sql_sequence/default.test b/mysql-test/suite/sql_sequence/default.test index 5cbfe237cd3..45ef4b31b5a 100644 --- a/mysql-test/suite/sql_sequence/default.test +++ b/mysql-test/suite/sql_sequence/default.test @@ -240,4 +240,27 @@ insert v1 values (default); drop view v1; drop table t1; +--echo # +--echo # MDEV-37172 Server crashes in Item_func_nextval::update_table after +--echo # INSERT to the table, that uses expression with nextval() as default +--echo # + +create sequence s1; +create sequence s2; +create table t1 (id int default(nextval(s1))); +create table t2 (id int default(nextval(s1)+nextval(s1))); +create table t3 (id1 int default(nextval(s1)+nextval(s1)), + id2 int default(nextval(s2))); +insert t1 values (); +select lastval(s1); +select * from t1; +insert t2 values (); +select lastval(s1); +select * from t2; +insert t3 values (); +select lastval(s1), lastval(s2); +select * from t3; +drop table t1,t2,t3; +drop sequence s1,s2; + --echo # End of 10.6 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 501d13f201c..7e83bf79fad 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4722,11 +4722,12 @@ bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_CSTRING *db, static TABLE_LIST *internal_table_exists(TABLE_LIST *global_list, - const char *table_name) + TABLE_LIST *table) { do { - if (global_list->table_name.str == table_name) + if (global_list->table_name.str == table->table_name.str && + global_list->db.str == table->db.str) return global_list; } while ((global_list= global_list->next_global)); return 0; @@ -4747,8 +4748,7 @@ add_internal_tables(THD *thd, Query_tables_list *prelocking_ctx, /* Skip table if already in the list. Can happen with prepared statements */ - if ((tmp= internal_table_exists(global_table_list, - tables->table_name.str))) + if ((tmp= internal_table_exists(global_table_list, tables))) { /* Use the original value for the next local, used by the diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 5b631f068d1..f5cbce26031 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8438,8 +8438,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, } } } - /* Store the table reference preceding the current one. */ - TABLE_LIST *UNINIT_VAR(previous_table_ref); /* The table preceding the current one. */ + /* Store the table reference preceding the current in previous_table_ref */ + TABLE_LIST *UNINIT_VAR(previous_table_ref); if (table_list.elements > 0 && likely(!ptr->sequence)) { /* diff --git a/sql/table.cc b/sql/table.cc index 8cd9957b88c..49297b6076c 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3942,6 +3942,7 @@ unpack_vcol_info_from_frm(THD *thd, TABLE *table, LEX *old_lex= thd->lex; LEX lex; bool error; + TABLE_LIST *sequence, *last; DBUG_ENTER("unpack_vcol_info_from_frm"); DBUG_ASSERT(vcol->expr == NULL); @@ -3959,11 +3960,12 @@ unpack_vcol_info_from_frm(THD *thd, TABLE *table, if (unlikely(error)) goto end; - if (lex.current_select->table_list.first[0].next_global) + if ((sequence= lex.current_select->table_list.first[0].next_global)) { - /* We are using NEXT VALUE FOR sequence. Remember table name for open */ - TABLE_LIST *sequence= lex.current_select->table_list.first[0].next_global; - sequence->next_global= table->internal_tables; + /* We are using NEXT VALUE FOR sequence. Remember table for open */ + for (last= sequence ; last->next_global ; last= last->next_global) + ; + last->next_global= table->internal_tables; table->internal_tables= sequence; } From e23824687288ce123bc8ec3ea8ad89d538e36fa0 Mon Sep 17 00:00:00 2001 From: Alexey Yurchenko Date: Sun, 3 Aug 2025 12:35:28 +0200 Subject: [PATCH 03/17] MDEV-37494 Diagnostics_area does not always contain apply error info It appears that some error conditions don't store error information in the Diagnostics_area. For example when table_def::compatible_with() check fails error message is stored in Relay_log_info instead. This results in optimistically identical votes and zero error buffer size breaks wsrep-lib logic as it relies on error buffer size to decide whether voting took place. To account for this, first try to obtain error info from Diagnostics_area, then fallback to Relay_log_info. If that fails use some "random" data to distinguish this condition from success in production. --- .../r/galera_vote_majority_dml.result | 70 +++++++++++++++++++ .../t/galera_vote_majority_dml.cnf | 5 ++ .../t/galera_vote_majority_dml.test | 67 ++++++++++++++++++ sql/wsrep_applier.cc | 58 +++++++++++++-- 4 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 mysql-test/suite/galera_3nodes/r/galera_vote_majority_dml.result create mode 100644 mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.test diff --git a/mysql-test/suite/galera_3nodes/r/galera_vote_majority_dml.result b/mysql-test/suite/galera_3nodes/r/galera_vote_majority_dml.result new file mode 100644 index 00000000000..cef1583eebc --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/galera_vote_majority_dml.result @@ -0,0 +1,70 @@ +connection node_2; +connection node_1; +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY, f2 BLOB) ENGINE=InnoDB; +connect node_3, 127.0.0.1, root, , test, $NODE_MYPORT_3; +connection node_3; +SET GLOBAL wsrep_on=OFF; +ALTER TABLE t1 MODIFY f2 LONGTEXT; +SET GLOBAL wsrep_on=ON; +INSERT INTO t1 VALUES (3, 'a'); +connection node_1; +SHOW STATUS LIKE 'wsrep_cluster_status'; +Variable_name Value +wsrep_cluster_status Primary +connection node_2; +SHOW STATUS LIKE 'wsrep_cluster_status'; +Variable_name Value +wsrep_cluster_status Primary +INSERT INTO t1 VALUES (2, 'a'); +connection node_3; +SET SESSION wsrep_sync_wait=0; +SET SESSION wsrep_on=OFF; +# restart +SET SESSION wsrep_on=ON; +INSERT INTO t1 VALUES (3, 'a'); +connection node_1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` blob DEFAULT NULL, + PRIMARY KEY (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +SELECT * FROM t1; +f1 f2 +2 a +3 a +connection node_2; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` blob DEFAULT NULL, + PRIMARY KEY (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +SELECT * FROM t1; +f1 f2 +2 a +3 a +connection node_3; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` blob DEFAULT NULL, + PRIMARY KEY (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +SELECT * FROM t1; +f1 f2 +2 a +3 a +DROP TABLE t1; +connection node_1; +CALL mtr.add_suppression("Replica SQL: Column 1 of table 'test.t1' cannot be converted from type 'longblob' to type 'blob', Error_code: MY-013146"); +CALL mtr.add_suppression("Event 3 Write_rows_v1 apply failed: 3, seqno"); +connection node_2; +CALL mtr.add_suppression("Replica SQL: Column 1 of table 'test.t1' cannot be converted from type 'longblob' to type 'blob', Error_code: MY-013146"); +CALL mtr.add_suppression("Event 3 Write_rows_v1 apply failed: 3, seqno"); +connection node_3; +CALL mtr.add_suppression("Vote 0 \\(success\\) on (.*) is inconsistent with group. Leaving cluster."); +CALL mtr.add_suppression("Plugin 'InnoDB' will be forced to shutdown"); diff --git a/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.cnf b/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.cnf new file mode 100644 index 00000000000..fcda8c89526 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.cnf @@ -0,0 +1,5 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep-ignore-apply-errors=0 + diff --git a/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.test b/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.test new file mode 100644 index 00000000000..606fe06ce65 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_vote_majority_dml.test @@ -0,0 +1,67 @@ +# +# MDEV-37494: Inconsistency voting: create conditions where applying would +# fail on replicas in table_def::compatible_with() and check that +# that replicas survive and the primary (trx source) bails out. +# +--source include/galera_cluster.inc + +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY, f2 BLOB) ENGINE=InnoDB; + +--connect node_3, 127.0.0.1, root, , test, $NODE_MYPORT_3 +--connection node_3 +SET GLOBAL wsrep_on=OFF; +ALTER TABLE t1 MODIFY f2 LONGTEXT; # Introducing schema inconsistency +SET GLOBAL wsrep_on=ON; +INSERT INTO t1 VALUES (3, 'a'); # Nodes 1 and 2 should fail to apply this + +--connection node_1 +# Wait until node #3 leaves the cluster +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM performance_schema.global_status WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc +SHOW STATUS LIKE 'wsrep_cluster_status'; + +--connection node_2 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM performance_schema.global_status WHERE VARIABLE_NAME = 'wsrep_cluster_size' +# Wait until node #3 leaves the cluster +--source include/wait_condition.inc +SHOW STATUS LIKE 'wsrep_cluster_status'; + +INSERT INTO t1 VALUES (2, 'a'); # Nodes 1 and 2 should successfully apply this + +--connection node_3 +SET SESSION wsrep_sync_wait=0; +--let $wait_condition = SELECT VARIABLE_VALUE = 'Disconnected' FROM performance_schema.global_status WHERE VARIABLE_NAME = 'wsrep_cluster_status'; +--source include/wait_condition.inc +SET SESSION wsrep_on=OFF; +--source include/restart_mysqld.inc +--source include/wait_wsrep_ready.inc +SET SESSION wsrep_on=ON; + +INSERT INTO t1 VALUES (3, 'a'); # All nodes should successfully apply this + +# Check that consistency is restored +--connection node_1 +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +--connection node_2 +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +--connection node_3 +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +DROP TABLE t1; + +--connection node_1 +CALL mtr.add_suppression("Replica SQL: Column 1 of table 'test.t1' cannot be converted from type 'longblob' to type 'blob', Error_code: MY-013146"); +CALL mtr.add_suppression("Event 3 Write_rows_v1 apply failed: 3, seqno"); + +--connection node_2 +CALL mtr.add_suppression("Replica SQL: Column 1 of table 'test.t1' cannot be converted from type 'longblob' to type 'blob', Error_code: MY-013146"); +CALL mtr.add_suppression("Event 3 Write_rows_v1 apply failed: 3, seqno"); + +--connection node_3 +CALL mtr.add_suppression("Vote 0 \\(success\\) on (.*) is inconsistent with group. Leaving cluster."); +CALL mtr.add_suppression("Plugin 'InnoDB' will be forced to shutdown"); diff --git a/sql/wsrep_applier.cc b/sql/wsrep_applier.cc index ee3da59d4f2..4ce644c52f7 100644 --- a/sql/wsrep_applier.cc +++ b/sql/wsrep_applier.cc @@ -83,9 +83,35 @@ wsrep_get_apply_format(THD* thd) return thd->wsrep_rgi->rli->relay_log.description_event_for_exec; } -void wsrep_store_error(const THD* const thd, - wsrep::mutable_buffer& dst, - bool const include_msg) +/* store error from rli */ +static void wsrep_store_error_rli(const THD* const thd, + wsrep::mutable_buffer& dst, + bool const include_msg) +{ + Slave_reporting_capability* const rli= thd->wsrep_rgi->rli; + if (rli && rli->last_error().number != 0) + { + auto error= rli->last_error(); + std::ostringstream os; + if (include_msg) + { + os << error.message << ","; + } + os << " Error_code: " << error.number << ';'; + std::string const err_str= os.str(); + dst.resize(err_str.length() + 1); + sprintf(dst.data(), "%s", err_str.c_str()); + + WSREP_DEBUG("Error buffer (RLI) for thd %u seqno %lld, %zu bytes: '%s'", + thd->thread_id, (long long)wsrep_thd_trx_seqno(thd), + dst.size(), dst.size() ? dst.data() : "(null)"); + } +} + +/* store error from diagnostic area */ +static void wsrep_store_error_da(const THD* const thd, + wsrep::mutable_buffer& dst, + bool const include_msg) { Diagnostics_area::Sql_condition_iterator it= thd->get_stmt_da()->sql_conditions(); @@ -123,11 +149,35 @@ void wsrep_store_error(const THD* const thd, dst.resize(slider - dst.data()); - WSREP_DEBUG("Error buffer for thd %llu seqno %lld, %zu bytes: '%s'", + WSREP_DEBUG("Error buffer (DA) for thd %llu seqno %lld, %zu bytes: '%s'", thd->thread_id, (long long)wsrep_thd_trx_seqno(thd), dst.size(), dst.size() ? dst.data() : "(null)"); } +/* store error info after applying error */ +void wsrep_store_error(const THD* const thd, + wsrep::mutable_buffer& dst, + bool const include_msg) +{ + dst.clear(); + wsrep_store_error_da(thd, dst, include_msg); + if (dst.size() == 0) + { + wsrep_store_error_rli(thd, dst, include_msg); + } + if (dst.size() == 0) + { + WSREP_WARN("Failed to get apply error description from either " + "Relay_log_info or Diagnostics_area, will use random data."); + DBUG_ASSERT(0); + uintptr_t const n1= reinterpret_cast(&dst); + uintptr_t const n2= reinterpret_cast(thd); + uintptr_t const data= n1 ^ (n2 < 1); + const char* const data_ptr= reinterpret_cast(&data); + dst.push_back(data_ptr, data_ptr + sizeof(data)); + } +} + int wsrep_apply_events(THD* thd, Relay_log_info* rli, const void* events_buf, From 32c1725b1b96a02ae12ca1f811395396e54a5eb1 Mon Sep 17 00:00:00 2001 From: Alexey Botchkov Date: Sun, 7 Sep 2025 00:38:17 +0400 Subject: [PATCH 04/17] MDEV-35713 UBSAN: runtime error: load of value 1341112147, which is not a valid value for type 'enum enum_schema_tables' in optimize_for_get_all_tables and get_all_tables and on SELECT from I_S geometry_columns. get_schema_table_idx() returns garbage if the SCHEMA table comes from a plugin. Added specific value to reflect this. --- sql/handler.h | 5 ++++- sql/sql_show.cc | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/sql/handler.h b/sql/handler.h index 6bb1dc65229..c6071bce85a 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1141,7 +1141,10 @@ enum enum_schema_tables SCH_TABLE_PRIVILEGES, SCH_TRIGGERS, SCH_USER_PRIVILEGES, - SCH_VIEWS + SCH_VIEWS, + + SCH_N_SERVER_TABLES, /* How many SCHEMA tables in the server. */ + SCH_PLUGIN_TABLE /* Schema table defined in plugin. */ }; struct TABLE_SHARE; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index b1e7dbdc52a..0ff8307e160 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4292,6 +4292,12 @@ bool get_lookup_field_values(THD *thd, COND *cond, TABLE_LIST *tables, enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table) { + if (schema_table < schema_tables || + schema_table > &schema_tables[SCH_N_SERVER_TABLES]) + { + return SCH_PLUGIN_TABLE; + } + return (enum enum_schema_tables) (schema_table - &schema_tables[0]); } From 764b893cb7f7492b587541fea1880cd7cc91d743 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 5 Sep 2025 15:02:07 +0200 Subject: [PATCH 05/17] suppress 'InnoDB: native AIO failed' under rr --- mysql-test/lib/My/Debugger.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/lib/My/Debugger.pm b/mysql-test/lib/My/Debugger.pm index 907638c9433..fa40f297b28 100644 --- a/mysql-test/lib/My/Debugger.pm +++ b/mysql-test/lib/My/Debugger.pm @@ -81,6 +81,7 @@ my %debuggers = ( options => '_RR_TRACE_DIR={log} rr record {exe} {args} --loose-skip-innodb-use-native-aio --loose-innodb-flush-method=fsync', run => 'env', pre => sub { + push @::global_suppressions, qr/InnoDB: native AIO failed/; ::mtr_error('rr requires kernel.perf_event_paranoid <= 1') if ::mtr_grab_file('/proc/sys/kernel/perf_event_paranoid') > 1; } From 62b21714d0bab340872de0aec572cce5b5dd3b09 Mon Sep 17 00:00:00 2001 From: Arcadiy Ivanov Date: Tue, 12 Aug 2025 23:25:11 -0400 Subject: [PATCH 06/17] Reproducible test case for MDEV-37434 Add debug logging to help with tracing Add the fix --- .../plugins/r/server_audit_query_id.result | 69 ++++++++++++++ .../suite/plugins/t/normalize_query_id.pl | 25 ++++++ .../suite/plugins/t/server_audit_query_id.opt | 5 ++ .../plugins/t/server_audit_query_id.test | 89 +++++++++++++++++++ plugin/server_audit/server_audit.c | 44 ++++++++- sql/sp_head.cc | 6 +- sql/sql_audit.h | 2 + sql/sql_parse.cc | 2 +- 8 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/plugins/r/server_audit_query_id.result create mode 100644 mysql-test/suite/plugins/t/normalize_query_id.pl create mode 100644 mysql-test/suite/plugins/t/server_audit_query_id.opt create mode 100644 mysql-test/suite/plugins/t/server_audit_query_id.test diff --git a/mysql-test/suite/plugins/r/server_audit_query_id.result b/mysql-test/suite/plugins/r/server_audit_query_id.result new file mode 100644 index 00000000000..3d2d9f18cc2 --- /dev/null +++ b/mysql-test/suite/plugins/r/server_audit_query_id.result @@ -0,0 +1,69 @@ +set global server_audit_logging=on; +USE test; +CREATE TABLE source ( +id bigint(20) NOT NULL AUTO_INCREMENT, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE dest ( +id bigint(20) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE dest_2 ( +id bigint(20) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TRIGGER test_trigger +AFTER INSERT ON source +FOR EACH ROW +INSERT INTO dest (id) VALUES(NEW.id)// +CREATE TRIGGER test_trigger_2 +AFTER INSERT ON dest +FOR EACH ROW +INSERT INTO dest_2 (id) VALUES(NEW.id)// +CREATE PROCEDURE test_procedure (IN id bigint(20)) +NOT DETERMINISTIC MODIFIES SQL DATA +BEGIN +INSERT INTO source VALUES (id), (NULL); +END;// +# Insert a row to trigger the AFTER trigger +INSERT INTO source VALUES (NULL); +# Insert another row to see the pattern +INSERT INTO source VALUES (NULL); +# Test with multi-row insert +INSERT INTO source VALUES (NULL), (NULL); +# Test with stored procedure +CALL test_procedure(NULL); +# Clean up +DROP PROCEDURE test_procedure; +DROP TABLE source, dest, dest_2; +set global server_audit_logging=off; +# Wait for audit events to be written +FOUND 1 /set global server_audit_logging=off/ in server_audit_query_id.log +TIMESTAMP,HOSTNAME,root,localhost,4,0,QUERY,test,'set global server_audit_logging=on',0 +TIMESTAMP,HOSTNAME,root,localhost,4,1,QUERY,test,'USE test',0 +TIMESTAMP,HOSTNAME,root,localhost,4,2,QUERY,test,'CREATE TABLE source (\nid bigint(20) NOT NULL AUTO_INCREMENT,\nPRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4',0 +TIMESTAMP,HOSTNAME,root,localhost,4,3,QUERY,test,'CREATE TABLE dest (\nid bigint(20) NOT NULL,\nPRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4',0 +TIMESTAMP,HOSTNAME,root,localhost,4,4,QUERY,test,'CREATE TABLE dest_2 (\nid bigint(20) NOT NULL,\nPRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4',0 +TIMESTAMP,HOSTNAME,root,localhost,4,5,QUERY,test,'CREATE TRIGGER test_trigger\nAFTER INSERT ON source\nFOR EACH ROW\nINSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,6,QUERY,test,'CREATE TRIGGER test_trigger_2\nAFTER INSERT ON dest\nFOR EACH ROW\nINSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,7,QUERY,test,'CREATE PROCEDURE test_procedure (IN id bigint(20))\nNOT DETERMINISTIC MODIFIES SQL DATA\nBEGIN\nINSERT INTO source VALUES (id), (NULL);\nEND',0 +TIMESTAMP,HOSTNAME,root,localhost,4,10,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,9,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,8,QUERY,test,'INSERT INTO source VALUES (NULL)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,13,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,12,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,11,QUERY,test,'INSERT INTO source VALUES (NULL)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,16,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,15,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,18,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,17,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,14,QUERY,test,'INSERT INTO source VALUES (NULL), (NULL)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,22,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,21,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,24,QUERY,test,'INSERT INTO dest_2 (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,23,QUERY,test,'INSERT INTO dest (id) VALUES(NEW.id)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,20,QUERY,test,'INSERT INTO source VALUES ( NAME_CONST(\'id\',NULL)), (NULL)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,19,QUERY,test,'CALL test_procedure(NULL)',0 +TIMESTAMP,HOSTNAME,root,localhost,4,25,QUERY,test,'DROP PROCEDURE test_procedure',0 +TIMESTAMP,HOSTNAME,root,localhost,4,26,QUERY,test,'DROP TABLE source, dest, dest_2',0 +TIMESTAMP,HOSTNAME,root,localhost,4,27,QUERY,test,'set global server_audit_logging=off',0 diff --git a/mysql-test/suite/plugins/t/normalize_query_id.pl b/mysql-test/suite/plugins/t/normalize_query_id.pl new file mode 100644 index 00000000000..601809a429d --- /dev/null +++ b/mysql-test/suite/plugins/t/normalize_query_id.pl @@ -0,0 +1,25 @@ +#!/usr/bin/perl +use strict; +use warnings; + +my $filename = $ARGV[0] or die "Usage: $0 \n"; + +# Read all lines into memory +open(my $fh, '<', $filename) or die "Cannot open file '$filename': $!"; +my @lines = <$fh>; +close($fh); + +# Process lines and normalize query IDs +my $first_id; +foreach my $line (@lines) { + if ($line =~ /,(\d+),QUERY/) { + $first_id = $1 unless defined $first_id; + my $normalized = $1 - $first_id; + $line =~ s/,$1,QUERY/,$normalized,QUERY/; + } +} + +# Write back to the same file +open($fh, '>', $filename) or die "Cannot write to file '$filename': $!"; +print $fh @lines; +close($fh); diff --git a/mysql-test/suite/plugins/t/server_audit_query_id.opt b/mysql-test/suite/plugins/t/server_audit_query_id.opt new file mode 100644 index 00000000000..869b5f81739 --- /dev/null +++ b/mysql-test/suite/plugins/t/server_audit_query_id.opt @@ -0,0 +1,5 @@ +--plugin-load-add=server_audit +--server_audit_mode=0 +--server_audit_file_path='server_audit_query_id.log' +--server_audit_output_type=file +--server_audit_events='QUERY' diff --git a/mysql-test/suite/plugins/t/server_audit_query_id.test b/mysql-test/suite/plugins/t/server_audit_query_id.test new file mode 100644 index 00000000000..0b88a6deef5 --- /dev/null +++ b/mysql-test/suite/plugins/t/server_audit_query_id.test @@ -0,0 +1,89 @@ +--source include/have_innodb.inc +--source include/have_plugin_auth.inc +--source include/not_embedded.inc + +if (!$SERVER_AUDIT_SO) { + skip No SERVER_AUDIT plugin; +} + +# An unfortunate wait for check-testcase.inc to complete disconnect. +let count_sessions= 1; +source include/wait_until_count_sessions.inc; + +let $MYSQLD_DATADIR= `SELECT @@datadir`; +let SEARCH_FILE= $MYSQLD_DATADIR/server_audit_query_id.log; + +--disable_ps_protocol + +set global server_audit_logging=on; + +USE test; + +CREATE TABLE source ( + id bigint(20) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE dest ( + id bigint(20) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE dest_2 ( + id bigint(20) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +DELIMITER //; +CREATE TRIGGER test_trigger +AFTER INSERT ON source +FOR EACH ROW +INSERT INTO dest (id) VALUES(NEW.id)// +DELIMITER ;// + +DELIMITER //; +CREATE TRIGGER test_trigger_2 +AFTER INSERT ON dest +FOR EACH ROW +INSERT INTO dest_2 (id) VALUES(NEW.id)// +DELIMITER ;// + +DELIMITER //; +CREATE PROCEDURE test_procedure (IN id bigint(20)) +NOT DETERMINISTIC MODIFIES SQL DATA +BEGIN + INSERT INTO source VALUES (id), (NULL); +END;// +DELIMITER ;// + +--echo # Insert a row to trigger the AFTER trigger +INSERT INTO source VALUES (NULL); + +--echo # Insert another row to see the pattern +INSERT INTO source VALUES (NULL); + +--echo # Test with multi-row insert +INSERT INTO source VALUES (NULL), (NULL); + +--echo # Test with stored procedure +CALL test_procedure(NULL); + +--echo # Clean up +DROP PROCEDURE test_procedure; +DROP TABLE source, dest, dest_2; + +set global server_audit_logging=off; + +--echo # Wait for audit events to be written +--let SEARCH_FILE = $MYSQLD_DATADIR/server_audit_query_id.log +--let SEARCH_PATTERN = set global server_audit_logging=off +--source include/search_pattern_in_file.inc + +# Read and normalize the log +--exec perl $MYSQL_TEST_DIR/suite/plugins/t/normalize_query_id.pl $SEARCH_FILE + +# Output the log without heavy replacements so we can see the actual order +--replace_regex /\d\d\d\d\d\d\d\d \d\d:\d\d:\d\d/TIMESTAMP/ /,[^,]+,root,localhost,/,HOSTNAME,root,localhost,/ + +cat_file $SEARCH_FILE; +remove_file $SEARCH_FILE; diff --git a/plugin/server_audit/server_audit.c b/plugin/server_audit/server_audit.c index e17f001ee47..2d5b89aca69 100644 --- a/plugin/server_audit/server_audit.c +++ b/plugin/server_audit/server_audit.c @@ -2013,6 +2013,16 @@ static int log_statement(const struct connection_info *cn, const struct mysql_event_general *event, const char *type) { + DBUG_PRINT("info", ("log_statement: event_subclass=%d, general_command=%.*s, " + "cn->query_id=%lld, cn->query=%.*s, event->query_id=%lld, " + "event->general_query=%.*s, type=%s", + event->event_subclass, event->general_command_length, + event->general_command, + cn->query_id, + cn->query_length == 0x4f4f4f4f ? 0 : cn->query_length, + cn->query == (const char *)0x4f4f4f4f4f4f4f4fL ? NULL : cn->query, + event->query_id, event->general_query_length, + event->general_query, type)); return log_statement_ex(cn, event->general_time, event->general_thread_id, event->general_query, event->general_query_length, event->general_error_code, type, 1); @@ -2113,6 +2123,16 @@ static void update_connection_info(MYSQL_THD thd, struct connection_info *cn, { const struct mysql_event_general *event = (const struct mysql_event_general *) ev; + DBUG_PRINT("info", ("update_connection_info: before: event_subclass=%d, " + "general_command=%.*s, cn->query_id=%lld, cn->query=%.*s, " + "event->query_id=%lld, event->general_query=%.*s", + event->event_subclass, event->general_command_length, + event->general_command, + cn->query_id, + cn->query_length == 0x4f4f4f4f ? 0 : cn->query_length, + cn->query == (const char *)0x4f4f4f4f4f4f4f4fL ? NULL : cn->query, + event->query_id, event->general_query_length, + event->general_query)); switch (event->event_subclass) { case MYSQL_AUDIT_GENERAL_LOG: { @@ -2142,8 +2162,8 @@ static void update_connection_info(MYSQL_THD thd, struct connection_info *cn, setup_connection_query(cn, event); else setup_connection_simple(cn); - break; } + break; case MYSQL_AUDIT_GENERAL_STATUS: if (event_query_command(event)) @@ -2151,10 +2171,14 @@ static void update_connection_info(MYSQL_THD thd, struct connection_info *cn, if (ci_needs_setup(cn)) setup_connection_query(cn, event); - if (mode == 0 && cn->db_length == 0 && event->database.length > 0) - get_str_n(cn->db, &cn->db_length, sizeof(cn->db), - event->database.str, event->database.length); + if (mode == 0) + { + if (cn->db_length == 0 && event->database.length > 0) + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + cn->query_id= event->query_id; + } if (event->general_error_code == 0) { /* We need to check if it's the USE command to change the DB */ @@ -2174,6 +2198,7 @@ static void update_connection_info(MYSQL_THD thd, struct connection_info *cn, update_general_user(cn, event); } break; + case MYSQL_AUDIT_GENERAL_ERROR: /* We need this because the MariaDB returns NULL query field for the @@ -2190,6 +2215,16 @@ static void update_connection_info(MYSQL_THD thd, struct connection_info *cn, break; default:; } + DBUG_PRINT("info", ("update_connection_info: after: event_subclass=%d, " + "general_command=%.*s, cn->query_id=%lld, cn->query=%.*s, " + "event->query_id=%lld, event->general_query=%.*s", + event->event_subclass, event->general_command_length, + event->general_command, + cn->query_id, + cn->query_length == 0x4f4f4f4f ? 0 : cn->query_length, + cn->query == (const char *)0x4f4f4f4f4f4f4f4fL ? NULL : cn->query, + event->query_id, event->general_query_length, + event->general_query)); break; } case MYSQL_AUDIT_TABLE_CLASS: @@ -2307,6 +2342,7 @@ void auditing(MYSQL_THD thd, unsigned int event_class, const void *ev) { log_statement(cn, event, "QUERY"); cn->query_length= 0; /* So the log_current_query() won't log this again. */ + cn->query_id= 0; cn->log_always= 0; } } diff --git a/sql/sp_head.cc b/sql/sp_head.cc index bd95b8060e5..07cba674c0e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1612,6 +1612,9 @@ sp_head::execute(THD *thd, bool merge_da_on_success) DBUG_ASSERT(thd->Item_change_list::is_empty()); old_change_list.move_elements_to(thd); thd->lex= old_lex; + DBUG_PRINT("info", ("sp_head::execute: query_id restore: old_query_id=%lld, query_id=%lld, query=%.*s", + old_query_id, + thd->query_id, thd->query_length(), thd->query())); thd->set_query_id(old_query_id); thd->set_query_inner(old_query); DBUG_ASSERT(!thd->derived_tables); @@ -3609,7 +3612,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->lex= m_lex; thd->set_query_id(next_query_id()); - + DBUG_PRINT("info", ("reset_lex_and_exec_core: query_id=%lld, query=%.*s", + thd->query_id, thd->query_length(), thd->query())); if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { /* diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 64500067699..49b877ada94 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -175,6 +175,8 @@ void mysql_audit_general(THD *thd, uint event_subtype, event.general_rows= thd->get_stmt_da()->current_row_for_warning(); event.database= thd->db; event.query_id= thd->query_id; + DBUG_PRINT("info", ("mysql_audit_general: query_id=%lld, query=%.*s, subtype=%d, error_code=%d, msg=%s", + thd->query_id, thd->query_length(), thd->query(), event_subtype, error_code, msg)); } else { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 758a57a0967..e6dad577f22 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1876,7 +1876,7 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD * (char *) thd->security_ctx->host_or_ip); char *packet_end= thd->query() + thd->query_length(); general_log_write(thd, command, thd->query(), thd->query_length()); - DBUG_PRINT("query",("%-.4096s",thd->query())); + DBUG_PRINT("query",("query_id=%lld, %.*s", thd->query_id, thd->query_length(), thd->query())); #if defined(ENABLED_PROFILING) thd->profiling.set_query_source(thd->query(), thd->query_length()); #endif From 1454d28cf8fea55eee8a9fc970f53ad64affaf17 Mon Sep 17 00:00:00 2001 From: mariadb-satishkumar Date: Wed, 17 Sep 2025 10:26:58 +0000 Subject: [PATCH 07/17] MDEV-37299: Fix crash when server read-only and encrption ON Modified srv_start to call fil_crypt_threads_init() only when srv_read_only_mode is not set. Modified encryption.innodb-read-only to capture number of encryption threads created for both scenarios when server is not read only as well as when server is read only. --- .../suite/encryption/r/innodb-read-only.result | 12 ++++++++++++ mysql-test/suite/encryption/t/innodb-read-only.test | 12 ++++++++++++ storage/innobase/fil/fil0crypt.cc | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/mysql-test/suite/encryption/r/innodb-read-only.result b/mysql-test/suite/encryption/r/innodb-read-only.result index 35ba28dbe1e..3d23d3142f4 100644 --- a/mysql-test/suite/encryption/r/innodb-read-only.result +++ b/mysql-test/suite/encryption/r/innodb-read-only.result @@ -1,4 +1,16 @@ # Wait max 10 min for key encryption threads to encrypt all spaces # Success! +SET GLOBAL innodb_encryption_threads=4; +SELECT COUNT(*) AS encrypt_threads_running +FROM performance_schema.threads +WHERE NAME LIKE '%encrypt%'; +encrypt_threads_running +4 # restart: --innodb-read-only=1 --innodb-encrypt-tables=1 +SET GLOBAL innodb_encryption_threads=4; +SELECT COUNT(*) AS encrypt_threads_running +FROM performance_schema.threads +WHERE NAME LIKE '%encrypt%'; +encrypt_threads_running +0 # All done diff --git a/mysql-test/suite/encryption/t/innodb-read-only.test b/mysql-test/suite/encryption/t/innodb-read-only.test index 10ec87467b6..d51825e1da5 100644 --- a/mysql-test/suite/encryption/t/innodb-read-only.test +++ b/mysql-test/suite/encryption/t/innodb-read-only.test @@ -25,10 +25,22 @@ if (!$success) } --echo # Success! +# Server in normal mode +SET GLOBAL innodb_encryption_threads=4; +SELECT COUNT(*) AS encrypt_threads_running +FROM performance_schema.threads +WHERE NAME LIKE '%encrypt%'; + # # MDEV-11835: InnoDB: Failing assertion: free_slot != NULL on # restarting server with encryption and read-only # --let $restart_parameters= --innodb-read-only=1 --innodb-encrypt-tables=1 --source include/restart_mysqld.inc + +# Server read-only mode +SET GLOBAL innodb_encryption_threads=4; +SELECT COUNT(*) AS encrypt_threads_running +FROM performance_schema.threads +WHERE NAME LIKE '%encrypt%'; --echo # All done diff --git a/storage/innobase/fil/fil0crypt.cc b/storage/innobase/fil/fil0crypt.cc index 4626ef85918..af5a6cb0f8a 100644 --- a/storage/innobase/fil/fil0crypt.cc +++ b/storage/innobase/fil/fil0crypt.cc @@ -2108,6 +2108,9 @@ Adjust thread count for key rotation @param[in] enw_cnt Number of threads to be used */ void fil_crypt_set_thread_cnt(const uint new_cnt) { + if (srv_read_only_mode) + return; + if (!fil_crypt_threads_inited) { if (srv_shutdown_state != SRV_SHUTDOWN_NONE) return; @@ -2261,6 +2264,8 @@ void fil_crypt_set_encrypt_tables(ulong val) Init threads for key rotation */ void fil_crypt_threads_init() { + ut_ad(!srv_read_only_mode); + if (!fil_crypt_threads_inited) { pthread_cond_init(&fil_crypt_cond, nullptr); pthread_cond_init(&fil_crypt_threads_cond, nullptr); From 70476c1127c2997fd14e7be0eba44c1ffb930079 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Sun, 31 Aug 2025 22:28:43 +0300 Subject: [PATCH 08/17] main.backup_locks main.information_schema fix Missing profiling dependency --- mysql-test/main/backup_locks.test | 1 + mysql-test/main/information_schema.test | 1 + 2 files changed, 2 insertions(+) diff --git a/mysql-test/main/backup_locks.test b/mysql-test/main/backup_locks.test index 6a1fe9f6094..c2618cbbadb 100644 --- a/mysql-test/main/backup_locks.test +++ b/mysql-test/main/backup_locks.test @@ -5,6 +5,7 @@ --source include/long_test.inc --source include/have_innodb.inc --source include/have_metadata_lock_info.inc +--source include/have_profiling.inc --source include/not_embedded.inc --source include/no_view_protocol.inc diff --git a/mysql-test/main/information_schema.test b/mysql-test/main/information_schema.test index 99e88c4f413..1ce70a52b34 100644 --- a/mysql-test/main/information_schema.test +++ b/mysql-test/main/information_schema.test @@ -1,6 +1,7 @@ # This test uses grants, which can't get tested for embedded server -- source include/not_embedded.inc -- source include/have_perfschema.inc +-- source include/have_profiling.inc # check that CSV engine was compiled in, as the result of the test depends # on the presence of the log tables (which are CSV-based). From 32e987191775fdc70b4f969b8124ebaff47c0296 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 10 Sep 2025 18:39:14 +0300 Subject: [PATCH 09/17] MDEV-37404 Cleanups Deadcode leftovers. --- sql/sql_insert.h | 5 ----- sql/sql_prepare.cc | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/sql/sql_insert.h b/sql/sql_insert.h index 414dfb4ff73..85058f7e9e8 100644 --- a/sql/sql_insert.h +++ b/sql/sql_insert.h @@ -34,16 +34,11 @@ bool mysql_insert(THD *thd,TABLE_LIST *table,List &fields, List &values, List &update_fields, List &update_values, enum_duplicates flag, bool ignore, select_result* result); -void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type, - enum_duplicates duplic, - bool is_multi_insert); int check_that_all_fields_are_given_values(THD *thd, TABLE *entry, TABLE_LIST *table_list); int vers_insert_history_row(TABLE *table); int check_duplic_insert_without_overlaps(THD *thd, TABLE *table, enum_duplicates duplic); -int write_record(THD *thd, TABLE *table, COPY_INFO *info, - select_result *returning= NULL); void kill_delayed_threads(void); bool binlog_create_table(THD *thd, TABLE *table, bool replace); bool binlog_drop_table(THD *thd, TABLE *table); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index e722a0d701d..218d110e7c5 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -97,7 +97,7 @@ When one supplies long data for a placeholder: #include "sql_view.h" // create_view_precheck #include "sql_delete.h" // mysql_prepare_delete #include "sql_select.h" // for JOIN -#include "sql_insert.h" // upgrade_lock_type_for_insert, mysql_prepare_insert +#include "sql_insert.h" // mysql_prepare_insert #include "sql_update.h" // mysql_prepare_update #include "sql_db.h" // mysql_opt_change_db, mysql_change_db #include "sql_derived.h" // mysql_derived_prepare, @@ -1310,8 +1310,6 @@ static bool mysql_test_insert_common(Prepared_statement *stmt, if (insert_precheck(thd, table_list)) goto error; - //upgrade_lock_type_for_insert(thd, &table_list->lock_type, duplic, - // values_list.elements > 1); /* open temporary memory pool for temporary data allocated by derived tables & preparation procedure From 1e7d451e99add748fd35979e74f65c9ec198b7bc Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Thu, 11 Sep 2025 08:16:04 +0300 Subject: [PATCH 10/17] MDEV-37404 InnoDB: Failing assertion: node->pcur->rel_pos == BTR_PCUR_ON Caused by optimization done in 2e2b2a04694. Cannot use lookup_handler in default branch of locate_dup_record() as InnoDB update depends on positioned record and update is done in table main handler. The patch reverts some non-pure changes done by 2e2b2a04694 to original logic from 72429cad. There was no long_unique_table condition to init search on table->file, so we get into default branch with long unique and table->file search uninitialized. ha_rnd_init_with_error() on demand for HA_DUPLICATE_POS branch was original logic as well. More info: 2e2b2a04694 reverts 5e345281e35, but it seems to be OK as MDEV-3888 test case passes. mysql-5.6.13 has the original code with HA_WHOLE_KEY as well. --- mysql-test/main/long_unique_bugs.result | 8 +++++++ mysql-test/main/long_unique_bugs.test | 10 +++++++++ sql/sql_insert.cc | 29 +++++++++++++++---------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/mysql-test/main/long_unique_bugs.result b/mysql-test/main/long_unique_bugs.result index 55cc07ce067..b195e1f7995 100644 --- a/mysql-test/main/long_unique_bugs.result +++ b/mysql-test/main/long_unique_bugs.result @@ -843,3 +843,11 @@ insert into t (a) values (1); update t set a=2; drop table t; # End of 10.6 tests +# +# MDEV-37404 InnoDB: Failing assertion: node->pcur->rel_pos == BTR_PCUR_ON +# +create table t(c int primary key, c2 blob unique) engine=innodb; +insert into t values (0, ''); +replace into t values (0, '123'); +drop table t; +# End of 10.11 tests diff --git a/mysql-test/main/long_unique_bugs.test b/mysql-test/main/long_unique_bugs.test index 256cfcf286f..fc171e67411 100644 --- a/mysql-test/main/long_unique_bugs.test +++ b/mysql-test/main/long_unique_bugs.test @@ -767,3 +767,13 @@ update t set a=2; drop table t; --echo # End of 10.6 tests + +--echo # +--echo # MDEV-37404 InnoDB: Failing assertion: node->pcur->rel_pos == BTR_PCUR_ON +--echo # +create table t(c int primary key, c2 blob unique) engine=innodb; +insert into t values (0, ''); +replace into t values (0, '123'); +drop table t; + +--echo # End of 10.11 tests diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 9f0c83cbbce..434f2c4c873 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -702,8 +702,7 @@ int prepare_for_replace(TABLE *table, enum_duplicates handle_duplicates, { create_lookup_handler= true; table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - if (table->file->ha_table_flags() & HA_DUPLICATE_POS || - table->s->long_unique_table) + if (table->file->ha_table_flags() & HA_DUPLICATE_POS) { if (table->file->ha_rnd_init_with_error(false)) return 1; @@ -2018,17 +2017,26 @@ int vers_insert_history_row(TABLE *table) */ int Write_record::locate_dup_record() { - handler *h= table->file; int error= 0; - if (h->ha_table_flags() & HA_DUPLICATE_POS || h->lookup_errkey != (uint)-1) + if (table->file->ha_table_flags() & HA_DUPLICATE_POS || + table->file->lookup_errkey != (uint)-1) { DBUG_PRINT("info", ("Locating offending record using rnd_pos()")); - error= h->ha_rnd_pos(table->record[1], h->dup_ref); + const bool init_lookup_handler= (table->file->inited == handler::NONE); + if (init_lookup_handler) + { + error= table->file->ha_rnd_init_with_error(false); + if (error) + return error; + } + error= table->file->ha_rnd_pos(table->record[1], table->file->dup_ref); + if (init_lookup_handler) + table->file->ha_rnd_end(); if (unlikely(error)) { DBUG_PRINT("info", ("rnd_pos() returns error %d",error)); - h->print_error(error, MYF(0)); + table->file->print_error(error, MYF(0)); } } else @@ -2036,10 +2044,7 @@ int Write_record::locate_dup_record() DBUG_PRINT("info", ("Locating offending record using ha_index_read_idx_map")); - if (h->lookup_handler) - h= h->lookup_handler; - - error= h->extra(HA_EXTRA_FLUSH_CACHE); + error= table->file->extra(HA_EXTRA_FLUSH_CACHE); if (unlikely(error)) { DBUG_PRINT("info",("Error when setting HA_EXTRA_FLUSH_CACHE")); @@ -2058,12 +2063,12 @@ int Write_record::locate_dup_record() key_copy(key, table->record[0], table->key_info + key_nr, 0); - error= h->ha_index_read_idx_map(table->record[1], key_nr, key, + error= table->file->ha_index_read_idx_map(table->record[1], key_nr, key, HA_WHOLE_KEY, HA_READ_KEY_EXACT); if (unlikely(error)) { DBUG_PRINT("info", ("index_read_idx() returns %d", error)); - h->print_error(error, MYF(0)); + table->file->print_error(error, MYF(0)); } } From cd36925ac16cf109c70939ee01ad5227a0cb9680 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 19 Sep 2025 17:17:48 +1000 Subject: [PATCH 11/17] MDEV-37428 JSON_VALUE returns NULL for a key with an empty string value rather than an empty string Regression from MDEV-36765 / 2b24ed87f0028180e692ba391132f55284377c02. json_unescape can return a string length 0 without it being an error. The regression caused this 0 length empty string to appear as an error and result in a NULL return value. --- mysql-test/main/func_json.result | 6 ++++++ mysql-test/main/func_json.test | 6 ++++++ sql/item_jsonfunc.cc | 10 ++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index 4474e55c1d7..de77e7aa4a7 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -2686,4 +2686,10 @@ json_detailed('[[123],456]') [123], 456 ] +# +# MDEV-37428 JSON_VALUE returns NULL for a key with an empty string value rather than an empty string +# +SELECT JSON_VALUE(JSON_OBJECT("a", ""), '$.a') = "" AS not_null; +not_null +1 # End of 10.11 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index 90a05e38e16..c488c63f06b 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -1947,4 +1947,10 @@ select json_detailed('[[123],456]'); set @@collation_connection=@save_collation_connection; select json_detailed('[[123],456]'); +--echo # +--echo # MDEV-37428 JSON_VALUE returns NULL for a key with an empty string value rather than an empty string +--echo # + +SELECT JSON_VALUE(JSON_OBJECT("a", ""), '$.a') = "" AS not_null; + --echo # End of 10.11 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 34e4aaac09e..5090ae1d2f7 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -109,12 +109,14 @@ bool st_append_json(String *s, return false; } - if ((str_len= json_unescape(json_cs, js, js + js_len, - s->charset(), (uchar *) s->end(), (uchar *) s->end() + str_len)) > 0) - { + str_len= json_unescape(json_cs, js, js + js_len, s->charset(), + (uchar *) s->end(), (uchar *) s->end() + str_len); + if (str_len > 0) s->length(s->length() + str_len); + + if (str_len >= 0) return false; - } + if (current_thd) { if (str_len == JSON_ERROR_OUT_OF_SPACE) From 573d3ad1c61a14f72483e756da38be5b105169d3 Mon Sep 17 00:00:00 2001 From: bsrikanth-mariadb Date: Tue, 16 Sep 2025 09:51:08 +0530 Subject: [PATCH 12/17] MDEV-33309: for update|delete analyze format=json doesn't show r_other_time_ms Single-table UPDATE/DELETEs only show r_total_time_ms in top-level query block. Replace it with r_table_time_ms and r_other_time_ms. --- mysql-test/main/analyze_engine_stats.result | 6 +- mysql-test/main/analyze_format_json.result | 118 +++++++++++++++++- mysql-test/main/analyze_format_json.test | 21 ++++ mysql-test/main/analyze_stmt_orderby.result | 9 +- .../explain_json_format_partitions.result | 6 +- mysql-test/main/subselect_mat.result | 6 +- sql/sql_delete.cc | 1 + sql/sql_explain.cc | 4 +- sql/sql_explain.h | 1 + 9 files changed, 159 insertions(+), 13 deletions(-) diff --git a/mysql-test/main/analyze_engine_stats.result b/mysql-test/main/analyze_engine_stats.result index b3212632c7e..190d3efdc53 100644 --- a/mysql-test/main/analyze_engine_stats.result +++ b/mysql-test/main/analyze_engine_stats.result @@ -68,7 +68,8 @@ X "rows": 10000, "r_rows": 10000, "r_filtered": 100, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": { "pages_accessed": "REPLACED", "pages_updated": "REPLACED" @@ -101,7 +102,8 @@ X "rows": 10000, "r_rows": 10000, "r_filtered": 50, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": { "pages_accessed": "REPLACED", "pages_updated": "REPLACED" diff --git a/mysql-test/main/analyze_format_json.result b/mysql-test/main/analyze_format_json.result index de7a481fc03..d68d860a4a2 100644 --- a/mysql-test/main/analyze_format_json.result +++ b/mysql-test/main/analyze_format_json.result @@ -388,7 +388,8 @@ ANALYZE "rows": 1000, "r_rows": 1000, "r_filtered": 100, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED } } @@ -449,7 +450,8 @@ ANALYZE "rows": 10, "r_rows": 10, "r_filtered": 50, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "t1.pk < 10 and t1.b > 4" } @@ -1205,3 +1207,115 @@ ANALYZE } set join_cache_level=@tmp; drop table t10, t11; +# +# MDEV-33309: ANALYZE FORMAT=JSON: UPDATE|DELETE don't show r_other_time_ms +# +create table t1 (pk int primary key, a int); +insert into t1 select seq, seq from seq_1_to_1000; +create table t2 like t1; +insert into t2 select * from t1; +# Top-level query block must have r_table_time_ms and r_other_time_ms +analyze format=json +update t1 set a=a+1 +where t1.pk > (select max(a) from t2 where t2.pk+1 = t1.pk+1 ) - 10; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "r_total_time_ms": "REPLACED", + "table": { + "update": 1, + "table_name": "t1", + "access_type": "ALL", + "rows": 1000, + "r_rows": 1000, + "r_filtered": 100, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "attached_condition": "t1.pk > (subquery#2) - 10" + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "r_loops": 1000, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 1000, + "rows": 1000, + "r_rows": 1000, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "filtered": 100, + "r_filtered": 0.1, + "attached_condition": "t2.pk + 1 = t1.pk + 1" + } + } + ] + } + } + ] + } +} +# Top-level query block must have r_table_time_ms and r_other_time_ms +analyze format=json +delete from t1 +where t1.pk > (select max(a) from t2 where t2.pk+1 = t1.pk+1 ) - 10; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "r_total_time_ms": "REPLACED", + "table": { + "delete": 1, + "table_name": "t1", + "access_type": "ALL", + "rows": 1000, + "r_rows": 1000, + "r_filtered": 100, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "attached_condition": "t1.pk > (subquery#2) - 10" + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "r_loops": 1000, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 1000, + "rows": 1000, + "r_rows": 1000, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "filtered": 100, + "r_filtered": 0.1, + "attached_condition": "t2.pk + 1 = t1.pk + 1" + } + } + ] + } + } + ] + } +} +drop table t1, t2; diff --git a/mysql-test/main/analyze_format_json.test b/mysql-test/main/analyze_format_json.test index 84f44869afe..d185620724e 100644 --- a/mysql-test/main/analyze_format_json.test +++ b/mysql-test/main/analyze_format_json.test @@ -267,4 +267,25 @@ where set join_cache_level=@tmp; drop table t10, t11; +--echo # +--echo # MDEV-33309: ANALYZE FORMAT=JSON: UPDATE|DELETE don't show r_other_time_ms +--echo # +--source include/have_sequence.inc +create table t1 (pk int primary key, a int); +insert into t1 select seq, seq from seq_1_to_1000; +create table t2 like t1; +insert into t2 select * from t1; +--echo # Top-level query block must have r_table_time_ms and r_other_time_ms +--source include/analyze-format.inc +analyze format=json +update t1 set a=a+1 +where t1.pk > (select max(a) from t2 where t2.pk+1 = t1.pk+1 ) - 10; + +--echo # Top-level query block must have r_table_time_ms and r_other_time_ms +--source include/analyze-format.inc +analyze format=json +delete from t1 +where t1.pk > (select max(a) from t2 where t2.pk+1 = t1.pk+1 ) - 10; + +drop table t1, t2; diff --git a/mysql-test/main/analyze_stmt_orderby.result b/mysql-test/main/analyze_stmt_orderby.result index 273c3f349bc..1312bba41fc 100644 --- a/mysql-test/main/analyze_stmt_orderby.result +++ b/mysql-test/main/analyze_stmt_orderby.result @@ -55,7 +55,8 @@ ANALYZE "rows": 10000, "r_rows": 10000, "r_filtered": 100, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED } } @@ -111,7 +112,8 @@ ANALYZE "rows": 9, "r_rows": 10, "r_filtered": 100, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "t2.a < 10" } @@ -165,7 +167,8 @@ ANALYZE "rows": 10000, "r_rows": 10000, "r_filtered": 100, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED } } diff --git a/mysql-test/main/explain_json_format_partitions.result b/mysql-test/main/explain_json_format_partitions.result index d33d744179c..3ec6943ddac 100644 --- a/mysql-test/main/explain_json_format_partitions.result +++ b/mysql-test/main/explain_json_format_partitions.result @@ -74,7 +74,8 @@ ANALYZE "rows": 10, "r_rows": 10, "r_filtered": 30, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "t1.a in (2,3,4)" } @@ -98,7 +99,8 @@ ANALYZE "rows": 10, "r_rows": 10, "r_filtered": 0, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "t1.a in (20,30,40)" } diff --git a/mysql-test/main/subselect_mat.result b/mysql-test/main/subselect_mat.result index cba51ac2f4f..f554b6f6786 100644 --- a/mysql-test/main/subselect_mat.result +++ b/mysql-test/main/subselect_mat.result @@ -3320,7 +3320,8 @@ ANALYZE "rows": 4, "r_rows": 4, "r_filtered": 25, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "!((t1.a,t1.a in (subquery#2)))" }, @@ -3395,7 +3396,8 @@ ANALYZE "rows": 2, "r_rows": 2, "r_filtered": 0, - "r_total_time_ms": "REPLACED", + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", "r_engine_stats": REPLACED, "attached_condition": "!((t1.a,t1.a in (subquery#2)))" }, diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 32a76326a49..ed02b77d3b1 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -125,6 +125,7 @@ bool Update_plan::save_explain_data_intern(THD *thd, (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_ENGINE)) { + explain->table_tracker.set_gap_tracker(&explain->extra_time_tracker); table->file->set_time_tracker(&explain->table_tracker); if (table->file->handler_stats && table->s->tmp_table != INTERNAL_TMP_TABLE) diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index e59b2e3cc8a..b26b7b6a489 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -2684,8 +2684,8 @@ void Explain_update::print_explain_json(Explain_query *query, if (table_tracker.has_timed_statistics()) { - writer->add_member("r_total_time_ms"). - add_double(table_tracker.get_time_ms()); + writer->add_member("r_table_time_ms").add_double(table_tracker.get_time_ms()); + writer->add_member("r_other_time_ms").add_double(extra_time_tracker.get_time_ms()); } } diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 5d5dde15649..36aa83b8104 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -1007,6 +1007,7 @@ public: /* TODO: This tracks time to read rows from the table */ Exec_time_tracker table_tracker; + Gap_time_tracker extra_time_tracker; /* The same as Explain_table_access::handler_for_stats */ handler *handler_for_stats; From 687b18648ca41aecb4211f14f6506410a9495f9f Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Mon, 22 Sep 2025 17:39:47 +0530 Subject: [PATCH 13/17] MDEV-35163 InnoDB persistent statistics fail to update after ALTER TABLE...ALGORITHM=COPY Problem: ======= - InnoDB statistics calculation for the table is done after every 10 seconds by default in background thread dict_stats_thread() - Doing multiple ALTER TABLE..ALGORITHM=COPY causes the dict_stats_thread() to lag behind, therefore calculation of stats for newly created intermediate table gets delayed Fix: ==== - Stats calculation for newly created intermediate table is made independent of background thread. After copying gets completed, stats for new table is calculated as part of ALTER TABLE ... ALGORITHM=COPY. dict_stats_rename_table(): Rename the table statistics from intermediate table to new table alter_stats_rebuild(): Removes the table name from the warning. Because this warning can print for intermediate table as well. Alter table using copy algorithm now calls alter_stats_rebuild() under a shared MDL lock on a temporary #sql-alter- table, differing from its previous use only during ALGORITHM=INPLACE operations on user-visible tables. dict_stats_schema_check(): Added a separate check for table readability before checking for tablespace existence. This could lead to detect of existence of persistent statistics storage eariler and fallback to transient statistics. This is a cherry-pick fix of mysql commit@cfe5f287ae99d004e8532a30003a7e8e77d379e3 --- .../suite/innodb/r/alter_copy_stats.result | 105 ++++++++++++++++++ .../suite/innodb/r/innodb-alter-debug.result | 5 +- ...p_release_locks_on_dict_stats_table.result | 2 +- .../suite/innodb/t/alter_copy_stats.test | 90 +++++++++++++++ .../suite/innodb/t/innodb-alter-debug.test | 2 +- ...xap_release_locks_on_dict_stats_table.test | 2 +- mysql-test/suite/innodb_fts/t/versioning.test | 3 + storage/innobase/dict/dict0stats.cc | 15 ++- storage/innobase/handler/ha_innodb.cc | 43 +++++-- storage/innobase/handler/ha_innodb.h | 7 ++ storage/innobase/handler/handler0alter.cc | 43 +------ 11 files changed, 257 insertions(+), 60 deletions(-) create mode 100644 mysql-test/suite/innodb/r/alter_copy_stats.result create mode 100644 mysql-test/suite/innodb/t/alter_copy_stats.test diff --git a/mysql-test/suite/innodb/r/alter_copy_stats.result b/mysql-test/suite/innodb/r/alter_copy_stats.result new file mode 100644 index 00000000000..15913a44f55 --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_copy_stats.result @@ -0,0 +1,105 @@ +CREATE TABLE t1 ( +c1 INT PRIMARY KEY, +c2 VARCHAR(50), +c3 VARCHAR(50), +c4 VARCHAR(50))ENGINE=InnoDB; +INSERT INTO t1 VALUES(1, 'AAA_2', 'AAA_3', 'AAA_4'), +(2, 'BBB_2', 'BBB_3', 'BBB_4'), +(3, 'CCC_2', 'CCC_3', 'CCC_4'), +(4, 'DDD_2', 'DDD_3', 'DDD_4'), +(5, 'EEE_2', 'EEE_3', 'EEE_4'); +ALTER TABLE t1 CONVERT TO CHARACTER SET 'utf8mb4',ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (c4), ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (c1), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = INPLACE; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = INPLACE; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); +n_rows database_name lower(table_name) +5 test t1 +CREATE TABLE t2 ( +c1 INT PRIMARY KEY, +c2 VARCHAR(50), +c3 VARCHAR(50), +c4 VARCHAR(50) +) ENGINE=InnoDB PARTITION BY RANGE (c1) ( +PARTITION p1 VALUES LESS THAN (6), +PARTITION p2 VALUES LESS THAN (11), +PARTITION p3 VALUES LESS THAN (16), +PARTITION p4 VALUES LESS THAN (21) +); +INSERT INTO t2 VALUES(1, 'AAA_2', 'AAA_3', 'AAA_4'), +(2, 'BBB_2', 'BBB_3', 'BBB_4'), +(3, 'CCC_2', 'CCC_3', 'CCC_4'), +(4, 'DDD_2', 'DDD_3', 'DDD_4'), +(5, 'EEE_2', 'EEE_3', 'EEE_4'), +(6, 'FFF_2', 'DDD_3', 'DDD_4'), +(7, 'GGG_2', 'DDD_3', 'DDD_4'), +(8, 'HHH_2', 'DDD_3', 'DDD_4'), +(9, 'III_2', 'DDD_3', 'DDD_4'), +(10, 'JJJ_2', 'DDD_3', 'DDD_4'), +(13, 'KKK_2', 'DDD_3', 'DDD_4'), +(20, 'LLL_2', 'DDD_3', 'DDD_4'); +ALTER TABLE t2 CONVERT TO CHARACTER SET 'utf8mb4',ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; +n_rows database_name lower(table_name) +5 test t2#p#p1 +5 test t2#p#p2 +1 test t2#p#p3 +1 test t2#p#p4 +ALTER TABLE t2 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; +n_rows database_name lower(table_name) +5 test t2#p#p1 +5 test t2#p#p2 +1 test t2#p#p3 +1 test t2#p#p4 +ALTER TABLE t2 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; +n_rows database_name lower(table_name) +5 test t2#p#p1 +5 test t2#p#p2 +1 test t2#p#p3 +1 test t2#p#p4 +# Test Cleanup. +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/suite/innodb/r/innodb-alter-debug.result b/mysql-test/suite/innodb/r/innodb-alter-debug.result index fd33581ffab..d1d72a375fd 100644 --- a/mysql-test/suite/innodb/r/innodb-alter-debug.result +++ b/mysql-test/suite/innodb/r/innodb-alter-debug.result @@ -117,8 +117,9 @@ ALTER TABLE t1 FORCE, ALGORITHM=COPY; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; BEGIN; -SELECT * FROM mysql.innodb_table_stats FOR UPDATE; -database_name table_name last_update n_rows clustered_index_size sum_of_other_index_sizes +SELECT database_name, table_name FROM mysql.innodb_table_stats FOR UPDATE; +database_name table_name +test t1 SET DEBUG_SYNC='now SIGNAL go'; connection con1; connection default; diff --git a/mysql-test/suite/innodb/r/xap_release_locks_on_dict_stats_table.result b/mysql-test/suite/innodb/r/xap_release_locks_on_dict_stats_table.result index 1a849f1c477..a2f25c528f4 100644 --- a/mysql-test/suite/innodb/r/xap_release_locks_on_dict_stats_table.result +++ b/mysql-test/suite/innodb/r/xap_release_locks_on_dict_stats_table.result @@ -6,7 +6,7 @@ SET @old_debug_dbug = @@global.debug_dbug; XA START 'a'; INSERT INTO mysql.innodb_index_stats SELECT '','' AS table_name,index_name,LAST_UPDATE,stat_name,0 AS stat_value,sample_size,stat_description FROM mysql.innodb_index_stats WHERE table_name='dummy' FOR UPDATE; SET GLOBAL debug_dbug = "+d,dict_stats_save_exit_notify"; -INSERT INTO t VALUES (1); +INSERT INTO t VALUES (1), (2); XA END 'a'; XA PREPARE 'a'; SET DEBUG_SYNC="now WAIT_FOR dict_stats_save_finished"; diff --git a/mysql-test/suite/innodb/t/alter_copy_stats.test b/mysql-test/suite/innodb/t/alter_copy_stats.test new file mode 100644 index 00000000000..3b0c59d5726 --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_copy_stats.test @@ -0,0 +1,90 @@ +--source include/have_innodb.inc +--source include/have_partition.inc + +CREATE TABLE t1 ( + c1 INT PRIMARY KEY, + c2 VARCHAR(50), + c3 VARCHAR(50), + c4 VARCHAR(50))ENGINE=InnoDB; + +INSERT INTO t1 VALUES(1, 'AAA_2', 'AAA_3', 'AAA_4'), + (2, 'BBB_2', 'BBB_3', 'BBB_4'), + (3, 'CCC_2', 'CCC_3', 'CCC_4'), + (4, 'DDD_2', 'DDD_3', 'DDD_4'), + (5, 'EEE_2', 'EEE_3', 'EEE_4'); + +ALTER TABLE t1 CONVERT TO CHARACTER SET 'utf8mb4',ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (c4), ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (c1), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 ADD COLUMN c5 VARCHAR(15), ALGORITHM = INPLACE; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +ALTER TABLE t1 DROP COLUMN c5, ALGORITHM = INPLACE; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name IN ('t1'); + +CREATE TABLE t2 ( + c1 INT PRIMARY KEY, + c2 VARCHAR(50), + c3 VARCHAR(50), + c4 VARCHAR(50) + ) ENGINE=InnoDB PARTITION BY RANGE (c1) ( + PARTITION p1 VALUES LESS THAN (6), + PARTITION p2 VALUES LESS THAN (11), + PARTITION p3 VALUES LESS THAN (16), + PARTITION p4 VALUES LESS THAN (21) +); +INSERT INTO t2 VALUES(1, 'AAA_2', 'AAA_3', 'AAA_4'), + (2, 'BBB_2', 'BBB_3', 'BBB_4'), + (3, 'CCC_2', 'CCC_3', 'CCC_4'), + (4, 'DDD_2', 'DDD_3', 'DDD_4'), + (5, 'EEE_2', 'EEE_3', 'EEE_4'), + (6, 'FFF_2', 'DDD_3', 'DDD_4'), + (7, 'GGG_2', 'DDD_3', 'DDD_4'), + (8, 'HHH_2', 'DDD_3', 'DDD_4'), + (9, 'III_2', 'DDD_3', 'DDD_4'), + (10, 'JJJ_2', 'DDD_3', 'DDD_4'), + (13, 'KKK_2', 'DDD_3', 'DDD_4'), + (20, 'LLL_2', 'DDD_3', 'DDD_4'); + +ALTER TABLE t2 CONVERT TO CHARACTER SET 'utf8mb4',ALGORITHM=COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; + +ALTER TABLE t2 ADD COLUMN c5 VARCHAR(15), ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; + +ALTER TABLE t2 DROP COLUMN c5, ALGORITHM = COPY; +SELECT n_rows, database_name, lower(table_name) +FROM mysql.innodb_table_stats WHERE table_name LIKE '%t2%'; + +--echo # Test Cleanup. +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/suite/innodb/t/innodb-alter-debug.test b/mysql-test/suite/innodb/t/innodb-alter-debug.test index 6b94bfd214f..ee899f813c2 100644 --- a/mysql-test/suite/innodb/t/innodb-alter-debug.test +++ b/mysql-test/suite/innodb/t/innodb-alter-debug.test @@ -156,7 +156,7 @@ ALTER TABLE t1 FORCE, ALGORITHM=COPY; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; BEGIN; -SELECT * FROM mysql.innodb_table_stats FOR UPDATE; +SELECT database_name, table_name FROM mysql.innodb_table_stats FOR UPDATE; SET DEBUG_SYNC='now SIGNAL go'; connection con1; diff --git a/mysql-test/suite/innodb/t/xap_release_locks_on_dict_stats_table.test b/mysql-test/suite/innodb/t/xap_release_locks_on_dict_stats_table.test index a02a032ef61..0de3e6f24ef 100644 --- a/mysql-test/suite/innodb/t/xap_release_locks_on_dict_stats_table.test +++ b/mysql-test/suite/innodb/t/xap_release_locks_on_dict_stats_table.test @@ -20,7 +20,7 @@ SET @old_debug_dbug = @@global.debug_dbug; XA START 'a'; INSERT INTO mysql.innodb_index_stats SELECT '','' AS table_name,index_name,LAST_UPDATE,stat_name,0 AS stat_value,sample_size,stat_description FROM mysql.innodb_index_stats WHERE table_name='dummy' FOR UPDATE; # Note the SELECT is empty SET GLOBAL debug_dbug = "+d,dict_stats_save_exit_notify"; -INSERT INTO t VALUES (1); +INSERT INTO t VALUES (1), (2); XA END 'a'; XA PREPARE 'a'; diff --git a/mysql-test/suite/innodb_fts/t/versioning.test b/mysql-test/suite/innodb_fts/t/versioning.test index 286597fba39..44d1e3da2e7 100644 --- a/mysql-test/suite/innodb_fts/t/versioning.test +++ b/mysql-test/suite/innodb_fts/t/versioning.test @@ -65,6 +65,9 @@ if ($MTR_COMBINATION_UPGRADE) { --disable_query_log call mtr.add_suppression("InnoDB: Table `mysql`.\`innodb_(table|index)_stats`"); +call mtr.add_suppression("InnoDB: Table mysql\\.innodb_table_stats is not readable"); +call mtr.add_suppression("InnoDB: Fetch of persistent statistics requested for table `test`\\.`articles[0-9]*` but the required system tables mysql.innodb_table_stats and mysql.innodb_index_stats are not present or have unexpected structure. Using transient stats instead."); +call mtr.add_suppression("InnoDB: Recalculation of persistent statistics requested for table `test`\\.`#sql-alter.*` but the required persistent statistics storage is not present or is corrupted. Using transient stats instead."); --enable_query_log --source include/shutdown_mysqld.inc --exec rm -f $datadir/test/*.ibd $datadir/ib* diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc index ddd5ee03f0d..41fa3c91623 100644 --- a/storage/innobase/dict/dict0stats.cc +++ b/storage/innobase/dict/dict0stats.cc @@ -380,7 +380,15 @@ dict_table_schema_check( return DB_TABLE_NOT_FOUND; } - if (!table->is_readable() && !table->space) { + if (!table->is_readable()) { + /* table is not readable */ + snprintf(errstr, errstr_sz, + "Table %s is not readable.", + req_schema->table_name_sql); + return DB_ERROR; + } + + if (!table->space) { /* missing tablespace */ snprintf(errstr, errstr_sz, "Tablespace for table %s is missing.", @@ -3921,8 +3929,9 @@ dberr_t dict_stats_rename_table(const char *old_name, const char *new_name, dict_fs2utf8(old_name, old_db, sizeof old_db, old_table, sizeof old_table); dict_fs2utf8(new_name, new_db, sizeof new_db, new_table, sizeof new_table); - if (dict_table_t::is_temporary_name(old_name) || - dict_table_t::is_temporary_name(new_name)) + /* Delete the stats only if renaming the table from old table to + intermediate table during COPY algorithm */ + if (dict_table_t::is_temporary_name(new_name)) { if (dberr_t e= dict_stats_delete_from_table_stats(old_db, old_table, trx)) return e; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 9a136cbc178..5767789fe63 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -15787,16 +15787,17 @@ ha_innobase::extra( /* Warning: since it is not sure that MariaDB calls external_lock() before calling this function, m_prebuilt->trx can be obsolete! */ trx_t* trx; + THD* thd = ha_thd(); switch (operation) { case HA_EXTRA_FLUSH: - (void)check_trx_exists(ha_thd()); + (void)check_trx_exists(thd); if (m_prebuilt->blob_heap) { row_mysql_prebuilt_free_blob_heap(m_prebuilt); } break; case HA_EXTRA_RESET_STATE: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); reset_template(); trx->duplicates = 0; stmt_boundary: @@ -15804,23 +15805,23 @@ ha_innobase::extra( trx->bulk_insert = false; break; case HA_EXTRA_NO_KEYREAD: - (void)check_trx_exists(ha_thd()); + (void)check_trx_exists(thd); m_prebuilt->read_just_key = 0; break; case HA_EXTRA_KEYREAD: - (void)check_trx_exists(ha_thd()); + (void)check_trx_exists(thd); m_prebuilt->read_just_key = 1; break; case HA_EXTRA_KEYREAD_PRESERVE_FIELDS: - (void)check_trx_exists(ha_thd()); + (void)check_trx_exists(thd); m_prebuilt->keep_other_fields_on_keyread = 1; break; case HA_EXTRA_INSERT_WITH_UPDATE: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); trx->duplicates |= TRX_DUP_IGNORE; goto stmt_boundary; case HA_EXTRA_NO_IGNORE_DUP_KEY: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); trx->duplicates &= ~TRX_DUP_IGNORE; if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table @@ -15829,11 +15830,11 @@ ha_innobase::extra( } goto stmt_boundary; case HA_EXTRA_WRITE_CAN_REPLACE: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); trx->duplicates |= TRX_DUP_REPLACE; goto stmt_boundary; case HA_EXTRA_WRITE_CANNOT_REPLACE: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); trx->duplicates &= ~TRX_DUP_REPLACE; if (trx->is_bulk_insert()) { /* Allow a subsequent INSERT into an empty table @@ -15842,7 +15843,7 @@ ha_innobase::extra( } goto stmt_boundary; case HA_EXTRA_BEGIN_ALTER_COPY: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); m_prebuilt->table->skip_alter_undo = 1; if (m_prebuilt->table->is_temporary() || !m_prebuilt->table->versioned_by_id()) { @@ -15855,7 +15856,7 @@ ha_innobase::extra( .first->second.set_versioned(0); break; case HA_EXTRA_END_ALTER_COPY: - trx = check_trx_exists(ha_thd()); + trx = check_trx_exists(thd); if (!m_prebuilt->table->skip_alter_undo) { /* This could be invoked inside INSERT...SELECT. We do not want any extra log writes, because @@ -15874,6 +15875,7 @@ ha_innobase::extra( handler::extra(HA_EXTRA_BEGIN_ALTER_COPY). */ log_buffer_flush_to_disk(); } + alter_stats_rebuild(m_prebuilt->table, thd); break; default:/* Do nothing */ ; @@ -21291,3 +21293,22 @@ ulint buf_pool_size_align(ulint size) noexcept return (ulint)((size / m + 1) * m); } } + +/** Adjust the persistent statistics after rebuilding ALTER TABLE. +Remove statistics for dropped indexes, add statistics for created indexes +and rename statistics for renamed indexes. +@param table InnoDB table that was rebuilt by ALTER TABLE +@param thd alter table thread */ +void alter_stats_rebuild(dict_table_t *table, THD *thd) +{ + DBUG_ENTER("alter_stats_rebuild"); + if (!table->space || !dict_stats_is_persistent_enabled(table)) + DBUG_VOID_RETURN; + + dberr_t ret= dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT); + if (ret != DB_SUCCESS) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_ALTER_INFO, "Error updating stats for table after" + " table rebuild: %s", ut_strerr(ret)); + DBUG_VOID_RETURN; +} diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 534936f8fae..349cbec2f8d 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -942,3 +942,10 @@ ib_push_frm_error( @return true if index column length exceeds limit */ MY_ATTRIBUTE((warn_unused_result)) bool too_big_key_part_length(size_t max_field_len, const KEY& key); + +/** Adjust the persistent statistics after rebuilding ALTER TABLE. +Remove statistics for dropped indexes, add statistics for created indexes +and rename statistics for renamed indexes. +@param table_name Table name in MySQL +@param thd alter table thread */ +void alter_stats_rebuild(dict_table_t *table, THD *thd); diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 3459be3a06d..1bd59e49929 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -11176,7 +11176,7 @@ Remove statistics for dropped indexes, add statistics for created indexes and rename statistics for renamed indexes. @param ha_alter_info Data used during in-place alter @param ctx In-place ALTER TABLE context -@param thd MySQL connection +@param thd alter table thread */ static void @@ -11206,43 +11206,6 @@ alter_stats_norebuild( DBUG_VOID_RETURN; } -/** Adjust the persistent statistics after rebuilding ALTER TABLE. -Remove statistics for dropped indexes, add statistics for created indexes -and rename statistics for renamed indexes. -@param table InnoDB table that was rebuilt by ALTER TABLE -@param table_name Table name in MySQL -@param thd MySQL connection -*/ -static -void -alter_stats_rebuild( -/*================*/ - dict_table_t* table, - const char* table_name, - THD* thd) -{ - DBUG_ENTER("alter_stats_rebuild"); - - if (!table->space - || !dict_stats_is_persistent_enabled(table)) { - DBUG_VOID_RETURN; - } - - dberr_t ret = dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT); - - if (ret != DB_SUCCESS) { - push_warning_printf( - thd, - Sql_condition::WARN_LEVEL_WARN, - ER_ALTER_INFO, - "Error updating stats for table '%s'" - " after table rebuild: %s", - table_name, ut_strerr(ret)); - } - - DBUG_VOID_RETURN; -} - /** Apply the log for the table rebuild operation. @param[in] ctx Inplace Alter table context @param[in] altered_table MySQL table that is being altered @@ -11908,9 +11871,7 @@ foreign_fail: (*pctx); DBUG_ASSERT(ctx->need_rebuild()); - alter_stats_rebuild( - ctx->new_table, table->s->table_name.str, - m_user_thd); + alter_stats_rebuild(ctx->new_table, m_user_thd); } } else { for (inplace_alter_handler_ctx** pctx = ctx_array; From f9bdff6162e535b416d4d8b3f63a997f9b359d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Tue, 23 Sep 2025 08:24:30 +0300 Subject: [PATCH 14/17] MDEV-37373 : InnoDB partition table disallow local GTIDs in galera Problem was that for partitioned tables base table storage engine is DB_TYPE_PARTITION_DB and naturally different than DB_TYPE_INNODB so operation was not allowed in Galera. Fixed by requesting implementing storage engine for partitioned tables i.e. table->file->partition_ht() or if that does not exist we can use base table storage engine. Resulting storage engine type is then used on condition is operation allowed when wsrep_mode=DISALLOW_LOCAL_GTID or not. Operations to InnoDB storage engine i.e DB_TYPE_INNODB should be allowed. --- .../galera/r/galera_partitioned_tables.result | 63 +++++++++++++++++-- .../t/galera_partitioned_tables.combinations | 5 ++ .../galera/t/galera_partitioned_tables.test | 39 +++++++++++- sql/wsrep_mysqld.cc | 10 ++- 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 mysql-test/suite/galera/t/galera_partitioned_tables.combinations diff --git a/mysql-test/suite/galera/r/galera_partitioned_tables.result b/mysql-test/suite/galera/r/galera_partitioned_tables.result index b68c1786ff7..dab39fe0493 100644 --- a/mysql-test/suite/galera/r/galera_partitioned_tables.result +++ b/mysql-test/suite/galera/r/galera_partitioned_tables.result @@ -94,8 +94,6 @@ ALTER TABLE t1 ADD COLUMN v2 int; ALTER TABLE t2 ADD COLUMN v2 int; ERROR HY000: Galera replication not supported INSERT INTO t1 VALUES (1,1),(2,2); -Warnings: -Warning 1290 WSREP: wsrep_mode = STRICT_REPLICATION enabled. Storage engine partition for table 'test'.'t1' is not supported in Galera INSERT INTO t2 VALUES (1),(2); Warnings: Warning 1290 WSREP: wsrep_mode = STRICT_REPLICATION enabled. Storage engine partition for table 'test'.'t2' is not supported in Galera @@ -104,8 +102,6 @@ ERROR HY000: Galera replication not supported ALTER TABLE t2 ADD COLUMN v3 int, ENGINE=Aria; ERROR HY000: Galera replication not supported UPDATE t1 SET v2 = v2 + 3; -Warnings: -Warning 1290 WSREP: wsrep_mode = STRICT_REPLICATION enabled. Storage engine partition for table 'test'.'t1' is not supported in Galera UPDATE t2 SET v1 = v1 + 3; Warnings: Warning 1290 WSREP: wsrep_mode = STRICT_REPLICATION enabled. Storage engine partition for table 'test'.'t2' is not supported in Galera @@ -173,4 +169,61 @@ SELECT @@wsrep_mode; STRICT_REPLICATION ALTER TABLE t2 ENGINE=InnoDB; DROP TABLE t2; -SET GLOBAL wsrep_mode = DEFAULT; +connection node_1; +# +# MDEV-37373 InnoDB partition table disallow local GTIDs in galera +# wsrep-mode= DISALLOW_LOCAL_GTID +# +SET GLOBAL wsrep_mode = "DISALLOW_LOCAL_GTID"; +SELECT @@wsrep_mode; +@@wsrep_mode +DISALLOW_LOCAL_GTID +CREATE TABLE `sales` ( +`customer_id` int(11) NOT NULL, +`customer_name` varchar(40) DEFAULT NULL, +`store_id` varchar(20) NOT NULL, +`bill_number` int(11) NOT NULL, +`bill_date` date NOT NULL, +`amount` decimal(8,2) NOT NULL, +PRIMARY KEY (`bill_date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +PARTITION BY RANGE (year(`bill_date`)) +(PARTITION `p0` VALUES LESS THAN (2016) ENGINE = InnoDB, +PARTITION `p1` VALUES LESS THAN (2017) ENGINE = InnoDB, +PARTITION `p2` VALUES LESS THAN (2018) ENGINE = InnoDB, +PARTITION `p3` VALUES LESS THAN (2020) ENGINE = InnoDB); +INSERT INTO sales +VALUES (1, 'Michael', 'S001', 101, '2015-01-02', 125.56), +(2, 'Jim', 'S003', 103, '2015-01-25', 476.50), +(3, 'Dwight', 'S012', 122, '2016-02-15', 335.00), +(4, 'Andy', 'S345', 121, '2016-03-26', 787.00), +(5, 'Pam', 'S234', 132, '2017-04-19', 678.00), +(6, 'Karen', 'S743', 111, '2017-05-31', 864.00), +(7, 'Toby', 'S234', 115, '2018-06-11', 762.00), +(8, 'Oscar', 'S012', 125, '2019-07-24', 300.00), +(9, 'Darryl', 'S456', 119, '2019-08-02', 492.20); +SELECT * FROM sales; +customer_id customer_name store_id bill_number bill_date amount +1 Michael S001 101 2015-01-02 125.56 +2 Jim S003 103 2015-01-25 476.50 +3 Dwight S012 122 2016-02-15 335.00 +4 Andy S345 121 2016-03-26 787.00 +5 Pam S234 132 2017-04-19 678.00 +6 Karen S743 111 2017-05-31 864.00 +7 Toby S234 115 2018-06-11 762.00 +8 Oscar S012 125 2019-07-24 300.00 +9 Darryl S456 119 2019-08-02 492.20 +SET GLOBAL wsrep_mode=DEFAULT; +connection node_2; +SELECT * FROM sales; +customer_id customer_name store_id bill_number bill_date amount +1 Michael S001 101 2015-01-02 125.56 +2 Jim S003 103 2015-01-25 476.50 +3 Dwight S012 122 2016-02-15 335.00 +4 Andy S345 121 2016-03-26 787.00 +5 Pam S234 132 2017-04-19 678.00 +6 Karen S743 111 2017-05-31 864.00 +7 Toby S234 115 2018-06-11 762.00 +8 Oscar S012 125 2019-07-24 300.00 +9 Darryl S456 119 2019-08-02 492.20 +DROP TABLE sales; diff --git a/mysql-test/suite/galera/t/galera_partitioned_tables.combinations b/mysql-test/suite/galera/t/galera_partitioned_tables.combinations new file mode 100644 index 00000000000..cef98e75213 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_partitioned_tables.combinations @@ -0,0 +1,5 @@ +[binlogon] +log-bin +log-slave-updates + +[binlogoff] diff --git a/mysql-test/suite/galera/t/galera_partitioned_tables.test b/mysql-test/suite/galera/t/galera_partitioned_tables.test index 8e52b59d97d..41d2689f171 100644 --- a/mysql-test/suite/galera/t/galera_partitioned_tables.test +++ b/mysql-test/suite/galera/t/galera_partitioned_tables.test @@ -130,4 +130,41 @@ SELECT @@wsrep_mode; ALTER TABLE t2 ENGINE=InnoDB; DROP TABLE t2; -SET GLOBAL wsrep_mode = DEFAULT; +--connection node_1 +--echo # +--echo # MDEV-37373 InnoDB partition table disallow local GTIDs in galera +--echo # wsrep-mode= DISALLOW_LOCAL_GTID +--echo # +SET GLOBAL wsrep_mode = "DISALLOW_LOCAL_GTID"; +SELECT @@wsrep_mode; +CREATE TABLE `sales` ( + `customer_id` int(11) NOT NULL, + `customer_name` varchar(40) DEFAULT NULL, + `store_id` varchar(20) NOT NULL, + `bill_number` int(11) NOT NULL, + `bill_date` date NOT NULL, + `amount` decimal(8,2) NOT NULL, + PRIMARY KEY (`bill_date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci + PARTITION BY RANGE (year(`bill_date`)) +(PARTITION `p0` VALUES LESS THAN (2016) ENGINE = InnoDB, + PARTITION `p1` VALUES LESS THAN (2017) ENGINE = InnoDB, + PARTITION `p2` VALUES LESS THAN (2018) ENGINE = InnoDB, + PARTITION `p3` VALUES LESS THAN (2020) ENGINE = InnoDB); + +INSERT INTO sales +VALUES (1, 'Michael', 'S001', 101, '2015-01-02', 125.56), +(2, 'Jim', 'S003', 103, '2015-01-25', 476.50), +(3, 'Dwight', 'S012', 122, '2016-02-15', 335.00), +(4, 'Andy', 'S345', 121, '2016-03-26', 787.00), +(5, 'Pam', 'S234', 132, '2017-04-19', 678.00), +(6, 'Karen', 'S743', 111, '2017-05-31', 864.00), +(7, 'Toby', 'S234', 115, '2018-06-11', 762.00), +(8, 'Oscar', 'S012', 125, '2019-07-24', 300.00), +(9, 'Darryl', 'S456', 119, '2019-08-02', 492.20); + +SELECT * FROM sales; +SET GLOBAL wsrep_mode=DEFAULT; +--connection node_2 +SELECT * FROM sales; +DROP TABLE sales; \ No newline at end of file diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index d709d7b5960..7fa31856c18 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1444,10 +1444,16 @@ bool wsrep_check_mode_after_open_table (THD *thd, if (!is_dml_stmt) return true; - const legacy_db_type db_type= hton->db_type; + TABLE *tbl= tables->table; + /* If this is partitioned table we need to find out + implementing storage engine handlerton. + */ + const handlerton *ht= tbl->file->partition_ht(); + if (!ht) ht= hton; + + const legacy_db_type db_type= ht->db_type; bool replicate= ((db_type == DB_TYPE_MYISAM && wsrep_check_mode(WSREP_MODE_REPLICATE_MYISAM)) || (db_type == DB_TYPE_ARIA && wsrep_check_mode(WSREP_MODE_REPLICATE_ARIA))); - TABLE *tbl= tables->table; if (replicate) { From f2ef683b7af8e34c690438e7064d2f7ae07ec72f Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 22 Sep 2025 11:58:50 +1000 Subject: [PATCH 15/17] MDEV-37705 main.lotofstack /main.sp-error fails in MSAN+Debug Tests on clang-20/21 had both of these tests overrunning the stack. The check_stack_overrun function checked the function earlier with a 2*STACK_MIN_SIZE margin. The exection within the processing is deeper then when check_stack_overrun was called. Raising STACK_MIN_SIZE to 44k was sufficient (and 40k wasn't oufficient). execution_constants also tested however the topic mention tests are bigger. Perfscheam tests * perfschema.statement_program_nesting_event_check * perfschema.statement_program_nested * perfschema.max_program_zero A small increase to the test thread-stack-size on statement_program_lost_inst allows this test to continue to pass. --- mysql-test/suite/perfschema/t/max_program_zero.test | 1 + .../suite/perfschema/t/statement_program_lost_inst.test | 2 +- .../suite/perfschema/t/statement_program_nested.test | 1 + .../t/statement_program_nesting_event_check.test | 1 + sql/sql_const.h | 7 +++++++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/perfschema/t/max_program_zero.test b/mysql-test/suite/perfschema/t/max_program_zero.test index 064ba2ae2d9..7cbe95c1c0c 100644 --- a/mysql-test/suite/perfschema/t/max_program_zero.test +++ b/mysql-test/suite/perfschema/t/max_program_zero.test @@ -11,6 +11,7 @@ --source include/not_embedded.inc --source include/have_perfschema.inc +--source include/not_msan_with_debug.inc --source ../include/start_server_common.inc diff --git a/mysql-test/suite/perfschema/t/statement_program_lost_inst.test b/mysql-test/suite/perfschema/t/statement_program_lost_inst.test index 0742043bba3..321bb649daf 100644 --- a/mysql-test/suite/perfschema/t/statement_program_lost_inst.test +++ b/mysql-test/suite/perfschema/t/statement_program_lost_inst.test @@ -22,7 +22,7 @@ let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; --write_line wait $restart_file --shutdown_server --source include/wait_until_disconnected.inc ---write_line "restart:--performance_schema_max_program_instances=7 --performance_schema_max_statement_stack=2 --thread_stack=655360" $restart_file +--write_line "restart:--performance_schema_max_program_instances=7 --performance_schema_max_statement_stack=2 --thread_stack=1048576" $restart_file --enable_reconnect --source include/wait_until_connected_again.inc diff --git a/mysql-test/suite/perfschema/t/statement_program_nested.test b/mysql-test/suite/perfschema/t/statement_program_nested.test index 183e9858226..75218359314 100644 --- a/mysql-test/suite/perfschema/t/statement_program_nested.test +++ b/mysql-test/suite/perfschema/t/statement_program_nested.test @@ -5,6 +5,7 @@ --source include/not_embedded.inc --source include/have_perfschema.inc --source include/have_innodb.inc +--source include/not_msan_with_debug.inc TRUNCATE TABLE performance_schema.events_statements_summary_by_program; TRUNCATE TABLE performance_schema.events_statements_history_long; diff --git a/mysql-test/suite/perfschema/t/statement_program_nesting_event_check.test b/mysql-test/suite/perfschema/t/statement_program_nesting_event_check.test index 73829be8b42..31368b9f7d6 100644 --- a/mysql-test/suite/perfschema/t/statement_program_nesting_event_check.test +++ b/mysql-test/suite/perfschema/t/statement_program_nesting_event_check.test @@ -8,6 +8,7 @@ --source include/not_embedded.inc --source include/have_perfschema.inc --source include/have_innodb.inc +--source include/not_msan_with_debug.inc TRUNCATE TABLE performance_schema.events_statements_history_long; diff --git a/sql/sql_const.h b/sql/sql_const.h index 354d942e160..bee1a56d784 100644 --- a/sql/sql_const.h +++ b/sql/sql_const.h @@ -167,7 +167,14 @@ Feel free to raise this by the smallest amount you can to get the "execution_constants" test to pass. */ +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if defined(__clang__) && __has_feature(memory_sanitizer) && !defined(DBUG_OFF) +#define STACK_MIN_SIZE 44000 +#else #define STACK_MIN_SIZE 16000 // Abort if less stack during eval. +#endif #define STACK_MIN_SIZE_FOR_OPEN (1024*80) #define STACK_BUFF_ALLOC 352 ///< For stack overrun checks From 8c7077346ca8895110d8cc068102824caa546883 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 29 Jul 2025 17:46:12 +1000 Subject: [PATCH 16/17] MDEV-35224 ASAN error in mariadb client with \c Mariadb monitor when a line is cancelled, provided its more than one line, passes that text to the add_history of libedit. This string that has been passed however isn't null terminated resulting in an ASAN error within the libedit code. fix_history where the add_history is called is called by the com_clear, associated with the \c command, and com_go. An using the c_ptr adds a null character onto the end of the string. --- client/mysql.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/mysql.cc b/client/mysql.cc index 50f7cb9fb9d..bce4a4e5fa2 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -2876,7 +2876,9 @@ static void fix_history(String *final_command) ptr++; } if (total_lines > 1) - add_history(fixed_buffer.ptr()); + { + add_history(fixed_buffer.c_ptr()); + } } /* From e1f12f149c198829e130eacbeddc19dce3f55b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 24 Sep 2025 10:20:06 +0300 Subject: [PATCH 17/17] MDEV-37626 clang -fsanitize=undefined errors in row0log.cc row_log_apply_ops(), row_log_table_apply_ops(): Instead of adding an offset to a potentially null pointer, subtract the offset from a never-null pointer and then compare to the potentially null pointer. Also, instead of adding a negative (wrapped-around) pointer offset, subtract a positive pointer offset. Reviewed by: Daniel Black --- storage/innobase/row/row0log.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/storage/innobase/row/row0log.cc b/storage/innobase/row/row0log.cc index ab049a11018..daa2036ee39 100644 --- a/storage/innobase/row/row0log.cc +++ b/storage/innobase/row/row0log.cc @@ -2691,8 +2691,8 @@ all_done: ut_ad((mrec == NULL) == (index->online_log->head.bytes == 0)); #ifdef UNIV_DEBUG - if (next_mrec_end == index->online_log->head.block - + srv_sort_buf_size) { + if (next_mrec_end - srv_sort_buf_size + == index->online_log->head.block) { /* If tail.bytes == 0, next_mrec_end can also be at the end of tail.block. */ if (index->online_log->tail.bytes == 0) { @@ -2706,8 +2706,8 @@ all_done: ut_ad(index->online_log->tail.blocks > index->online_log->head.blocks); } - } else if (next_mrec_end == index->online_log->tail.block - + index->online_log->tail.bytes) { + } else if (next_mrec_end - index->online_log->tail.bytes + == index->online_log->tail.block) { ut_ad(next_mrec == index->online_log->tail.block + index->online_log->head.bytes); ut_ad(index->online_log->tail.blocks == 0); @@ -2808,7 +2808,7 @@ process_next_block: } else { memcpy(index->online_log->head.buf, mrec, ulint(mrec_end - mrec)); - mrec_end += ulint(index->online_log->head.buf - mrec); + mrec_end -= ulint(mrec - index->online_log->head.buf); mrec = index->online_log->head.buf; goto process_next_block; } @@ -3601,8 +3601,8 @@ all_done: ut_ad((mrec == NULL) == (index->online_log->head.bytes == 0)); #ifdef UNIV_DEBUG - if (next_mrec_end == index->online_log->head.block - + srv_sort_buf_size) { + if (next_mrec_end - srv_sort_buf_size + == index->online_log->head.block) { /* If tail.bytes == 0, next_mrec_end can also be at the end of tail.block. */ if (index->online_log->tail.bytes == 0) { @@ -3616,8 +3616,8 @@ all_done: ut_ad(index->online_log->tail.blocks > index->online_log->head.blocks); } - } else if (next_mrec_end == index->online_log->tail.block - + index->online_log->tail.bytes) { + } else if (next_mrec_end - index->online_log->tail.bytes + == index->online_log->tail.block) { ut_ad(next_mrec == index->online_log->tail.block + index->online_log->head.bytes); ut_ad(index->online_log->tail.blocks == 0); @@ -3700,7 +3700,7 @@ process_next_block: } else { memcpy(index->online_log->head.buf, mrec, ulint(mrec_end - mrec)); - mrec_end += ulint(index->online_log->head.buf - mrec); + mrec_end -= ulint(mrec - index->online_log->head.buf); mrec = index->online_log->head.buf; goto process_next_block; }