From 507cbde68f337cc34bc1c95ce9c959247975dc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 27 May 2025 08:05:19 +0300 Subject: [PATCH 01/20] MDEV-36882: Inconsistent DBUG_ASSERT trips GCC -Og my_time_fraction_remainder(): Remove a DBUG_ASSERT, because there is none in sec_part_shift() or sec_part_unshift() either. A buffer overflow should be caught by cmake -DWITH_ASAN=ON in all three. This fixes a build with GCC 14.2 and cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=-Og. Reviewed by: Daniel Black --- include/my_time.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/my_time.h b/include/my_time.h index 9f3e61b944f..90a8885a293 100644 --- a/include/my_time.h +++ b/include/my_time.h @@ -230,7 +230,6 @@ static inline longlong sec_part_unshift(longlong second_part, uint digits) /* Date/time rounding and truncation functions */ static inline long my_time_fraction_remainder(long nr, uint decimals) { - DBUG_ASSERT(decimals <= TIME_SECOND_PART_DIGITS); return nr % (long) log_10_int[TIME_SECOND_PART_DIGITS - decimals]; } static inline void my_datetime_trunc(MYSQL_TIME *ltime, uint decimals) From df414933f1962a0956931453fe3a8b93afd017cc Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 23 May 2025 20:02:45 +1000 Subject: [PATCH 02/20] MDEV-36316/MDEV-36327/MDEV-36328 Debug msan Clang ~16+ on MSAN became quite strict with uninitalized data being passed and returned from functions. Non-debug builds have a basic optimization that hides these from those builds Two innodb cases violate the assumptions, however once inlined with a basic optimization those that existed for uninitialized values are removed. (MDEV-36316) rec_set_bit_field_2 calling mach_read_from_2 hits a read of bits it wasn't actually changing. (MDEV-36327) The function dict_process_sys_columns_rec left nth_v_col uninitialized unless it was a virtual column. This was ok as the function i_s_sys_columns_fill_table also didn't read this value unless it was a virtual column. --- extra/CMakeLists.txt | 8 ++++++++ storage/innobase/CMakeLists.txt | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/extra/CMakeLists.txt b/extra/CMakeLists.txt index 06dc8900968..00fe76433ca 100644 --- a/extra/CMakeLists.txt +++ b/extra/CMakeLists.txt @@ -82,6 +82,14 @@ IF(WITH_INNOBASE_STORAGE_ENGINE) ) + # clang ~16+ with return values being undefined is resolved by basic optimization + # compiler flags for the function mach_read_from_2 (per MDEV-36316) + IF(WITH_MSAN AND CMAKE_BUILD_TYPE STREQUAL "Debug") + SET_SOURCE_FILES_PROPERTIES( + ${INNOBASE_SOURCES} + innochecksum.cc + PROPERTIES COMPILE_FLAGS -Og) + ENDIF() MYSQL_ADD_EXECUTABLE(innochecksum innochecksum.cc ${INNOBASE_SOURCES}) TARGET_LINK_LIBRARIES(innochecksum mysys mysys_ssl) ADD_DEPENDENCIES(innochecksum GenError) diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 37b2703e171..122cdb73342 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -473,6 +473,42 @@ IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" ) ENDIF() +# clang ~16+ with return values being uninitialized is resolved by basic optimization +# compiler flags. The inlining of these function means the uninitalized paths are +# elimated from mach_read_from_2 (per MDEV-36316) and i_s_dict_fill_sys_columns MDEV-36327 +IF(WITH_MSAN AND CMAKE_BUILD_TYPE STREQUAL "Debug") + SET_SOURCE_FILES_PROPERTIES( + btr/btr0btr.cc + btr/btr0bulk.cc + data/data0data.cc + dict/dict0load.cc + dict/dict0mem.cc + fil/fil0crypt.cc + fil/fil0pagecompress.cc + fsp/fsp0fsp.cc + fut/fut0lst.cc + gis/gis0rtree.cc + handler/ha_innodb.cc + handler/i_s.cc + ibuf/ibuf0ibuf.cc + log/log0recv.cc + page/page0cur.cc + page/page0page.cc + page/page0zip.cc + rem/rem0rec.cc + row/row0import.cc + row/row0mysql.cc + row/row0purge.cc + row/row0uins.cc + row/row0undo.cc + row/row0upd.cc + trx/trx0purge.cc + trx/trx0rec.cc + trx/trx0trx.cc + trx/trx0undo.cc + PROPERTIES COMPILE_FLAGS -Og) +ENDIF() + # Older gcc version insist on -mhtm flag for including the # htmxlintrin.h header. This is also true for new gcc versions # like 11.2.0 in Debian Sid From 50124023301a1a094a07b7c5482e3000d3436482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 23 May 2025 19:55:20 +1000 Subject: [PATCH 03/20] MDEV-34388 Alpine Stack Overflow - reduce Grant_tables::open_and_lock The stacksize was still too large. Back port from the 10.11 merge commit 1c7209e828139236c4e8561a26cc88568e23e2f1 --- sql/sql_acl.cc | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index b783c562b74..b61f18c41cc 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -2012,13 +2012,25 @@ class Grant_tables { DBUG_ENTER("Grant_tables::open_and_lock"); - TABLE_LIST tables[USER_TABLE+1], *first= NULL; + TABLE_LIST *first= nullptr, *tables= + static_cast(my_malloc(PSI_NOT_INSTRUMENTED, + (USER_TABLE + 1) * sizeof *tables, + MYF(MY_WME))); + int res= -1; + + if (!tables) + { + func_exit: + my_free(tables); + DBUG_RETURN(res); + } + if (build_table_list(thd, &first, which_tables, lock_type, tables)) - DBUG_RETURN(-1); + goto func_exit; uint counter; - int res= really_open(thd, first, &counter); + res= really_open(thd, first, &counter); /* if User_table_json wasn't found, let's try User_table_tabular */ if (!res && (which_tables & Table_user) && !tables[USER_TABLE].table) @@ -2044,12 +2056,15 @@ class Grant_tables } } if (res) - DBUG_RETURN(res); + goto func_exit; if (lock_tables(thd, first, counter, MYSQL_LOCK_IGNORE_TIMEOUT | MYSQL_OPEN_IGNORE_LOGGING_FORMAT)) - DBUG_RETURN(-1); + { + res= -1; + goto func_exit; + } p_user_table->set_table(tables[USER_TABLE].table); m_db_table.set_table(tables[DB_TABLE].table); @@ -2059,7 +2074,7 @@ class Grant_tables m_procs_priv_table.set_table(tables[PROCS_PRIV_TABLE].table); m_proxies_priv_table.set_table(tables[PROXIES_PRIV_TABLE].table); m_roles_mapping_table.set_table(tables[ROLES_MAPPING_TABLE].table); - DBUG_RETURN(0); + goto func_exit; } inline const User_table& user_table() const From 8490901307b062873d97f216160a3e479ba0463e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 23 May 2025 19:59:58 +1000 Subject: [PATCH 04/20] MDEV-34388: Stack overflow on Alpine Linux (postfix - ASAN/MSAN+Debug) In original fix, commit 82d7419e0600a70b1a1c993d33ed6cf79fbd6129, a 16k stack frame limit was imposed. Under the stack usage is doubled due to MSAN. Debug builds without optimization can use more as well. ASAN Debug builds also exceeded the 16k stack frame limit. To keep some safety limit, a 64k limit is imposed to the compiler under MSAN or ASAN with CMAKE_BUILD_TYPE=Debug. --- cmake/maintainer.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmake/maintainer.cmake b/cmake/maintainer.cmake index 5ca870b771c..5debb42897a 100644 --- a/cmake/maintainer.cmake +++ b/cmake/maintainer.cmake @@ -19,6 +19,12 @@ IF(MSVC OR MYSQL_MAINTAINER_MODE STREQUAL "NO") RETURN() ENDIF() +IF((WITH_MSAN OR WITH_ASAN) AND CMAKE_BUILD_TYPE STREQUAL "Debug") + SET(STACK_FRAME_LIMIT 65536) +ELSE() + SET(STACK_FRAME_LIMIT 16384) +ENDIF() + # Common warning flags for GCC, G++, Clang and Clang++ SET(MY_WARNING_FLAGS -Wall @@ -41,7 +47,7 @@ SET(MY_WARNING_FLAGS -Wvla -Wwrite-strings -Wcast-function-type-strict - -Wframe-larger-than=16384 + -Wframe-larger-than=${STACK_FRAME_LIMIT} ) # Warning flags that are in testing before moving From 2811559337afc964b0d512fba3a303f541190a82 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 24 May 2025 15:55:29 +1000 Subject: [PATCH 05/20] version string - memory sanitizer isn't the same as valgrind Despite being included in the HAVE_valgrind define. As such it's best differenciated from valgrind in the server identifier as they have for the purposes a distinct and different set of behaviours. MSAN has its own set of test inclusions that that are different from valgrind and such including "valgrind" in a server string that gets tested for valgrind will incorrectly exclude some tests that are suitable for MSAN but not valgrind. There's a have_sanitizer system variable for exposing the sanitizer being used so there's no need for version verboseness. Correct have_sanitizer system variable description to include MSAN has been possible for a while. --- sql/mysqld.cc | 2 +- sql/sys_vars.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 138cc52db27..5707ce90349 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -8778,7 +8778,7 @@ char *set_server_version(char *buf, size_t size) bool is_log= opt_log || global_system_variables.sql_log_slow || opt_bin_log; bool is_debug= IF_DBUG(!strstr(MYSQL_SERVER_SUFFIX_STR, "-debug"), 0); const char *is_valgrind= -#ifdef HAVE_valgrind +#if defined(HAVE_valgrind) && !__has_feature(memory_sanitizer) !strstr(MYSQL_SERVER_SUFFIX_STR, "-valgrind") ? "-valgrind" : #endif ""; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 9477a26beec..5fe3eb4586b 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -5231,7 +5231,8 @@ static Sys_var_charptr Sys_have_santitizer( "have_sanitizer", "If the server is compiled with sanitize (compiler option), this " "variable is set to the sanitizer mode used. Possible values are " - "ASAN (Address sanitizer) or UBSAN (The Undefined Behavior Sanitizer).", + "ASAN (Address sanitizer) and/or UBSAN (Undefined Behavior Sanitizer)," + " or MSAN (memory sanitizer).", READ_ONLY GLOBAL_VAR(have_sanitizer), NO_CMD_LINE, DEFAULT(SANITIZER_MODE)); #endif /* defined(__SANITIZE_ADDRESS__) || defined(WITH_UBSAN) */ From 8d2665e56b1298bb0a4daed671b38be92258695c Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 20 Mar 2025 09:24:37 +1100 Subject: [PATCH 06/20] MDEV-34388 default stack size under MSAN needs increasing Without this increase the mtr test case pre/post conditions will fail as the stack usage has increased under MSAN with clang-20.1. ASAN takes a 11M stack, however there was no obvious gain in MSAN test success after 2M. The resulting behaviour observed on smaller stack size was a SEGV normally. Hide the default stack size from the sysvar tests that expose thread-stack as a variable with its default value. --- include/my_pthread.h | 12 +++++++++++- mysql-test/main/mysqld--help.result | 1 - mysql-test/main/mysqld--help.test | 2 +- mysql-test/suite/sys_vars/t/thread_stack_basic.test | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/my_pthread.h b/include/my_pthread.h index 5dceacb6399..9157e8204bc 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -668,7 +668,17 @@ extern void my_mutex_end(void); We need to have at least 256K stack to handle calls to myisamchk_init() with the current number of keys and key parts. */ -# if defined(__SANITIZE_ADDRESS__) || defined(WITH_UBSAN) +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif +#if defined(__clang__) && __has_feature(memory_sanitizer) && !defined(DBUG_OFF) +/* + MSAN in Debug with clang-20.1 required more memory to complete + mtr begin/end checks. The result without increase was MSAN + errors triggered on a call instruction. +*/ +# define DEFAULT_THREAD_STACK (2L<<20) +# elif defined(__SANITIZE_ADDRESS__) || defined(WITH_UBSAN) /* Optimized WITH_ASAN=ON executables produced by GCC 12.3.0, GCC 13.2.0, or clang 16.0.6 diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index abe7c10952b..7ce8ef6cd97 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1891,7 +1891,6 @@ thread-pool-oversubscribe 3 thread-pool-prio-kickup-timer 1000 thread-pool-priority auto thread-pool-stall-limit 500 -thread-stack 299008 time-format %H:%i:%s tmp-disk-table-size 18446744073709551615 tmp-memory-table-size 16777216 diff --git a/mysql-test/main/mysqld--help.test b/mysql-test/main/mysqld--help.test index 44449cd3ad5..ce75924c757 100644 --- a/mysql-test/main/mysqld--help.test +++ b/mysql-test/main/mysqld--help.test @@ -27,7 +27,7 @@ perl; large-files-support lower-case-file-system system-time-zone collation-server character-set-server log-tc-size table-cache table-open-cache table-open-cache-instances max-connections - server-uid tls-version version.* analyze-max-length/; + server-uid tls-version version.* analyze-max-length thread-stack/; # Plugins which may or may not be there: @plugins=qw/innodb archive blackhole federated partition s3 diff --git a/mysql-test/suite/sys_vars/t/thread_stack_basic.test b/mysql-test/suite/sys_vars/t/thread_stack_basic.test index 39f120e0de1..a1f52576d15 100644 --- a/mysql-test/suite/sys_vars/t/thread_stack_basic.test +++ b/mysql-test/suite/sys_vars/t/thread_stack_basic.test @@ -3,6 +3,7 @@ # --source include/not_asan.inc --source include/not_ubsan.inc +--source include/not_msan.inc --replace_result 392192 299008 select @@global.thread_stack; --error ER_INCORRECT_GLOBAL_LOCAL_VAR From 495153feac652ba521dee603d1d05a45c8caa479 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 28 May 2025 11:48:04 +1000 Subject: [PATCH 07/20] MDEV-36893 THD::reset_sub_statement_state swaps with uninitialized structure THD::reset_sub_statement_state and THD::restore_sub_staement_state swap auto_inc_intervals_forced(Discrete_intervals_list) of a THD class with a local variable temporary to execute other things before restoring at the end of Table_triggers_list::process_triggers under a rpl_master_erroneous_autoinc(true) condition as exposed by the rpl.rpl_trigger test. The uninitialized data isn't used and the only required action is to copy the data in one direction. As the intent is for the auto_inc_intervals_forced value to be overwritten or unused, MEM_UNDEFINED is used on it to ensure the previous state is considered invalid. The other uses of reset_sub_statement_state in Item_sp::execute_impl also follow the same pattern of taking a copy to restore within the same function. --- sql/sql_class.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 0effdbfcdb5..da8120abaab 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -5913,7 +5913,8 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, if (rpl_master_erroneous_autoinc(this)) { DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0); - auto_inc_intervals_forced.swap(&backup->auto_inc_intervals_forced); + backup->auto_inc_intervals_forced.copy_shallow(&auto_inc_intervals_forced); + MEM_UNDEFINED(&auto_inc_intervals_forced, sizeof auto_inc_intervals_forced); } #endif @@ -5961,7 +5962,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) */ if (rpl_master_erroneous_autoinc(this)) { - backup->auto_inc_intervals_forced.swap(&auto_inc_intervals_forced); + auto_inc_intervals_forced.copy_shallow(&backup->auto_inc_intervals_forced); DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0); } #endif From 5dbfb52d04d15d2d98db2ed6fd9fe3f73993c4b1 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 28 May 2025 14:03:40 +1000 Subject: [PATCH 08/20] MDEV-36894 JSNX::SetArrayOptions and BJNX::SetArrayOptions unused nm arg Calling SetArrayOptions with Nodes[i - 1].Key as its nm argument exposed a MemorySanitizer error when i=0 for the tests: * connect.bson_udf * connect.json_udf * connect.json_udf_bin Its assumed that a basic optimization would have eliminated these invalid expressions. As the nm argument was unused, it has been removed. --- storage/connect/bsonudf.cpp | 4 ++-- storage/connect/bsonudf.h | 2 +- storage/connect/jsonudf.cpp | 4 ++-- storage/connect/jsonudf.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/storage/connect/bsonudf.cpp b/storage/connect/bsonudf.cpp index 33a462944b5..bed40a05249 100644 --- a/storage/connect/bsonudf.cpp +++ b/storage/connect/bsonudf.cpp @@ -191,7 +191,7 @@ my_bool BJNX::SetJpath(PGLOBAL g, char* path, my_bool jb) /*********************************************************************************/ /* Analyse array processing options. */ /*********************************************************************************/ -my_bool BJNX::SetArrayOptions(PGLOBAL g, char* p, int i, PSZ nm) +my_bool BJNX::SetArrayOptions(PGLOBAL g, char* p, int i) { int n = (int)strlen(p); my_bool dg = true, b = false; @@ -339,7 +339,7 @@ my_bool BJNX::ParseJpath(PGLOBAL g) // Jpath must be explicit if (a || *p == 0 || *p == '[' || IsNum(p)) { // Analyse intermediate array processing - if (SetArrayOptions(g, p, i, Nodes[i - 1].Key)) + if (SetArrayOptions(g, p, i)) return true; } else if (*p == '*') { diff --git a/storage/connect/bsonudf.h b/storage/connect/bsonudf.h index 90cabf72e21..7d7598574c0 100644 --- a/storage/connect/bsonudf.h +++ b/storage/connect/bsonudf.h @@ -116,7 +116,7 @@ public: PBSON MakeBinResult(UDF_ARGS* args, PBVAL top, ulong len, int n = 2); protected: - my_bool SetArrayOptions(PGLOBAL g, char* p, int i, PSZ nm); + my_bool SetArrayOptions(PGLOBAL g, char* p, int i); PVAL GetColumnValue(PGLOBAL g, PBVAL row, int i); PVAL ExpandArray(PGLOBAL g, PBVAL arp, int n); PVAL CalculateArray(PGLOBAL g, PBVAL arp, int n); diff --git a/storage/connect/jsonudf.cpp b/storage/connect/jsonudf.cpp index 60870dc3618..9ce1ff5e48b 100644 --- a/storage/connect/jsonudf.cpp +++ b/storage/connect/jsonudf.cpp @@ -112,7 +112,7 @@ my_bool JSNX::SetJpath(PGLOBAL g, char *path, my_bool jb) /*********************************************************************************/ /* Analyse array processing options. */ /*********************************************************************************/ -my_bool JSNX::SetArrayOptions(PGLOBAL g, char *p, int i, PSZ nm) +my_bool JSNX::SetArrayOptions(PGLOBAL g, char *p, int i) { int n = (int)strlen(p); my_bool dg = true, b = false; @@ -262,7 +262,7 @@ my_bool JSNX::ParseJpath(PGLOBAL g) // Jpath must be explicit if (a || *p == 0 || *p == '[' || IsNum(p)) { // Analyse intermediate array processing - if (SetArrayOptions(g, p, i, Nodes[i-1].Key)) + if (SetArrayOptions(g, p, i)) return true; } else if (*p == '*') { diff --git a/storage/connect/jsonudf.h b/storage/connect/jsonudf.h index 782d17acb12..a7defed8e54 100644 --- a/storage/connect/jsonudf.h +++ b/storage/connect/jsonudf.h @@ -330,7 +330,7 @@ public: char *LocateAll(PGLOBAL g, PJSON jsp, PJVAL jvp, int mx = 10); protected: - my_bool SetArrayOptions(PGLOBAL g, char *p, int i, PSZ nm); + my_bool SetArrayOptions(PGLOBAL g, char *p, int i); PVAL GetColumnValue(PGLOBAL g, PJSON row, int i); PVAL ExpandArray(PGLOBAL g, PJAR arp, int n); PVAL GetCalcValue(PGLOBAL g, PJAR bap, int n); From 676aea8cad14562c8237e17c99a9821c1398e6c7 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 28 May 2025 15:03:05 +1000 Subject: [PATCH 09/20] MDEV-36848: identify tests with various MSAN suitability With MSAN the following test behavious where observed: * funcs_1.myisam_views-big - normal big test for non-debug * innodb_gis.rtree_purge - normal big test with MSAN * main.alter_table_lock - very quick - unclear why disabled * main.cte_recursive - slow on Debug only * main.join_cache_notasan - special MSAN handing for returning OOM added * main.sum_distinct-big - 90 seconds on non-debug - still big however * maria.max_length - normal big test with MSAN * perfschema.statement_digest_long_query - overflows stack on debug Timingsi (on old memory constrained hardware): non-debug: funcs_1.myisam_views-big w2 [ pass ] 78564 innodb_gis.rtree_purge '16k' w2 [ pass ] 5784 innodb_gis.rtree_purge '32k' w2 [ pass ] 5242 innodb_gis.rtree_purge '4k' w1 [ pass ] 8303 innodb_gis.rtree_purge '64k' w1 [ pass ] 6348 innodb_gis.rtree_purge '8k' w2 [ pass ] 5870 main.alter_table_lock w1 [ pass ] 41 main.cte_recursive w1 [ pass ] 15485 main.join_cache_notasan w1 [ pass ] 39 main.sum_distinct-big w2 [ pass ] 96256 maria.max_length w1 [ pass ] 92990 perfschema.statement_digest_long_query w2 [ pass ] 8 debug: funcs_1.myisam_views-big w1 [ skipped ] Can't be run WITH_MSAN and CMAKE_BUILD_TYPE=Debug innodb_gis.rtree_purge '16k' w2 [ pass ] 109788 innodb_gis.rtree_purge '32k' w2 [ pass ] 62361 innodb_gis.rtree_purge '4k' w1 [ pass ] 89423 innodb_gis.rtree_purge '64k' w1 [ pass ] 72082 innodb_gis.rtree_purge '8k' w1 [ pass ] 98452 main.alter_table_lock w2 [ pass ] 38 main.cte_recursive w2 [ pass ] 180047 main.join_cache_notasan w1 [ pass ] 166 main.sum_distinct-big w1 [ skipped ] Can't be run WITH_MSAN and CMAKE_BUILD_TYPE=Debug maria.max_length w1 [ skipped ] Can't be run WITH_MSAN and CMAKE_BUILD_TYPE=Debug perfschema.statement_digest_long_query w1 [ skipped ] Can't be run WITH_MSAN and CMAKE_BUILD_TYPE=Debug --- mysql-test/include/no_msan_without_big.inc | 8 ++++++++ mysql-test/include/not_msan.inc | 3 ++- mysql-test/include/not_msan_with_debug.inc | 9 +++++++++ mysql-test/main/alter_table_lock.test | 2 -- mysql-test/main/cte_recursive.test | 2 +- mysql-test/main/join_cache_notasan.cnf | 5 +++++ mysql-test/main/join_cache_notasan.result | 1 + mysql-test/main/join_cache_notasan.test | 4 +++- mysql-test/main/sum_distinct-big.test | 2 +- mysql-test/suite/funcs_1/t/myisam_views-big.test | 3 +-- mysql-test/suite/innodb_gis/t/rtree_purge.test | 3 +-- mysql-test/suite/maria/max_length.test | 2 +- .../suite/perfschema/t/statement_digest_long_query.test | 1 + 13 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 mysql-test/include/no_msan_without_big.inc create mode 100644 mysql-test/include/not_msan_with_debug.inc create mode 100644 mysql-test/main/join_cache_notasan.cnf diff --git a/mysql-test/include/no_msan_without_big.inc b/mysql-test/include/no_msan_without_big.inc new file mode 100644 index 00000000000..158b3986768 --- /dev/null +++ b/mysql-test/include/no_msan_without_big.inc @@ -0,0 +1,8 @@ +# Slow with MSAN, but if mtr --big-test specified, then it should complete +if (!$BIG_TEST) +{ + if (`select count(*) from information_schema.system_variables where variable_name='have_sanitizer' and global_value like "MSAN%"`) + { + --skip Can't be run WITH_MSAN unless using --big-test + } +} diff --git a/mysql-test/include/not_msan.inc b/mysql-test/include/not_msan.inc index ca1e2c1d7bd..678139199a2 100644 --- a/mysql-test/include/not_msan.inc +++ b/mysql-test/include/not_msan.inc @@ -1,4 +1,5 @@ -# This file should only be used with tests that are too big or slow for MSAN. +# This file should only be used with tests that are too big or slow for MSAN (even with --big-test). +# Use no_msan_without_big instead unless this really won't complete in a test timeout period. if (`select count(*) from information_schema.system_variables where variable_name='have_sanitizer' and global_value like "MSAN%"`) { diff --git a/mysql-test/include/not_msan_with_debug.inc b/mysql-test/include/not_msan_with_debug.inc new file mode 100644 index 00000000000..155e05991a0 --- /dev/null +++ b/mysql-test/include/not_msan_with_debug.inc @@ -0,0 +1,9 @@ +# This file should only be used with tests that are too big or slow for MSAN with Debug. + +if (`select count(*) from information_schema.system_variables where variable_name='have_sanitizer' and global_value like "MSAN%"`) +{ + if (`select version() like '%debug%'`) + { + --skip Can't be run WITH_MSAN and CMAKE_BUILD_TYPE=Debug + } +} diff --git a/mysql-test/main/alter_table_lock.test b/mysql-test/main/alter_table_lock.test index bd26c1ac7d0..500cc3ced57 100644 --- a/mysql-test/main/alter_table_lock.test +++ b/mysql-test/main/alter_table_lock.test @@ -1,5 +1,3 @@ ---source include/not_msan.inc - --echo # --echo # MDEV-23836: Assertion `! is_set() || m_can_overwrite_status' in --echo # Diagnostics_area::set_error_status (interrupted ALTER TABLE under LOCK) diff --git a/mysql-test/main/cte_recursive.test b/mysql-test/main/cte_recursive.test index 4ecdaa4f0fa..6e2dd3b1f16 100644 --- a/mysql-test/main/cte_recursive.test +++ b/mysql-test/main/cte_recursive.test @@ -1,6 +1,6 @@ --source include/default_optimizer_switch.inc # This is too slow on MSAN ---source include/not_msan.inc +--source include/no_msan_without_big.inc --source include/not_valgrind.inc --source include/have_innodb.inc diff --git a/mysql-test/main/join_cache_notasan.cnf b/mysql-test/main/join_cache_notasan.cnf new file mode 100644 index 00000000000..7b2fb3bcb15 --- /dev/null +++ b/mysql-test/main/join_cache_notasan.cnf @@ -0,0 +1,5 @@ +!include include/default_my.cnf + +[ENV] +MSAN_OPTIONS=allocator_may_return_null=1:abort_on_error=1 + diff --git a/mysql-test/main/join_cache_notasan.result b/mysql-test/main/join_cache_notasan.result index 3cec949f5c6..885eece83d6 100644 --- a/mysql-test/main/join_cache_notasan.result +++ b/mysql-test/main/join_cache_notasan.result @@ -1,3 +1,4 @@ +call mtr.add_suppression("MemorySanitizer failed to allocate"); # # MDEV-28217 Incorrect Join Execution When Controlling Join Buffer Size # diff --git a/mysql-test/main/join_cache_notasan.test b/mysql-test/main/join_cache_notasan.test index c2ff670f044..5271d3fae9c 100644 --- a/mysql-test/main/join_cache_notasan.test +++ b/mysql-test/main/join_cache_notasan.test @@ -4,11 +4,13 @@ --source include/have_64bit.inc # Disable asan it asan builds crashes when trying to allocate too much memory --source include/not_asan.inc ---source include/not_msan.inc # Valgrind is useful here, but very slow as lots of memory is allocated --source include/no_valgrind_without_big.inc --source include/have_innodb.inc +# MSAN runs, but ignore its notice. ER_OUTOFMEMORY is expected by tests +call mtr.add_suppression("MemorySanitizer failed to allocate"); + --echo # --echo # MDEV-28217 Incorrect Join Execution When Controlling Join Buffer Size --echo # diff --git a/mysql-test/main/sum_distinct-big.test b/mysql-test/main/sum_distinct-big.test index 8820c191ae9..54fc676740f 100644 --- a/mysql-test/main/sum_distinct-big.test +++ b/mysql-test/main/sum_distinct-big.test @@ -5,7 +5,7 @@ --source include/big_test.inc # Test will take more than one hour with valgrind --source include/not_valgrind.inc ---source include/not_msan.inc +--source include/not_msan_with_debug.inc --source include/have_innodb.inc --source include/have_sequence.inc diff --git a/mysql-test/suite/funcs_1/t/myisam_views-big.test b/mysql-test/suite/funcs_1/t/myisam_views-big.test index 60fe1b8eaba..7199542d3d6 100644 --- a/mysql-test/suite/funcs_1/t/myisam_views-big.test +++ b/mysql-test/suite/funcs_1/t/myisam_views-big.test @@ -4,8 +4,7 @@ # because of a pair of slow Solaris Sparc machines in pb2, # this test is marked as big: --source include/big_test.inc -# This test often times out with MSAN ---source include/not_msan.inc +--source include/not_msan_with_debug.inc # MyISAM tables should be used # diff --git a/mysql-test/suite/innodb_gis/t/rtree_purge.test b/mysql-test/suite/innodb_gis/t/rtree_purge.test index f89f590acf0..4efb7213e28 100644 --- a/mysql-test/suite/innodb_gis/t/rtree_purge.test +++ b/mysql-test/suite/innodb_gis/t/rtree_purge.test @@ -4,8 +4,7 @@ --source include/innodb_page_size.inc --source include/have_sequence.inc --source include/not_valgrind.inc -# This test often times out with MSAN ---source include/not_msan.inc +--source include/no_msan_without_big.inc create table t ( b point not null,d point not null, spatial key (d),spatial key (b) diff --git a/mysql-test/suite/maria/max_length.test b/mysql-test/suite/maria/max_length.test index 02df51b33d7..c6859d5267d 100644 --- a/mysql-test/suite/maria/max_length.test +++ b/mysql-test/suite/maria/max_length.test @@ -6,7 +6,7 @@ --source include/big_test.inc # This test is too slow for valgrind --source include/not_valgrind.inc ---source include/not_msan.inc +--source include/not_msan_with_debug.inc drop table if exists t1,t2; diff --git a/mysql-test/suite/perfschema/t/statement_digest_long_query.test b/mysql-test/suite/perfschema/t/statement_digest_long_query.test index efa33800b58..23ff15c9102 100644 --- a/mysql-test/suite/perfschema/t/statement_digest_long_query.test +++ b/mysql-test/suite/perfschema/t/statement_digest_long_query.test @@ -6,6 +6,7 @@ --source include/have_perfschema.inc # Test requires: sp-protocol/ps-protocol/view-protocol/cursor-protocol disabled --source include/no_protocol.inc +--source include/not_msan_with_debug.inc # Thread stack overrun on solaris let $have_solaris = `select convert(@@version_compile_os using latin1) LIKE ("solaris%")`; if ($have_solaris) From 88d35c5c51e550843933eb3c6e0f2190a2f16529 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 28 May 2025 17:07:50 +1000 Subject: [PATCH 10/20] MDEV-34388: Stack overflow on Alpine Linux (postfix) mroonga+asan The following mroonga functions had approaching 64k stack frames, so exclude these: * chunk_merge - ~60k * buffer_merge - ~78k * grn_ii_update_one - ~60k --- storage/mroonga/vendor/groonga/lib/ii.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/storage/mroonga/vendor/groonga/lib/ii.c b/storage/mroonga/vendor/groonga/lib/ii.c index 8ce4857bfc2..e6c97c30bf2 100644 --- a/storage/mroonga/vendor/groonga/lib/ii.c +++ b/storage/mroonga/vendor/groonga/lib/ii.c @@ -44,6 +44,8 @@ # include #endif +#include "my_attribute.h" + #define MAX_PSEG 0x20000 #define MAX_PSEG_SMALL 0x00200 /* MAX_PSEG_MEDIUM has enough space for the following source: @@ -2833,6 +2835,8 @@ chunk_flush(grn_ctx *ctx, grn_ii *ii, chunk_info *cinfo, uint8_t *enc, uint32_t return ctx->rc; } +PRAGMA_DISABLE_CHECK_STACK_FRAME + static grn_rc chunk_merge(grn_ctx *ctx, grn_ii *ii, buffer *sb, buffer_term *bt, chunk_info *cinfo, grn_id rid, datavec *dv, @@ -2940,6 +2944,8 @@ chunk_merge(grn_ctx *ctx, grn_ii *ii, buffer *sb, buffer_term *bt, return ctx->rc; } +PRAGMA_REENABLE_CHECK_STACK_FRAME + static void buffer_merge_dump_datavec(grn_ctx *ctx, grn_ii *ii, @@ -2989,6 +2995,8 @@ buffer_merge_dump_datavec(grn_ctx *ctx, GRN_OBJ_FIN(ctx, &buffer); } +PRAGMA_DISABLE_CHECK_STACK_FRAME + /* If dc doesn't have enough space, program may be crashed. * TODO: Support auto space extension or max size check. */ @@ -3314,6 +3322,8 @@ buffer_merge(grn_ctx *ctx, grn_ii *ii, uint32_t seg, grn_hash *h, return ctx->rc; } +PRAGMA_REENABLE_CHECK_STACK_FRAME + static void fake_map(grn_ctx *ctx, grn_io *io, grn_io_win *iw, void *addr, uint32_t seg, uint32_t size) { @@ -4509,6 +4519,9 @@ grn_ii_get_disk_usage(grn_ctx *ctx, grn_ii *ii) return usage; } + +PRAGMA_DISABLE_CHECK_STACK_FRAME + #define BIT11_01(x) ((x >> 1) & 0x7ff) #define BIT31_12(x) (x >> 12) @@ -4784,6 +4797,8 @@ exit : return ctx->rc; } +PRAGMA_REENABLE_CHECK_STACK_FRAME + grn_rc grn_ii_delete_one(grn_ctx *ctx, grn_ii *ii, grn_id tid, grn_ii_updspec *u, grn_hash *h) { From e021a61b6fbf0b4c5929027e025d073e0c3418d5 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 2 May 2025 17:13:28 +1000 Subject: [PATCH 11/20] MDEV-36729: ha_example::show_func_example is incorrectly defined In the main.plugin this function is called assuming the function prototype int (*)(THD *, st_mysql_show_var *, void *, system_status_var *, enum_var_type)' as changed in b4ff64568c88ab3ce559e7bd39853d9cbf86704a. We update the ha_example::show_func_example to match the prototype on which it is called. --- storage/example/ha_example.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/example/ha_example.cc b/storage/example/ha_example.cc index c66c33a7818..5b0d7dcc74b 100644 --- a/storage/example/ha_example.cc +++ b/storage/example/ha_example.cc @@ -1078,11 +1078,11 @@ static struct st_mysql_sys_var* example_system_variables[]= { // this is an example of SHOW_SIMPLE_FUNC and of my_snprintf() service // If this function would return an array, one should use SHOW_FUNC static int show_func_example(MYSQL_THD thd, struct st_mysql_show_var *var, - char *buf) + void *buf, system_status_var *, enum_var_type) { var->type= SHOW_CHAR; var->value= buf; // it's of SHOW_VAR_FUNC_BUFF_SIZE bytes - my_snprintf(buf, SHOW_VAR_FUNC_BUFF_SIZE, + my_snprintf((char*) buf, SHOW_VAR_FUNC_BUFF_SIZE, "enum_var is %lu, ulong_var is %lu, int_var is %d, " "double_var is %f, %.6b", // %b is a MySQL extension srv_enum_var, srv_ulong_var, THDVAR(thd, int_var), From 0b2434d2e9531ed986039fa184f92405155247d9 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 28 May 2025 11:28:16 +0300 Subject: [PATCH 12/20] MDEV-25158 Segfault on INTERSECT ALL with UNION in Oracle mode Oracle mode has different set operator precedence and handling (not by standard). In Oracle mode the below test case is handled as-is, in plain order from left to right. In MariaDB default mode follows SQL standard and makes INTERSECT prioritized, so UNION is taken from derived table which is INTERSECT result (here and below the same applies for EXCEPT). Non-distinct set operator (UNION ALL/INTERSECT ALL) works via unique key release but it can be done only once. We cannot add index to non-empty heap table (see heap_enable_indexes()). So every UNION ALL before rightmost UNION DISTINCT works as UNION DISTINCT. That is common syntax, MySQL, MSSQL and Oracle work that way. There is union_distinct property which indicates the rightmost distinct UNION (at least, so the algorithm works simple: it releases the unique key after union_distinct in the loop (st_select_lex_unit::exec()). INTERSECT ALL code (implemented by MDEV-18844 in a896beb) does not know about Oracle mode and treats union_distinct as the last operation, that's why it releases unique key on union_distinct operation. INTERSECT ALL requires unique key for it to work, so before any INTERSECT ALL unique key must not be released (see select_unit_ext::send_data()). The patch tweaks INTERSECT ALL code for Oracle mode. In disable_index_if_needed() it does not allow unique key release before the last operation and it allows unfold on the last operation. Test case with UNION DISTINCT following INTERSECT ALL at least does not include invalid data, but in fact the whole INTERSECT ALL code could be refactored for better semantical triggers. The patch fixes typo in st_select_lex_unit::prepare() where have_except_all_or_intersect_all masked eponymous data member which wrongly triggered unique key release in st_select_lex_unit::prepare(). The patch fixes unknown error in case ha_disable_indexes() fails. Note: optimize_bag_operation() does some operator substitutions, but it does not run under PS. So if there is difference in test with --ps that means non-optimized (have_except_all_or_intersect_all == true) code path is not good. Note 2: VIEW is stored and executed in normal mode (see Sql_mode_save_for_frm_handling) hence when SELECT order is different in Oracle mode (defined by parsed_select_expr_cont()) it must be covered by --disable_view_protocol. --- mysql-test/main/intersect_all.result | 110 +++++++++++++++++++++++++++ mysql-test/main/intersect_all.test | 68 ++++++++++++++++- sql/sql_union.cc | 45 ++++++++--- 3 files changed, 212 insertions(+), 11 deletions(-) diff --git a/mysql-test/main/intersect_all.result b/mysql-test/main/intersect_all.result index e72209c5f89..2bda1bb37a5 100644 --- a/mysql-test/main/intersect_all.result +++ b/mysql-test/main/intersect_all.result @@ -920,3 +920,113 @@ NULL INTERSECT RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 union all /* select#4 */ select `__4`.`a` AS `a` from (/* select#2 */ select `test`.`t2`.`a` AS `a` from `test`.`t2` where `test`.`t2`.`a` < 5 intersect all /* select#3 */ select `test`.`t3`.`a` AS `a` from `test`.`t3` where `test`.`t3`.`a` < 5) `__4` drop table t1,t2,t3; +# +# MDEV-25158 Segfault on INTERSECT ALL with UNION in Oracle mode +# +create table t3 (x int); +create table u3 (x int); +create table i3 (x int); +explain SELECT * from t3 union select * from u3 intersect all select * from i3; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t3 system NULL NULL NULL NULL 0 Const row not found +4 UNION ALL NULL NULL NULL NULL 2 +2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table +3 INTERSECT NULL NULL NULL NULL NULL NULL NULL no matching row in const table +NULL INTERSECT RESULT ALL NULL NULL NULL NULL NULL +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +set sql_mode= 'oracle'; +explain SELECT * from t3 union select * from u3 intersect all select * from i3; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t3 system NULL NULL NULL NULL 0 Const row not found +2 UNION u3 system NULL NULL NULL NULL 0 Const row not found +3 INTERSECT i3 system NULL NULL NULL NULL 0 Const row not found +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +select * from t3 union select * from u3 intersect select * from i3; +x +SELECT * from t3 union select * from u3 intersect all select * from i3; +x +insert into t3 values (0); +insert into i3 values (0); +Select * from t3 union select * from u3 intersect select * from i3; +x +0 +SELECT * FROM t3 UNION SELECT * FROM u3 INTERSECT ALL SELECT * FROM i3; +x +0 +drop tables t3, u3, i3; +# First line of these results is column names, not the result +# (pay attention to "affected rows") +values (1, 2) union all values (1, 2); +1 2 +1 2 +1 2 +affected rows: 2 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3); +1 2 +1 2 +4 3 +4 3 +affected rows: 3 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3) union all values (1, 2); +1 2 +1 2 +4 3 +4 3 +1 2 +affected rows: 4 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3) union all values (1, 2) union values (1, 2); +1 2 +1 2 +4 3 +affected rows: 2 +create table t1 (a int, b int); +create table t2 like t1; +insert t1 values (1, 2), (1, 2), (1, 2), (2, 3), (2, 3), (3, 4), (3, 4); +insert t2 values (1, 2), (1, 2), (2, 3), (2, 3), (2, 3), (2, 3), (4, 5); +select * from t1 intersect select * from t2; +a b +1 2 +2 3 +select * from t1 intersect all select * from t2; +a b +1 2 +2 3 +1 2 +2 3 +# Default: first INTERSECT ALL, then UNION +# Oracle: first UNION, then INTERSECT ALL +select * from t1 union values (1, 2) intersect all select * from t2; +a b +1 2 +2 3 +select * from t1 union (values (1, 2) intersect all select * from t2); +a b +1 2 +2 3 +3 4 +(select * from t1 union values (1, 2)) intersect all select * from t2; +a b +1 2 +2 3 +select * from t1 intersect all select * from t2 union values (1, 2); +a b +1 2 +2 3 +1 2 +2 3 +select * from t1 intersect all (select * from t2 union values (1, 2)); +a b +1 2 +2 3 +(select * from t1 intersect all select * from t2) union values (1, 2); +a b +1 2 +2 3 +explain select * from t1 intersect all select * from t2 union values (1, 2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 7 +2 INTERSECT t2 ALL NULL NULL NULL NULL 7 +3 UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +drop tables t1, t2; +set sql_mode= default; diff --git a/mysql-test/main/intersect_all.test b/mysql-test/main/intersect_all.test index 5d2b038fde9..8217e4ebcbc 100644 --- a/mysql-test/main/intersect_all.test +++ b/mysql-test/main/intersect_all.test @@ -325,4 +325,70 @@ select * from t2 where a < 5 intersect all select * from t3 where a < 5; -drop table t1,t2,t3; \ No newline at end of file +drop table t1,t2,t3; + +--echo # +--echo # MDEV-25158 Segfault on INTERSECT ALL with UNION in Oracle mode +--echo # +create table t3 (x int); +create table u3 (x int); +create table i3 (x int); +explain SELECT * from t3 union select * from u3 intersect all select * from i3; +set sql_mode= 'oracle'; +explain SELECT * from t3 union select * from u3 intersect all select * from i3; +select * from t3 union select * from u3 intersect select * from i3; +SELECT * from t3 union select * from u3 intersect all select * from i3; +insert into t3 values (0); +insert into i3 values (0); +Select * from t3 union select * from u3 intersect select * from i3; +SELECT * FROM t3 UNION SELECT * FROM u3 INTERSECT ALL SELECT * FROM i3; +drop tables t3, u3, i3; + +--enable_info +--echo # First line of these results is column names, not the result +--echo # (pay attention to "affected rows") + +# MSSQL: +# 1 2 +# 1 2 +values (1, 2) union all values (1, 2); + +# MSSQL: +# 1 2 +# 4 3 +# 4 3 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3); + +# MSSQL: +# 1 2 +# 4 3 +# 4 3 +# 1 2 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3) union all values (1, 2); + +# MSSQL: +# 1 2 +# 4 3 +values (1, 2) union all values (1, 2) union values (4, 3) union all values (4, 3) union all values (1, 2) union values (1, 2); +--disable_info + +create table t1 (a int, b int); +create table t2 like t1; +insert t1 values (1, 2), (1, 2), (1, 2), (2, 3), (2, 3), (3, 4), (3, 4); +insert t2 values (1, 2), (1, 2), (2, 3), (2, 3), (2, 3), (2, 3), (4, 5); +select * from t1 intersect select * from t2; +select * from t1 intersect all select * from t2; +--echo # Default: first INTERSECT ALL, then UNION +--echo # Oracle: first UNION, then INTERSECT ALL +# VIEW is stored and executed normal mode (see Sql_mode_save_for_frm_handling) +--disable_view_protocol +select * from t1 union values (1, 2) intersect all select * from t2; +--enable_view_protocol +select * from t1 union (values (1, 2) intersect all select * from t2); +(select * from t1 union values (1, 2)) intersect all select * from t2; +select * from t1 intersect all select * from t2 union values (1, 2); +select * from t1 intersect all (select * from t2 union values (1, 2)); +(select * from t1 intersect all select * from t2) union values (1, 2); +explain select * from t1 intersect all select * from t2 union values (1, 2); +drop tables t1, t2; +set sql_mode= default; diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 6b451349cdc..dd9998bcb9b 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -474,21 +474,30 @@ int select_unit::update_counter(Field* counter, longlong value) Try to disable index @retval - true index is disabled this time + true index is disabled and unfold is needed false this time did not disable the index */ bool select_unit_ext::disable_index_if_needed(SELECT_LEX *curr_sl) { + const bool oracle_mode= thd->variables.sql_mode & MODE_ORACLE; if (is_index_enabled && - (curr_sl == curr_sl->master_unit()->union_distinct || + ((!oracle_mode && + curr_sl == curr_sl->master_unit()->union_distinct) || !curr_sl->next_select()) ) { is_index_enabled= false; - if (table->file->ha_disable_indexes(key_map(0), false)) + int error= table->file->ha_disable_indexes(key_map(0), false); + if (error) + { + table->file->print_error(error, MYF(0)); + DBUG_ASSERT(0); return false; + } table->no_keyread=1; - return true; + /* In case of Oracle mode we unfold at the last operator */ + DBUG_ASSERT(!oracle_mode || !curr_sl->next_select()); + return oracle_mode || !curr_sl->distinct; } return false; } @@ -772,8 +781,7 @@ bool select_unit_ext::send_eof() next_sl && next_sl->get_linkage() == INTERSECT_TYPE && !next_sl->distinct; - bool need_unfold= (disable_index_if_needed(curr_sl) && - !curr_sl->distinct); + bool need_unfold= disable_index_if_needed(curr_sl); if (((curr_sl->distinct && !is_next_distinct) || curr_op_type == INTERSECT_ALL || @@ -781,7 +789,8 @@ bool select_unit_ext::send_eof() !need_unfold) { if (!next_sl) - DBUG_ASSERT(curr_op_type != INTERSECT_ALL); + DBUG_ASSERT((thd->variables.sql_mode & MODE_ORACLE) || + curr_op_type != INTERSECT_ALL); bool need_update_row; if (unlikely(table->file->ha_rnd_init_with_error(1))) return 1; @@ -1295,8 +1304,8 @@ bool st_select_lex_unit::prepare(TABLE_LIST *derived_arg, uint union_part_count= 0; select_result *tmp_result; bool is_union_select; - bool have_except= false, have_intersect= false, - have_except_all_or_intersect_all= false; + bool have_except= false, have_intersect= false; + have_except_all_or_intersect_all= false; bool instantiate_tmp_table= false; bool single_tvc= !first_sl->next_select() && first_sl->tvc; bool single_tvc_wo_order= single_tvc && !first_sl->order_list.elements; @@ -2160,6 +2169,7 @@ bool st_select_lex_unit::exec() bool first_execution= !executed; DBUG_ENTER("st_select_lex_unit::exec"); bool was_executed= executed; + int error; if (executed && !uncacheable && !describe) DBUG_RETURN(FALSE); @@ -2243,17 +2253,32 @@ bool st_select_lex_unit::exec() if (likely(!saved_error)) { records_at_start= table->file->stats.records; + + /* select_unit::send_data() writes rows to (temporary) table */ if (sl->tvc) sl->tvc->exec(sl); else sl->join->exec(); + /* + Allow UNION ALL to work: disable unique key. We cannot disable indexes + in the middle of the query because enabling indexes requires table to be empty + (see heap_enable_indexes()). So there is special union_distinct property + which is the rightmost distinct UNION in the expression and we release + the unique key after the last (rightmost) distinct UNION, therefore only the + subsequent UNION ALL work as non-distinct. + */ if (sl == union_distinct && !have_except_all_or_intersect_all && !(with_element && with_element->is_recursive)) { // This is UNION DISTINCT, so there should be a fake_select_lex DBUG_ASSERT(fake_select_lex != NULL); - if (table->file->ha_disable_indexes(key_map(0), false)) + error= table->file->ha_disable_indexes(key_map(0), false); + if (error) + { + table->file->print_error(error, MYF(0)); + DBUG_ASSERT(0); DBUG_RETURN(TRUE); + } table->no_keyread=1; } if (!sl->tvc) From fe6a5c220046514ad6ce055f881dc569281863c2 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 28 May 2025 11:28:17 +0300 Subject: [PATCH 13/20] MDEV-29155 CREATE OR REPLACE with self-referencing CHECK hangs forever, cannot be killed mysql_rm_table_no_locks() does TDC_RT_REMOVE_ALL which waits while share is closed. The table normally is open only as OPEN_STUB, this is what parser does for CREATE TABLE. But for SELECT the table is opened not as a stub. If it is the same table name we anyway have two TABLE_LIST objects: stub and not stub. So for "not stub" TDC_RT_REMOVE_ALL sees open count and decides to wait until it is closed. And it hangs because that was opened in the same thread. The fix disables subqueries in CHECK expression at parser level. Thanks to Sergei Golubchik for the patch. --- mysql-test/main/check_constraint.result | 2 +- mysql-test/main/check_constraint.test | 2 +- mysql-test/suite/vcol/r/not_supported.result | 31 ++++++++++++++++++ mysql-test/suite/vcol/t/not_supported.test | 33 ++++++++++++++++++++ sql/sql_yacc.yy | 7 +++-- 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/check_constraint.result b/mysql-test/main/check_constraint.result index e3986dcf75b..060407d62d5 100644 --- a/mysql-test/main/check_constraint.result +++ b/mysql-test/main/check_constraint.result @@ -139,7 +139,7 @@ drop table t1; create or replace table t1( c1 int auto_increment primary key, check( c1 > 0 or c1 is null ) ); ERROR HY000: Function or expression 'AUTO_INCREMENT' cannot be used in the CHECK clause of `c1` create table t1 (a int check (@b in (select user from mysql.user))); -ERROR HY000: Function or expression 'select ...' cannot be used in the CHECK clause of `a` +ERROR 42000: CHECK does not support subqueries or stored functions create table t1 (a int check (a > @b)); ERROR HY000: Function or expression '@b' cannot be used in the CHECK clause of `a` create table t1 (a int check (a = 1)); diff --git a/mysql-test/main/check_constraint.test b/mysql-test/main/check_constraint.test index e12468e9013..056d5d06b60 100644 --- a/mysql-test/main/check_constraint.test +++ b/mysql-test/main/check_constraint.test @@ -87,7 +87,7 @@ create or replace table t1( c1 int auto_increment primary key, check( c1 > 0 or # # MDEV-12421 Check constraint with query crashes server and renders DB unusable # ---error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +--error ER_SUBQUERIES_NOT_SUPPORTED create table t1 (a int check (@b in (select user from mysql.user))); --error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED create table t1 (a int check (a > @b)); diff --git a/mysql-test/suite/vcol/r/not_supported.result b/mysql-test/suite/vcol/r/not_supported.result index 37ce4865dcc..034bca6a97c 100644 --- a/mysql-test/suite/vcol/r/not_supported.result +++ b/mysql-test/suite/vcol/r/not_supported.result @@ -55,3 +55,34 @@ ERROR HY000: Function or expression 'b' cannot be used in the GENERATED ALWAYS A # # End of 10.3 tests # +# +# MDEV-29155 CREATE OR REPLACE with self-referencing CHECK hangs forever, cannot be killed +# +create table t1 (a int); +create table t2 (b int) +# create or replace table t (b int); +create table t3 (c int, check(exists(select a from t1) or exists(select b from t2))); +ERROR 42000: CHECK does not support subqueries or stored functions +create table t3 (c int, check(exists(select c from t3))); +ERROR 42000: CHECK does not support subqueries or stored functions +create table t3 (d int); +create or replace table t3 (c int, check(exists(select a from t1) or exists(select b from t2))); +ERROR 42000: CHECK does not support subqueries or stored functions +drop table t3; +create table t3 (d int); +create or replace table t3 (c int, check(exists(select c from t3))); +ERROR 42000: CHECK does not support subqueries or stored functions +drop table t3; +create table t3 (c int); +alter table t3 add check(exists(select a from t1) or exists(select b from t2)); +ERROR 42000: CHECK does not support subqueries or stored functions +alter table t3 add check(exists(select c from t3)); +ERROR 42000: CHECK does not support subqueries or stored functions +create table t3 (c int default (select a from t1)); +ERROR HY000: Function or expression 'select ...' cannot be used in the DEFAULT clause of `c` +create table t3 (c int, d int generated always as (select a from t1 limit 1)); +ERROR HY000: Function or expression 'select ...' cannot be used in the GENERATED ALWAYS AS clause of `d` +drop tables t1, t2, t3; +# +# End of 10.4 tests +# diff --git a/mysql-test/suite/vcol/t/not_supported.test b/mysql-test/suite/vcol/t/not_supported.test index d58b207a7eb..143c0160084 100644 --- a/mysql-test/suite/vcol/t/not_supported.test +++ b/mysql-test/suite/vcol/t/not_supported.test @@ -64,3 +64,36 @@ create table t1 (a int auto_increment primary key, --echo # --echo # End of 10.3 tests --echo # + +--echo # +--echo # MDEV-29155 CREATE OR REPLACE with self-referencing CHECK hangs forever, cannot be killed +--echo # +create table t1 (a int); +create table t2 (b int) +# create or replace table t (b int); +--error ER_SUBQUERIES_NOT_SUPPORTED +create table t3 (c int, check(exists(select a from t1) or exists(select b from t2))); +--error ER_SUBQUERIES_NOT_SUPPORTED +create table t3 (c int, check(exists(select c from t3))); +create table t3 (d int); +--error ER_SUBQUERIES_NOT_SUPPORTED +create or replace table t3 (c int, check(exists(select a from t1) or exists(select b from t2))); +drop table t3; +create table t3 (d int); +--error ER_SUBQUERIES_NOT_SUPPORTED +create or replace table t3 (c int, check(exists(select c from t3))); +drop table t3; +create table t3 (c int); +--error ER_SUBQUERIES_NOT_SUPPORTED +alter table t3 add check(exists(select a from t1) or exists(select b from t2)); +--error ER_SUBQUERIES_NOT_SUPPORTED +alter table t3 add check(exists(select c from t3)); +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +create table t3 (c int default (select a from t1)); +--error ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED +create table t3 (c int, d int generated always as (select a from t1 limit 1)); +drop tables t1, t2, t3; + +--echo # +--echo # End of 10.4 tests +--echo # diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 688330989ab..03b37f5dec9 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -6126,9 +6126,12 @@ opt_check_constraint: ; check_constraint: - CHECK_SYM '(' expr ')' + CHECK_SYM '(' + { Lex->clause_that_disallows_subselect= "CHECK"; } + expr ')' { - Virtual_column_info *v= add_virtual_expression(thd, $3); + Virtual_column_info *v= add_virtual_expression(thd, $4); + Lex->clause_that_disallows_subselect= NULL; if (unlikely(!v)) MYSQL_YYABORT; $$= v; From f533333f82256e8cddf890d407939f02efb5e21c Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 29 May 2025 11:28:15 +1000 Subject: [PATCH 14/20] MDEV-34388: Stack overflow on Alpine Linux (postfix) - sanitizers Remove stack limits for sanitizers. Other tests cover them. --- cmake/maintainer.cmake | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cmake/maintainer.cmake b/cmake/maintainer.cmake index 5debb42897a..394f623f3e0 100644 --- a/cmake/maintainer.cmake +++ b/cmake/maintainer.cmake @@ -19,12 +19,6 @@ IF(MSVC OR MYSQL_MAINTAINER_MODE STREQUAL "NO") RETURN() ENDIF() -IF((WITH_MSAN OR WITH_ASAN) AND CMAKE_BUILD_TYPE STREQUAL "Debug") - SET(STACK_FRAME_LIMIT 65536) -ELSE() - SET(STACK_FRAME_LIMIT 16384) -ENDIF() - # Common warning flags for GCC, G++, Clang and Clang++ SET(MY_WARNING_FLAGS -Wall @@ -47,9 +41,12 @@ SET(MY_WARNING_FLAGS -Wvla -Wwrite-strings -Wcast-function-type-strict - -Wframe-larger-than=${STACK_FRAME_LIMIT} ) +IF(NOT (WITH_MSAN OR WITH_ASAN OR WITH_UBSAN)) + SET(MY_WARNING_FLAGS ${MY_WARNING_FLAGS} -Wframe-larger-than=16384) +ENDIF() + # Warning flags that are in testing before moving # to MY_WARNING_FLAGS if stable. SET(MY_WARNING_FLAGS_NON_FATAL From 22024da64e8febc152c3a822d2d2fc67e85dc175 Mon Sep 17 00:00:00 2001 From: Monty Date: Thu, 8 May 2025 15:08:02 +0300 Subject: [PATCH 15/20] MDEV-36143 Row event replication with Aria does not honour BLOCK_COMMIT This commit fixes a bug where Aria tables are used in (master->slave1->slave2) and a backup is taken on slave2. In this case it is possible that the replication position in the backup, stored in mysql.gtid_slave_pos, will be wrong. This will lead to replication errors if one is trying to use the backup as a new slave. Analyze: Replicated row events are committed with trans_commit_stmt() and thd->transaction->all.ha_list != 0. This means that backup_commit_lock is not taken for Aria tables, which means the rows are committed and binary logged on the slave under BLOCK_COMMIT which should not happen. This issue does not occur on the master as thd->transaction->all.ha_list is == 0 under AUTO_COMMIT, which sets 'is_real_trans' and 'rw_trans' which in turn causes backup_commit_lock to be taken. Fixed by checking in ha_check_and_coalesce_trx_read_only() if all handlers supports rollback and if not, then wait for BLOCK_COMMIT also for statement commit. --- mysql-test/main/backup_lock.result | 1 + mysql-test/main/backup_lock.test | 1 + sql/handler.cc | 39 ++++++++++++++++++++++++++---- sql/handler.h | 14 +++++++++-- sql/wsrep_mysqld.cc | 3 ++- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/mysql-test/main/backup_lock.result b/mysql-test/main/backup_lock.result index 488e81fd6d4..ced757265b2 100644 --- a/mysql-test/main/backup_lock.result +++ b/mysql-test/main/backup_lock.result @@ -266,6 +266,7 @@ col1 SET AUTOCOMMIT = 0; UPDATE t_permanent_innodb SET col1 = 9; UPDATE t_permanent_aria SET col1 = 9; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction UPDATE t_permanent_myisam SET col1 = 9; ERROR HY000: Lock wait timeout exceeded; try restarting transaction UPDATE t_permanent_aria2 SET col1 = 9; diff --git a/mysql-test/main/backup_lock.test b/mysql-test/main/backup_lock.test index f86f2f3670e..015041babaf 100644 --- a/mysql-test/main/backup_lock.test +++ b/mysql-test/main/backup_lock.test @@ -328,6 +328,7 @@ select * from t_permanent_aria2; SET AUTOCOMMIT = 0; UPDATE t_permanent_innodb SET col1 = 9; +--error ER_LOCK_WAIT_TIMEOUT UPDATE t_permanent_aria SET col1 = 9; --error ER_LOCK_WAIT_TIMEOUT UPDATE t_permanent_myisam SET col1 = 9; diff --git a/sql/handler.cc b/sql/handler.cc index 706e2f0df6a..cd8d2e4287d 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1586,6 +1586,12 @@ uint ha_count_rw_2pc(THD *thd, bool all) /** Check if we can skip the two-phase commit. + @param thd Thread handler + @param ha_list List of all engines participating on the commit + @param all True if this is final commit (not statement commit) + @param no_rollback Set to 1 if one of the engines doing writes does + not support rollback + A helper function to evaluate if two-phase commit is mandatory. As a side effect, propagates the read-only/read-write flags of the statement transaction to its enclosing normal transaction. @@ -1604,16 +1610,21 @@ uint ha_count_rw_2pc(THD *thd, bool all) uint ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list, - bool all) + bool all, bool *no_rollback) { /* The number of storage engines that have actual changes. */ unsigned rw_ha_count= 0; Ha_trx_info *ha_info; + *no_rollback= false; for (ha_info= ha_list; ha_info; ha_info= ha_info->next()) { if (ha_info->is_trx_read_write()) + { ++rw_ha_count; + if (ha_info->is_trx_no_rollback()) + *no_rollback= true; + } if (! all) { @@ -1636,7 +1647,18 @@ ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list, information up, and the need for two-phase commit has been already established. Break the loop prematurely. */ - break; + if (*no_rollback == 0) + { + while ((ha_info= ha_info->next())) + { + if (ha_info->is_trx_read_write() && ha_info->is_trx_no_rollback()) + { + *no_rollback= 1; + break; + } + } + break; + } } } return rw_ha_count; @@ -1771,7 +1793,9 @@ int ha_commit_trans(THD *thd, bool all) if (is_real_trans) /* not a statement commit */ thd->stmt_map.close_transient_cursors(); - uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all); + bool no_rollback; + uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all, + &no_rollback); /* rw_trans is TRUE when we in a transaction changing data */ bool rw_trans= is_real_trans && rw_ha_count > 0; MDL_request mdl_backup; @@ -1784,7 +1808,7 @@ int ha_commit_trans(THD *thd, bool all) calling ha_commit_trans() from spader_commit(). */ - if (rw_trans && !thd->backup_commit_lock) + if ((rw_trans || no_rollback) && !thd->backup_commit_lock) { /* Acquire a metadata lock which will ensure that COMMIT is blocked @@ -2114,7 +2138,9 @@ int ha_commit_one_phase(THD *thd, bool all) static bool is_ro_1pc_trans(THD *thd, Ha_trx_info *ha_info, bool all, bool is_real_trans) { - uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all); + bool no_rollback; + uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all, + &no_rollback); bool rw_trans= is_real_trans && (rw_ha_count > (thd->is_current_stmt_binlog_disabled()?0U:1U)); @@ -5145,6 +5171,9 @@ void handler::mark_trx_read_write_internal() */ if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE) ha_info->set_trx_read_write(); + /* Mark if we are using a table that cannot do rollback */ + if (ht->flags & HTON_NO_ROLLBACK) + ha_info->set_trx_no_rollback(); } } diff --git a/sql/handler.h b/sql/handler.h index b6046091f4f..05541765a0b 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -2014,6 +2014,16 @@ public: DBUG_ASSERT(is_started()); return m_flags & (int) TRX_READ_WRITE; } + void set_trx_no_rollback() + { + DBUG_ASSERT(is_started()); + m_flags|= (int) TRX_NO_ROLLBACK; + } + bool is_trx_no_rollback() const + { + DBUG_ASSERT(is_started()); + return m_flags & (int) TRX_NO_ROLLBACK; + } bool is_started() const { return m_ht != NULL; } /** Mark this transaction read-write if the argument is read-write. */ void coalesce_trx_with(const Ha_trx_info *stmt_trx) @@ -2038,7 +2048,7 @@ public: return m_ht; } private: - enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 }; + enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1, TRX_NO_ROLLBACK= 2 }; /** Auxiliary, used for ha_list management */ Ha_trx_info *m_next; /** @@ -5531,7 +5541,7 @@ uint ha_count_rw_all(THD *thd, Ha_trx_info **ptr_ha_info); bool non_existing_table_error(int error); uint ha_count_rw_2pc(THD *thd, bool all); uint ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list, - bool all); + bool all, bool *no_rollback); int get_select_field_pos(Alter_info *alter_info, int select_field_count, bool versioned); diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 469cbc9da2d..010cab56614 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1446,7 +1446,8 @@ bool wsrep_check_mode_after_open_table (THD *thd, } // Check are we inside a transaction - uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, thd->transaction->all.ha_list, true); + bool not_used; + uint rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, thd->transaction->all.ha_list, true, ¬_used); bool changes= wsrep_has_changes(thd); // Roll back current stmt if exists From 6878c1400042f45ad0ba466c49ba7b0a6e750308 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 20 May 2025 10:39:53 +0300 Subject: [PATCH 16/20] Updated storage/maria/ma_test_big.sh to use aria_ instead of maria_ I also improved the script to use 'tmp' for storing test files instead of current directory --- storage/maria/ma_test_big.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) mode change 100644 => 100755 storage/maria/ma_test_big.sh diff --git a/storage/maria/ma_test_big.sh b/storage/maria/ma_test_big.sh old mode 100644 new mode 100755 index 6419d05e3a4..41260151cd7 --- a/storage/maria/ma_test_big.sh +++ b/storage/maria/ma_test_big.sh @@ -4,19 +4,23 @@ # finding bugs in blob handling # +mkdir -p tmp +cd tmp set -e a=15 while test $a -le 5000 do echo $a - rm -f maria_log* - ma_test2 -s -L -K -W -P -M -T -c -b32768 -t4 -A1 -m$a > /dev/null - maria_read_log -a -s >& /dev/null - maria_chk -es test2 - maria_read_log -a -s >& /dev/null - maria_chk -es test2 + rm -f aria_log* + ../ma_test2 -s -L -K -W -P -M -T -c -b32768 -t4 -A1 -m$a > /dev/null + ../aria_read_log -a -s >& /dev/null + ../aria_chk -ess test2 + ../aria_read_log -a -s >& /dev/null + ../aria_chk -ess test2 rm test2.MA? - maria_read_log -a -s >& /dev/null - maria_chk -es test2 + ../aria_read_log -a -s >& /dev/null + ../aria_chk -ess test2 a=$((a+1)) done +cd .. +rm -r tmp From 5f83b219bb05f3e24fedb3ddc90a7d81c1a73d0e Mon Sep 17 00:00:00 2001 From: Monty Date: Wed, 21 May 2025 10:59:34 +0300 Subject: [PATCH 17/20] Fixed compiler warning from clang in connect/tabxcl.cpp --- storage/connect/tabxcl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/connect/tabxcl.cpp b/storage/connect/tabxcl.cpp index d354f556ca1..8f77ecb8357 100644 --- a/storage/connect/tabxcl.cpp +++ b/storage/connect/tabxcl.cpp @@ -186,8 +186,8 @@ bool TDBXCL::OpenDB(PGLOBAL g) /*********************************************************************/ /* Physically open the object table. */ /*********************************************************************/ - if (Tdbp->OpenDB(g)) - return TRUE; + if (Tdbp->OpenDB(g)) + return TRUE; Use = USE_OPEN; return FALSE; From ce4f83e6b93756ad9dc94dab61afaf0db06417b3 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 25 May 2025 15:47:26 +0300 Subject: [PATCH 18/20] MDEV-29157 SELECT using ror_merged scan fails with s3 tables handler::clone() call did not work with read only tables like S3. It gave a wrong error message (out of memory instead of a permission error) and aborted the query. The issue was that the clone call had a wrong parameter to ha_open(). This now fixed. I also changed the clone call to provide the correct error message if things fails. This patch fixes an 'out of memory' error when using the S3 engine for queries that could use multiple indexes together to find the matching rows, like the following: SELECT * FROM t1 WHERE key1 = 99 OR key2 = 2 --- mysql-test/suite/s3/clone.result | 12 ++++++++++++ mysql-test/suite/s3/clone.test | 14 ++++++++++++++ sql/handler.cc | 11 +++++++++-- sql/opt_range.cc | 9 +-------- 4 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 mysql-test/suite/s3/clone.result create mode 100644 mysql-test/suite/s3/clone.test diff --git a/mysql-test/suite/s3/clone.result b/mysql-test/suite/s3/clone.result new file mode 100644 index 00000000000..a33c8c4259a --- /dev/null +++ b/mysql-test/suite/s3/clone.result @@ -0,0 +1,12 @@ +# +# SELECT using ror_merged scan fails with s3 tables +# +DROP TABLE IF EXISTS t1; +Warnings: +Note 1051 Unknown table 'test.t1' +CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; +INSERT INTO t1 VALUES (0,0),(0,10),(3,10); +ALTER TABLE t1 ENGINE=S3; +SELECT * FROM t1 WHERE a = 99 OR b = 2; +a b +DROP TABLE t1; diff --git a/mysql-test/suite/s3/clone.test b/mysql-test/suite/s3/clone.test new file mode 100644 index 00000000000..5695257c404 --- /dev/null +++ b/mysql-test/suite/s3/clone.test @@ -0,0 +1,14 @@ +--source include/have_s3.inc +--source include/have_sequence.inc +--source include/have_innodb.inc + +--echo # +--echo # SELECT using ror_merged scan fails with s3 tables +--echo # + +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; +INSERT INTO t1 VALUES (0,0),(0,10),(3,10); +ALTER TABLE t1 ENGINE=S3; +SELECT * FROM t1 WHERE a = 99 OR b = 2; +DROP TABLE t1; diff --git a/sql/handler.cc b/sql/handler.cc index cd8d2e4287d..44a9cf8b173 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -3317,12 +3317,16 @@ int ha_delete_table(THD *thd, handlerton *hton, const char *path, handler *handler::clone(const char *name, MEM_ROOT *mem_root) { + int error= 0; handler *new_handler= get_new_handler(table->s, mem_root, ht); if (!new_handler) return NULL; if (new_handler->set_ha_share_ref(ha_share)) + { + error= ER_OUT_OF_RESOURCES; goto err; + } /* TODO: Implement a more efficient way to have more than one index open for @@ -3331,14 +3335,17 @@ handler *handler::clone(const char *name, MEM_ROOT *mem_root) This is not critical as the engines already have the table open and should be able to use the original instance of the table. */ - if (new_handler->ha_open(table, name, table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED, mem_root)) + if ((error= new_handler->ha_open(table, name, + table->db_stat & HA_READ_ONLY ? + O_RDONLY : O_RDWR, + HA_OPEN_IGNORE_IF_LOCKED, mem_root))) goto err; new_handler->handler_stats= handler_stats; return new_handler; err: + new_handler->print_error(error, MYF(0)); delete new_handler; return NULL; } diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 975e1b7b1f7..0a3d71abe93 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -1532,14 +1532,7 @@ int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler, if (!(file= head->file->clone(head->s->normalized_path.str, local_alloc))) { - /* - Manually set the error flag. Note: there seems to be quite a few - places where a failure could cause the server to "hang" the client by - sending no response to a query. ATM those are not real errors because - the storage engine calls in question happen to never fail with the - existing storage engines. - */ - my_error(ER_OUT_OF_RESOURCES, MYF(0)); /* purecov: inspected */ + /* clone() has already generated an error message */ /* Caller will free the memory */ goto failure; /* purecov: inspected */ } From 0a91bbdc41f7be703f422ed7b9ed393271d36b36 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 27 May 2025 10:57:16 +0300 Subject: [PATCH 19/20] Get debug version to compile with gcc 7.5.0 --- include/my_attribute.h | 15 ++++++++++++++- sql/sql_show.cc | 7 +++++++ storage/innobase/handler/i_s.cc | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/my_attribute.h b/include/my_attribute.h index 2ffd65fae3d..04f6a62b4a1 100644 --- a/include/my_attribute.h +++ b/include/my_attribute.h @@ -91,9 +91,22 @@ _Pragma("GCC diagnostic ignored \"-Wframe-larger-than=\"") #define PRAGMA_REENABLE_CHECK_STACK_FRAME \ _Pragma("GCC diagnostic pop") +/* + The following check is for older gcc version that allocates + a lot of stack during startup that does not need to be checked +*/ + +#if !defined(__clang__) && __GNUC__ < 13 +#define PRAGMA_DISABLE_CHECK_STACK_FRAME_EXTRA PRAGMA_DISABLE_CHECK_STACK_FRAME #else +#define PRAGMA_DISABLE_CHECK_STACK_FRAME_EXTRA +#endif /* !defined(__clang__) && __GNUC__ < 13 */ + +#else /*! __GNUC__ */ #define PRAGMA_DISABLE_CHECK_STACK_FRAME #define PRAGMA_REENABLE_CHECK_STACK_FRAME -#endif +#define PRAGMA_DISABLE_CHECK_STACK_FRAME +#define PRAGMA_DISABLE_CHECK_STACK_FRAME_EXTRA +#endif /* __GNUC__ */ #endif /* _my_attribute_h */ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 6ba6f8088fa..b1e7dbdc52a 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -9747,6 +9747,13 @@ void init_fill_schema_files_row(TABLE* table) } +/* + gcc 7.5.0 uses a lot of stack at startup to resolve Column() expressions + Note, do not use PRAGMA_REENABLE_CHECK_STACK_FRAME later on in this file + as this causes compilation to fail. +*/ +PRAGMA_DISABLE_CHECK_STACK_FRAME_EXTRA + namespace Show { ST_FIELD_INFO referential_constraints_fields_info[]= diff --git a/storage/innobase/handler/i_s.cc b/storage/innobase/handler/i_s.cc index c38ff61b0e4..6385f31fe62 100644 --- a/storage/innobase/handler/i_s.cc +++ b/storage/innobase/handler/i_s.cc @@ -1209,6 +1209,7 @@ struct st_maria_plugin i_s_innodb_cmp_reset = MariaDB_PLUGIN_MATURITY_STABLE, }; +PRAGMA_DISABLE_CHECK_STACK_FRAME_EXTRA namespace Show { /* Fields of the dynamic tables From 643319a7fb1e273797c2a1e46d76cfac0fa1da8f Mon Sep 17 00:00:00 2001 From: Monty Date: Fri, 30 May 2025 11:22:58 +0300 Subject: [PATCH 20/20] MDEV-36465 MDEV-33813 Regression, Queries in 'Waiting for someone to free space' state will not automatically retry IO and hang forever MDEV-33813 caused a regressing in that when a disk got full when writing to a MyISAM or Aria table the MariaDB connection would, instead of doing a retry after 60 seconds, hang until the query was killed. Fixed by changing mysql_coind_wait() top mysql_cond_timedwait() Author: Thomas Stangner --- sql/sql_class.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index da8120abaab..62d1cb068c3 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -8509,16 +8509,19 @@ void mariadb_sleep_for_space(unsigned int seconds) { THD *thd= current_thd; PSI_stage_info old_stage; + struct timespec abstime; if (!thd) { sleep(seconds); return; } - mysql_mutex_lock(&thd->LOCK_wakeup_ready); + set_timespec(abstime, seconds); + mysql_mutex_lock(&thd->LOCK_wakeup_ready); thd->ENTER_COND(&thd->COND_wakeup_ready, &thd->LOCK_wakeup_ready, &stage_waiting_for_disk_space, &old_stage); if (!thd->killed) - mysql_cond_wait(&thd->COND_wakeup_ready, &thd->LOCK_wakeup_ready); + mysql_cond_timedwait(&thd->COND_wakeup_ready, &thd->LOCK_wakeup_ready, + &abstime); thd->EXIT_COND(&old_stage); return; }