From 4ff2efbcf8dbd23c186e0a6af6c3d8e89cd0c4b7 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 09:46:02 -0400 Subject: [PATCH 01/11] MDEV-33616: Routines of a mixed case database are not listed At lower_case_table_names=2 this returns nothing. CREATE DATABASE Db1; CREATE FUNCTION Db1.f1(a INT) RETURNS INT RETURN a; SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='Db1'; mysql.proc records the function's database as db1, in lower case. Creating a routine lower-cases its database name whenever lower_case_table_names is anything but 0, at sql/sp_head.h:121. The datadir, SCHEMATA and DATABASE() all keep Db1. CALL Db1.f1() still works, because calling a routine lower-cases the database name too and then searches mysql.proc for db1. The query above never lower-cases it. It searches for Db1, and mysql.proc.db collates utf8mb3_bin, so the comparison runs byte for byte and no row matches. At setting 1 the server lower-cases the filter value as well, at sql/sql_show.cc:4394, and lower-cases every name it stores, so the query and the table always agree. Setting 2 lower-cases the routine's copy and nothing else. The fix lower-cases the filter value before the search. Sorting the same query brings the row back. SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; The sort keeps the filter from reaching that search. The server reads all of mysql.proc instead, then applies the WHERE to ROUTINE_SCHEMA, which compares case insensitively. That shape answered correctly all along. The same search fills PARAMETERS and backs SHOW FUNCTION STATUS, SHOW PROCEDURE STATUS, SHOW PACKAGE STATUS and SHOW PACKAGE BODY STATUS. Every one returned nothing for Db1. mariadb-dump lists routines with SHOW FUNCTION STATUS WHERE Db=..., at client/mysqldump.cc:2859, which is the main.mysqldump failure. Setting 0 keeps Db1 and db1 as two databases holding two routines. A case sensitive volume confirms both stay distinct before and after this change. beb9a5459d4 (MDEV-20609) added the search in 10.11.1. main.lowercase_routines runs both query shapes. --- mysql-test/main/lowercase_routines.opt | 1 + mysql-test/main/lowercase_routines.result | 46 ++++++++++++++++++++++ mysql-test/main/lowercase_routines.test | 47 +++++++++++++++++++++++ sql/sql_show.cc | 15 ++++++++ 4 files changed, 109 insertions(+) create mode 100644 mysql-test/main/lowercase_routines.opt create mode 100644 mysql-test/main/lowercase_routines.result create mode 100644 mysql-test/main/lowercase_routines.test diff --git a/mysql-test/main/lowercase_routines.opt b/mysql-test/main/lowercase_routines.opt new file mode 100644 index 0000000000000..ac4d3211e8914 --- /dev/null +++ b/mysql-test/main/lowercase_routines.opt @@ -0,0 +1 @@ +--lower-case-table-names=2 diff --git a/mysql-test/main/lowercase_routines.result b/mysql-test/main/lowercase_routines.result new file mode 100644 index 0000000000000..761b98613d7a6 --- /dev/null +++ b/mysql-test/main/lowercase_routines.result @@ -0,0 +1,46 @@ +# +# MDEV-33616 Routines of a mixed case database are not listed when +# lower_case_table_names is 2 +# +CREATE DATABASE Db1; +USE Db1; +CREATE FUNCTION f1(a INT) RETURNS INT RETURN a; +CREATE PROCEDURE p1(b INT) BEGIN END; +SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='Db1'; +SCHEMA_NAME +Db1 +SELECT db, name FROM mysql.proc WHERE name IN ('f1','p1'); +db name +db1 f1 +db1 p1 +SELECT ROUTINE_NAME FROM information_schema.ROUTINES +WHERE ROUTINE_SCHEMA='Db1'; +ROUTINE_NAME +f1 +p1 +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME +FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1'; +SPECIFIC_NAME ORDINAL_POSITION PARAMETER_NAME +f1 0 NULL +f1 1 a +p1 1 b +SHOW FUNCTION STATUS WHERE Db='Db1'; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +db1 f1 FUNCTION root@localhost # # DEFINER latin1 latin1_swedish_ci latin1_swedish_ci +SHOW PROCEDURE STATUS WHERE Db='Db1'; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +db1 p1 PROCEDURE root@localhost # # DEFINER latin1 latin1_swedish_ci latin1_swedish_ci +SELECT ROUTINE_NAME FROM information_schema.ROUTINES +WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; +ROUTINE_NAME +f1 +p1 +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME +FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1' + ORDER BY SPECIFIC_NAME, ORDINAL_POSITION; +SPECIFIC_NAME ORDINAL_POSITION PARAMETER_NAME +f1 0 NULL +f1 1 a +p1 1 b +USE test; +DROP DATABASE Db1; diff --git a/mysql-test/main/lowercase_routines.test b/mysql-test/main/lowercase_routines.test new file mode 100644 index 0000000000000..4489d530ba732 --- /dev/null +++ b/mysql-test/main/lowercase_routines.test @@ -0,0 +1,47 @@ +# +# Tests that require lower_case_table_names to be 2 +# (the default when the data directory is on a case insensitive file system) +# +--source include/have_lowercase2.inc + +--echo # +--echo # MDEV-33616 Routines of a mixed case database are not listed when +--echo # lower_case_table_names is 2 +--echo # + +CREATE DATABASE Db1; +USE Db1; +CREATE FUNCTION f1(a INT) RETURNS INT RETURN a; +CREATE PROCEDURE p1(b INT) BEGIN END; + +# The database keeps the case it was created with, mysql.proc does not. +SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='Db1'; +--sorted_result +SELECT db, name FROM mysql.proc WHERE name IN ('f1','p1'); + +# The name from the WHERE clause becomes the key of an index read on +# mysql.proc. mysql.proc.db is utf8mb3_bin and holds the folded name, so +# an unfolded key finds nothing. +--sorted_result +SELECT ROUTINE_NAME FROM information_schema.ROUTINES + WHERE ROUTINE_SCHEMA='Db1'; +--sorted_result +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME + FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1'; +--replace_column 5 # 6 # +SHOW FUNCTION STATUS WHERE Db='Db1'; +--replace_column 5 # 6 # +SHOW PROCEDURE STATUS WHERE Db='Db1'; + +# ORDER BY keeps the condition from reaching the routine that fills the +# table, so mysql.proc is read in full and the WHERE is applied afterwards +# over a utf8mb3_general_ci column. That shape returned the rows even +# while the queries above returned none. +SELECT ROUTINE_NAME FROM information_schema.ROUTINES + WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME + FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1' + ORDER BY SPECIFIC_NAME, ORDINAL_POSITION; + +USE test; +DROP DATABASE Db1; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 66999c555a522..8ab4588ec461e 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -7115,6 +7115,21 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond) DBUG_RETURN(0); } + /* + A routine records its database in mysql.proc in lower case whenever + lower_case_table_names is set, see sp_name::sp_name(). The lookup value + is the name as the query spelled it, and get_lookup_field_values folds it + to lower case only for lower_case_table_names 1. Fold it here for the + remaining setting so that the index read below, and the comparison in + check_proc_record, both see the form that was stored. mysql.proc.db is + utf8mb3_bin, so neither of them can absorb the difference in case. + The value is a buffer allocated by get_lookup_field_values, so it can be + changed in place. + */ + if (lower_case_table_names && lookup.db_value.length) + lookup.db_value.length= my_casedn_str(files_charset_info, + (char*) lookup.db_value.str); + start_new_trans new_trans(thd); if (!(proc_table= open_proc_table_for_read(thd))) From 946892b2002b13e09e3efc4b2765aba7c0c6bfd2 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 11:33:45 -0400 Subject: [PATCH 02/11] MDEV-33616: Only one of two routines named in a statement is found With lower_case_table_names 0 the server can have databases Db1 and db1, each with a function f1. A single statement naming both databases, like SELECT Db1.f1(), db1.f1(), reported that db1.f1 does not exist. The set of routines a statement uses compared its entries without regard to case. Only one routine was loaded but the reference to the other found nothing. The set now compares its entries exactly, as the routine cache and the lock manager already do. --- mysql-test/main/lowercase_routines0.result | 46 +++++++++++++++ mysql-test/main/lowercase_routines0.test | 65 ++++++++++++++++++++++ sql/sp.cc | 10 +++- sql/sp_head.cc | 3 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/lowercase_routines0.result create mode 100644 mysql-test/main/lowercase_routines0.test diff --git a/mysql-test/main/lowercase_routines0.result b/mysql-test/main/lowercase_routines0.result new file mode 100644 index 0000000000000..cafb7b25d3ee1 --- /dev/null +++ b/mysql-test/main/lowercase_routines0.result @@ -0,0 +1,46 @@ +# +# MDEV-33616 A statement cannot reference two routines whose databases +# differ only in case +# +CREATE DATABASE Db1; +CREATE DATABASE db1; +CREATE FUNCTION Db1.f1() RETURNS INT RETURN 1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 2; +CREATE PROCEDURE test.p1() SELECT Db1.f1(), db1.f1(); +SELECT Db1.f1(); +Db1.f1() +1 +SELECT db1.f1(); +db1.f1() +2 +connect con1,localhost,root,,test; +SELECT Db1.f1(), db1.f1(); +Db1.f1() db1.f1() +1 2 +disconnect con1; +connect con2,localhost,root,,test; +SELECT db1.f1(), Db1.f1(); +db1.f1() Db1.f1() +2 1 +disconnect con2; +connect con3,localhost,root,,test; +CALL test.p1(); +Db1.f1() db1.f1() +1 2 +disconnect con3; +connect con4,localhost,root,,test; +SELECT db1.f1(); +db1.f1() +2 +connection default; +DROP FUNCTION db1.f1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 99; +connection con4; +SELECT Db1.f1(), db1.f1(); +Db1.f1() db1.f1() +1 99 +disconnect con4; +connection default; +DROP PROCEDURE test.p1; +DROP DATABASE Db1; +DROP DATABASE db1; diff --git a/mysql-test/main/lowercase_routines0.test b/mysql-test/main/lowercase_routines0.test new file mode 100644 index 0000000000000..178f17dee279e --- /dev/null +++ b/mysql-test/main/lowercase_routines0.test @@ -0,0 +1,65 @@ +# +# Tests that require lower_case_table_names to be 0 +# (the default when the data directory is on a case sensitive file system) +# +--source include/have_lowercase0.inc +--source include/have_case_sensitive_file_system.inc + +--echo # +--echo # MDEV-33616 A statement cannot reference two routines whose databases +--echo # differ only in case +--echo # + +CREATE DATABASE Db1; +CREATE DATABASE db1; +CREATE FUNCTION Db1.f1() RETURNS INT RETURN 1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 2; +CREATE PROCEDURE test.p1() SELECT Db1.f1(), db1.f1(); + +SELECT Db1.f1(); +SELECT db1.f1(); + +# Every routine a statement names joins one set, keyed by the database and +# the routine name. The database name is case sensitive here, so the two +# routines have to stay apart and each reference has to reach its own. A +# connection that already holds one of them in its routine cache resolves +# both either way, so every check below starts from a connection that holds +# neither. +connect (con1,localhost,root,,test); +# A view compares its column names without regard to case, so under the +# view protocol the second column of a query like this one is given a +# generated name. +--disable_view_protocol +SELECT Db1.f1(), db1.f1(); +--enable_view_protocol +disconnect con1; + +connect (con2,localhost,root,,test); +--disable_view_protocol +SELECT db1.f1(), Db1.f1(); +--enable_view_protocol +disconnect con2; + +# A statement inside a routine body builds the same set. +connect (con3,localhost,root,,test); +CALL test.p1(); +disconnect con3; + +# A routine missing from the set is neither locked nor checked against the +# version of the routine cache, so it could execute a definition that +# another connection had already replaced. +connect (con4,localhost,root,,test); +SELECT db1.f1(); +connection default; +DROP FUNCTION db1.f1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 99; +connection con4; +--disable_view_protocol +SELECT Db1.f1(), db1.f1(); +--enable_view_protocol +disconnect con4; +connection default; + +DROP PROCEDURE test.p1; +DROP DATABASE Db1; +DROP DATABASE db1; diff --git a/sql/sp.cc b/sql/sp.cc index b5069603ed615..4d9c9e924c54f 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -2346,7 +2346,15 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, const Sp_handler *handler, TABLE_LIST *belong_to_view) { - my_hash_init_opt(PSI_INSTRUMENT_ME, &prelocking_ctx->sroutines, system_charset_info, + /* + Compare keys byte for byte. The key carries the database name as the + statement spelled it, and with lower_case_table_names 0 two databases + can differ only in case. A case insensitive comparison would collapse + the routines of both into one entry, so only one of them would be + loaded and locked, and a reference to the other would not find it. + */ + my_hash_init_opt(PSI_INSTRUMENT_ME, &prelocking_ctx->sroutines, + &my_charset_bin, Query_tables_list::START_SROUTINES_HASH_SIZE, 0, 0, sp_sroutine_key, 0, 0); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 98f302e2aa3e3..9dc02125dfee2 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -604,7 +604,8 @@ sp_head::sp_head(MEM_ROOT *mem_root_arg, sp_package *parent, sizeof(sp_instr *), 16, 8, MYF(0)); my_hash_init(key_memory_sp_head_main_root, &m_sptabs, table_alias_charset, 0, 0, 0, sp_table_key, 0, 0); - my_hash_init(key_memory_sp_head_main_root, &m_sroutines, system_charset_info, + /* Keys are compared byte for byte, the database name is case sensitive. */ + my_hash_init(key_memory_sp_head_main_root, &m_sroutines, &my_charset_bin, 0, 0, 0, sp_sroutine_key, 0, 0); DBUG_VOID_RETURN; From 7de1c575028585c505f84cbb0dedc747e96da635 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:04 -0400 Subject: [PATCH 03/11] MDEV-33616: Detect select() on macOS macOS declares select() in sys/select.h, which the HAVE_SELECT probe did not include. clang rejects a call to an undeclared function, so the probe failed and HAVE_SELECT was left undefined. my_sleep() then took its last fallback, a busy loop on time() that rounds the requested interval up to a whole second. Every sub-second sleep in the server became a one second spin on a CPU, which is what made rpl.rpl_perfschema_applier_status_by_worker, rpl.rpl_shutdown_sighup and rpl.rpl_semi_sync_shutdown_await_ack fail. --- configure.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.cmake b/configure.cmake index 93b87f3a5dd3e..e4aee5448b4eb 100644 --- a/configure.cmake +++ b/configure.cmake @@ -590,6 +590,8 @@ CHECK_C_SOURCE_COMPILES(" #include #include #include +/* macOS declares select() here and nowhere else that this test includes */ +#include #endif int main() { From 74fb9cbc195a933f0423457d993bd2fe1afc63db Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:04 -0400 Subject: [PATCH 04/11] MDEV-33616: Make two tests independent of lower_case_table_names macOS puts the data directory on a case insensitive file system, so lower_case_table_names is 2 and both tests recorded an answer that only holds for 0. period.i_s_notembedded looked up I_S.PERIODS and I_S.KEY_PERIOD_USAGE by the schema name TEST. That comparison follows the table name comparison, so it finds the table under 1 and 2 and finds nothing under 0. Those four queries move to the new test period.i_s_case_sensitive, which requires lower_case_table_names=0. The win rdiff of period.i_s_notembedded covered the same difference and is no longer needed. atomic.drop_db_long_names generated table and view names in upper case and compared the DROP statements that DDL recovery writes to the binary log. Under 2 the names come back from the directory in lower case. Generating them in lower case to begin with gives the same names on every setting. Lower case also changes where the view name sorts against its table name for the letters after v, which moves one view between two of the recorded DROP VIEW statements. --- .../suite/atomic/drop_db_long_names.result | 12 +++++----- .../suite/atomic/drop_db_long_names.test | 6 +++-- .../suite/period/r/i_s_case_sensitive.result | 14 ++++++++++++ .../suite/period/r/i_s_notembedded,win.rdiff | 20 ----------------- .../suite/period/r/i_s_notembedded.result | 11 ---------- .../suite/period/t/i_s_case_sensitive.test | 22 +++++++++++++++++++ .../suite/period/t/i_s_notembedded.test | 14 ++---------- 7 files changed, 48 insertions(+), 51 deletions(-) create mode 100644 mysql-test/suite/period/r/i_s_case_sensitive.result delete mode 100644 mysql-test/suite/period/r/i_s_notembedded,win.rdiff create mode 100644 mysql-test/suite/period/t/i_s_case_sensitive.test diff --git a/mysql-test/suite/atomic/drop_db_long_names.result b/mysql-test/suite/atomic/drop_db_long_names.result index e1d177ab44730..ec1cc598260de 100644 --- a/mysql-test/suite/atomic/drop_db_long_names.result +++ b/mysql-test/suite/atomic/drop_db_long_names.result @@ -1,11 +1,11 @@ RESET MASTER; "engine: aria crash point: ddl_log_drop_after_drop_tables position: 1" -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`,`tACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`,`tADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD`,`tAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE`,`tAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`,`tAGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG`,`tAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH`,`tAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII`,`tAJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ`,`tAKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK`,`tALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBv`,`tACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCv`,`tADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDv`,`tAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEv`,`tAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFv`,`tAGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGv`,`tAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHv`,`tAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIv`,`tAJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJv`,`tAKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKv` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tAMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM`,`tANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN`,`tAOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO`,`tAPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP`,`tAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ`,`tARRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR`,`tASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS`,`tATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT`,`tAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU`,`tAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV`,`tAWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLv`,`tAMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMv`,`tANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNv`,`tAOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOv`,`tAPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPv`,`tAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQv`,`tARRRRRRRRRRRRRRRRRRRRRRRRRRRRRRv`,`tASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSv`,`tATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTv`,`tAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUv`,`tAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVv` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`,`tAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY`,`tAZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ`,`tBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`,`tBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`,`tBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`,`tBDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD`,`tBEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tAWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWv`,`tAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXv`,`tAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYv`,`tAZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZv`,`tBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv`,`tBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBv`,`tBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCv`,`tBDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDv`,`tBEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`,`tacccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc`,`tadddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd`,`taeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`,`taffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`,`tagggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg`,`tahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh`,`taiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii`,`tajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj`,`takkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk`,`tallllllllllllllllllllllllllllllllllllllllllllllllllllllllllll` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbv`,`taccccccccccccccccccccccccccccccv`,`taddddddddddddddddddddddddddddddv`,`taeeeeeeeeeeeeeeeeeeeeeeeeeeeeeev`,`taffffffffffffffffffffffffffffffv`,`taggggggggggggggggggggggggggggggv`,`tahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhv`,`taiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiv`,`tajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjv`,`takkkkkkkkkkkkkkkkkkkkkkkkkkkkkkv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm`,`tannnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn`,`taoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo`,`tapppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp`,`taqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq`,`tarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr`,`tassssssssssssssssssssssssssssssssssssssssssssssssssssssssssss`,`tatttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt`,`tauuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu`,`tavvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv`,`tawwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tallllllllllllllllllllllllllllllv`,`tammmmmmmmmmmmmmmmmmmmmmmmmmmmmmv`,`tannnnnnnnnnnnnnnnnnnnnnnnnnnnnnv`,`taoooooooooooooooooooooooooooooov`,`tappppppppppppppppppppppppppppppv`,`taqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv`,`tarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrv`,`tassssssssssssssssssssssssssssssv`,`tattttttttttttttttttttttttttttttv`,`tauuuuuuuuuuuuuuuuuuuuuuuuuuuuuuv`,`tavvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv`,`tawwwwwwwwwwwwwwwwwwwwwwwwwwwwwwv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `taxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,`tayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy`,`tazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz`,`tbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,`tbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`,`tbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc`,`tbdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd`,`tbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `taxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv`,`tayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv`,`tazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzv`,`tbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaav`,`tbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbv`,`tbccccccccccccccccccccccccccccccv`,`tbddddddddddddddddddddddddddddddv`,`tbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeev` /* generated by ddl recovery */ "engine: aria crash point: ddl_log_drop_before_binlog position: 1" master-bin.000002 # Query # # DROP DATABASE IF EXISTS `test2` /* generated by ddl recovery */ Warnings: diff --git a/mysql-test/suite/atomic/drop_db_long_names.test b/mysql-test/suite/atomic/drop_db_long_names.test index 1256a9dc92f61..0686d0ca54d2d 100644 --- a/mysql-test/suite/atomic/drop_db_long_names.test +++ b/mysql-test/suite/atomic/drop_db_long_names.test @@ -58,8 +58,10 @@ while ($e < $engine_count) while ($t < $max_tables) { inc $t; - let $name=`select concat("t",char(floor(65+$t/26)),repeat(char(65+mod($t,26)),60))`; - let $view=`select concat("t",char(floor(65+$t/26)),repeat(char(65+mod($t,26)),30),'v')`; + # Generate the names in lower case so that they are unchanged by a + # lower_case_table_names setting of 1 or 2. + let $name=`select concat("t",char(floor(97+$t/26)),repeat(char(97+mod($t,26)),60))`; + let $view=`select concat("t",char(floor(97+$t/26)),repeat(char(97+mod($t,26)),30),'v')`; --eval create table $name (a int not null) $extra_option --eval create view $view as select * from $name } diff --git a/mysql-test/suite/period/r/i_s_case_sensitive.result b/mysql-test/suite/period/r/i_s_case_sensitive.result new file mode 100644 index 0000000000000..db678aec4d24b --- /dev/null +++ b/mysql-test/suite/period/r/i_s_case_sensitive.result @@ -0,0 +1,14 @@ +# MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity +create table t (a int, b date, c date, period for app(b,c), +unique idx(a, app without overlaps)); +set names latin1 collate latin1_general_cs; +select table_name from information_schema.periods where table_schema = 'TEST'; +table_name +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +table_name +set names latin1 collate latin1_general_ci; +select table_name from information_schema.periods where table_schema = 'TEST'; +table_name +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +table_name +drop table t; diff --git a/mysql-test/suite/period/r/i_s_notembedded,win.rdiff b/mysql-test/suite/period/r/i_s_notembedded,win.rdiff deleted file mode 100644 index eee35182c728c..0000000000000 --- a/mysql-test/suite/period/r/i_s_notembedded,win.rdiff +++ /dev/null @@ -1,20 +0,0 @@ ---- suite/period/r/i_s_notembedded.result 2024-01-01 19:50:37.000000000 +0100 -+++ suite/period/r/i_s_notembedded,win.reject 2024-01-01 19:57:18.888306500 +0100 -@@ -69,13 +69,17 @@ - set names latin1 collate latin1_general_cs; - select table_name from information_schema.periods where table_schema = 'TEST'; - table_name -+t - select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - table_name -+t - set names latin1 collate latin1_general_ci; - select table_name from information_schema.periods where table_schema = 'TEST'; - table_name -+t - select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - table_name -+t - # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME - # does not work - select constraint_name from information_schema.key_period_usage where table_name = 't'; diff --git a/mysql-test/suite/period/r/i_s_notembedded.result b/mysql-test/suite/period/r/i_s_notembedded.result index 80070ef61161e..8e92cf9fd60e1 100644 --- a/mysql-test/suite/period/r/i_s_notembedded.result +++ b/mysql-test/suite/period/r/i_s_notembedded.result @@ -63,19 +63,8 @@ def test t2 SYSTEM_TIME vs ve def test t2 mytime s e connection default; drop tables t1, t2; -# MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity create table t (a int, b date, c date, period for app(b,c), unique idx(a, app without overlaps)); -set names latin1 collate latin1_general_cs; -select table_name from information_schema.periods where table_schema = 'TEST'; -table_name -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -table_name -set names latin1 collate latin1_general_ci; -select table_name from information_schema.periods where table_schema = 'TEST'; -table_name -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -table_name # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME # does not work select constraint_name from information_schema.key_period_usage where table_name = 't'; diff --git a/mysql-test/suite/period/t/i_s_case_sensitive.test b/mysql-test/suite/period/t/i_s_case_sensitive.test new file mode 100644 index 0000000000000..639454b05a30e --- /dev/null +++ b/mysql-test/suite/period/t/i_s_case_sensitive.test @@ -0,0 +1,22 @@ +# +# Looking up I_S.PERIODS and I_S.KEY_PERIOD_USAGE by schema name compares the +# name the same way table names are compared, so the answer depends on +# lower_case_table_names. These cases record the case sensitive answer. +# +--source include/have_lowercase0.inc + +--echo # MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity + +create table t (a int, b date, c date, period for app(b,c), + unique idx(a, app without overlaps)); + +set names latin1 collate latin1_general_cs; + +select table_name from information_schema.periods where table_schema = 'TEST'; +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +set names latin1 collate latin1_general_ci; + +select table_name from information_schema.periods where table_schema = 'TEST'; +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; + +drop table t; diff --git a/mysql-test/suite/period/t/i_s_notembedded.test b/mysql-test/suite/period/t/i_s_notembedded.test index 5bfb4e14e8bde..065d334292600 100644 --- a/mysql-test/suite/period/t/i_s_notembedded.test +++ b/mysql-test/suite/period/t/i_s_notembedded.test @@ -1,5 +1,4 @@ --source include/not_embedded.inc ---source include/platform.inc select * from information_schema.periods; @@ -56,21 +55,12 @@ select * from information_schema.periods where table_schema = 'test'; --connection default drop tables t1, t2; ---echo # MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity +# The case sensitive lookups of MDEV-32503 need lower_case_table_names=0 and +# live in period.i_s_case_sensitive create table t (a int, b date, c date, period for app(b,c), unique idx(a, app without overlaps)); - -set names latin1 collate latin1_general_cs; - -select table_name from information_schema.periods where table_schema = 'TEST'; -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -set names latin1 collate latin1_general_ci; - -select table_name from information_schema.periods where table_schema = 'TEST'; -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - --echo # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME --echo # does not work disable_warnings; # storage engine 'Innodb' is not found From fa942f5269accb9d04fbee320127106513af2d6b Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 05/11] MDEV-33616: MTR flag to mark tests as incompatible with macOS Introduces a new MTR include, not_mac.inc, which when included at the top of a test, prevents that test from running on macOS. sys_vars.sysvars_readonly_debug is the first user. It expects the server to fault when a read only sysvar is written behind the sysvar interface. That protection needs the ro_after_init section, which a linker script places and ld64 has no option to take, so HAVE_RO_AFTER_INIT stays undefined on macOS. Without it no variable is moved into the read only root either, so neither of the two assignments is refused. --- mysql-test/include/not_mac.inc | 4 ++++ mysql-test/suite.pm | 1 + mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 mysql-test/include/not_mac.inc diff --git a/mysql-test/include/not_mac.inc b/mysql-test/include/not_mac.inc new file mode 100644 index 0000000000000..3c4f2fbb03758 --- /dev/null +++ b/mysql-test/include/not_mac.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped if run under macOS +# diff --git a/mysql-test/suite.pm b/mysql-test/suite.pm index 97d406b2fc4e1..e2a1aa5550a19 100644 --- a/mysql-test/suite.pm +++ b/mysql-test/suite.pm @@ -49,6 +49,7 @@ sub skip_combinations { $skip{'include/not_windows.inc'} = 'Requires not Windows' if IS_WINDOWS; $skip{'include/not_aix.inc'} = 'Requires not AIX' if IS_AIX; + $skip{'include/not_mac.inc'} = 'Requires not macOS' if IS_MAC; $skip{'include/not_ssl.inc'} = 'Skipped if --ssl' if $::opt_ssl; $skip{'main/plugin_loaderr.test'} = 'needs compiled-in innodb' diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test index 192c481b4cef3..3f71ed9b2ce31 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -2,6 +2,11 @@ --source include/not_asan.inc --source include/not_embedded.inc --source include/not_valgrind.inc +# The read only segment is placed by a linker script and ld64 has no option to +# take one, so HAVE_RO_AFTER_INIT stays undefined on macOS. Without it no +# variable is moved into the read only root either, so neither assignment +# below is refused by the memory protection. +--source include/not_mac.inc --echo # --echo # MDEV-40341 store read-only sysvars in a read-only root and a read-only segment From c33abfbfb0df5d66c7cab0161a6bb5b2b63e3295 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 06/11] MDEV-33616: Exclude innodb_log_file_mmap from sys_vars.sysvars_innodb Its default value depends on the operating system, ON where the log can be memory mapped and OFF elsewhere, so the recorded row only holds on some platforms. The other variables whose default depends on the operating system are already excluded the same way. --- .../sys_vars/r/sysvars_innodb,32bit.rdiff | 68 +++++++++---------- .../suite/sys_vars/r/sysvars_innodb.result | 13 +--- .../suite/sys_vars/t/sysvars_innodb.test | 1 + 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff b/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff index 513bc5e90e29c..f0dcefe80371e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff @@ -1,6 +1,6 @@ --- sysvars_innodb.result +++ sysvars_innodb.result,32bit -@@ -47,7 +47,7 @@ +@@ -48,7 +48,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 8 VARIABLE_SCOPE GLOBAL @@ -9,7 +9,7 @@ VARIABLE_COMMENT Number of InnoDB Adaptive Hash Index Partitions (default 8) NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 512 -@@ -83,7 +83,7 @@ +@@ -84,7 +84,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -18,7 +18,7 @@ VARIABLE_COMMENT The AUTOINC lock modes supported by InnoDB: 0 => Old style AUTOINC locking (for backward compatibility); 1 => New style AUTOINC locking; 2 => No AUTOINC locking (unsafe for SBR) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2 -@@ -95,10 +95,10 @@ +@@ -96,10 +96,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -31,7 +31,7 @@ NUMERIC_BLOCK_SIZE 1048576 ENUM_VALUE_LIST NULL READ_ONLY YES -@@ -131,7 +131,7 @@ +@@ -132,7 +132,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 25 VARIABLE_SCOPE GLOBAL @@ -40,7 +40,7 @@ VARIABLE_COMMENT Dump only the hottest N% of each buffer pool, defaults to 25 NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100 -@@ -203,10 +203,10 @@ +@@ -204,10 +204,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 134217728 VARIABLE_SCOPE GLOBAL @@ -53,7 +53,7 @@ NUMERIC_BLOCK_SIZE 1048576 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -215,11 +215,11 @@ +@@ -216,11 +216,11 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -68,7 +68,7 @@ ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED -@@ -239,7 +239,7 @@ +@@ -240,7 +240,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -77,7 +77,7 @@ VARIABLE_COMMENT A number between [0, 100] that tells how oftern buffer pool dump status in percentages should be printed. E.g. 10 means that buffer pool dump status is printed when every 10% of number of buffer pool pages are dumped. Default is 0 (only start and end status is printed). NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -359,7 +359,7 @@ +@@ -360,7 +360,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 5 VARIABLE_SCOPE GLOBAL @@ -86,7 +86,7 @@ VARIABLE_COMMENT If the compression failure rate of a table is greater than this number more padding is added to the pages to reduce the failures. A value of zero implies no padding NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -383,7 +383,7 @@ +@@ -384,7 +384,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 50 VARIABLE_SCOPE GLOBAL @@ -95,7 +95,7 @@ VARIABLE_COMMENT Percentage of empty space on a data page that can be reserved to make the page compressible. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 75 -@@ -671,7 +671,7 @@ +@@ -672,7 +672,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 600 VARIABLE_SCOPE GLOBAL @@ -104,7 +104,7 @@ VARIABLE_COMMENT Maximum number of seconds that semaphore times out in InnoDB. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -719,7 +719,7 @@ +@@ -720,7 +720,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 30 VARIABLE_SCOPE GLOBAL @@ -113,7 +113,7 @@ VARIABLE_COMMENT Number of iterations over which the background flushing is averaged. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1000 -@@ -743,7 +743,7 @@ +@@ -744,7 +744,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -122,7 +122,7 @@ VARIABLE_COMMENT Controls the durability/speed trade-off for commits. Set to 0 (write and flush redo log to disk only once per second), 1 (flush to disk at each commit), 2 (write to log at commit but flush to disk only once per second) or 3 (flush to disk at prepare and at commit, slower and usually redundant). 1 and 3 guarantees that after a crash, committed transactions will not be lost and will be consistent with the binlog and other transactional engines. 2 can get inconsistent and lose transactions if there is a power failure or kernel crash but not if mysqld crashes. 0 has no guarantees in case of crash. 0 and 2 can be faster than 1 or 3. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 3 -@@ -767,7 +767,7 @@ +@@ -768,7 +768,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -131,7 +131,7 @@ VARIABLE_COMMENT Set to 0 (don't flush neighbors from buffer pool), 1 (flush contiguous neighbors from buffer pool) or 2 (flush neighbors from buffer pool), when flushing a block NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2 -@@ -803,7 +803,7 @@ +@@ -804,7 +804,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -140,7 +140,7 @@ VARIABLE_COMMENT Helps to save your data in case the disk image of the database becomes corrupt. Value 5 can return bogus data, and 6 can permanently corrupt data. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 6 -@@ -827,10 +827,10 @@ +@@ -828,10 +828,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 8000000 VARIABLE_SCOPE GLOBAL @@ -153,7 +153,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -863,7 +863,7 @@ +@@ -864,7 +864,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 84 VARIABLE_SCOPE GLOBAL @@ -162,7 +162,7 @@ VARIABLE_COMMENT InnoDB Fulltext search maximum token size in characters NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 84 -@@ -875,7 +875,7 @@ +@@ -876,7 +876,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 3 VARIABLE_SCOPE GLOBAL @@ -171,7 +171,7 @@ VARIABLE_COMMENT InnoDB Fulltext search minimum token size in characters NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16 -@@ -887,7 +887,7 @@ +@@ -888,7 +888,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 2000 VARIABLE_SCOPE GLOBAL @@ -180,7 +180,7 @@ VARIABLE_COMMENT InnoDB Fulltext search number of words to optimize for each optimize table call NUMERIC_MIN_VALUE 1000 NUMERIC_MAX_VALUE 10000 -@@ -899,10 +899,10 @@ +@@ -900,10 +900,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 2000000000 VARIABLE_SCOPE GLOBAL @@ -193,7 +193,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -923,7 +923,7 @@ +@@ -924,7 +924,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 2 VARIABLE_SCOPE GLOBAL @@ -202,7 +202,7 @@ VARIABLE_COMMENT InnoDB Fulltext search parallel sort degree, will round up to nearest power of 2 number NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 16 -@@ -935,10 +935,10 @@ +@@ -936,10 +936,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 640000000 VARIABLE_SCOPE GLOBAL @@ -215,7 +215,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -983,7 +983,7 @@ +@@ -972,7 +972,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 200 VARIABLE_SCOPE GLOBAL @@ -224,7 +224,7 @@ VARIABLE_COMMENT Number of IOPs the server can do. Tunes the background IO rate NUMERIC_MIN_VALUE 100 NUMERIC_MAX_VALUE 4294967295 -@@ -995,7 +995,7 @@ +@@ -984,7 +984,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 4294967295 VARIABLE_SCOPE GLOBAL @@ -233,7 +233,7 @@ VARIABLE_COMMENT Limit to which innodb_io_capacity can be inflated. NUMERIC_MIN_VALUE 100 NUMERIC_MAX_VALUE 4294967295 -@@ -1115,10 +1115,10 @@ +@@ -1104,10 +1104,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 32 VARIABLE_SCOPE GLOBAL @@ -246,7 +246,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1127,10 +1127,10 @@ +@@ -1116,10 +1116,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 1536 VARIABLE_SCOPE GLOBAL @@ -259,7 +259,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1163,10 +1163,10 @@ +@@ -1152,10 +1152,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -272,7 +272,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1175,7 +1175,7 @@ +@@ -1164,7 +1164,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -281,7 +281,7 @@ VARIABLE_COMMENT Maximum delay of user threads in micro-seconds NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 10000000 -@@ -1307,10 +1307,10 @@ +@@ -1296,10 +1296,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -294,7 +294,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY YES -@@ -1331,7 +1331,7 @@ +@@ -1320,7 +1320,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 16384 VARIABLE_SCOPE GLOBAL @@ -303,7 +303,7 @@ VARIABLE_COMMENT Page size to use for all InnoDB tablespaces. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 65536 -@@ -1367,7 +1367,7 @@ +@@ -1356,7 +1356,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 127 VARIABLE_SCOPE GLOBAL @@ -312,7 +312,7 @@ VARIABLE_COMMENT Number of UNDO log pages to purge in one batch from the history list. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 5000 -@@ -1379,7 +1379,7 @@ +@@ -1368,7 +1368,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 128 VARIABLE_SCOPE GLOBAL @@ -321,7 +321,7 @@ VARIABLE_COMMENT Deprecated parameter with no effect NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 128 -@@ -1415,7 +1415,7 @@ +@@ -1404,7 +1404,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 56 VARIABLE_SCOPE GLOBAL @@ -330,7 +330,7 @@ VARIABLE_COMMENT Number of pages that must be accessed sequentially for InnoDB to trigger a readahead. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 64 -@@ -1499,7 +1499,7 @@ +@@ -1488,7 +1488,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1048576 VARIABLE_SCOPE GLOBAL @@ -339,7 +339,7 @@ VARIABLE_COMMENT Memory buffer size for index creation NUMERIC_MIN_VALUE 65536 NUMERIC_MAX_VALUE 67108864 -@@ -1667,10 +1667,10 @@ +@@ -1656,10 +1656,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 30 VARIABLE_SCOPE GLOBAL diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index cad0893798fc2..0b5b7eab05872 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -7,6 +7,7 @@ variable_name not in ( 'innodb_buffer_pool_size_max', # default value depends on OS 'innodb_buffer_pool_in_core_dump', # only available on Linux and FreeBSD 'innodb_log_file_buffering', # only available on Linux and Windows +'innodb_log_file_mmap', # default value depends on OS 'innodb_linux_aio', # existence depends on OS 'innodb_buffer_pool_load_pages_abort') # debug build only, and is only for testing order by variable_name; @@ -946,18 +947,6 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL -VARIABLE_NAME INNODB_LOG_FILE_MMAP -SESSION_VALUE NULL -DEFAULT_VALUE ON -VARIABLE_SCOPE GLOBAL -VARIABLE_TYPE BOOLEAN -VARIABLE_COMMENT Whether ib_logfile0 resides in persistent memory (when supported) or should initially be memory-mapped -NUMERIC_MIN_VALUE NULL -NUMERIC_MAX_VALUE NULL -NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST OFF,ON -READ_ONLY YES -COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME INNODB_LOG_FILE_SIZE SESSION_VALUE NULL DEFAULT_VALUE 100663296 diff --git a/mysql-test/suite/sys_vars/t/sysvars_innodb.test b/mysql-test/suite/sys_vars/t/sysvars_innodb.test index 7cda33d9fb612..219d39be91516 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_innodb.test +++ b/mysql-test/suite/sys_vars/t/sysvars_innodb.test @@ -18,6 +18,7 @@ select VARIABLE_NAME, SESSION_VALUE, DEFAULT_VALUE, VARIABLE_SCOPE, VARIABLE_TYP 'innodb_buffer_pool_size_max', # default value depends on OS 'innodb_buffer_pool_in_core_dump', # only available on Linux and FreeBSD 'innodb_log_file_buffering', # only available on Linux and Windows + 'innodb_log_file_mmap', # default value depends on OS 'innodb_linux_aio', # existence depends on OS 'innodb_buffer_pool_load_pages_abort') # debug build only, and is only for testing order by variable_name; From 4e82067ce9e86621d58ad3995ce1118f995a9958 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 07/11] MDEV-33616: Widen the block count filter in the buffer pool resize test The test replaces the number of buffer pool blocks with a fixed value so that the message is stable. The pattern only accepted 5.., and macOS builds without a futex use SUX_LOCK_GENERIC, which enlarges buf_block_t enough to bring the count down into 4... --- .../suite/innodb/t/innodb_buffer_pool_resize_temporary.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test index f96f89c39d566..9d8e66e242642 100644 --- a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test @@ -36,7 +36,7 @@ send SET GLOBAL innodb_buffer_pool_size=8388608; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; # adjust for 32-bit and SUX_LOCK_GENERIC ---replace_regex /(5..)\/\1/505\/505/ +--replace_regex /([45]..)\/\1/505\/505/ SHOW STATUS LIKE 'innodb_buffer_pool_resize_status'; SET DEBUG_SYNC='now SIGNAL go'; connection con1; From 513ad07301b5e53cf16ac4cb62881bc15117575b Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:06:38 -0400 Subject: [PATCH 08/11] MDEV-33616: Normalize the strerror text in innodb_fts.index_table The injected deadlock reaches the client as ER_GET_ERRNO carrying errno 11, and the text comes from my_strerror(). 11 is EAGAIN on Linux and EDEADLK on macOS, so the message reads "Resource temporarily unavailable" on one and "Resource deadlock avoided" on the other. Replace the quoted text so the test does not depend on it. --- mysql-test/suite/innodb_fts/t/index_table.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mysql-test/suite/innodb_fts/t/index_table.test b/mysql-test/suite/innodb_fts/t/index_table.test index 89c0905323083..3ad193fd04a85 100644 --- a/mysql-test/suite/innodb_fts/t/index_table.test +++ b/mysql-test/suite/innodb_fts/t/index_table.test @@ -17,6 +17,9 @@ CREATE TABLE articles ( content TEXT ) ENGINE= InnoDB; +# The error number is reported through my_strerror(), whose text for 11 +# differs between systems, so normalize it +--replace_regex /".*" from/"Resource temporarily unavailable" from/ --error ER_GET_ERRNO SET STATEMENT debug_dbug='+d,innodb_report_deadlock' FOR CREATE FULLTEXT INDEX idx ON articles (title, content); From 954e784506d19346f94842a6fa425db30245776b Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:06:38 -0400 Subject: [PATCH 09/11] MDEV-33616: Match the macOS dlopen error in plugins.multiauth The client reports why it could not load client_ed25519, and macOS names every path that dlopen() tried. Two expressions are added, one for the chunk that holds the start of that message and one for the chunks that continue it. Whether the message arrives in one chunk or several depends on the vardir, because the path appears four times in the dlopen text. With --vardir /Volumes//var the line is 417 bytes and fits the 512 byte buffer that --exec output is read in. With the default vardir it does not. Both expressions stop at a newline. reg_replace compiles with REG_DOTALL, so an unrestricted .* runs past the line terminator whenever the whole message reaches the replacement in one chunk, and the error line then joins the line after it. --- mysql-test/suite/plugins/t/multiauth.test | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/plugins/t/multiauth.test b/mysql-test/suite/plugins/t/multiauth.test index fa902ab3b2cec..feccece1b3ba1 100644 --- a/mysql-test/suite/plugins/t/multiauth.test +++ b/mysql-test/suite/plugins/t/multiauth.test @@ -206,8 +206,15 @@ create user mysqltest1 identified via ed25519 as password("good"); grant select on test.* to mysqltest1; show create user mysqltest1; --echo # no plugin = failure -# covers Linux (1st re), FreeBSD (2nd), AIX (3rd and 4th) ---replace_regex /loaded: .*client_ed25519.so: cannot open shared object file: No such file or directory/loaded: no such file/ /loaded: Cannot open.*client_ed25519.so./loaded: no such file/ /loaded: .*Could not load module.*client_ed25519.so.\n/loaded: no such file/ /System error: No such file or directory// +# covers Linux (1st re), FreeBSD (2nd), AIX (3rd and 4th), macOS (5th and 6th). +# The macOS text names every path that dlopen() tried. Its length depends on +# the vardir, so it either fits in the 512 byte buffer that --exec output is +# read in or reaches the replacement as several chunks, each matched on its +# own. The 5th expression rewrites the chunk holding the start of the +# message, the 6th drops the chunks that continue it. Both stop at a newline +# because the expressions are compiled with REG_DOTALL, where an unrestricted +# .* would swallow the line terminator and join this line to the next one. +--replace_regex /loaded: .*client_ed25519.so: cannot open shared object file: No such file or directory/loaded: no such file/ /loaded: Cannot open.*client_ed25519.so./loaded: no such file/ /loaded: .*Could not load module.*client_ed25519.so.\n/loaded: no such file/ /System error: No such file or directory// /loaded: dlopen\([^\n]*/loaded: no such file/ /^[^\n]*client_ed25519\.so[^\n]*// --error 1 --exec $try_auth -u mysqltest1 -pgood --plugin-dir=$plugindir/no alter user mysqltest1 identified via ed25519 as password("good") OR mysql_native_password as password("works"); From 950f10c55743aee6f006bcfe2bd8e1b8cc693f75 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 16:00:21 -0400 Subject: [PATCH 10/11] MDEV-33616: Charge and credit the same size for the recovery buffer main.large_pages fails on macOS with "Warning: Memory not freed: 16375" at shutdown and no accompanying safemalloc report. The residual stays at 16375 whether innodb_buffer_pool_size is 8M or 128M, and dropping --large-pages makes it go away. recv_sys_t::find_checkpoint() asks for tmp_buf_size, which is MTR_SIZE_MAX + 9, or 1048585 bytes. my_large_malloc() rounds that up to a multiple of the large page size and charges the rounded figure to global_memory_used, while recv_sys_t::tmp_free() credits back the 1048585 that was requested. The page size here is 16384, 1048585 rounds up to 1064960, and the difference is the 16375 reported. The caller cannot see the rounded figure because ut_malloc_dontdump() takes the size by value and, with a null ut_new_pfx_t, has nowhere to report what it allocated. ut_malloc_dontdump_size() writes the size back, and recv_sys_t keeps it in tmp_buf_alloc_size for the free. tmp_buf_size remains the capacity that parse() asserts against. Only macOS rounds up. my_get_large_page_sizes() has no huge page interface to consult there, so its fallback branch reports the ordinary page size as the only large page size and the plain mmap() always succeeds. On Linux the candidate is 2 MiB, the MAP_HUGETLB mapping fails with ENOMEM when no huge pages are reserved, and the retry loop settles on large_page_size == 0, which records the request unrounded. No memory was lost either way, since munmap() rounds its length up to a whole page. The counter was wrong, and the counter is what MTR checks. Co-Authored-By: Claude Opus 5 --- storage/innobase/include/log0recv.h | 7 +++++-- storage/innobase/include/ut0new.h | 21 +++++++++++++++++++++ storage/innobase/log/log0recv.cc | 5 +++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/storage/innobase/include/log0recv.h b/storage/innobase/include/log0recv.h index f6cca36238ec9..b0e445cdc31a3 100644 --- a/storage/innobase/include/log0recv.h +++ b/storage/innobase/include/log0recv.h @@ -260,12 +260,15 @@ struct recv_sys_t /** iterator to pages, used by parse() */ map::iterator pages_it; - /** The allocated size of tmp_buf. The 1+8 extra bytes are - needed for FORMAT_ENC_11 in parse(). */ + /** The size of tmp_buf that the parser requires. The 1+8 extra bytes + are needed for FORMAT_ENC_11 in parse(). */ static constexpr size_t tmp_buf_size{MTR_SIZE_MAX + 9}; /** buffer for decrypting mini-transactions or handling non-contiguous mini-transactions */ byte *tmp_buf; + /** the number of bytes allocated for tmp_buf, which is tmp_buf_size + rounded up to a multiple of the large page size */ + size_t tmp_buf_alloc_size; /** Process a record that indicates that a tablespace size is being shrunk. @param page_id first page that is not in the file diff --git a/storage/innobase/include/ut0new.h b/storage/innobase/include/ut0new.h index 398dd0dcc9ecd..30a726f2db3ab 100644 --- a/storage/innobase/include/ut0new.h +++ b/storage/innobase/include/ut0new.h @@ -1087,6 +1087,27 @@ static inline void *ut_malloc_dontdump(size_t n_bytes, ...) #endif /* UNIV_PFS_MEMORY */ +/** Allocate memory that is excluded from core dumps, reporting back the +size that was actually allocated. + +my_large_malloc() rounds the request up to a multiple of the large page +size and charges the rounded figure to the memory accounting, so a caller +whose request is not already such a multiple must release the rounded +figure rather than the one it asked for. + +@param[in,out] n_bytes bytes to allocate on entry, bytes allocated on exit +@return the allocated memory, or NULL */ +static inline void *ut_malloc_dontdump_size(size_t *n_bytes) +{ + void *ptr = my_large_malloc(n_bytes, MYF(0)); + + if (ptr) { + ut_dontdump(ptr, *n_bytes, true); + os_total_large_mem_allocated += *n_bytes; + } + return ptr; +} + static inline void ut_free_dodump(void *ptr, size_t size) { ut_dodump(ptr, size); diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 28e2763d7fe4a..f46ded9cfbfd7 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -1480,7 +1480,7 @@ void recv_sys_t::tmp_free() noexcept { if (tmp_buf) { - ut_free_dodump(tmp_buf, tmp_buf_size); + ut_free_dodump(tmp_buf, tmp_buf_alloc_size); tmp_buf= nullptr; } } @@ -1869,8 +1869,9 @@ dberr_t recv_sys_t::find_checkpoint() if (!tmp_buf) { + tmp_buf_alloc_size= tmp_buf_size; tmp_buf= static_cast - (ut_malloc_dontdump(tmp_buf_size, PSI_INSTRUMENT_ME)); + (ut_malloc_dontdump_size(&tmp_buf_alloc_size)); if (!tmp_buf) return DB_OUT_OF_MEMORY; } From 343dc55fd761b503a7c80eb111d3d313ac88ad41 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 9 Sep 2026 08:25:33 -0400 Subject: [PATCH 11/11] MDEV-33616: Skip the redo log upgrade tests without sparse file support innodb.log_upgrade and innodb.log_upgrade_101_flags build 8GB redo log files by seeking past the end of an empty file and writing a single byte. That needs a filesystem which leaves the skipped range unallocated. HFS on macOS allocates every block of it instead, so the write fails with ENOSPC and the test reports a perl failure. include/have_sparse_files.inc probes the vardir by writing one byte 64MB into an empty file and comparing the allocated block count against that offset. The offset stays above 16MB since APFS allocates the whole range for a file smaller than that rather than recording a hole. --- mysql-test/include/have_sparse_files.inc | 39 +++++++++++++++++++ mysql-test/suite/innodb/t/log_upgrade.test | 9 +++-- .../suite/innodb/t/log_upgrade_101_flags.test | 1 + 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 mysql-test/include/have_sparse_files.inc diff --git a/mysql-test/include/have_sparse_files.inc b/mysql-test/include/have_sparse_files.inc new file mode 100644 index 0000000000000..a6233c8ccf701 --- /dev/null +++ b/mysql-test/include/have_sparse_files.inc @@ -0,0 +1,39 @@ +# Skip the test unless the vardir filesystem stores files with holes. +# +# A test that builds a large file by seeking past its end and writing a +# single byte relies on the skipped range staying unallocated. HFS on +# macOS allocates every block of that range, so the file consumes its full +# apparent size and the write fails with ENOSPC once the range exceeds the +# free space on the volume. +# +# The probe writes one byte 64MB past the start of an empty file and compares +# the allocated block count against that offset. A filesystem that leaves a +# hole allocates only the block holding the byte. The offset has to stay well +# above 16MB, since APFS fills the range of a file smaller than that rather +# than recording a hole. + +--error 0,1 +perl; +use strict; +use warnings; + +my $probe= "$ENV{MYSQLTEST_VARDIR}/tmp/sparse_probe"; +my $offset= 64 * 1024 * 1024; +my $sparse= 0; + +if (open(my $fh, '>', $probe)) +{ + binmode $fh; + if (seek($fh, $offset, 0) and print($fh chr(0)) and close($fh)) + { + my $blocks= (stat $probe)[12]; + $sparse= 1 if defined($blocks) and $blocks * 512 < $offset; + } +} +unlink $probe; +exit($sparse ? 0 : 1); +EOF +if ($errno) +{ + --skip Requires a vardir filesystem that stores files with holes +} diff --git a/mysql-test/suite/innodb/t/log_upgrade.test b/mysql-test/suite/innodb/t/log_upgrade.test index a3d237875feac..0287bf0743123 100644 --- a/mysql-test/suite/innodb/t/log_upgrade.test +++ b/mysql-test/suite/innodb/t/log_upgrade.test @@ -1,11 +1,14 @@ --source include/have_innodb.inc --source include/have_innodb_16k.inc -# Some operating systems or file systems do not support sparse files. -# For example, tmpfs on FreeBSD does not support them. -# On Microsoft Windows, sparse files have to be created in a special way. --source include/big_test.inc # include/shutdown_mysqld.inc does not work in ./mtr --embedded --source include/not_embedded.inc +# Some operating systems or file systems do not support sparse files. +# For example, tmpfs on FreeBSD does not support them. +# On macOS, HFS allocates every block of the skipped range. APFS allocates +# the whole range for a file smaller than 16MB, and records a hole above that. +# On Microsoft Windows, sparse files have to be created in a special way. +--source include/have_sparse_files.inc call mtr.add_suppression("InnoDB: The change buffer is corrupted"); diff --git a/mysql-test/suite/innodb/t/log_upgrade_101_flags.test b/mysql-test/suite/innodb/t/log_upgrade_101_flags.test index 7b19986f73e8e..8ad4083040660 100644 --- a/mysql-test/suite/innodb/t/log_upgrade_101_flags.test +++ b/mysql-test/suite/innodb/t/log_upgrade_101_flags.test @@ -1,6 +1,7 @@ --source include/have_innodb.inc --source include/big_test.inc --source include/not_embedded.inc +--source include/have_sparse_files.inc call mtr.add_suppression("InnoDB: The change buffer is corrupted"); call mtr.add_suppression("InnoDB: Tablespace size stored in header is 768 pages, but the sum of data file sizes is 384 pages"); call mtr.add_suppression("InnoDB: adjusting FSP_SPACE_FLAGS of file");