diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 7907708d199..7dff1054c52 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -207,7 +207,7 @@ static char TMPDIR[FN_REFLEN]; static char global_subst_from[200]; static char global_subst_to[200]; static char *global_subst= NULL; -static char *read_command_buf= NULL; +static char *read_command_buf= NULL, *read_command_buf_end; static MEM_ROOT require_file_root; static const my_bool my_true= 1; static const my_bool my_false= 0; @@ -3873,9 +3873,24 @@ end: #endif if (error) { - uint status= WEXITSTATUS(error); + uint status; int i; +#ifdef _WIN32 + status= WEXITSTATUS(error); +#else + /* WEXITSTATUS() is only valid for a normal exit; a process killed by an + uncaught signal must be translated using the shell's 128+signal + convention, or the real error is silently lost as status 0. */ + if (WIFEXITED(error)) + status= WEXITSTATUS(error); + else if (WIFSIGNALED(error)) + status= 128 + WTERMSIG(error); + else + status= error; + +#endif + if (command->abort_on_error) { report_or_die("exec of '%s' failed, error: %d, status: %d, errno: %d\n" @@ -5638,7 +5653,7 @@ static void primary(Expression_value *result, const char **s) enum func_type func_type= get_expr_function_type(start, end - start); if (func_type == FUNC_UNKNOWN) - die("Syntax error: Unknown function"); + die("Syntax error: Unknown function '%.*s'", (int) (end - start), start); *s= end + 1; // skip '(' handle_expr_function_call(func_type, result, s); @@ -6085,47 +6100,48 @@ static void expr(Expression_value *result, const char **s) static struct { const char *name; + size_t length; enum func_type type; } function_table[]= { // Numeric functions - {"abs", FUNC_ABS}, - {"bin", FUNC_BIN}, - {"conv", FUNC_CONV}, - {"hex", FUNC_HEX}, - {"oct", FUNC_OCT}, + {STRING_WITH_LEN("abs"), FUNC_ABS}, + {STRING_WITH_LEN("bin"), FUNC_BIN}, + {STRING_WITH_LEN("conv"), FUNC_CONV}, + {STRING_WITH_LEN("hex"), FUNC_HEX}, + {STRING_WITH_LEN("oct"), FUNC_OCT}, // String functions - {"concat", FUNC_CONCAT}, - {"concat_ws", FUNC_CONCAT_WS}, - {"greatest", FUNC_GREATEST}, - {"insert", FUNC_INSERT}, - {"instr", FUNC_INSTR}, - {"lcase", FUNC_LOWER}, - {"least", FUNC_LEAST}, - {"length", FUNC_LENGTH}, - {"locate", FUNC_LOCATE}, - {"lower", FUNC_LOWER}, - {"lpad", FUNC_LPAD}, - {"ltrim", FUNC_LTRIM}, - {"repeat", FUNC_REPEAT}, - {"replace", FUNC_REPLACE}, - {"reverse", FUNC_REVERSE}, - {"rpad", FUNC_RPAD}, - {"rtrim", FUNC_RTRIM}, - {"substr", FUNC_SUBSTR}, - {"substring", FUNC_SUBSTR}, - {"substring_index", FUNC_SUBSTR_IDX}, - {"trim", FUNC_TRIM}, - {"ucase", FUNC_UPPER}, - {"upper", FUNC_UPPER}, + {STRING_WITH_LEN("concat"), FUNC_CONCAT}, + {STRING_WITH_LEN("concat_ws"), FUNC_CONCAT_WS}, + {STRING_WITH_LEN("greatest"), FUNC_GREATEST}, + {STRING_WITH_LEN("insert"), FUNC_INSERT}, + {STRING_WITH_LEN("instr"), FUNC_INSTR}, + {STRING_WITH_LEN("lcase"), FUNC_LOWER}, + {STRING_WITH_LEN("least"), FUNC_LEAST}, + {STRING_WITH_LEN("length"), FUNC_LENGTH}, + {STRING_WITH_LEN("locate"), FUNC_LOCATE}, + {STRING_WITH_LEN("lower"), FUNC_LOWER}, + {STRING_WITH_LEN("lpad"), FUNC_LPAD}, + {STRING_WITH_LEN("ltrim"), FUNC_LTRIM}, + {STRING_WITH_LEN("repeat"), FUNC_REPEAT}, + {STRING_WITH_LEN("replace"), FUNC_REPLACE}, + {STRING_WITH_LEN("reverse"), FUNC_REVERSE}, + {STRING_WITH_LEN("rpad"), FUNC_RPAD}, + {STRING_WITH_LEN("rtrim"), FUNC_RTRIM}, + {STRING_WITH_LEN("substr"), FUNC_SUBSTR}, + {STRING_WITH_LEN("substring"), FUNC_SUBSTR}, + {STRING_WITH_LEN("substring_index"), FUNC_SUBSTR_IDX}, + {STRING_WITH_LEN("trim"), FUNC_TRIM}, + {STRING_WITH_LEN("ucase"), FUNC_UPPER}, + {STRING_WITH_LEN("upper"), FUNC_UPPER}, // Regexp functions - {"regexp_instr", FUNC_REGEXP_INSTR}, - {"regexp_replace", FUNC_REGEXP_REPLACE}, - {"regexp_substr", FUNC_REGEXP_SUBSTR}, + {STRING_WITH_LEN("regexp_instr"), FUNC_REGEXP_INSTR}, + {STRING_WITH_LEN("regexp_replace"), FUNC_REGEXP_REPLACE}, + {STRING_WITH_LEN("regexp_substr"), FUNC_REGEXP_SUBSTR}, // Null functions - {"coalesce", FUNC_COALESCE}, - {"ifnull", FUNC_IFNULL}, - {"nullif", FUNC_NULLIF}, - {NULL, FUNC_UNKNOWN} + {STRING_WITH_LEN("coalesce"), FUNC_COALESCE}, + {STRING_WITH_LEN("ifnull"), FUNC_IFNULL}, + {STRING_WITH_LEN("nullif"), FUNC_NULLIF}, + {NULL, 0, FUNC_UNKNOWN} }; @@ -6365,9 +6381,9 @@ void func_oct(Expression_value args[], int count, Expression_value *result) @param[out] result Expression_value to store result @details - Converts a number to hexadecimal representation. HEX(N) returns a string representation of the hexadecimal value of N. This is equivalent to CONV(N, 10, 16). + HEX(str) returns a hexadecimal representation of the bytes of str. @note Dies if argument count != 1 */ @@ -6377,7 +6393,15 @@ void func_hex(Expression_value args[], int count, Expression_value *result) if (count != 1) die("hex() expects 1 argument, got %d", count); - convert_base_helper(args[0].to_string(), 10, 16, result); + if (args[0].is_numeric) + convert_base_helper(args[0].to_string(), 10, 16, result); + else + { + My_string str= args[0].to_string(); + My_string hex_str; + hex_str.set_hex(str.ptr(), str.length()); + result->set_string(hex_str.ptr(), hex_str.length()); + } } @@ -7641,7 +7665,8 @@ enum func_type get_expr_function_type(const char *name, size_t len) { for (int i= 0; function_table[i].name; ++i) { - if (!strncasecmp(function_table[i].name, name, len)) + if (function_table[i].length == len && + !strncasecmp(function_table[i].name, name, len)) return function_table[i].type; } return FUNC_UNKNOWN; @@ -9743,7 +9768,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", delimiter, cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if ((c == '{' && (!my_strnncoll_simple(charset_info, (const uchar*) "while", 5, @@ -9756,7 +9781,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '\'' || c == '"' || c == '`') { @@ -9790,7 +9815,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found newline in comment at line: %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } break; @@ -9810,7 +9835,7 @@ int read_line() DBUG_PRINT("info", ("Found two new lines in a row")); *p++= c; *p= 0; - DBUG_RETURN(0); + goto ret; } /* Query hasn't started yet */ @@ -9827,7 +9852,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", delimiter, cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '}') { @@ -9836,7 +9861,7 @@ int read_line() *p= 0; DBUG_PRINT("exit", ("Found '}' in beginning of a line at line: %d", cur_file->lineno)); - DBUG_RETURN(0); + goto ret; } else if (c == '\'' || c == '"' || c == '`') { @@ -9895,6 +9920,8 @@ int read_line() } } } +ret: + read_command_buf_end= p; DBUG_RETURN(0); } @@ -9910,14 +9937,13 @@ int read_line() */ -void convert_to_format_v1(char* query) +void convert_to_format_v1(char* query, char **end) { int last_c_was_quote= 0; char *p= query, *to= query; - char *end= strend(query); char last_c; - while (p <= end) + while (p <= *end) { if (*p == '\n' && !last_c_was_quote) { @@ -9948,6 +9974,7 @@ void convert_to_format_v1(char* query) last_c_was_quote= 0; } } + *end= to; } @@ -10064,9 +10091,9 @@ int read_command(struct st_command** command_ptr) } if (opt_result_format_version == 1) - convert_to_format_v1(read_command_buf); + convert_to_format_v1(read_command_buf, &read_command_buf_end); - char *p= read_command_buf; + char *p= read_command_buf, *end= read_command_buf_end; DBUG_PRINT("info", ("query: '%s'", read_command_buf)); if (*p == '#') { @@ -10083,29 +10110,32 @@ int read_command(struct st_command** command_ptr) } /* Skip leading spaces */ - while (*p && my_isspace(charset_info, *p)) + while (p < end && my_isspace(charset_info, *p)) p++; - if (!(command->query_buf= command->query= my_strdup(PSI_NOT_INSTRUMENTED, p, MYF(MY_WME)))) + if (!(command->query_buf= command->query= + (char*)my_memdup(PSI_NOT_INSTRUMENTED, p, end - p, MYF(MY_WME)))) die("Out of memory"); + command->query_len= (int)(end - p - 1); + command->end= command->query + command->query_len; + p= command->query; + end= command->end; /* Calculate first word length(the command), terminated - by 'space' , '(' or 'delimiter' */ - p= command->query; - while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) + by 'space' , '(' or 'delimiter' + */ + while (p < end && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) p++; command->first_word_len= (uint) (p - command->query); DBUG_PRINT("info", ("first_word: %.*s", command->first_word_len, command->query)); /* Skip spaces between command and first argument */ - while (*p && my_isspace(charset_info, *p)) + while (p < end && my_isspace(charset_info, *p)) p++; command->first_argument= p; - command->end= strend(command->query); - command->query_len= (int)(command->end - command->query); parser.read_lines++; DBUG_RETURN(0); } @@ -12403,7 +12433,7 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) else { query = command->query; - query_len = strlen(query); + query_len = command->query_len; } /* @@ -12793,6 +12823,7 @@ void get_command_type(struct st_command* command) if (type == Q_QUERY) { /* Skip the "query" part */ + command->query_len-= (int)(command->first_argument - command->query); command->query= command->first_argument; } } @@ -13353,7 +13384,8 @@ int main(int argc, char **argv) if (command->query == command->query_buf) { /* Skip the first part of command, i.e query_xxx */ - command->query= command->first_argument; + command->query_len-= (int)(command->first_argument - command->query); + command->query= command->first_argument; command->first_word_len= 0; } /* fall through */ @@ -13415,7 +13447,10 @@ int main(int argc, char **argv) /* Remove "send" if this is first iteration */ if (command->query == command->query_buf) + { + command->query_len-= (int)(command->first_argument - command->query); command->query= command->first_argument; + } /* run_query() can execute a query partially, depending on the flags. diff --git a/cmake/cpack_rpm.cmake b/cmake/cpack_rpm.cmake index e27c06534c6..b72a29a16b9 100644 --- a/cmake/cpack_rpm.cmake +++ b/cmake/cpack_rpm.cmake @@ -263,7 +263,7 @@ IF(WITH_WSREP) SETA(CPACK_RPM_server_PACKAGE_REQUIRES "galera-4" "rsync" "grep" "gawk" "iproute" "coreutils" "findutils" "tar") - SETA(CPACK_RPM_server_PACKAGE_RECOMMENDS "lsof" "socat" "pv") + SETA(CPACK_RPM_server_PACKAGE_RECOMMENDS "lsof" "socat" "pv" "stunnel") SETA(CPACK_RPM_test_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES}" "socat") ENDIF() diff --git a/cmake/install_layout.cmake b/cmake/install_layout.cmake index 55e960a3c15..a1850732faf 100644 --- a/cmake/install_layout.cmake +++ b/cmake/install_layout.cmake @@ -64,6 +64,9 @@ # # - INSTALL_UNIX_ADDRDIR (path to mysql.sock) # +# - INSTALL_RUNDIR (runtime state files that mariadbd must not write, +# e.g. what systemd reads as EnvironmentFile) +# IF(NOT INSTALL_LAYOUT) IF(DEB) @@ -163,7 +166,7 @@ SET(INSTALL_UNIX_ADDRDIR_RPM "${INSTALL_MYSQLDATADIR_RPM}/mysql.sock" SET(INSTALL_SYSTEMD_UNITDIR_RPM "/usr/lib/systemd/system") SET(INSTALL_SYSTEMD_SYSUSERSDIR_RPM "/usr/lib/sysusers.d") SET(INSTALL_SYSTEMD_TMPFILESDIR_RPM "/usr/lib/tmpfiles.d") -SET(INSTALL_RUNDATADIR_RPM "/run/mariadb") +SET(INSTALL_RUNDIR_RPM "/run") SET(INSTALL_PAMDIR_RPM "/${INSTALL_LIBDIR_RPM}/security") SET(INSTALL_PAMDATADIR_RPM "/etc/security") @@ -194,8 +197,8 @@ SET(INSTALL_SUPPORTFILESDIR_DEB "share/mariadb") # SET(INSTALL_MYSQLDATADIR_DEB "/var/lib/mysql") -SET(INSTALL_RUNDATADIR_DEB "/run/mysqld") -SET(INSTALL_UNIX_ADDRDIR_DEB "${INSTALL_RUNDATADIR_DEB}/mysqld.sock") +SET(INSTALL_RUNDIR_DEB "/run") +SET(INSTALL_UNIX_ADDRDIR_DEB "${INSTALL_RUNDIR_DEB}/mysqld/mysqld.sock") SET(INSTALL_SYSTEMD_UNITDIR_DEB "/lib/systemd/system") SET(INSTALL_SYSTEMD_SYSUSERSDIR_DEB "/usr/lib/sysusers.d") SET(INSTALL_SYSTEMD_TMPFILESDIR_DEB "/usr/lib/tmpfiles.d") @@ -259,7 +262,10 @@ IF(NOT MYSQL_UNIX_ADDR) SET(MYSQL_UNIX_ADDR ${INSTALL_UNIX_ADDRDIR}) ENDIF() -IF(NOT INSTALL_RUNDATADIR) +IF(NOT INSTALL_RUNDIR) + # No /run in a tarball installation - fall back to the socket directory. + # Note that mariadbd can write there, so the isolation that /run gives to + # packages (see EnvironmentFile= in mariadb.service) is not achieved. get_filename_component(MYSQL_UNIX_DIR ${MYSQL_UNIX_ADDR} DIRECTORY) - SET(INSTALL_RUNDATADIR "${MYSQL_UNIX_DIR}" CACHE FILEPATH "Rundata installation directory" ${FORCE}) + SET(INSTALL_RUNDIR "${MYSQL_UNIX_DIR}" CACHE FILEPATH "Runtime state directory" ${FORCE}) ENDIF() diff --git a/config.h.cmake b/config.h.cmake index e0c65c3e3a7..193fcf83fb9 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. - + Copyright (c) 2026, MariaDB plc. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. @@ -183,6 +184,7 @@ #cmakedefine HAVE_MMAP 1 #cmakedefine HAVE_MMAP64 1 #cmakedefine HAVE_MPROTECT 1 +#cmakedefine HAVE_RO_AFTER_INIT 1 #cmakedefine HAVE_PERROR 1 #cmakedefine HAVE_POLL 1 #cmakedefine HAVE_POSIX_FALLOCATE 1 diff --git a/configure.cmake b/configure.cmake index 61f30a7506f..93b87f3a5dd 100644 --- a/configure.cmake +++ b/configure.cmake @@ -1,5 +1,6 @@ # Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. -# +# Copyright (c) 2026, MariaDB plc. +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. @@ -369,6 +370,24 @@ CHECK_FUNCTION_EXISTS (mmap HAVE_MMAP) CHECK_FUNCTION_EXISTS (mmap64 HAVE_MMAP64) CHECK_FUNCTION_EXISTS (mprotect HAVE_MPROTECT) CHECK_FUNCTION_EXISTS (perror HAVE_PERROR) + +IF(HAVE_SYS_MMAN_H AND HAVE_MPROTECT) + SET(SAVE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + SET(CMAKE_REQUIRED_FLAGS + "${CMAKE_REQUIRED_FLAGS} -Wl,-T,${CMAKE_SOURCE_DIR}/sql/mysqld_ro.lds") + CHECK_C_SOURCE_COMPILES(" + #include + __attribute__((section(\"ro_after_init\"))) int hardened= 1; + extern char __ro_after_init_start[] __attribute__((weak)); + extern char __ro_after_init_end[] __attribute__((weak)); + int main(void) { + size_t size= __ro_after_init_end - __ro_after_init_start; + mprotect(__ro_after_init_start, size, PROT_READ | PROT_WRITE); + return (int)size + hardened; + }" HAVE_RO_AFTER_INIT) + SET(CMAKE_REQUIRED_FLAGS "${SAVE_CMAKE_REQUIRED_FLAGS}") +ENDIF() + CHECK_FUNCTION_EXISTS (poll HAVE_POLL) CHECK_FUNCTION_EXISTS (posix_fallocate HAVE_POSIX_FALLOCATE) CHECK_FUNCTION_EXISTS (pread HAVE_PREAD) diff --git a/debian/control b/debian/control index 947d936991a..9edf123ea47 100644 --- a/debian/control +++ b/debian/control @@ -757,7 +757,8 @@ Suggests: mariadb-test, netcat-openbsd Recommends: libhtml-template-perl, mariadb-server-compat, - pv + pv, + stunnel Pre-Depends: adduser (>= 3.40), debconf, mariadb-common (>= ${source:Version}) diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc index 4697602bc22..e6ea310bd84 100644 --- a/extra/mariabackup/backup_mysql.cc +++ b/extra/mariabackup/backup_mysql.cc @@ -88,6 +88,25 @@ static mysql_cond_t kill_query_thread_stop; bool sql_thread_started = false; char *mysql_slave_position = NULL; char *mysql_binlog_position = NULL; +/* + MDEV-38147: the exact binary log file name that + write_current_binlog_file() rotated to and shipped into the backup under + --galera-info. Remembered here so that write_binlog_info() records the very + same file name in xtrabackup_binlog_info, i.e. the file the SST joiner looks + for is guaranteed to be the file that was actually sent (no rotation race). +*/ +char *mysql_binlog_file = NULL; +/* + Position and GTID of that shipped binary log, captured together with + mysql_binlog_file at flush time (see write_current_binlog_file()). Used by + write_binlog_info() so that, if a rotation intervenes, the whole coordinate + tuple it records stays consistent with the file that was actually shipped - + which matters for a manual --galera-info backup used as a replication seed, + not only for the SST joiner (which reads just the file name). +*/ +static char *mysql_binlog_file_pos = NULL; +static char *mysql_binlog_file_gtid_executed = NULL; +static char *mysql_binlog_file_gtid_current_pos = NULL; char *buffer_pool_filename = NULL; /* History on server */ @@ -348,6 +367,30 @@ read_mysql_one_value(MYSQL *mysql, const char *query) return read_mysql_one_value(mysql, query, 0/*offset*/, 1/*total columns*/); } +/** Poll InnoDB's durably flushed redo LSN from the running server. +Log copier uses this as its parse limit; it never accepts a redo log +record whose end exceeds this LSN +@param connection MariaDB client connection +@retval 0 on failure, lsn on success */ +uint64_t get_log_flushed_lsn(MYSQL *connection) noexcept +{ + if (!connection) + return 0; + + uint64_t lsn= 0; + MYSQL_RES *res= xb_mysql_query(connection, + "SHOW STATUS LIKE 'Innodb_lsn_flushed'", + true, false); + if (!res) + return 0; + + if (MYSQL_ROW row= mysql_fetch_row(res)) + if (row[1]) + lsn= strtoull(row[1], nullptr, 10); + mysql_free_result(res); + return lsn; +} + static bool @@ -1506,6 +1549,23 @@ write_galera_info(ds_ctxt *datasink, MYSQL *connection) domain_id ? domain_id : domain_id55); } + /* + MDEV-38147: Flush and copy the donor's current binary log into + the backup so that it is shipped to the SST joiner. + + A new joiner discards this file and starts a fresh binary log seeded from + the storage-engine checkpoint (see wsrep_seed_binlog_gtid_state() in + sql/log.cc and the joiner code in scripts/wsrep_sst_mariabackup.sh), which + avoids error 1950 with gtid_strict_mode=ON. The file is still shipped for + backward compatibility with an old joiner that expects it, and so that a + new joiner can deterministically identify and remove exactly the file that + was sent instead of colliding with it. + + write_current_binlog_file() remembers the rotated file name in + mysql_binlog_file so that write_binlog_info() records the same file in + xtrabackup_binlog_info - closing the old race where a concurrent rotation + could make the shipped file and the recorded file diverge. + */ if (result) write_current_binlog_file(datasink, connection); @@ -1526,13 +1586,25 @@ cleanup: /*********************************************************************//** Flush and copy the current binary log file into the backup, -if GTID is enabled */ +if GTID is enabled. + +MDEV-38147: the file name that FLUSH BINARY LOGS rotates to is +remembered in the global mysql_binlog_file. write_binlog_info() then records +that exact name in xtrabackup_binlog_info, so the file the SST joiner looks +for is guaranteed to be the file that was shipped. Previously the shipped file +(determined here) and the recorded file (determined independently later by +write_binlog_info()) were read by two separate SHOW MASTER STATUS calls; a +binary log rotation happening in between made them diverge and the wrong file +was sent. */ bool write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) { char *executed_gtid_set = NULL; char *gtid_binlog_state = NULL; + char *gtid_current_pos = NULL; char *log_bin_file = NULL; + char *log_bin_pos = NULL; + char *log_bin_gtid_executed = NULL; char *log_bin_dir = NULL; bool gtid_exists; bool result = true; @@ -1543,13 +1615,18 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) {NULL, NULL} }; + /* Capture the shipped file's coordinates together, right after the flush, + so they describe exactly the file that is copied below. */ mysql_variable status_after_flush[] = { {"File", &log_bin_file}, + {"Position", &log_bin_pos}, + {"Executed_Gtid_Set", &log_bin_gtid_executed}, {NULL, NULL} }; mysql_variable vars[] = { {"gtid_binlog_state", >id_binlog_state}, + {"gtid_current_pos", >id_current_pos}, {"log_bin_basename", &log_bin_dir}, {NULL, NULL} }; @@ -1579,6 +1656,32 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) log_bin_dir = strdup("./"); } + if (log_bin_dir == NULL || log_bin_file == NULL) { + msg("Failed to get master binlog coordinates from " + "SHOW MASTER STATUS"); + result = false; + goto cleanup; + } + + /* + Remember the file we just rotated to (before any further + rotation can happen), together with its position and GTID, so + that write_binlog_info() records this very file - and a matching + position/GTID - in xtrabackup_binlog_info, and the joiner looks + for exactly the file that is shipped below. + */ + free(mysql_binlog_file); + mysql_binlog_file = strdup(log_bin_file); + free(mysql_binlog_file_pos); + mysql_binlog_file_pos = + log_bin_pos ? strdup(log_bin_pos) : NULL; + free(mysql_binlog_file_gtid_executed); + mysql_binlog_file_gtid_executed = + log_bin_gtid_executed ? strdup(log_bin_gtid_executed) : NULL; + free(mysql_binlog_file_gtid_current_pos); + mysql_binlog_file_gtid_current_pos = + gtid_current_pos ? strdup(gtid_current_pos) : NULL; + dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length); /* strip final slash if it is not the only path component */ @@ -1587,13 +1690,6 @@ write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection) log_bin_dir[log_bin_dir_length - 1] = 0; } - if (log_bin_dir == NULL || log_bin_file == NULL) { - msg("Failed to get master binlog coordinates from " - "SHOW MASTER STATUS"); - result = false; - goto cleanup; - } - snprintf(filepath, sizeof(filepath), "%s%c%s", log_bin_dir, FN_LIBCHAR, log_bin_file); result = datasink->copy_file(filepath, log_bin_file, 0); @@ -1615,11 +1711,15 @@ bool write_binlog_info(ds_ctxt *datasink, MYSQL *connection) { char *filename = NULL; + const char *out_filename; + const char *out_position; + const char *out_gtid_executed; + const char *out_gtid_current_pos; char *position = NULL; char *gtid_mode = NULL; char *gtid_current_pos = NULL; char *gtid_executed = NULL; - char *gtid = NULL; + const char *gtid = NULL; bool result; bool mysql_gtid; bool mariadb_gtid; @@ -1647,25 +1747,53 @@ write_binlog_info(ds_ctxt *datasink, MYSQL *connection) goto cleanup; } - mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0)); - mariadb_gtid = (gtid_current_pos != NULL); + /* + MDEV-38147: if write_current_binlog_file() already rotated + and shipped a binary log under --galera-info, record that exact file + name here rather than whatever SHOW MASTER STATUS reports now. The two + are normally identical, but a binary log rotation between the two + SHOW MASTER STATUS calls would otherwise make mariadb_backup_binlog_info + name a file different from the one that was shipped, so the SST joiner + would look for a file that is not there. Use a separate pointer so the + string owned by the status[] array is still freed at cleanup. + */ + out_filename = filename; + out_position = position; + out_gtid_executed = gtid_executed; + out_gtid_current_pos = gtid_current_pos; + if (mysql_binlog_file != NULL && strcmp(filename, mysql_binlog_file)) { + msg("Binary log rotated to '%s' after '%s' was shipped; recording " + "the shipped file and its coordinates in " + MB_BINLOG_INFO, filename, mysql_binlog_file); + /* Record the whole tuple from the shipped file, not just its name, + so file/position/GTID stay consistent (the position and GTID from + the SHOW MASTER STATUS above belong to the current, newer file). */ + out_filename = mysql_binlog_file; + out_position = mysql_binlog_file_pos; + out_gtid_executed = mysql_binlog_file_gtid_executed; + out_gtid_current_pos = mysql_binlog_file_gtid_current_pos; + } - gtid = (gtid_executed != NULL ? gtid_executed : gtid_current_pos); + mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0)); + mariadb_gtid = (out_gtid_current_pos != NULL); + + gtid = (out_gtid_executed != NULL ? out_gtid_executed + : out_gtid_current_pos); if (mariadb_gtid || mysql_gtid) { ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s', " "GTID of the last change '%s'", - filename, position, gtid) != -1); + out_filename, out_position, gtid) != -1); result = datasink->backup_file_printf(MB_BINLOG_INFO, - "%s\t%s\t%s\n", filename, position, + "%s\t%s\t%s\n", out_filename, out_position, gtid); } else { ut_a(asprintf(&mysql_binlog_position, "filename '%s', position '%s'", - filename, position) != -1); + out_filename, out_position) != -1); result = datasink->backup_file_printf(MB_BINLOG_INFO, - "%s\t%s\n", filename, position); + "%s\t%s\n", out_filename, out_position); } cleanup: @@ -1999,6 +2127,10 @@ backup_cleanup() { free(mysql_slave_position); free(mysql_binlog_position); + free(mysql_binlog_file); + free(mysql_binlog_file_pos); + free(mysql_binlog_file_gtid_executed); + free(mysql_binlog_file_gtid_current_pos); free(buffer_pool_filename); if (mysql_connection) { diff --git a/extra/mariabackup/backup_mysql.h b/extra/mariabackup/backup_mysql.h index 55700dddf6d..5bdd34c7160 100644 --- a/extra/mariabackup/backup_mysql.h +++ b/extra/mariabackup/backup_mysql.h @@ -2,6 +2,7 @@ #define XTRABACKUP_BACKUP_MYSQL_H #include +#include #include #include #include "datasink.h" @@ -26,6 +27,7 @@ extern time_t history_lock_time; extern bool sql_thread_started; extern char *mysql_slave_position; extern char *mysql_binlog_position; +extern char *mysql_binlog_file; extern char *buffer_pool_filename; /** connection to mysql server */ @@ -97,4 +99,6 @@ bool write_slave_info(ds_ctxt *datasink, MYSQL *connection); ulonglong get_current_lsn(MYSQL *connection); + +uint64_t get_log_flushed_lsn(MYSQL *connection) noexcept; #endif diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index faef13cfadf..d8c959185e2 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -205,7 +205,7 @@ struct xb_filter_entry_t{ /** whether log_copying_thread() is active; protected by recv_sys.mutex */ static bool log_copying_running; /** the log parsing function for --backup */ -static recv_sys_t::parser backup_log_parse; +static recv_sys_t::parser backup_log_parse_low; /** for --backup, target LSN to copy the log to; protected by recv_sys.mutex */ lsn_t metadata_to_lsn; @@ -251,6 +251,7 @@ const char *defaults_group = "mysqld"; #define HA_INNOBASE_ROWS_IN_TABLE 10000 /* to get optimization right */ #define HA_INNOBASE_RANGE_COUNT 100 +#define METADATA_LSN_ERROR 1 /* The default values for the following, type long or longlong, start-up parameters are declared in mysqld.cc: */ @@ -3464,6 +3465,41 @@ skip: return(FALSE); } +/* Maximum LSN the copier may parse up to. +Usually derived from the server's durably-flushed redo LSN. +When a final target LSN is set, this limit is raised to that +target so the last partial block can be copied in full. +The copier refuses to accept a mini-transaction whose end +exceeds this, so it never parses the volatile tail block +the server is still rewriting +(which mmap can read torn, leading to a mis-computed mtr +length and permanent mid-mtr drift). A pread() of the same +still-being-written tail block can likewise return a torn read +which can be observed on ext4, though not on XFS). +Read/written only under recv_sys.mutex. */ +static lsn_t max_parse_lsn; + +/* Set by backup_log_parse() when the last parse was +rejected because the mtr crossed max_parse_lsn. It means +"caught up, wait" condition, not a stall. */ +static bool reached_parse_limit; + +static recv_sys_t::parse_mtr_result backup_log_parse() +{ + const lsn_t prev_lsn= recv_sys.lsn; + const size_t prev_offset= recv_sys.offset; + reached_parse_limit= false; + recv_sys_t::parse_mtr_result r= backup_log_parse_low(false); + if (r == recv_sys_t::OK && max_parse_lsn && recv_sys.lsn > max_parse_lsn) + { + recv_sys.lsn= prev_lsn; + recv_sys.offset= prev_offset; + reached_parse_limit= true; + return recv_sys_t::GOT_EOF; + } + return r; +} + static int xtrabackup_copy_mmap_snippet(ds_file_t *ds, const byte *start, const byte *end) { @@ -3489,7 +3525,7 @@ static bool xtrabackup_copy_mmap_logfile() const byte *start= &log_sys.buf[recv_sys.offset]; ut_d(recv_sys_t::parse_mtr_result r); - if ((ut_d(r=) backup_log_parse(false)) == recv_sys_t::OK) + if ((ut_d(r=) backup_log_parse()) == recv_sys_t::OK) { do { @@ -3507,7 +3543,7 @@ static bool xtrabackup_copy_mmap_logfile() start = seq + 1; } } - while ((ut_d(r=) backup_log_parse(false)) == recv_sys_t::OK); + while ((ut_d(r=) backup_log_parse()) == recv_sys_t::OK); if (xtrabackup_copy_mmap_snippet(dst_log_file, start, &log_sys.buf[recv_sys.offset])) @@ -3580,7 +3616,7 @@ static bool xtrabackup_copy_logfile(bool early_exit) if (log_sys.buf[recv_sys.offset] <= 1) break; - if (backup_log_parse(false) == recv_sys_t::OK) + if (backup_log_parse() == recv_sys_t::OK) { do { @@ -3590,7 +3626,7 @@ static bool xtrabackup_copy_logfile(bool early_exit) sequence_offset)); *seq= 1; } - while ((r= backup_log_parse(false)) == recv_sys_t::OK); + while ((r= backup_log_parse()) == recv_sys_t::OK); if (ds_write(dst_log_file, log_sys.buf + start_offset, recv_sys.offset - start_offset)) @@ -3620,6 +3656,9 @@ static bool xtrabackup_copy_logfile(bool early_exit) if (retry_count == 100) break; + if (reached_parse_limit) + break; + mysql_mutex_unlock(&recv_sys.mutex); if (!retry_count++) msg("Retrying read of log at LSN=" LSN_PF, recv_sys.lsn); @@ -3634,13 +3673,32 @@ static bool xtrabackup_copy_logfile(bool early_exit) return false; } +/** Handles backup log copy timeout or incomplete LSN copy. +Checks if the target LSN was reached. If not, +logs diagnostic messages advising whether to increase +innodb_log_file_size (log wrapped around) or to check +server/backup configuration mismatches. +@param lsn Target LSN expected by the backup. +@param last_lsn Actual maximum LSN copied so far. +@return true if target LSN was reached, false otherwise. */ static bool backup_wait_timeout(lsn_t lsn, lsn_t last_lsn) { if (last_lsn >= lsn) return true; + + const lsn_t checkpoint_lsn= log_sys.last_checkpoint_lsn.load(); + const lsn_t capacity= log_sys.file_size - log_sys.START_OFFSET; + const lsn_t needed= lsn - checkpoint_lsn; + msg("Was only able to copy log from " LSN_PF " to " LSN_PF - ", not " LSN_PF "; try increasing innodb_log_file_size", - log_sys.last_checkpoint_lsn.load(), last_lsn, lsn); + ", not " LSN_PF, checkpoint_lsn, last_lsn, lsn); + + if (needed <= capacity) + msg("mariabackup: The required redo still fits within the log " + "capacity, so it has not been overwritten; check whether the " + "server and backup configuration are the same."); + else + msg("mariabackup: Try increasing the innodb_log_file_size."); return false; } @@ -3695,16 +3753,93 @@ static bool backup_wait_for_lsn(lsn_t lsn) static void log_copying_thread() { my_thread_init(); - mysql_mutex_lock(&recv_sys.mutex); - while (!xtrabackup_copy_logfile(false) && - (!metadata_last_lsn || metadata_last_lsn > recv_sys.lsn)) + MYSQL *limit_con= xb_mysql_connect(); + if (!limit_con) { + /* Without this connection we cannot poll the durably-flushed + LSN, so max_parse_lsn could never advance and the copier + would either stall or parse the volatile tail block. + Fail the copy gracefully and let the main thread report + it via backup_wait_timeout(). */ + msg("mariabackup: Error: cannot open a server connection for " + "the log copying thread; the backup will fail."); + mysql_mutex_lock(&recv_sys.mutex); + log_copying_running= false; + pthread_cond_broadcast(&scanned_lsn_cond); + mysql_mutex_unlock(&recv_sys.mutex); + my_thread_end(); + return; + } + + /* + This thread polls Innodb_lsn_flushed via SHOW STATUS on its own connection. + On a Galera donor wsrep_sync_wait may include SHOW, which would make that + poll wait until the node has applied the latest cluster transactions. During + a backup the donor's commit position can legitimately lag (e.g. a transaction + sitting between its binary log write and engine commit), so the poll could + block indefinitely and stall the redo log copier - failing the backup with a + misleading "Was only able to copy log ..." error. The main backup connection + already disables wsrep_sync_wait for the same reason, so do the same here. + */ + if (have_galera_enabled) + xb_mysql_query(limit_con, "SET SESSION wsrep_sync_wait=0", false); + + mysql_mutex_lock(&recv_sys.mutex); + for (;;) + { + /* metadata_last_lsn is set to 1 when error was encountered. + Abort the log copier if an error was signaled */ + if (metadata_last_lsn == METADATA_LSN_ERROR) + break; + /* Refresh the max_parse_lsn before each copy pass */ + const lsn_t final_target= metadata_last_lsn > metadata_to_lsn + ? metadata_last_lsn : metadata_to_lsn; + if (final_target) + /* Final phase (BLOCK_DDL/BLOCK_COMMIT): fence at the exact target + instead of the polled flushed LSN. This is only safe because the + target was derived from get_current_lsn(), which runs + FLUSH ENGINE LOGS and thus makes the redo durable up to at least + final_target; so final_target <= Innodb_lsn_flushed holds here and + the bytes up to it are stable. Do not feed this branch an LSN that + has not been flushed by the server, or the copier will parse the + volatile tail block again (MDEV-39468). */ + max_parse_lsn= final_target; + else + { + mysql_mutex_unlock(&recv_sys.mutex); + lsn_t flushed= get_log_flushed_lsn(limit_con); + mysql_mutex_lock(&recv_sys.mutex); + if (flushed > log_sys.get_first_lsn()) + max_parse_lsn= flushed; + else if (!max_parse_lsn) + { + /* We could not obtain a flushed LSN and none was seeded before, + so the parse limit is unset and the copier would fall back to + parsing the volatile tail block (the MDEV-39468 failure). Warn + once so this is diagnosable rather than a silent stall. */ + static bool warned; + if (!warned) + { + warned= true; + msg("mariabackup: Warning: could not read Innodb_lsn_flushed; " + "the redo log copier has no parse limit."); + } + } + } + + if (xtrabackup_copy_logfile(false)) + break; + if (final_target && final_target <= recv_sys.lsn) + break; + timespec abstime; set_timespec_nsec(abstime, 1000000ULL * xtrabackup_log_copy_interval); mysql_cond_timedwait(&log_copying_stop, &recv_sys.mutex, &abstime); } log_copying_running= false; mysql_mutex_unlock(&recv_sys.mutex); + if (limit_con) + mysql_close(limit_con); my_thread_end(); } @@ -4909,7 +5044,7 @@ static bool backup_wait_for_commit_lsn() else { msg("Error: recv_sys.find_checkpoint() failed."); - metadata_last_lsn= 1; + metadata_last_lsn= METADATA_LSN_ERROR; stop_backup_threads(); mysql_mutex_unlock(&recv_sys.mutex); return false; @@ -5508,7 +5643,7 @@ static bool xtrabackup_backup_func() fail: if (log_copying_running) { mysql_mutex_lock(&recv_sys.mutex); - metadata_last_lsn = 1; + metadata_last_lsn = METADATA_LSN_ERROR; stop_backup_threads(); mysql_mutex_unlock(&recv_sys.mutex); } @@ -5635,9 +5770,15 @@ fail: /* copy log file by current position */ mysql_mutex_lock(&recv_sys.mutex); - backup_log_parse = recv_sys.get_backup_parser(); + backup_log_parse_low = recv_sys.get_backup_parser(); recv_sys.lsn = log_sys.last_checkpoint_lsn; + if (lsn_t flushed= get_log_flushed_lsn(mysql_connection)) + { + if (flushed > log_sys.get_first_lsn()) + max_parse_lsn= flushed; + } + const bool log_copy_failed = xtrabackup_copy_logfile(true); mysql_mutex_unlock(&recv_sys.mutex); diff --git a/include/heap.h b/include/heap.h index 3fac752abd0..4df384d73d8 100644 --- a/include/heap.h +++ b/include/heap.h @@ -30,7 +30,6 @@ extern "C" { #include #include -#include "my_compare.h" #include "my_tree.h" /* defines used by heap-functions */ @@ -109,6 +108,7 @@ typedef struct st_heap_block } HP_BLOCK; struct st_heap_info; /* For reference */ +struct st_HA_KEYSEG; typedef struct st_hp_keydef /* Key definition with open */ { @@ -116,7 +116,7 @@ typedef struct st_hp_keydef /* Key definition with open */ uint keysegs; /* Number of key-segment */ uint length; /* Length of key (automatic) */ uint8 algorithm; /* HASH / BTREE */ - HA_KEYSEG *seg; + struct st_HA_KEYSEG *seg; HP_BLOCK block; /* Where keys are saved */ /* Number of buckets used in hash table. Used only to provide diff --git a/include/json_lib.h b/include/json_lib.h index 9a87ef3722c..62f26dfb905 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -114,6 +114,7 @@ typedef struct st_json_path_t } json_path_t; +__attribute__((nonnull, warn_unused_result)) int json_path_setup(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end); diff --git a/include/my_global.h b/include/my_global.h index 5f4623e5dbe..9a2d80519bb 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001, 2013, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -820,6 +820,7 @@ typedef long long my_ptrdiff_t; #define MY_ALIGN(A,L) (((A) + (L) - 1) & ~((L) - 1)) #define MY_ALIGN_DOWN(A,L) ((A) & ~((L) - 1)) + #define ALIGN_SIZE(A) MY_ALIGN((A),sizeof(double)) #define ALIGN_MAX_UNIT (sizeof(double)) /* Size to make addressable obj. */ @@ -1021,6 +1022,20 @@ typedef ulong myf; /* Type of MyFlags in my_funcs */ #endif #endif +/* + Tag a global variable as "read-only after init". It'll be made read-only + just before the server starts accepting connection. All READ_ONLY sysvars + must be tagged this way. +*/ +#if defined(HAVE_RO_AFTER_INIT) && !defined(EMBEDDED_LIBRARY) +#define READ_ONLY_SYSVAR __attribute__((section("ro_after_init"))) +#elif defined(_MSC_VER) && !defined(EMBEDDED_LIBRARY) +#pragma section("ro_after_init$m", read, write) +#define READ_ONLY_SYSVAR __declspec(allocate("ro_after_init$m")) +#else +#define READ_ONLY_SYSVAR +#endif + #include /* Some helper macros */ diff --git a/include/my_sys.h b/include/my_sys.h index a9bbc5dc38b..1ec5cdf5afa 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -92,7 +92,7 @@ C_MODE_START #define MY_SYNC 4096U /* my_copy(): sync dst file */ #define MY_SYNC_DIR 32768U /* my_create/delete/rename: sync directory */ #define MY_THREAD_SPECIFIC 0x10000U /* my_malloc(): thread specific */ -#define MY_ROOT_USE_MPROTECT 0x20000U /* init_alloc_root: read only segments */ +#define MY_ROOT_USE_VMEM 0x20000U /* init_alloc_root: use my_virtual_mem_commit */ /* Tree that should delete things automatically */ #define MY_TREE_WITH_DELETE 0x40000U diff --git a/include/my_virtual_mem.h b/include/my_virtual_mem.h index b4f26ca979c..689c75d5258 100644 --- a/include/my_virtual_mem.h +++ b/include/my_virtual_mem.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2025, MariaDB +/* Copyright (c) 2025, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,12 +24,13 @@ extern "C" { #endif -#ifdef _WIN32 +enum my_vmem_prot { MY_VMEM_READONLY= 0, MY_VMEM_READWRITE }; + char *my_virtual_mem_reserve(size_t *size); -#endif char *my_virtual_mem_commit(char *ptr, size_t size); void my_virtual_mem_decommit(char *ptr, size_t size); void my_virtual_mem_release(char *ptr, size_t size); +void my_virtual_mem_protect(void *ptr, size_t size, enum my_vmem_prot prot); #ifdef __cplusplus } diff --git a/libmariadb b/libmariadb index df953d6b76d..8153da40fe7 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit df953d6b76d2f68265422cd929889a36405f7716 +Subproject commit 8153da40fe720b1d25db12aa3f993918da15bd3b diff --git a/mysql-test/main/alter_table_combinations.result b/mysql-test/main/alter_table_combinations.result index cbf98f620ef..7eeb97d659d 100644 --- a/mysql-test/main/alter_table_combinations.result +++ b/mysql-test/main/alter_table_combinations.result @@ -321,4 +321,13 @@ drop table t1; # # End of 10.5 tests # +# +# MDEV-31808 Server crash upon altering table with NEXTVAL for default under exclusive lock +# +CREATE SEQUENCE s ENGINE=Aria; +CREATE TABLE t (a INT DEFAULT(NEXTVAL(s)), b INT); +ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=EXCLUSIVE; +DROP TABLE t; +DROP SEQUENCE s; +# End of 10.6 tests set @@default_storage_engine= @save_default_engine; diff --git a/mysql-test/main/alter_table_combinations.test b/mysql-test/main/alter_table_combinations.test index 7c8d7f42096..b208f0e4416 100644 --- a/mysql-test/main/alter_table_combinations.test +++ b/mysql-test/main/alter_table_combinations.test @@ -260,4 +260,18 @@ drop table t1; --echo # End of 10.5 tests --echo # +--echo # +--echo # MDEV-31808 Server crash upon altering table with NEXTVAL for default under exclusive lock +--echo # + +CREATE SEQUENCE s ENGINE=Aria; +CREATE TABLE t (a INT DEFAULT(NEXTVAL(s)), b INT); +ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=EXCLUSIVE; + +# Cleanup +DROP TABLE t; +DROP SEQUENCE s; + +--echo # End of 10.6 tests + set @@default_storage_engine= @save_default_engine; diff --git a/mysql-test/main/binlog_invalid_row_v2_tag.result b/mysql-test/main/binlog_invalid_row_v2_tag.result new file mode 100644 index 00000000000..63f13f71399 --- /dev/null +++ b/mysql-test/main/binlog_invalid_row_v2_tag.result @@ -0,0 +1 @@ +FOUND 3 /[Uu]nknown [Ee]vent/ in invalid_row_v2_tag.sql diff --git a/mysql-test/main/binlog_invalid_row_v2_tag.test b/mysql-test/main/binlog_invalid_row_v2_tag.test new file mode 100644 index 00000000000..283e27ff66a --- /dev/null +++ b/mysql-test/main/binlog_invalid_row_v2_tag.test @@ -0,0 +1,14 @@ +# MDEV-39485 Heap-buffer-overflow upon read in `Rows_log_event` constructor +# +# This binlog file contains a normal Format Description +# Event followed by 3 malformed v2 Write Rows Events: +# 1. A tag followed by no data +# 2. An undersized tagged data followed by an unrecognized tag +# 3. A tag followed by a length longer than the entire event + +--source include/not_embedded.inc + +--let SEARCH_PATTERN= [Uu]nknown [Ee]vent +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/invalid_row_v2_tag.sql +--exec $MYSQL_BINLOG --force-read --verbose std_data/binlog_invalid_row_v2_tag.001 > $SEARCH_FILE +--source include/search_pattern_in_file.inc diff --git a/mysql-test/main/ctype_binary.result b/mysql-test/main/ctype_binary.result index 5218f37a3af..c14ff8d28fb 100644 --- a/mysql-test/main/ctype_binary.result +++ b/mysql-test/main/ctype_binary.result @@ -3255,9 +3255,21 @@ HEX(a) HEX(b) 61 62 61 0061 DROP TABLE t1; -0x00 in the beginning of a value of a non-last ENUM/SET causes an error: +0x00 in the beginning of a value of a non-last ENUM/SET (fixed by MDEV-28498): CREATE TABLE t1 (a ENUM('a',0x0061), b ENUM('b')); -ERROR HY000: Incorrect information in file: 'DIR/t1.frm' +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('a','\0a') DEFAULT NULL, + `b` enum('b') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (1,1); +INSERT INTO t1 VALUES (2,1); +SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; +HEX(a) HEX(b) +61 62 +0061 62 +DROP TABLE t1; # # End of 10.1 tests # diff --git a/mysql-test/main/ctype_binary.test b/mysql-test/main/ctype_binary.test index 9537f09e9b1..a3d97289573 100644 --- a/mysql-test/main/ctype_binary.test +++ b/mysql-test/main/ctype_binary.test @@ -116,10 +116,13 @@ INSERT INTO t1 VALUES (1,2); SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; DROP TABLE t1; ---echo 0x00 in the beginning of a value of a non-last ENUM/SET causes an error: ---replace_regex /'.*t1.frm'/'DIR\/t1.frm'/ ---error ER_NOT_FORM_FILE +--echo 0x00 in the beginning of a value of a non-last ENUM/SET (fixed by MDEV-28498): CREATE TABLE t1 (a ENUM('a',0x0061), b ENUM('b')); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (1,1); +INSERT INTO t1 VALUES (2,1); +SELECT HEX(a), HEX(b) FROM t1 ORDER BY a, b; +DROP TABLE t1; --echo # diff --git a/mysql-test/main/events_processlist.test b/mysql-test/main/events_processlist.test index 9fce24a7731..f4fd045cd44 100644 --- a/mysql-test/main/events_processlist.test +++ b/mysql-test/main/events_processlist.test @@ -26,7 +26,8 @@ CREATE EVENT e # Wait until the event worker appears in PROCESSLIST for the current user let $wait_condition= SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO LIKE '%SLEEP(10000)%' AND ID <> CONNECTION_ID(); + WHERE INFO LIKE '%SLEEP(10000)%' AND ID <> CONNECTION_ID() + AND STATE='User sleep'; --source include/wait_condition.inc # Check that show processlist works diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index 23e65712f70..2401164d05d 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -2956,6 +2956,133 @@ NULL SELECT JSON_OVERLAPS(JSON_ARRAY_INSERT(NULL,'$[0]',1), '[]') AS x; x NULL +# +# MDEV-32331 JSON path functions with no charset on path crash server +# +SELECT 1 FROM +(SELECT CASE WHEN x * x THEN x END as a +FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 +) dt +WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +x +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +x +NULL +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); +1 +# +# MDEV-40572 SIGSEGV in JSON_OVERLAPS() on a truncated nested JSON object with different character sets/collations +# +SET @col=@@collation_connection; +SET @char=@@character_set_connection; +SET collation_connection=ucs2_general_ci; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_ucs2; +exp_ucs2 +0 +Warnings: +Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_overlaps' +SET character_set_connection=utf16; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_utf16; +exp_utf16 +0 +Warnings: +Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_overlaps' +SET @@collation_connection=@col; +SET @@character_set_connection=@char; +# +# MDEV-40165 JSON_EQUAL/JSON_NORMALIZE/JSON_CONTAINS error handling +# +CREATE TABLE t(c1 JSON, c2 JSON); +INSERT INTO t VALUES ('{"a":1,"b":2}', '{"b":2E0, "a":1E0}'), +(NULL, '["a", "b"]'), +('{"r": 1}', NULL), +('[{"a": 3}, {"d": [1, 2, 3]}]', '[{"d": [1E0, 2E0, 3E0]}, {"a":3E0}]'); +SELECT c1, JSON_EQUALS(c1, '{"r": 1E0}') AS je_c1_const FROM t; +c1 je_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_EQUALS(c1, c2) AS je_c1_c2 FROM t; +c1 c2 je_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 0 +SELECT c1, JSON_CONTAINS(c1, '{"r": 1E0}') AS jc_c1_const FROM t; +c1 jc_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_CONTAINS(c1, c2) AS jc_c1_c2 FROM t; +c1 c2 jc_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 1 +SELECT c1, JSON_OVERLAPS(c1, '{"r": 1E0}') AS jo_c1_const FROM t; +c1 jo_c1_const +{"a":1,"b":2} 0 +NULL NULL +{"r": 1} 1 +[{"a": 3}, {"d": [1, 2, 3]}] 0 +SELECT c1, c2, JSON_OVERLAPS(c1, c2) AS jo_c1_c2 FROM t; +c1 c2 jo_c1_c2 +{"a":1,"b":2} {"b":2E0, "a":1E0} 1 +NULL ["a", "b"] NULL +{"r": 1} NULL NULL +[{"a": 3}, {"d": [1, 2, 3]}] [{"d": [1E0, 2E0, 3E0]}, {"a":3E0}] 1 +DROP TABLE t; # End of 10.11 Test # # MDEV-32007: JSON_VALUE and JSON_EXTRACT doesn't handle dash (-) diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index 5b8fb393afa..e33f41a2450 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -2104,6 +2104,61 @@ SELECT JSON_ARRAY_INSERT (0,NULL,1) as j; SELECT JSON_EQUALS('[]', JSON_ARRAY_INSERT(NULL,'$[0]',1)) AS x; SELECT JSON_OVERLAPS(JSON_ARRAY_INSERT(NULL,'$[0]',1), '[]') AS x; +--echo # +--echo # MDEV-32331 JSON path functions with no charset on path crash server +--echo # + +SELECT 1 FROM + (SELECT CASE WHEN x * x THEN x END as a + FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 + ) dt + WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; + +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); + +--echo # +--echo # MDEV-40572 SIGSEGV in JSON_OVERLAPS() on a truncated nested JSON object with different character sets/collations +--echo # + +SET @col=@@collation_connection; +SET @char=@@character_set_connection; + +SET collation_connection=ucs2_general_ci; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_ucs2; +SET character_set_connection=utf16; +SELECT JSON_OVERLAPS ('{"B":{}}', '{"B":{') AS exp_utf16; + +SET @@collation_connection=@col; +SET @@character_set_connection=@char; + +--echo # +--echo # MDEV-40165 JSON_EQUAL/JSON_NORMALIZE/JSON_CONTAINS error handling +--echo # + +CREATE TABLE t(c1 JSON, c2 JSON); +INSERT INTO t VALUES ('{"a":1,"b":2}', '{"b":2E0, "a":1E0}'), + (NULL, '["a", "b"]'), + ('{"r": 1}', NULL), + ('[{"a": 3}, {"d": [1, 2, 3]}]', '[{"d": [1E0, 2E0, 3E0]}, {"a":3E0}]'); + +SELECT c1, JSON_EQUALS(c1, '{"r": 1E0}') AS je_c1_const FROM t; +SELECT c1, c2, JSON_EQUALS(c1, c2) AS je_c1_c2 FROM t; + +SELECT c1, JSON_CONTAINS(c1, '{"r": 1E0}') AS jc_c1_const FROM t; +SELECT c1, c2, JSON_CONTAINS(c1, c2) AS jc_c1_c2 FROM t; + +SELECT c1, JSON_OVERLAPS(c1, '{"r": 1E0}') AS jo_c1_const FROM t; +SELECT c1, c2, JSON_OVERLAPS(c1, c2) AS jo_c1_c2 FROM t; +DROP TABLE t; + --echo # End of 10.11 Test --echo # diff --git a/mysql-test/main/func_sformat.result b/mysql-test/main/func_sformat.result index 053717ce705..7877ce7913c 100644 --- a/mysql-test/main/func_sformat.result +++ b/mysql-test/main/func_sformat.result @@ -459,13 +459,8 @@ select sformat('{:f}', @a); sformat('{:f}', @a) 3.140000 set names latin1; -# # End of 10.7 tests # -# -# Start of 10.8 tests -# -# # MDEV-29646 sformat('Num [{:20}]', 42) gives incorrect result in view # create view v1 as select sformat('Num [{:20}]', 42); @@ -498,4 +493,36 @@ bar 2 foo 1 drop view v; drop table t; +# +# MDEV-40411 SFORMAT ignores max_allowed_packet +# +set @old_max_allowed_packet=@@global.max_allowed_packet; +set @@global.max_allowed_packet=512*1024; +connect u,localhost,root; +select length(sformat('{:2147483647}', 1)); +length(sformat('{:2147483647}', 1)) +NULL +select length(sformat('{:524288}', 1)); +length(sformat('{:524288}', 1)) +524288 +select length(sformat('{:524289}', 1)); +length(sformat('{:524289}', 1)) +NULL +select length(sformat('{:524287}.', 1)); +length(sformat('{:524287}.', 1)) +524288 +select length(sformat('{:524287}..', 1)); +length(sformat('{:524287}..', 1)) +NULL +disconnect u; +connection default; +set @@global.max_allowed_packet= @old_max_allowed_packet; +set max_session_mem_used=512*1024; +select length(sformat('{:524288}', 1)); +length(sformat('{:524288}', 1)) +524288 +select length(sformat('{:524289}', 1)); +length(sformat('{:524289}', 1)) +NULL +set max_session_mem_used=default; # End of 10.11 tests diff --git a/mysql-test/main/func_sformat.test b/mysql-test/main/func_sformat.test index 9e86615d268..332d5a67027 100644 --- a/mysql-test/main/func_sformat.test +++ b/mysql-test/main/func_sformat.test @@ -236,13 +236,7 @@ set @a=3.14; select sformat('{:f}', @a); set names latin1; -echo #; echo # End of 10.7 tests; -echo #; - -echo #; -echo # Start of 10.8 tests; -echo #; echo #; echo # MDEV-29646 sformat('Num [{:20}]', 42) gives incorrect result in view; @@ -274,4 +268,30 @@ select * from v where a != sformat('{}', 'qux'); drop view v; drop table t; +echo #; +echo # MDEV-40411 SFORMAT ignores max_allowed_packet; +echo #; +set @old_max_allowed_packet=@@global.max_allowed_packet; +set @@global.max_allowed_packet=512*1024; +connect u,localhost,root; +# these three case: fmt allocates the requested size at once +select length(sformat('{:2147483647}', 1)); +select length(sformat('{:524288}', 1)); +select length(sformat('{:524289}', 1)); +# here it allocates 524287 and then grows by *1.5 +select length(sformat('{:524287}.', 1)); +select length(sformat('{:524287}..', 1)); +disconnect u; +connection default; +set @@global.max_allowed_packet= @old_max_allowed_packet; + +disable_cursor_protocol; +disable_view_protocol; +set max_session_mem_used=512*1024; +select length(sformat('{:524288}', 1)); +select length(sformat('{:524289}', 1)); +set max_session_mem_used=default; +enable_view_protocol; +enable_cursor_protocol; + echo # End of 10.11 tests; diff --git a/mysql-test/main/gis-json.result b/mysql-test/main/gis-json.result index 49ec7739458..912fb405327 100644 --- a/mysql-test/main/gis-json.result +++ b/mysql-test/main/gis-json.result @@ -110,6 +110,8 @@ Warning 4076 Incorrect GeoJSON format - empty 'coordinates' array. SELECT ST_GEOMFROMGEOJSON("{ \"type\": \"Feature\", \"geometry\": [10, 20] }") as exp; exp NULL +Warnings: +Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. SELECT ST_ASTEXT (ST_GEOMFROMGEOJSON ('{ "type": "GEOMETRYCOLLECTION", "coordinates": [102.0, 0.0]}')) as exp; exp NULL @@ -135,7 +137,126 @@ repeat(']}', 2000) exp NULL Warnings: -Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 473 +Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 688 +# Boundary condition: foreign-member nesting at depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; +SET @doc := CONCAT( +REPEAT('{"x":', 29), +@geom, +REPEAT('}', 29) +); +SELECT ST_ASTEXT( ST_GeomFromGeoJSON(@doc) ) AS expr; +expr +NULL +Warnings: +Warning 4048 Incorrect GeoJSON format specified for st_geomfromgeojson function. +# Foreign-member nesting exceeds depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; +SET @doc := CONCAT( +REPEAT('{"x":', 30), +@geom, +REPEAT('}', 30) +); +SELECT ST_GeomFromGeoJSON(@doc) AS expr; +expr +NULL +Warnings: +Warning 4040 Limit of 32 on JSON nested structures depth is reached in argument 1 to function 'st_geomfromgeojson' at position 181 +# Deep foreign member attached to otherwise valid GeoJSON +SET @foreign := CONCAT( +REPEAT('{"x":', 28), +'{"leaf":1}', +REPEAT('}', 28) +); +SET @doc := CONCAT( +'{"type":"Point","coordinates":[10,20],"foreign":', +@foreign, +'}' +); +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +POINT(10 20) +# Foreign member exceeds depth limit +SET @foreign := CONCAT( +REPEAT('{"x":', 29), +'{"leaf":1}', +REPEAT('}', 29) +); +SET @doc := CONCAT( +'{"type":"Point","coordinates":[10,20],"foreign":', +@foreign, +'}' +); +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +POINT(10 20) +# GeometryCollection containing 33 valid GeoJSON Point objects +SET @point := '{"type":"Point","coordinates":[1,2]}'; +SET @geoms := CONCAT( +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point, ',', @point, ',', @point, ',', +@point, ',', @point, ',', @point +); +SET @doc := CONCAT( +'{"type":"GeometryCollection","geometries":[', +@geoms, +']}' +); +SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; +expr +GEOMETRYCOLLECTION(POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2),POINT(1 2)) +# +# MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +# +# GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(0 1)) +# GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4)) +# Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')) as expr; +expr +POINT(0 1) +# LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')) as expr; +expr +LINESTRING(0 0,1 1,2 2) +# Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')) as expr; +expr +POLYGON((0 0,1 0,1 1,0 1,0 0)) +# FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"geometry":{"type":"Point","coordinates":[5,6]},"type":"Feature","properties":{}}],"type":"FeatureCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(POINT(5 6)) +# Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"coordinates":[7,8],"type":"Point"},"type":"Feature","properties":{}}')) as expr; +expr +POINT(7 8) +# Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')) as expr; +expr +GEOMETRYCOLLECTION EMPTY +# Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"geometries":[{"type":"Point","coordinates":[9,10]}],"type":"GeometryCollection"}],"type":"GeometryCollection"}')) as expr; +expr +GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(9 10))) # End of 10.6 tests # # MDEV-34079: ST_AsGeoJSON returns incorrect value for empty geometry diff --git a/mysql-test/main/gis-json.test b/mysql-test/main/gis-json.test index ca402d1c2ef..dbbf7b7f667 100644 --- a/mysql-test/main/gis-json.test +++ b/mysql-test/main/gis-json.test @@ -70,6 +70,120 @@ SELECT ST_GeomFromGeoJSON( repeat(']}', 2000) )) as exp; +--echo # Boundary condition: foreign-member nesting at depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; + +SET @doc := CONCAT( + REPEAT('{"x":', 29), + @geom, + REPEAT('}', 29) +); + +SELECT ST_ASTEXT( ST_GeomFromGeoJSON(@doc) ) AS expr; + + +--echo # Foreign-member nesting exceeds depth 32 +SET @geom := '{"type":"Point","coordinates":[1,2]}'; + +SET @doc := CONCAT( + REPEAT('{"x":', 30), + @geom, + REPEAT('}', 30) +); + +SELECT ST_GeomFromGeoJSON(@doc) AS expr; + + +--echo # Deep foreign member attached to otherwise valid GeoJSON +SET @foreign := CONCAT( + REPEAT('{"x":', 28), + '{"leaf":1}', + REPEAT('}', 28) +); + +SET @doc := CONCAT( + '{"type":"Point","coordinates":[10,20],"foreign":', + @foreign, + '}' +); + +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; + + +--echo # Foreign member exceeds depth limit +SET @foreign := CONCAT( + REPEAT('{"x":', 29), + '{"leaf":1}', + REPEAT('}', 29) +); + +SET @doc := CONCAT( + '{"type":"Point","coordinates":[10,20],"foreign":', + @foreign, + '}' +); + +SELECT ST_ASTEXT(ST_GeomFromGeoJSON(@doc)) AS expr; + +--echo # GeometryCollection containing 33 valid GeoJSON Point objects +SET @point := '{"type":"Point","coordinates":[1,2]}'; + +SET @geoms := CONCAT( + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point, ',', @point, ',', @point, ',', + @point, ',', @point, ',', @point +); + +SET @doc := CONCAT( + '{"type":"GeometryCollection","geometries":[', + @geoms, + ']}' +); + +SELECT ST_AsText(ST_GeomFromGeoJSON(@doc)) AS expr; + +--echo # +--echo # MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +--echo # + +--echo # GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')) as expr; + +--echo # GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')) as expr; + +--echo # GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')) as expr; + +--echo # GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')) as expr; + +--echo # Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')) as expr; + +--echo # LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')) as expr; + +--echo # Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')) as expr; + +--echo # FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"geometry":{"type":"Point","coordinates":[5,6]},"type":"Feature","properties":{}}],"type":"FeatureCollection"}')) as expr; + +--echo # Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"coordinates":[7,8],"type":"Point"},"type":"Feature","properties":{}}')) as expr; + +--echo # Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')) as expr; + +--echo # Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"geometries":[{"type":"Point","coordinates":[9,10]}],"type":"GeometryCollection"}],"type":"GeometryCollection"}')) as expr; + --echo # End of 10.6 tests --echo # diff --git a/mysql-test/main/gis-precise.result b/mysql-test/main/gis-precise.result index 7173d1d0c54..d127d016114 100644 --- a/mysql-test/main/gis-precise.result +++ b/mysql-test/main/gis-precise.result @@ -881,6 +881,66 @@ SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) ') a NULL # +# MDEV-40584 ST_CROSSES always returns 0 for geometries of different dimensions +# +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 3, 2 2, 3 2)'), +ST_GeomFromText('LINESTRING(0 3, 1 1, 2 2, 2 0)')) a; +a +1 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 1, 1 3, 2 3)'), +ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +a +1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)'), +ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))')) a; +a +1 +# line x line, crossing (dims 1,1) -> 1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), +ST_GeomFromText('LINESTRING(0 2, 2 0)')) a; +a +1 +# line x line, not crossing (dims 1,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 0)'), +ST_GeomFromText('LINESTRING(0 1, 2 1)')) a; +a +0 +# point x line, disjoint (dims 0,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(10 10)'), +ST_GeomFromText('LINESTRING(0 0, 1 1)')) a; +a +0 +# point x polygon, disjoint (dims 0,2) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(100 100)'), +ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +a +0 +# line x point (g2 is a point) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 1 1)'), +ST_GeomFromText('POINT(5 5)')) a; +a +NULL +# line x multipoint (g2 is a multipoint) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 5 5)'), +ST_GeomFromText('MULTIPOINT(1 1, 2 2)')) a; +a +NULL +# multipolygon x line (g1 is a multipolygon) -> NULL +SELECT ST_Crosses(ST_GeomFromText('MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))'), +ST_GeomFromText('LINESTRING(1 1, 2 2)')) a; +a +NULL +# polygon x line, reversed order of a valid crossing case -> NULL +SELECT ST_Crosses(ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))'), +ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)')) a; +a +NULL +# line x same line, equal (not lower-dim intersection) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), +ST_GeomFromText('LINESTRING(0 0, 2 2)')) a; +a +0 +# # MDEV-31267 Server crash or assertion failure in get_n_sincos with nested ST_BUFFER. # set @geom=ST_BUFFER(ST_BUFFER(ST_POLYGONFROMTEXT('POLYGON((0 0, 5 5, 6 6, 0 0))'),1),2); diff --git a/mysql-test/main/gis-precise.test b/mysql-test/main/gis-precise.test index 533ef8f40a7..341b5de0257 100644 --- a/mysql-test/main/gis-precise.test +++ b/mysql-test/main/gis-precise.test @@ -494,6 +494,45 @@ SELECT ST_CROSSES( ST_GEOMFROMTEXT('point(1 1)'), st_geomfromtext('point(1 1)') SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) '), st_geomfromtext('polygon((2 2,2 4, 4 2,2 2))') ) a; SELECT ST_CROSSES( ST_GEOMFROMTEXT('POLYGON ((59 18,67 18,67 13,59 13,59 18)) '), st_geomfromtext('point(1 1)') ) a; +--echo # +--echo # MDEV-40584 ST_CROSSES always returns 0 for geometries of different dimensions +--echo # + +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 3, 2 2, 3 2)'), + ST_GeomFromText('LINESTRING(0 3, 1 1, 2 2, 2 0)')) a; +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(1 1, 1 3, 2 3)'), + ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)'), + ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))')) a; + +--echo # line x line, crossing (dims 1,1) -> 1 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), + ST_GeomFromText('LINESTRING(0 2, 2 0)')) a; +--echo # line x line, not crossing (dims 1,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 0)'), + ST_GeomFromText('LINESTRING(0 1, 2 1)')) a; +--echo # point x line, disjoint (dims 0,1) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(10 10)'), + ST_GeomFromText('LINESTRING(0 0, 1 1)')) a; +--echo # point x polygon, disjoint (dims 0,2) -> 0 +SELECT ST_Crosses(ST_GeomFromText('MULTIPOINT(100 100)'), + ST_GeomFromText('POLYGON((0 0, 0 3, 2 0, 0 0))')) a; +--echo # line x point (g2 is a point) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 1 1)'), + ST_GeomFromText('POINT(5 5)')) a; +--echo # line x multipoint (g2 is a multipoint) -> NULL +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 5 5)'), + ST_GeomFromText('MULTIPOINT(1 1, 2 2)')) a; +--echo # multipolygon x line (g1 is a multipolygon) -> NULL +SELECT ST_Crosses(ST_GeomFromText('MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))'), + ST_GeomFromText('LINESTRING(1 1, 2 2)')) a; +--echo # polygon x line, reversed order of a valid crossing case -> NULL +SELECT ST_Crosses(ST_GeomFromText('POLYGON((1 0, 1 2, 2 3, 2 1, 1 0))'), + ST_GeomFromText('LINESTRING(0 1, 3 2, 4 1)')) a; +--echo # line x same line, equal (not lower-dim intersection) -> 0 +SELECT ST_Crosses(ST_GeomFromText('LINESTRING(0 0, 2 2)'), + ST_GeomFromText('LINESTRING(0 0, 2 2)')) a; + --echo # --echo # MDEV-31267 Server crash or assertion failure in get_n_sincos with nested ST_BUFFER. --echo # diff --git a/mysql-test/main/gis.result b/mysql-test/main/gis.result index 0fe92b2f50c..d60f8da2d5d 100644 --- a/mysql-test/main/gis.result +++ b/mysql-test/main/gis.result @@ -5644,4 +5644,13 @@ NULL SELECT ST_GEOMFROMWKB (0x01050000000100000082040000000100000000000000000000000000000000000000) as g; g NULL +# +# MDEV-40552 UBSAN : load of value X, which is not a valid value for type 'wkbByteOrder' Gis_multi_point::init_from_wkb +# +SELECT ST_GEOMFROMWKB (0x0104000000010000000201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +multipoint_bad_inner_bo +NULL +SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +multipoint_bad_inner_bo +NULL # End of 10.11 tests diff --git a/mysql-test/main/gis.test b/mysql-test/main/gis.test index 609c1509396..1efed8d0769 100644 --- a/mysql-test/main/gis.test +++ b/mysql-test/main/gis.test @@ -3605,4 +3605,11 @@ SELECT ST_GEOMFROMWKB (0x01070000000100000082010000000000000000000000) as g; SELECT ST_GEOMFROMWKB (0x01050000000100000002040000000100000000000000000000000000000000000000) as g; SELECT ST_GEOMFROMWKB (0x01050000000100000082040000000100000000000000000000000000000000000000) as g; +--echo # +--echo # MDEV-40552 UBSAN : load of value X, which is not a valid value for type 'wkbByteOrder' Gis_multi_point::init_from_wkb +--echo # + +SELECT ST_GEOMFROMWKB (0x0104000000010000000201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; +SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; + --echo # End of 10.11 tests diff --git a/mysql-test/main/grant5.result b/mysql-test/main/grant5.result index 7a6e317d9b1..266c73c346f 100644 --- a/mysql-test/main/grant5.result +++ b/mysql-test/main/grant5.result @@ -497,6 +497,34 @@ CREATE USER for u2@localhost CREATE USER `u2`@`localhost` IDENTIFIED BY PASSWORD '*975B2CD4FF9AE554FE8AD33168FBFC326D2021DD' drop user u1@localhost; drop user u2@localhost; +# +# MDEV-40541 role vs user@localhost acl_cache key confusion +# +create database db; +create table db.t1(a varchar(40)); +insert into db.t1 values ('role-cache-collision-row'); +create role r1; +create user r1@localhost identified by 'r1pass'; +create user u1@'127.0.0.1' identified by 'u1pass'; +grant select on db.* to r1@localhost; +grant r1 to u1@'127.0.0.1'; +connect r1,localhost,r1,r1pass; +select a as precache from db.t1; +precache +role-cache-collision-row +connect u1,127.0.0.1,u1,u1pass; +set role r1; +show grants; +Grants for u1@127.0.0.1 +GRANT `r1` TO `u1`@`127.0.0.1` +GRANT USAGE ON *.* TO `u1`@`127.0.0.1` IDENTIFIED BY PASSWORD '*4C5C0A2C80D7567C7CFBDFC09409901AADC4B390' +GRANT USAGE ON *.* TO `r1` +select a from db.t1; +ERROR 42000: SELECT command denied to user 'u1'@'localhost' for table `db`.`t1` +connection default; +drop database db; +drop role r1; +drop user r1@localhost, u1@'127.0.0.1'; # End of 10.6 tests # # MDEV-37256: The permission check of LOAD INDEX INTO CACHE and CACHE INDEX is broken @@ -519,4 +547,50 @@ disconnect foo; connection default; drop table t1; drop user foo@localhost; +# +# MDEV-23086 Error codes/messages reveal information about table structure +# +create database db; +create table db.t1 (a int, b int, c int); +create user foo@localhost; +grant select (a,b) on db.t1 to foo@localhost; +alter table db.t1 drop column b; +create table db.t2 (x int, y int); +grant select on db.t2 to foo@localhost; +create table t1 (k int, l int, m int); +grant select on test.t1 to foo@localhost; +connect con1,localhost,foo,,db; +select a from t1; +a +select b from t1; +ERROR 42S22: Unknown column 'b' in 'SELECT' +select c from t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select d from t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select a from t0; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for table `db`.`t0` +use db2; +ERROR 42000: Access denied for user 'foo'@'localhost' to database 'db2' +select a from t2; +ERROR 42S22: Unknown column 'a' in 'SELECT' +select c from t2, t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select c from t1, t2; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'c' in table 't1' +select d from t2, t1; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select d from t1, t2; +ERROR 42000: SELECT command denied to user 'foo'@'localhost' for column 'd' in table 't1' +select t2.c from t2,t1; +ERROR 42S22: Unknown column 't2.c' in 'SELECT' +select * from test.t1; +k l m +select test.t1.c from t1,test.t1; +ERROR 42S22: Unknown column 'test.t1.c' in 'SELECT' +connection default; +disconnect con1; +drop user foo@localhost; +drop database db; +drop table t1; # End of 10.11 tests diff --git a/mysql-test/main/grant5.test b/mysql-test/main/grant5.test index 2931388ce40..068a995cf0f 100644 --- a/mysql-test/main/grant5.test +++ b/mysql-test/main/grant5.test @@ -456,6 +456,35 @@ show create user u2@localhost; drop user u1@localhost; drop user u2@localhost; +--echo # +--echo # MDEV-40541 role vs user@localhost acl_cache key confusion +--echo # +create database db; +create table db.t1(a varchar(40)); +insert into db.t1 values ('role-cache-collision-row'); + +create role r1; +create user r1@localhost identified by 'r1pass'; +create user u1@'127.0.0.1' identified by 'u1pass'; + +grant select on db.* to r1@localhost; +grant r1 to u1@'127.0.0.1'; +#flush privileges; + +connect r1,localhost,r1,r1pass; +select a as precache from db.t1; + +connect u1,127.0.0.1,u1,u1pass; +set role r1; +show grants; +--error ER_TABLEACCESS_DENIED_ERROR +select a from db.t1; + +connection default; +drop database db; +drop role r1; +drop user r1@localhost, u1@'127.0.0.1'; + --echo # End of 10.6 tests --echo # @@ -482,4 +511,45 @@ load index into cache test.t2 key (i2); drop table t1; drop user foo@localhost; +--echo # +--echo # MDEV-23086 Error codes/messages reveal information about table structure +--echo # + +create database db; +create table db.t1 (a int, b int, c int); +create user foo@localhost; +grant select (a,b) on db.t1 to foo@localhost; +alter table db.t1 drop column b; + +create table db.t2 (x int, y int); +grant select on db.t2 to foo@localhost; + +create table t1 (k int, l int, m int); +grant select on test.t1 to foo@localhost; + +connect con1,localhost,foo,,db; + +disable_abort_on_error; +select a from t1; # can access, column exist +select b from t1; # can access, column doesn't exist +select c from t1; # no access, column exists +select d from t1; # no access, column doesn't exists +select a from t0; # no access, table doesn't exists +use db2; # no access, db doesn't exists +select a from t2; # table level grant, column doesn't exists +select c from t2, t1; # mix tbl/col grant, no access, column exist +select c from t1, t2; # mix tbl/col grant, no access, column exist +select d from t2, t1; # mix tbl/col grant, no access, column doesn't exists +select d from t1, t2; # mix tbl/col grant, no access, column doesn't exist +select t2.c from t2,t1; # mix tbl/col grant, explicit tbl, column doesn't exist +select * from test.t1; # this should work +select test.t1.c from t1,test.t1; # mix, explicit db name, column doesn't exist +enable_abort_on_error; + +connection default; +disconnect con1; +drop user foo@localhost; +drop database db; +drop table t1; + --echo # End of 10.11 tests diff --git a/mysql-test/main/group_min_max_innodb.result b/mysql-test/main/group_min_max_innodb.result index 44905a80811..ec4610c4090 100644 --- a/mysql-test/main/group_min_max_innodb.result +++ b/mysql-test/main/group_min_max_innodb.result @@ -478,3 +478,57 @@ drop table t1; set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; +# +# Begin 10.11 tests +# +CREATE TABLE t1 (pk INT, a INT, b INT, PRIMARY KEY (pk), KEY (a,b)) ENGINE=InnoDB; +INSERT INTO t1 (pk,a) VALUES (1,11),(2,12),(3,13),(4,14); +connect con1,localhost,root,,; +DELETE FROM t1 WHERE pk <= 3; +connection default; +INSERT INTO t1 (pk) VALUES (5); +connection con1; +START TRANSACTION; +INSERT INTO t1 (pk) VALUES (6); +connection default; +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +EXPLAIN SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 10 NULL # Using index for group-by +SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; +connection con1; +COMMIT; +DROP TABLE t1; +disconnect con1; +connection default; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT, +KEY (a,b,c)) ENGINE=InnoDB; +INSERT INTO t1 (a,b,c) VALUES +(1,1,10),(1,1,11),(1,2,20),(1,2,21), +(2,1,10),(2,1,11),(2,2,20),(2,2,21); +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +connect con1,localhost,root,,; +START TRANSACTION; +INSERT INTO t1 (a,b,c) VALUES (1,2,0); +connection default; +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +EXPLAIN SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 15 NULL # Using where; Using index for group-by +SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; +connection con1; +COMMIT; +DROP TABLE t1; +disconnect con1; +connection default; +# +# End 10.11 tests +# diff --git a/mysql-test/main/group_min_max_innodb.test b/mysql-test/main/group_min_max_innodb.test index 6d2389d22c8..e16de89e987 100644 --- a/mysql-test/main/group_min_max_innodb.test +++ b/mysql-test/main/group_min_max_innodb.test @@ -360,3 +360,93 @@ drop table t1; set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; + +--echo # +--echo # Begin 10.11 tests +--echo # + +# Default client connection to the database. +CREATE TABLE t1 (pk INT, a INT, b INT, PRIMARY KEY (pk), KEY (a,b)) ENGINE=InnoDB; +INSERT INTO t1 (pk,a) VALUES (1,11),(2,12),(3,13),(4,14); + +# Start a new, separate client connection to the database called 'con1'. +--connect (con1,localhost,root,,) +--send +DELETE FROM t1 WHERE pk <= 3; + +# On the default connection, insert a value into the table. +--connection default +INSERT INTO t1 (pk) VALUES (5); + +# On 'con1', insert a value within a transaction and hold the transaction while +# on the default connection try to compute an aggregate. +--connection con1 +--reap + +START TRANSACTION; +INSERT INTO t1 (pk) VALUES (6); + +--connection default +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +# SELECT ... FOR UPDATE locks the rows for write operations and prevents other transactions +# from modifying or reading the selected rows until the transaction ends. +# This should lead to the error, not a crash. +--replace_column 9 # +EXPLAIN SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +--error ER_LOCK_WAIT_TIMEOUT +SELECT MIN(b), MAX(b), a FROM t1 GROUP BY a FOR UPDATE; +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; + +# Cleanup, commit the 'con1' transaction. +--connection con1 +COMMIT; +DROP TABLE t1; +--disconnect con1 + +# This scenario passes with or without the patch. The query requests only a +# MIN, so the max branch never runs. The scenario is kept for coverage, to +# execute the early return, which the patch adds in the min branch for symmetry +# with the max branch and to honor the convention that no further reads follow +# a fatal handler error. +--connection default +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT, + KEY (a,b,c)) ENGINE=InnoDB; + +# Two groups over key part a. Each group has rows with b=1 and rows with +# b=2, with enough rows overall that the optimizer chooses the loose +# index scan ("Using index for group-by") over a full index scan. +INSERT INTO t1 (a,b,c) VALUES + (1,1,10),(1,1,11),(1,2,20),(1,2,21), + (2,1,10),(2,1,11),(2,2,20),(2,2,21); +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; +INSERT INTO t1 (a,b,c) SELECT a,b,c FROM t1; + +--connect (con1,localhost,root,,) +START TRANSACTION; +# This uncommitted row becomes the first row of the a=1, b=2 prefix because its +# c is smaller than every committed c in that subgroup. The MIN read seeks +# exactly this position, so it waits for con1. +INSERT INTO t1 (a,b,c) VALUES (1,2,0); + +--connection default +SET @save_innodb_lock_wait_timeout= @@innodb_lock_wait_timeout; +SET innodb_lock_wait_timeout= 1; +# The MIN read inside the loose index scan lands on the row locked by con1. +--replace_column 9 # +EXPLAIN SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +--error ER_LOCK_WAIT_TIMEOUT +SELECT MIN(c) FROM t1 WHERE b = 2 GROUP BY a FOR UPDATE; +SET innodb_lock_wait_timeout= @save_innodb_lock_wait_timeout; + +# Cleanup, commit the 'con1' transaction. +--connection con1 +COMMIT; +DROP TABLE t1; +--disconnect con1 +--connection default + +--echo # +--echo # End 10.11 tests +--echo # diff --git a/mysql-test/main/intersect_all.result b/mysql-test/main/intersect_all.result index 119691d7699..673e1e22762 100644 --- a/mysql-test/main/intersect_all.result +++ b/mysql-test/main/intersect_all.result @@ -1413,3 +1413,94 @@ a b 1 2 drop tables t1, t2, t3; set sql_mode= default; +# +# MDEV-38722 server crash hp_rec_key_cmp +# +# original reproducer (used to crash), returns a single row +SELECT 1 INTERSECT SELECT 1 UNION ALL SELECT 1 EXCEPT ALL SELECT 1; +1 +1 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT i FROM t1 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3 ORDER BY i; +i +1 +2 +2 +(((SELECT i FROM t1) INTERSECT (SELECT i FROM t3)) UNION ALL (SELECT i FROM t2)) +EXCEPT ALL (SELECT i FROM t3) ORDER BY i; +i +1 +2 +2 +# trailing UNION ALL after the EXCEPT ALL +SELECT i FROM t1 INTERSECT SELECT i FROM t2 UNION ALL SELECT i FROM t2 +EXCEPT ALL SELECT i FROM t3 UNION ALL SELECT i FROM t1 ORDER BY i; +i +1 +1 +1 +2 +2 +2 +2 +3 +((((SELECT i FROM t1) INTERSECT (SELECT i FROM t2)) UNION ALL (SELECT i FROM t2)) +EXCEPT ALL (SELECT i FROM t3)) UNION ALL (SELECT i FROM t1) ORDER BY i; +i +1 +1 +1 +2 +2 +2 +2 +3 +# a leading INTERSECT ALL keeps the operation extended while the last +# INTERSECT DISTINCT node is the index-release point +SELECT i FROM t1 INTERSECT ALL SELECT i FROM t2 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 ORDER BY i; +i +1 +1 +2 +2 +(((SELECT i FROM t1) INTERSECT ALL (SELECT i FROM t2)) INTERSECT (SELECT i FROM t3)) +UNION ALL (SELECT i FROM t2) ORDER BY i; +i +1 +1 +2 +2 +DROP TABLE t1,t2,t3; +# End of 10.6 tests +# +# MDEV-38722 fix merge problem with view/derived +# +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT * FROM (SELECT i FROM t1 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3) d ORDER BY i; +i +1 +2 +2 +CREATE VIEW v1 AS SELECT i FROM t1 INTERSECT SELECT i FROM t3 +UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3; +SELECT * FROM v1 ORDER BY i; +i +1 +2 +2 +DROP VIEW v1; +DROP TABLE t1,t2,t3; +# End of 11.4 tests diff --git a/mysql-test/main/intersect_all.test b/mysql-test/main/intersect_all.test index 6ac8df4be14..45967a40e6d 100644 --- a/mysql-test/main/intersect_all.test +++ b/mysql-test/main/intersect_all.test @@ -499,3 +499,53 @@ select * from t1 except all select * from t2 intersect all select * from t1 unio select * from t1 except all select * from t2 intersect all select * from t1 union all select * from t2 order by a desc limit 3; drop tables t1, t2, t3; set sql_mode= default; + +--echo # +--echo # MDEV-38722 server crash hp_rec_key_cmp +--echo # +--echo # original reproducer (used to crash), returns a single row +SELECT 1 INTERSECT SELECT 1 UNION ALL SELECT 1 EXCEPT ALL SELECT 1; + +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT i FROM t1 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3 ORDER BY i; +(((SELECT i FROM t1) INTERSECT (SELECT i FROM t3)) UNION ALL (SELECT i FROM t2)) + EXCEPT ALL (SELECT i FROM t3) ORDER BY i; +--echo # trailing UNION ALL after the EXCEPT ALL +SELECT i FROM t1 INTERSECT SELECT i FROM t2 UNION ALL SELECT i FROM t2 + EXCEPT ALL SELECT i FROM t3 UNION ALL SELECT i FROM t1 ORDER BY i; +((((SELECT i FROM t1) INTERSECT (SELECT i FROM t2)) UNION ALL (SELECT i FROM t2)) + EXCEPT ALL (SELECT i FROM t3)) UNION ALL (SELECT i FROM t1) ORDER BY i; +--echo # a leading INTERSECT ALL keeps the operation extended while the last +--echo # INTERSECT DISTINCT node is the index-release point +SELECT i FROM t1 INTERSECT ALL SELECT i FROM t2 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 ORDER BY i; +(((SELECT i FROM t1) INTERSECT ALL (SELECT i FROM t2)) INTERSECT (SELECT i FROM t3)) + UNION ALL (SELECT i FROM t2) ORDER BY i; +DROP TABLE t1,t2,t3; + +--echo # End of 10.6 tests + +--echo # +--echo # MDEV-38722 fix merge problem with view/derived +--echo # +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(1),(2),(3); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (1),(2),(2); +CREATE TABLE t3 (i INT); +INSERT INTO t3 VALUES (1),(3); +SELECT * FROM (SELECT i FROM t1 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3) d ORDER BY i; +CREATE VIEW v1 AS SELECT i FROM t1 INTERSECT SELECT i FROM t3 + UNION ALL SELECT i FROM t2 EXCEPT ALL SELECT i FROM t3; +SELECT * FROM v1 ORDER BY i; +DROP VIEW v1; +DROP TABLE t1,t2,t3; + +--echo # End of 11.4 tests diff --git a/mysql-test/main/invisible_field_grant_completely.result b/mysql-test/main/invisible_field_grant_completely.result index ac24aee2fb8..dd5b7fea3be 100644 --- a/mysql-test/main/invisible_field_grant_completely.result +++ b/mysql-test/main/invisible_field_grant_completely.result @@ -39,7 +39,7 @@ a 2 3 select invisible,a from t1; -ERROR 42S22: Unknown column 'invisible' in 'SELECT' +ERROR 42000: SELECT command denied to user 'user_1'@'localhost' for column 'invisible' in table 't1' delete from t1 where a =1; update t1 set a=1 where a=3; select * from t1; diff --git a/mysql-test/main/invisible_field_grant_completely.test b/mysql-test/main/invisible_field_grant_completely.test index b27071ada56..13c6ff4f501 100644 --- a/mysql-test/main/invisible_field_grant_completely.test +++ b/mysql-test/main/invisible_field_grant_completely.test @@ -29,7 +29,7 @@ insert into t1 values(2); select * from t1; insert into t1(a) values(3); select * from t1; ---error ER_BAD_FIELD_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR select invisible,a from t1; delete from t1 where a =1; update t1 set a=1 where a=3; diff --git a/mysql-test/main/kill-2-master.opt b/mysql-test/main/kill-2-master.opt index ab6ca1731f5..94ed5e3778b 100644 --- a/mysql-test/main/kill-2-master.opt +++ b/mysql-test/main/kill-2-master.opt @@ -1 +1 @@ ---skip-name-resolve +--skip-name-resolve --bind-address=::1,127.0.0.1 diff --git a/mysql-test/main/kill-2.result b/mysql-test/main/kill-2.result index 95164fda164..174b8a8b6b5 100644 --- a/mysql-test/main/kill-2.result +++ b/mysql-test/main/kill-2.result @@ -35,8 +35,87 @@ root kill user b; ERROR HY000: Operation KILL USER failed for b@% connection default; +disconnect a; +disconnect b; drop user a@'127.0.0.1'; drop user b@'127.0.0.1'; -# # End of 10.3 tests # +# MDEV-40554 KILL checks user (not priv_user) and doesn't verify hostname +# +create user u1@'::1' identified by 'pw1'; +create user ''@'127.0.0.1'; +connect u1_ipv6,::1,u1,pw1; +select current_user(); +current_user() +u1@::1 +# the reported case: same login name, but an anonymous account +connect u1_anon,127.0.0.1,u1,; +select user(), current_user(); +user() current_user() +u1@127.0.0.1 @127.0.0.1 +# anonymous users don't see the process list at all +show processlist; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +select count(*) as `threads visible` from information_schema.processlist; +threads visible +1 +select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +u1@'::1' visible +0 +kill $cid_sock; +ERROR HY000: You are not owner of thread CID +show explain for $cid_sock; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +user() current_user() +u1@::1 u1@::1 +connection u1_anon; +connection default; +disconnect u1_anon; +drop user ''@'127.0.0.1'; +# same login name, a real account, but a different host +create user u1@'127.0.0.1' identified by 'pw2'; +connect u1_ipv4,127.0.0.1,u1,pw2; +select current_user(); +current_user() +u1@127.0.0.1 +# u1@127.0.0.1 must not see u1@'::1' - same user name, other account +show processlist; +Id User Host db Command Time State Info Progress +# u1 # # Query # # # # +select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +u1@'::1' visible +0 +kill $cid_sock; +ERROR HY000: You are not owner of thread CID +show explain for $cid_sock; +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +user() current_user() +u1@::1 u1@::1 +connection u1_ipv4; +# but another u1@127.0.0.1 connection is the same account, so it may +connect u1_tcp2,127.0.0.1,u1,pw2; +select count(*) as `u1@127.0.0.1 visible` + from information_schema.processlist where id=$cid_tcp; +u1@127.0.0.1 visible +1 +kill $cid_tcp; +# u1@'::1' was not killed +connection u1_ipv6; +select 1; +1 +1 +connection default; +disconnect u1_ipv6; +disconnect u1_tcp2; +drop user u1@'::1'; +drop user u1@'127.0.0.1'; +# End of 10.11 tests diff --git a/mysql-test/main/kill-2.test b/mysql-test/main/kill-2.test index 1fdf693c414..b5fd8fec189 100644 --- a/mysql-test/main/kill-2.test +++ b/mysql-test/main/kill-2.test @@ -6,7 +6,9 @@ # connection does not read() from a socket, but returns control to the # application. 'mysqltest' does not handle the kill request. # --- source include/not_embedded.inc +--source include/not_embedded.inc +--source include/count_sessions.inc +--source include/check_ipv6.inc --echo # --echo # MDEV-6896 kill user command cause MariaDB crash! @@ -50,9 +52,92 @@ evalp select user from information_schema.processlist where id in (connection_id --error ER_KILL_DENIED_ERROR kill user b; --connection default +--disconnect a +--disconnect b drop user a@'127.0.0.1'; drop user b@'127.0.0.1'; ---echo # --echo # End of 10.3 tests + --echo # +--echo # MDEV-40554 KILL checks user (not priv_user) and doesn't verify hostname +--echo # + +create user u1@'::1' identified by 'pw1'; +create user ''@'127.0.0.1'; + +connect u1_ipv6,::1,u1,pw1; +select current_user(); +let $cid_sock=`select connection_id()`; + +--echo # the reported case: same login name, but an anonymous account +connect u1_anon,127.0.0.1,u1,; +select user(), current_user(); + +--echo # anonymous users don't see the process list at all +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +show processlist; +select count(*) as `threads visible` from information_schema.processlist; +evalp select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +--replace_regex /thread [0-9]+/thread CID/ +--error ER_KILL_DENIED_ERROR +evalp kill $cid_sock; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +evalp show explain for $cid_sock; + +# killing a non-matching user doesn't report an error to not disclose +# that the user exists. but doesn't kill either +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +connection u1_anon; + +connection default; +disconnect u1_anon; +drop user ''@'127.0.0.1'; + +--echo # same login name, a real account, but a different host +create user u1@'127.0.0.1' identified by 'pw2'; +connect u1_ipv4,127.0.0.1,u1,pw2; +select current_user(); +let $cid_tcp=`select connection_id()`; + +--echo # u1@127.0.0.1 must not see u1@'::1' - same user name, other account +--replace_result Execute Query +--replace_column 1 # 3 # 4 # 6 # 7 # 8 # 9 # +show processlist; +evalp select count(*) as `u1@'::1' visible` + from information_schema.processlist where id=$cid_sock; +--replace_regex /thread [0-9]+/thread CID/ +--error ER_KILL_DENIED_ERROR +evalp kill $cid_sock; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +evalp show explain for $cid_sock; + +# doesn't report an error, doesn't kill +kill user u1@'::1'; +connection u1_ipv6; +select user(), current_user(); +connection u1_ipv4; + +--echo # but another u1@127.0.0.1 connection is the same account, so it may +connect u1_tcp2,127.0.0.1,u1,pw2; +evalp select count(*) as `u1@127.0.0.1 visible` + from information_schema.processlist where id=$cid_tcp; +evalp kill $cid_tcp; + +--echo # u1@'::1' was not killed +connection u1_ipv6; +select 1; + +connection default; +disconnect u1_ipv6; +#u1_ipv4 was killed, must not be disconnected +disconnect u1_tcp2; +--source include/wait_until_count_sessions.inc + +drop user u1@'::1'; +drop user u1@'127.0.0.1'; + +--echo # End of 10.11 tests diff --git a/mysql-test/main/long_unique_innodb.result b/mysql-test/main/long_unique_innodb.result index fd2b3daf8e5..8c23d61f2bc 100644 --- a/mysql-test/main/long_unique_innodb.result +++ b/mysql-test/main/long_unique_innodb.result @@ -169,4 +169,15 @@ id f 1 foo 2 foo drop table t1; +# +# MDEV-39846 UNIQUE BLOB contains duplicate records after ALTER IGNORE with READ COMMITTED +# +create table t1 (pk int primary key, b blob) engine=innodb; +insert into t1 values (1,'foo'),(2,'foo'); +set transaction isolation level read committed; +alter ignore table t1 add unique (b); +select * from t1; +pk b +1 foo +drop table t1; # End of 10.6 tests diff --git a/mysql-test/main/long_unique_innodb.test b/mysql-test/main/long_unique_innodb.test index cbe2d7431fe..fc51817e45a 100644 --- a/mysql-test/main/long_unique_innodb.test +++ b/mysql-test/main/long_unique_innodb.test @@ -176,4 +176,14 @@ update ignore t1 set id = 2; select * from t1; drop table t1; +--echo # +--echo # MDEV-39846 UNIQUE BLOB contains duplicate records after ALTER IGNORE with READ COMMITTED +--echo # +create table t1 (pk int primary key, b blob) engine=innodb; +insert into t1 values (1,'foo'),(2,'foo'); +set transaction isolation level read committed; +alter ignore table t1 add unique (b); +select * from t1; +drop table t1; + --echo # End of 10.6 tests diff --git a/mysql-test/main/lotofstack.result b/mysql-test/main/lotofstack.result index 6f776eb6733..eb51ccd8e04 100644 --- a/mysql-test/main/lotofstack.result +++ b/mysql-test/main/lotofstack.result @@ -103,4 +103,16 @@ drop table t3| # MDEV-40409 ST_GeomFromText stack overrun on deeply nested GeometryCollection # SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',REPEAT(')',5000))); +# +# MDEV-40540 ST_GeomFromWKB stack overrun on deeply nested GeometryCollection +# +set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); +SELECT ST_GeomFromWKB(@a); +# +# MDEV-39750 ExtractValue does not control recursion depth. +# +SELECT ExtractValue('', REPEAT('(', 100000)); +ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. +SELECT ExtractValue('', REPEAT('/n', 100000)); +ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. # End of 10.6 tests diff --git a/mysql-test/main/lotofstack.test b/mysql-test/main/lotofstack.test index 20d9789b99c..b8ce7f566c4 100644 --- a/mysql-test/main/lotofstack.test +++ b/mysql-test/main/lotofstack.test @@ -147,4 +147,24 @@ delimiter ;| SELECT ST_GeomFromText(CONCAT(REPEAT('GEOMETRYCOLLECTION(',5000),'POINT(1 1)',REPEAT(')',5000))); --enable_result_log +--echo # +--echo # MDEV-40540 ST_GeomFromWKB stack overrun on deeply nested GeometryCollection +--echo # +set @a=concat(0x0107000000, repeat(0x010000000107000000, 120000), 0x00000000); +--disable_result_log +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ST_GeomFromWKB(@a); +--enable_result_log + +--echo # +--echo # MDEV-39750 ExtractValue does not control recursion depth. +--echo # +--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ExtractValue('', REPEAT('(', 100000)); + +--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ +--error ER_STACK_OVERRUN_NEED_MORE +SELECT ExtractValue('', REPEAT('/n', 100000)); + --echo # End of 10.6 tests diff --git a/mysql-test/main/mdev-37000.result b/mysql-test/main/mdev-37000.result new file mode 100644 index 00000000000..73e6812382a --- /dev/null +++ b/mysql-test/main/mdev-37000.result @@ -0,0 +1,8 @@ +# +# MDEV-37000 Deleting a row from Aria table results to 'Index is corrupt' +# +CREATE OR REPLACE TABLE t (a INT, b CHAR(8) NOT NULL, KEY(b)) ENGINE=Aria DEFAULT CHARSET=utf8; +INSERT INTO t VALUES (1,'20070101'), (2,'20070101'); +DELETE FROM t LIMIT 2; +DROP TABLE t; +# End of 10.6 tests diff --git a/mysql-test/main/mdev-37000.test b/mysql-test/main/mdev-37000.test new file mode 100644 index 00000000000..631be073f8c --- /dev/null +++ b/mysql-test/main/mdev-37000.test @@ -0,0 +1,9 @@ +--echo # +--echo # MDEV-37000 Deleting a row from Aria table results to 'Index is corrupt' +--echo # +CREATE OR REPLACE TABLE t (a INT, b CHAR(8) NOT NULL, KEY(b)) ENGINE=Aria DEFAULT CHARSET=utf8; +INSERT INTO t VALUES (1,'20070101'), (2,'20070101'); +DELETE FROM t LIMIT 2; +DROP TABLE t; + +--echo # End of 10.6 tests diff --git a/mysql-test/main/mysql_json_table_recreate.result b/mysql-test/main/mysql_json_table_recreate.result index c8c53c9036a..25a72885317 100644 --- a/mysql-test/main/mysql_json_table_recreate.result +++ b/mysql-test/main/mysql_json_table_recreate.result @@ -245,6 +245,14 @@ DECLARE a mysql_json; END; $$ ERROR HY000: 'MYSQL_JSON' is not allowed in this context -# # End of 10.5 tests # +# MDEV-40678 mysql_json plugin OOB reads +# +alter table t1 force; +ERROR HY000: Error parsing MySQL JSON format, please dump this table from MySQL and then restore it to be able to use it in MariaDB. +flush tables; +alter table t1 force; +ERROR HY000: Error parsing MySQL JSON format, please dump this table from MySQL and then restore it to be able to use it in MariaDB. +drop table t1; +# End of 10.6 tests diff --git a/mysql-test/main/mysql_json_table_recreate.test b/mysql-test/main/mysql_json_table_recreate.test index 94477d4f328..26cc7a6cc6b 100644 --- a/mysql-test/main/mysql_json_table_recreate.test +++ b/mysql-test/main/mysql_json_table_recreate.test @@ -17,7 +17,7 @@ call mtr.add_suppression("Table rebuild required"); call mtr.add_suppression("is marked as crashed"); call mtr.add_suppression("Checking"); -let $MYSQLD_DATADIR= `select @@datadir`; +let MYSQLD_DATADIR= `select @@datadir`; SET NAMES utf8; @@ -161,6 +161,40 @@ END; $$ DELIMITER ;$$ ---echo # --echo # End of 10.5 tests + --echo # +--echo # MDEV-40678 mysql_json plugin OOB reads +--echo # + +--copy_file std_data/mysql_json/mysql_json_test.frm $MYSQLD_DATADIR/test/t1.frm +--copy_file std_data/mysql_json/mysql_json_test.MYI $MYSQLD_DATADIR/test/t1.MYI +--copy_file std_data/mysql_json/mysql_json_test.MYD $MYSQLD_DATADIR/test/t1.MYD + +perl; + my $f="$ENV{MYSQLD_DATADIR}/test/t1.MYD"; + open F, '+<', $f or die "open($f): $!"; + sysseek F, 0x3c5, 0 or die "sysseek: $!"; + syswrite F, '(' or die "syswrite: $!"; +EOF + +--error ER_UNKNOWN_ERROR +alter table t1 force; +flush tables; + +--remove_file $MYSQLD_DATADIR/test/t1.MYD +--copy_file std_data/mysql_json/mysql_json_test.MYD $MYSQLD_DATADIR/test/t1.MYD + +perl; + my $f="$ENV{MYSQLD_DATADIR}/test/t1.MYD"; + open F, '+<', $f or die "open($f): $!"; + sysseek F, 0x3bc, 0 or die "sysseek: $!"; + syswrite F, '(' or die "syswrite: $!"; +EOF + +--error ER_UNKNOWN_ERROR +alter table t1 force; + +drop table t1; + +--echo # End of 10.6 tests diff --git a/mysql-test/main/mysqld--help,win.rdiff b/mysql-test/main/mysqld--help,win.rdiff index 6016db9eac9..67f2651bdc8 100644 --- a/mysql-test/main/mysqld--help,win.rdiff +++ b/mysql-test/main/mysqld--help,win.rdiff @@ -16,7 +16,17 @@ --net-buffer-length=# Buffer length for TCP/IP and socket communication --net-read-timeout=# -@@ -1374,6 +1376,10 @@ +@@ -1216,8 +1218,7 @@ + (Defaults to on; use --skip-secure-auth to disable.) + --secure-file-priv=name + Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to +- files within specified directory. Empty value means no +- limits except /proc ++ files within specified directory. + --secure-timestamp=name + Restricts direct setting of a session timestamp. Possible + levels are: YES - timestamp cannot deviate from the +@@ -1375,6 +1376,10 @@ Alias for log_slow_query_file. Log slow queries to given log file. Defaults logging to 'hostname'-slow.log. Must be enabled to activate other slow log options @@ -27,7 +37,7 @@ --socket=name Socket file to use for connection --sort-buffer-size=# Each thread that needs to do a sort allocates a buffer of -@@ -1394,6 +1400,7 @@ +@@ -1395,6 +1400,7 @@ aborted. Prevents the common mistake of accidentally deleting or updating every row in a table. --stack-trace Print a symbolic stack trace on failure @@ -35,7 +45,7 @@ --standard-compliant-cte Allow only CTEs compliant to SQL standard (Defaults to on; use --skip-standard-compliant-cte to disable.) -@@ -1472,6 +1479,12 @@ +@@ -1473,6 +1479,12 @@ --thread-pool-max-threads=# Maximum allowed number of worker threads in the thread pool @@ -48,7 +58,7 @@ --thread-pool-oversubscribe=# How many additional active worker threads in a group are allowed. -@@ -1510,8 +1523,8 @@ +@@ -1511,8 +1523,8 @@ automatically convert it to an on-disk MyISAM or Aria table. -t, --tmpdir=name Path for temporary files. Several paths may be specified, @@ -59,7 +69,7 @@ --transaction-alloc-block-size=# Allocation block size for transactions to be stored in binary log -@@ -1731,6 +1744,7 @@ +@@ -1732,6 +1744,7 @@ myisam-stats-method NULLS_UNEQUAL myisam-use-mmap FALSE mysql56-temporal-format TRUE @@ -67,7 +77,7 @@ net-buffer-length 16384 net-read-timeout 30 net-retry-count 10 -@@ -1891,6 +1905,7 @@ +@@ -1892,6 +1905,7 @@ slave-type-conversions slow-launch-time 2 slow-query-log FALSE @@ -75,7 +85,7 @@ sort-buffer-size 2097152 sql-mode STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION sql-safe-updates FALSE -@@ -1917,6 +1932,8 @@ +@@ -1918,6 +1932,8 @@ thread-pool-exact-stats FALSE thread-pool-idle-timeout 60 thread-pool-max-threads 65536 diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 2d514559477..6c13ce88bf4 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1274,7 +1274,8 @@ The following specify which files/extra groups are read (specified before remain (Defaults to on; use --skip-secure-auth to disable.) --secure-file-priv=name Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to - files within specified directory + files within specified directory. Empty value means no + limits except /proc --secure-timestamp=name Restricts direct setting of a session timestamp. Possible levels are: YES - timestamp cannot deviate from the diff --git a/mysql-test/main/mysqltest.result b/mysql-test/main/mysqltest.result index af12773b193..747d6f2e706 100644 --- a/mysql-test/main/mysqltest.result +++ b/mysql-test/main/mysqltest.result @@ -1023,6 +1023,12 @@ the false block has written nothing mysqltest: At line 1: End of file encountered before 'eval' delimiter was found # Only 'eval' is allowed as the third argument mysqltest: At line 1: Invalid argument 'junk' to 'write_file', only 'eval' is allowed +# +# \0 in a query +# +select hex("12"); +hex("1\x002") +310032 # End of 10.6 tests # # MDEV-29344: engines/iuds.insert_time cannot run with PS protocol (syntax error) diff --git a/mysql-test/main/mysqltest.test b/mysql-test/main/mysqltest.test index f4061c09045..8932b061c57 100644 --- a/mysql-test/main/mysqltest.test +++ b/mysql-test/main/mysqltest.test @@ -3054,6 +3054,11 @@ EOF --error 1 --exec echo "--write_file $file EOF junk" | $MYSQL_TEST 2>&1 +--echo # +--echo # \0 in a query +--echo # +select hex("12"); + --echo # End of 10.6 tests --echo # @@ -3077,12 +3082,11 @@ SELECT 1 /* doesn't throw error */; --rmdir $srcdir +--echo # End of 10.11 tests + # Wait till we reached the initial number of concurrent sessions --source include/wait_until_count_sessions.inc --disable_query_log --eval SET GLOBAL max_connections = $saved_max_connections --enable_query_log - ---echo # End of 10.11 tests - diff --git a/mysql-test/main/mysqltest_expression_evaluation.result b/mysql-test/main/mysqltest_expression_evaluation.result index d9fa0ccb3b8..b74fc767d47 100644 --- a/mysql-test/main/mysqltest_expression_evaluation.result +++ b/mysql-test/main/mysqltest_expression_evaluation.result @@ -505,4 +505,11 @@ mysqltest: At line 1: Evaluation error: Modulo by zero # Test case: Overflow mysqltest: At line 1: Range error: 18446744073709551616 value out of range for Integer type mysqltest: At line 1: Range error: 18446744073709551616 value out of range for Integer type +# +# MDEV-37865: IF() function is returning incorrect error +# +# Test case: unknown function name that is a prefix of a known one +mysqltest: At line 1: Syntax error: Unknown function 'if' +mysqltest: At line 1: Syntax error: Unknown function 'l' # Expression evaluation tests completed successfully +# End of 10.6 tests diff --git a/mysql-test/main/mysqltest_expression_evaluation.test b/mysql-test/main/mysqltest_expression_evaluation.test index 7a108f2fe17..b76648284fc 100644 --- a/mysql-test/main/mysqltest_expression_evaluation.test +++ b/mysql-test/main/mysqltest_expression_evaluation.test @@ -894,4 +894,17 @@ let $large_xor = $(0x7FFFFFFFFFFFFFFF ^ 0x7FFFFFFFFFFFFFFE); --error 1 --exec echo "let \$a = \$(-18446744073709551616);" | $MYSQL_TEST 2>&1 +--echo # +--echo # MDEV-37865: IF() function is returning incorrect error +--echo # + +--echo # Test case: unknown function name that is a prefix of a known one +--error 1 +--exec echo "let \$a = \$(if(null, 'fallback', 'actual'));" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "let \$a = \$(l('AB'));" | $MYSQL_TEST 2>&1 + --echo # Expression evaluation tests completed successfully + +--echo # End of 10.6 tests diff --git a/mysql-test/main/mysqltest_string_functions.result b/mysql-test/main/mysqltest_string_functions.result index eb3d56e6554..2f4eb0c94b0 100644 --- a/mysql-test/main/mysqltest_string_functions.result +++ b/mysql-test/main/mysqltest_string_functions.result @@ -1116,4 +1116,11 @@ insert(upper('hello world'), 7, 5, lower('MARIADB')) -> HELLO mariadb concat_ws(' ', upper('hello'), lower('WORLD')) -> HELLO world ifnull(nullif('same', 'same'), 'default') -> default coalesce(nullif('test', 'test'), 'backup') -> backup +# +# MDEV-37859: Mysqltest hex() not working for string values +# +hex("abc") -> 616263 +hex("255") -> 323535 +hex('') -> # Expression evaluation string functions tests completed successfully +# End of 10.6 tests diff --git a/mysql-test/main/mysqltest_string_functions.test b/mysql-test/main/mysqltest_string_functions.test index 10e0d546e15..4ccdfec59c0 100644 --- a/mysql-test/main/mysqltest_string_functions.test +++ b/mysql-test/main/mysqltest_string_functions.test @@ -2128,4 +2128,19 @@ let $result5 = $(ifnull(nullif('same', 'same'), 'default')); let $result6 = $(coalesce(nullif('test', 'test'), 'backup')); --echo coalesce(nullif('test', 'test'), 'backup') -> $result6 +--echo # +--echo # MDEV-37859: Mysqltest hex() not working for string values +--echo # + +let $hex_str1 = $(hex("abc")); +--echo hex("abc") -> $hex_str1 + +let $hex_str2 = $(hex("255")); +--echo hex("255") -> $hex_str2 + +let $hex_str3 = $(hex('')); +--echo hex('') -> $hex_str3 + --echo # Expression evaluation string functions tests completed successfully + +--echo # End of 10.6 tests diff --git a/mysql-test/main/ps_ddl1.result b/mysql-test/main/ps_ddl1.result index e702a5567d8..6f81a695a4d 100644 --- a/mysql-test/main/ps_ddl1.result +++ b/mysql-test/main/ps_ddl1.result @@ -480,3 +480,48 @@ drop procedure if exists p1; drop function if exists f1; drop view if exists t1; drop schema if exists mysqltest; +# +# MDEV-38472 Assertion `! is_set() || m_can_overwrite_status' +# failed in Diagnostics_area::set_error_status +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +PREPARE stmt FROM "UPDATE t1 SET a = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING 1, DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1; +CREATE TABLE t1 (a INT, b INT); +CREATE TABLE t2 (a INT, c INT); +INSERT INTO t1 VALUES (1,1),(2,2); +INSERT INTO t2 VALUES (1,1),(2,2); +CREATE VIEW v1 AS SELECT t1.a, t1.b, t2.c FROM t1 JOIN t2 ON t1.a = t2.a; +PREPARE stmt FROM "UPDATE v1 SET b = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING 1, DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP VIEW v1; +DROP TABLE t1, t2; +CREATE TABLE t1 (a INT); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "INSERT INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "REPLACE INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +EXECUTE stmt USING DEFAULT; +ERROR HY000: Default/ignore value is not supported for such parameter usage +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/ps_ddl1.test b/mysql-test/main/ps_ddl1.test index e0441cb0ab8..0c7d3e9f843 100644 --- a/mysql-test/main/ps_ddl1.test +++ b/mysql-test/main/ps_ddl1.test @@ -398,3 +398,54 @@ drop function if exists f1; drop view if exists t1; drop schema if exists mysqltest; --enable_warnings + +--echo # +--echo # MDEV-38472 Assertion `! is_set() || m_can_overwrite_status' +--echo # failed in Diagnostics_area::set_error_status +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +PREPARE stmt FROM "UPDATE t1 SET a = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING 1, DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1; + +CREATE TABLE t1 (a INT, b INT); +CREATE TABLE t2 (a INT, c INT); +INSERT INTO t1 VALUES (1,1),(2,2); +INSERT INTO t2 VALUES (1,1),(2,2); +CREATE VIEW v1 AS SELECT t1.a, t1.b, t2.c FROM t1 JOIN t2 ON t1.a = t2.a; +PREPARE stmt FROM "UPDATE v1 SET b = ? LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING 1, DEFAULT; +DEALLOCATE PREPARE stmt; +DROP VIEW v1; +DROP TABLE t1, t2; + +CREATE TABLE t1 (a INT); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "INSERT INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; + +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t3 (a INT); +INSERT INTO t3 VALUES (1),(2),(3); +PREPARE stmt FROM "REPLACE INTO t1 SELECT a FROM t3 LIMIT ?"; +ALTER TABLE t1 FORCE; +--error ER_INVALID_DEFAULT_PARAM +EXECUTE stmt USING DEFAULT; +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t3; + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/mysql-test/main/rli_run_lock.result b/mysql-test/main/rli_run_lock.result new file mode 100644 index 00000000000..e2776a356d0 --- /dev/null +++ b/mysql-test/main/rli_run_lock.result @@ -0,0 +1,18 @@ +CHANGE MASTER TO master_host='127.0.0.1', master_user='root'; +START SLAVE SQL_THREAD; +include/wait_for_slave_sql_to_start.inc +SET @@SESSION.debug_sync= +'hold_sss_with_run_lock SIGNAL sss_got_run_lock WAIT_FOR sss_continue'; +SHOW SLAVE STATUS; +connect stopper, 127.0.0.1, root, , , $SERVER_MYPORT_1; +SET @@SESSION.debug_sync= 'now WAIT_FOR sss_got_run_lock'; +STOP SLAVE SQL_THREAD; +connect continuer, 127.0.0.1, root, , , $SERVER_MYPORT_1; +SET @@SESSION.debug_sync= 'now SIGNAL sss_continue'; +connection stopper; +disconnect continuer; +connection default; +disconnect stopper; +SET @@SESSION.debug_sync= RESET; +include/wait_for_slave_sql_to_stop.inc +RESET SLAVE ALL; diff --git a/mysql-test/main/rli_run_lock.test b/mysql-test/main/rli_run_lock.test new file mode 100644 index 00000000000..ca8eaf4d929 --- /dev/null +++ b/mysql-test/main/rli_run_lock.test @@ -0,0 +1,43 @@ +# MDEV-40298 +# Use-After-Free when SQL Thread stops in the middle of SHOW SLAVE STATUS +# +# Test that the correct synchronization is performed + +--source include/have_debug_sync.inc +--source include/have_binlog_format_mixed.inc # No actual replication required +CHANGE MASTER TO master_host='127.0.0.1', master_user='root'; # basic setup + +START SLAVE SQL_THREAD; +--source include/wait_for_slave_sql_to_start.inc + + +SET @@SESSION.debug_sync= + 'hold_sss_with_run_lock SIGNAL sss_got_run_lock WAIT_FOR sss_continue'; +--send SHOW SLAVE STATUS + +--connect (stopper, 127.0.0.1, root, , , $SERVER_MYPORT_1) + SET @@SESSION.debug_sync= 'now WAIT_FOR sss_got_run_lock'; + --send STOP SLAVE SQL_THREAD + # Wait a bit for the thread to "shut down" + # (It should not shut down, but block waiting for the ongoing SSS; + # the tested bug instead pulled this carpet under the SSS's feet.) + --sleep 1 + + --connect (continuer, 127.0.0.1, root, , , $SERVER_MYPORT_1) + SET @@SESSION.debug_sync= 'now SIGNAL sss_continue'; + --connection stopper + --disconnect continuer + + --reap +--connection default +--disconnect stopper + + +--disable_result_log + --reap +--enable_result_log + +# Clean-up +SET @@SESSION.debug_sync= RESET; +--source include/wait_for_slave_sql_to_stop.inc +RESET SLAVE ALL; diff --git a/mysql-test/main/ssl_autoverify.result b/mysql-test/main/ssl_autoverify.result index 71e27bbf773..8c78a21d6f9 100644 --- a/mysql-test/main/ssl_autoverify.result +++ b/mysql-test/main/ssl_autoverify.result @@ -38,7 +38,7 @@ yes test.have_ssl() yes # mysql -unohash -ponetwothree --ssl-verify-server-cert -e "select test.have_ssl()" -ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure +ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate # mysql -umulti -ppw1 --ssl-verify-server-cert -e "select test.have_ssl()" test.have_ssl() yes diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index ef716a8b318..b892fb6091b 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1110,5 +1110,81 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 2 DROP TABLE t1, t2; # +# MDEV-36610: Subquery wrongly marked as eliminated by table elimination +# when equality propagation shares a WHERE-clause subquery +# into an eliminated outer join's ON expression +# +CREATE TABLE t0 (k INT); +INSERT INTO t0 VALUES (20),(9); +CREATE TABLE t1 (a BIGINT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT PRIMARY KEY); +INSERT INTO t2 VALUES (3),(4); +INSERT INTO t2 VALUES (5),(6); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (2); +CREATE TABLE t4 (a BIGINT, x INT); +INSERT INTO t4 VALUES (2,20),(3,30); +# t2 is eliminated, but the subquery must still be shown and executed: +explain SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) +WHERE t1.a = (SELECT c FROM t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) +WHERE t1.a = (SELECT c FROM t3); +a +2 +# t2 is eliminated; the subquery must still be shown and executed: +explain SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 +1 PRIMARY t4 ALL NULL NULL NULL NULL 2 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +k x +20 20 +9 NULL +truncate t2; +DROP TABLE t4; +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2),(7); +# The subquery is shared with the WHERE clause, so it must still be +# shown and executed: +explain SELECT t1.* FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a) +LEFT JOIN t4 ON (t4.d = t1.a) +WHERE t1.a = (SELECT c FROM t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 system PRIMARY NULL NULL NULL 0 Const row not found +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +1 PRIMARY t4 ALL NULL NULL NULL NULL 3 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.* FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a) +LEFT JOIN t4 ON (t4.d = t1.a) +WHERE t1.a = (SELECT c FROM t3); +a +2 +explain SELECT t1.a FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) +LEFT JOIN t4 ON (t4.d = t1.a); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 system PRIMARY NULL NULL NULL 0 Const row not found +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 +1 PRIMARY t4 ALL NULL NULL NULL NULL 3 Using where; Using join buffer (flat, BNL join) +2 SUBQUERY t3 system NULL NULL NULL NULL 1 +SELECT t1.a FROM t1 +LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) +LEFT JOIN t4 ON (t4.d = t1.a); +a +1 +2 +DROP TABLE t0, t1, t2, t3, t4; +# # End of 10.11 tests # diff --git a/mysql-test/main/table_elim.test b/mysql-test/main/table_elim.test index 1d4f64e98b6..90305870c5b 100644 --- a/mysql-test/main/table_elim.test +++ b/mysql-test/main/table_elim.test @@ -841,6 +841,63 @@ select t1.null_col from t1 left join t2 on (t2.unique_col<=>t1.notnull_col); DROP TABLE t1, t2; +--echo # +--echo # MDEV-36610: Subquery wrongly marked as eliminated by table elimination +--echo # when equality propagation shares a WHERE-clause subquery +--echo # into an eliminated outer join's ON expression +--echo # +CREATE TABLE t0 (k INT); +INSERT INTO t0 VALUES (20),(9); +CREATE TABLE t1 (a BIGINT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT PRIMARY KEY); +INSERT INTO t2 VALUES (3),(4); +INSERT INTO t2 VALUES (5),(6); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (2); +CREATE TABLE t4 (a BIGINT, x INT); +INSERT INTO t4 VALUES (2,20),(3,30); + +--echo # t2 is eliminated, but the subquery must still be shown and executed: + +let $q= +SELECT t1.* FROM t1 LEFT JOIN t2 ON (t2.b = t1.a) + WHERE t1.a = (SELECT c FROM t3); +eval explain $q; +eval $q; + +--echo # t2 is eliminated; the subquery must still be shown and executed: +let $q= +SELECT t0.k, t4.x FROM t0 +LEFT JOIN (t4 LEFT JOIN t2 ON t2.b = t4.a) +ON (t0.k = t4.x AND t4.a = (SELECT c FROM t3)); +eval explain $q; +eval $q; + +truncate t2; +DROP TABLE t4; +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2),(7); +--echo # The subquery is shared with the WHERE clause, so it must still be +--echo # shown and executed: +let $q= +SELECT t1.* FROM t1 + LEFT JOIN t2 ON (t2.b = t1.a) + LEFT JOIN t4 ON (t4.d = t1.a) + WHERE t1.a = (SELECT c FROM t3); +eval explain $q; +eval $q; + +let $q= +SELECT t1.a FROM t1 + LEFT JOIN t2 ON (t2.b = t1.a AND t1.a <> (SELECT c FROM t3)) + LEFT JOIN t4 ON (t4.d = t1.a); +eval explain $q; +eval $q; + + +DROP TABLE t0, t1, t2, t3, t4; + --echo # --echo # End of 10.11 tests --echo # diff --git a/mysql-test/main/type_enum.result b/mysql-test/main/type_enum.result index ddb330f154f..b05d7fdf341 100644 --- a/mysql-test/main/type_enum.result +++ b/mysql-test/main/type_enum.result @@ -2624,4 +2624,33 @@ select * from t1; a drop table t1; set sql_mode=default; +# +# MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +# +CREATE TABLE t1 (a ENUM ('',0x0000),b ENUM ('')); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('','\0\0') DEFAULT NULL, + `b` enum('') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +SELECT HEX(a) FROM t1; +HEX(a) +0000 +0000 +DROP TABLE t1; +CREATE TABLE t1 (a ENUM ('',0x0000) AS (0x0000) VIRTUAL,b ENUM ('') DEFAULT ''); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('','\0\0') GENERATED ALWAYS AS (0x0000) VIRTUAL, + `b` enum('') DEFAULT '' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (); +SELECT HEX(a) FROM t1; +HEX(a) +0000 +DROP TABLE t1; # End of 10.11 tests diff --git a/mysql-test/main/type_enum.test b/mysql-test/main/type_enum.test index 2c685b5594c..6c2ce9f1e7e 100644 --- a/mysql-test/main/type_enum.test +++ b/mysql-test/main/type_enum.test @@ -630,4 +630,21 @@ select * from t1; drop table t1; set sql_mode=default; +--echo # +--echo # MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +--echo # + +CREATE TABLE t1 (a ENUM ('',0x0000),b ENUM ('')); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +SELECT HEX(a) FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (a ENUM ('',0x0000) AS (0x0000) VIRTUAL,b ENUM ('') DEFAULT ''); +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (); +SELECT HEX(a) FROM t1; +DROP TABLE t1; + --echo # End of 10.11 tests diff --git a/mysql-test/main/type_set.result b/mysql-test/main/type_set.result index c033b73eb56..4df3a37a485 100644 --- a/mysql-test/main/type_set.result +++ b/mysql-test/main/type_set.result @@ -699,4 +699,35 @@ Catalog Database Table Table_alias Column Column_alias Type Length Max length Is def COALESCE(c_int, c_set) 253 33 0 Y 0 39 33 COALESCE(c_int, c_set) DROP TABLE t1; +# +# MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +# +CREATE TABLE t1 (a SET ('',0x0000),b SET ('')) CHARACTER SET latin1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` set('','\0\0') DEFAULT NULL, + `b` set('') DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +INSERT INTO t1 VALUES ('',''); +SELECT a+0, HEX(a) FROM t1; +a+0 HEX(a) +2 0000 +2 0000 +0 +DROP TABLE t1; +CREATE TABLE t1 (a SET ('',0x0000) AS (0x0000) VIRTUAL,b SET ('') DEFAULT '') CHARACTER SET latin1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` set('','\0\0') GENERATED ALWAYS AS (0x0000) VIRTUAL, + `b` set('') DEFAULT '' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +INSERT INTO t1 VALUES (); +SELECT a+0, HEX(a) FROM t1; +a+0 HEX(a) +2 0000 +DROP TABLE t1; # End of 10.11 tests diff --git a/mysql-test/main/type_set.test b/mysql-test/main/type_set.test index 1873a54897a..87ad00b094d 100644 --- a/mysql-test/main/type_set.test +++ b/mysql-test/main/type_set.test @@ -334,4 +334,22 @@ SELECT COALESCE(c_int, c_set) FROM t1; DROP TABLE t1; +--echo # +--echo # MDEV-28498 Incorrect information in file: './test/t0.frm' on CREATE TABLE +--echo # + +CREATE TABLE t1 (a SET ('',0x0000),b SET ('')) CHARACTER SET latin1; +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (0x0000,''); +INSERT INTO t1 VALUES ('\0\0',''); +INSERT INTO t1 VALUES ('',''); +SELECT a+0, HEX(a) FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (a SET ('',0x0000) AS (0x0000) VIRTUAL,b SET ('') DEFAULT '') CHARACTER SET latin1; +SHOW CREATE TABLE t1; +INSERT INTO t1 VALUES (); +SELECT a+0, HEX(a) FROM t1; +DROP TABLE t1; + --echo # End of 10.11 tests diff --git a/mysql-test/main/view.result b/mysql-test/main/view.result index 91d07b6ae70..62e73d04db0 100644 --- a/mysql-test/main/view.result +++ b/mysql-test/main/view.result @@ -1913,8 +1913,6 @@ test.v5 check error Corrupt test.v6 check status OK drop view v1, v2, v3, v4, v5, v6; drop table t2; -drop function if exists f1; -drop function if exists f2; CREATE TABLE t1 (col1 time); CREATE TABLE t2 (col1 time); CREATE TABLE t3 (col1 time); @@ -2151,8 +2149,6 @@ CREATE VIEW v1 AS SELECT f1(); ERROR HY000: View's SELECT refers to a temporary table 't1' DROP FUNCTION f1; DROP TABLE t1; -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; CREATE TABLE t1 (f4 CHAR(5)); CREATE VIEW v1 AS SELECT * FROM t1; DESCRIBE v1; @@ -2438,7 +2434,6 @@ f1 sum(f2) NULL 12 drop view v1; drop table t1; -drop procedure if exists p1; create procedure p1 () deterministic begin create view v1 as select 1; @@ -2451,7 +2446,6 @@ v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VI drop view v1; drop procedure p1; CREATE VIEW v1 AS SELECT 42 AS Meaning; -DROP FUNCTION IF EXISTS f1; CREATE FUNCTION f1() RETURNS INTEGER BEGIN DECLARE retn INTEGER; @@ -2869,7 +2863,6 @@ SHOW TABLES; Tables_in_test t1 DROP TABLE t1; -DROP VIEW IF EXISTS v1; set GLOBAL sql_mode=""; set LOCAL sql_mode=""; CREATE DATABASE bug21261DB; @@ -2913,9 +2906,6 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`f1` AS `f1` from `t1` where `t1`.`f1` between current_timestamp() and current_timestamp() + interval 1 minute latin1 latin1_swedish_ci drop view v1; drop table t1; -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; -DROP VIEW IF EXISTS v2; CREATE TABLE t1(a INT, b INT); CREATE DEFINER=longer_than_80_456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789@localhost VIEW v1 AS SELECT a FROM t1; @@ -2924,10 +2914,6 @@ CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890 VIEW v2 AS SELECT b FROM t1; ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTYa...' is too long for host name (should be no longer than 255) DROP TABLE t1; -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT); CREATE VIEW v1 AS SELECT * FROM t1; CREATE FUNCTION f1() RETURNS INT @@ -2990,9 +2976,6 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=MERGE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`pk` AS `pk` from (`t1` join `t2` on(`t2`.`fk` = `t1`.`pk` and `t2`.`ver` = (select max(`t`.`ver`) from `t2` `t` where `t`.`org` = `t2`.`org`))) latin1 latin1_swedish_ci DROP VIEW v1; DROP TABLE t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT); INSERT INTO t1 VALUES (1); CREATE VIEW v1 AS SELECT MAX(i) FROM t1; @@ -3014,8 +2997,6 @@ UPDATE v1 SET val=6 WHERE id=2; ERROR 44000: CHECK OPTION failed `test`.`v1` DROP VIEW v1; DROP TABLE t1; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT); CREATE VIEW v1 AS SELECT j FROM t1; CREATE VIEW v2 AS SELECT * FROM t1; @@ -3059,7 +3040,6 @@ SELECT * FROM v; x 5 DROP VIEW v; -DROP VIEW IF EXISTS v1; CREATE VIEW v1 AS SELECT 'The\ZEnd'; SELECT * FROM v1; TheEnd @@ -3170,15 +3150,11 @@ code COUNT(DISTINCT country) 100 2 DROP VIEW v1; DROP TABLE t1; -DROP VIEW IF EXISTS v1; SELECT * FROM (SELECT 1) AS t into @w; CREATE VIEW v1 AS SELECT * FROM (SELECT 1) AS t into @w; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'into @w' at line 1 # Previously the following would fail. SELECT * FROM (SELECT 1) AS t into @w; -drop view if exists view_24532_a; -drop view if exists view_24532_b; -drop table if exists table_24532; create table table_24532 ( a int, b bigint, @@ -3642,38 +3618,24 @@ DROP TABLE t1; # ----------------------------------------------------------------- # -- Bug#34337 Server crash when Altering a view using a table name. # ----------------------------------------------------------------- - -DROP TABLE IF EXISTS t1; - CREATE TABLE t1(c1 INT); - SELECT * FROM t1; c1 ALTER ALGORITHM=TEMPTABLE SQL SECURITY INVOKER VIEW t1 (c2) AS SELECT (1); ERROR HY000: 'test.t1' is not of type 'VIEW' - DROP TABLE t1; - -# -- End of test case for Bug#34337. - # ----------------------------------------------------------------- # -- Bug#35193 VIEW query is rewritten without "FROM DUAL", # -- causing syntax error # ----------------------------------------------------------------- - CREATE VIEW v1 AS SELECT 1 FROM DUAL WHERE 1; - SELECT * FROM v1; 1 1 SHOW CREATE TABLE v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select 1 AS `1` from DUAL where 1 latin1 latin1_swedish_ci - DROP VIEW v1; - -# -- End of test case for Bug#35193. - CREATE VIEW v1 AS SELECT 1; DROP VIEW v1; CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 INT, INDEX (c2)); @@ -3834,7 +3796,6 @@ DROP TABLE t1; # ----------------------------------------------------------------- # -- End of 5.0 tests. # ----------------------------------------------------------------- -DROP DATABASE IF EXISTS `d-1`; CREATE DATABASE `d-1`; USE `d-1`; CREATE TABLE `t-1` (c1 INT); @@ -3853,93 +3814,61 @@ DROP TABLE `t-2`; DROP VIEW `v-2`; DROP DATABASE `d-1`; USE test; - # # Bug#26676 VIEW using old table schema in a session. # - -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; CREATE TABLE t1(c1 INT, c2 INT); INSERT INTO t1 VALUES (1, 2), (3, 4); - SELECT * FROM t1; c1 c2 1 2 3 4 - CREATE VIEW v1 AS SELECT * FROM t1; - SELECT * FROM v1; c1 c2 1 2 3 4 - ALTER TABLE t1 ADD COLUMN c3 INT AFTER c2; - SELECT * FROM t1; c1 c2 c3 1 2 NULL 3 4 NULL - SELECT * FROM v1; c1 c2 1 2 3 4 - SHOW CREATE VIEW v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`c1` AS `c1`,`t1`.`c2` AS `c2` from `t1` latin1 latin1_swedish_ci - DROP VIEW v1; DROP TABLE t1; - -# End of test case for Bug#26676. - # ----------------------------------------------------------------- # -- Bug#32538 View definition picks up character set, but not collation # ----------------------------------------------------------------- - -DROP VIEW IF EXISTS v1; - SET collation_connection = latin1_general_ci; CREATE VIEW v1 AS SELECT _latin1 'text1' AS c1, 'text2' AS c2; - SELECT COLLATION(c1), COLLATION(c2) FROM v1; COLLATION(c1) COLLATION(c2) latin1_swedish_ci latin1_general_ci - SHOW CREATE VIEW v1; View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select _latin1'text1' AS `c1`,'text2' AS `c2` latin1 latin1_general_ci - SELECT * FROM v1 WHERE c1 = 'text1'; ERROR HY000: Illegal mix of collations (latin1_swedish_ci,COERCIBLE) and (latin1_general_ci,COERCIBLE) for operation '=' - SELECT * FROM v1 WHERE c2 = 'text2'; c1 c2 text1 text2 - use test; SET names latin1; - SELECT COLLATION(c1), COLLATION(c2) FROM v1; COLLATION(c1) COLLATION(c2) latin1_swedish_ci latin1_general_ci - SELECT * FROM v1 WHERE c1 = 'text1'; c1 c2 text1 text2 - SELECT * FROM v1 WHERE c2 = 'text2'; ERROR HY000: Illegal mix of collations (latin1_general_ci,COERCIBLE) and (latin1_swedish_ci,COERCIBLE) for operation '=' - DROP VIEW v1; - -# -- End of test case for Bug#32538. - -drop view if exists a; -drop procedure if exists p; create procedure p() begin declare continue handler for sqlexception begin end; @@ -3962,9 +3891,6 @@ DROP TABLE t1; # Bug#48449: hang on show create view after upgrading when # view contains function of view # -DROP VIEW IF EXISTS v1,v2; -DROP TABLE IF EXISTS t1,t2; -DROP FUNCTION IF EXISTS f1; CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); CREATE FUNCTION f1() RETURNS INT @@ -4728,8 +4654,6 @@ c a b c 0 93 1 0 DROP VIEW v4; DROP TABLE t1,t2,t3,t4; -drop table if exists t_9801; -drop view if exists v_9801; create table t_9801 (s1 int); create view v_9801 as select sum(s1) from t_9801 with check option; @@ -4744,8 +4668,6 @@ drop table t_9801; # # Bug #47335 assert in get_table_share # -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; CREATE TEMPORARY TABLE t1 (id INT); ALTER VIEW t1 AS SELECT 1 AS f1; ERROR 42S02: Table 'test.t1' doesn't exist @@ -4762,8 +4684,6 @@ DROP VIEW v1; # Bug #47635 assert in start_waiting_global_read_lock # during CREATE VIEW # -DROP TABLE IF EXISTS t1, t2; -DROP VIEW IF EXISTS t2; CREATE TABLE t1 (f1 integer); CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); CREATE TEMPORARY TABLE t2 (f1 integer); @@ -4777,8 +4697,6 @@ DROP TABLE t1, t2; # Bug#48315 Metadata lock is not taken for merged views that # use an INFORMATION_SCHEMA table # -DROP VIEW IF EXISTS v1; -DROP PROCEDURE IF EXISTS p1; connect con2, localhost, root; connect con3, localhost, root; connection default; @@ -4821,7 +4739,6 @@ disconnect con3; # Bug#12626844: WRONG ERROR MESSAGE WHILE CREATING A VIEW ON A # NON EXISTING DATABASE # -DROP DATABASE IF EXISTS nodb; CREATE VIEW nodb.a AS SELECT 1; ERROR 42000: Unknown database 'nodb' # @@ -6946,10 +6863,8 @@ SELECT * FROM v1; b'' DROP VIEW v1; -# # End of 10.3 tests # -# # MDEV-25206: view specification contains unknown column reference # CREATE TABLE t1 (a int); diff --git a/mysql-test/main/view.test b/mysql-test/main/view.test index eedce3d421b..75d690548b2 100644 --- a/mysql-test/main/view.test +++ b/mysql-test/main/view.test @@ -70,7 +70,6 @@ create view v3 (c) as select a+1 from v1; -- error ER_BAD_FIELD_ERROR create view v3 (c) as select b+1 from v1; - # VIEW on VIEW test with mixing different algorithms on different order create view v3 (c) as select c+1 from v1; select c from v3; @@ -159,7 +158,6 @@ select * from v2 natural left join v1; drop view v1, v2; drop table t1; - # # DISTINCT option for VIEW # @@ -758,7 +756,6 @@ show create view v1; drop view v1; drop table t1; - # # VIEW with floating point (long number) as column # @@ -1680,10 +1677,6 @@ CHECK TABLE v1, v2, v3, v4, v5, v6; drop view v1, v2, v3, v4, v5, v6; drop table t2; ---disable_warnings -drop function if exists f1; -drop function if exists f2; ---enable_warnings CREATE TABLE t1 (col1 time); CREATE TABLE t2 (col1 time); CREATE TABLE t3 (col1 time); @@ -1737,7 +1730,6 @@ show create view v1; drop view v1; drop table t1; - # # Correct inserting data check (absence of default value) for view # underlying tables (Bug#6443) @@ -1756,7 +1748,6 @@ set sql_mode=default; drop view v2,v1; drop table t1; - # # Bug#11399 Use an alias in a select statement on a view # @@ -1767,7 +1758,6 @@ select f1 as alias from v1; drop view v1; drop table t1; - # # Test for Bug#6120 SP cache to be invalidated when altering a view # @@ -1788,7 +1778,6 @@ DROP PROCEDURE p1; DROP VIEW v1; DROP TABLE t1; - # # Test for Bug#11709 View was ordered by wrong column # @@ -1799,7 +1788,6 @@ select * from v1 order by f1; drop view v1; drop table t1; - # # Test for Bug#11771 wrong query_id in SELECT * FROM # @@ -1814,7 +1802,6 @@ SELECT * FROM t1; DROP VIEW v1; DROP TABLE t1; - # # opening table in correct locking mode (Bug#9597) # @@ -1833,7 +1820,6 @@ DROP PROCEDURE p1; DROP VIEW v1; DROP TABLE t1; - # # Bug#11760 Typo in Item_func_add_time::print() results in NULLs returned # subtime() in view @@ -1844,7 +1830,6 @@ select * from v1; drop view v1; drop table t1; - # # Test for Bug#11412 query over a multitable view with GROUP_CONCAT # @@ -1869,7 +1854,6 @@ SELECT pid,GROUP_CONCAT(CONCAT(fn,' ',ln) ORDER BY 1) FROM v1 GROUP BY pid; DROP VIEW v1; DROP TABLE t1,t2; - # # Test for Bug#12382 SELECT * FROM view after INSERT command # @@ -1887,7 +1871,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Test for Bug#12470 crash for a simple select from a view defined # as a join over 5 tables @@ -1907,7 +1890,6 @@ SELECT a FROM v1; DROP VIEW v1; DROP TABLE t1,t2,t3,t4,t5; - # # Bug#12298 Typo in function name results in erroneous view being created. # @@ -1927,7 +1909,6 @@ call p1(); call p1(); drop procedure p1; - # # Bug#10624 Views with multiple UNION and UNION ALL produce incorrect results # @@ -1940,7 +1921,6 @@ select * from v1; drop view v1; drop table t1,t2; - # # Test for Bug#10970 view referring a temporary table indirectly # @@ -1953,14 +1933,9 @@ CREATE VIEW v1 AS SELECT f1(); DROP FUNCTION f1; DROP TABLE t1; - # # Bug#12533 (crash on DESCRIBE after renaming base table column) # ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; ---enable_warnings CREATE TABLE t1 (f4 CHAR(5)); CREATE VIEW v1 AS SELECT * FROM t1; @@ -1972,7 +1947,6 @@ DESCRIBE v1; DROP TABLE t1; DROP VIEW v1; - # # Bug#12489 wrongly printed strcmp() function results in creation of broken # view @@ -1982,7 +1956,6 @@ select * from v1; drop view v1; drop table t1; - # # Bug#12922 if(sum(),...) with group from view returns wrong results # @@ -1993,7 +1966,6 @@ select if(sum(f1)>1,f2,f3) from v1 group by f1; drop view v1; drop table t1; - # Bug#12941 # create table t1 ( @@ -2026,7 +1998,6 @@ order by users_names; drop view v1, v2; drop table t1, t2; - # # Bug#6808 Views: CREATE VIEW v ... FROM t AS v fails # @@ -2036,7 +2007,6 @@ create view abc as select * from t1 as abc; drop table t1; drop view abc; - # # Bug#12993 View column rename broken in subselect # @@ -2076,7 +2046,6 @@ create view v1 as SELECT CONVERT_TZ('2004-01-01 12:00:00','GMT','MET'); select * from v1; drop view v1; - # # Bugs#12963, #13000 wrong creation of VIEW with DAYNAME, DAYOFWEEK, and WEEKDAY # @@ -2108,7 +2077,6 @@ SELECT * FROM v3; DROP TABLE t1; DROP VIEW v1, v2, v3; - # # Bug#13411 crash when using non-qualified view column in HAVING clause # @@ -2122,7 +2090,6 @@ SELECT v1.a FROM v1 GROUP BY v1.a HAVING a > 1; DROP VIEW v1; DROP TABLE t1; - # # Bug#13410 failed name resolution for qualified view column in HAVING # @@ -2138,7 +2105,6 @@ SELECT v_1.a FROM v1 AS v_1 GROUP BY v_1.a HAVING v_1.a IN (1,2,3); DROP VIEW v1; DROP TABLE t1; - # # Bug#13327 view wasn't using index for const condition # @@ -2157,7 +2123,6 @@ EXPLAIN SELECT * FROM v2 WHERE a=1; DROP VIEW v1,v2; DROP TABLE t1,t2,t3; - # # Bug#13622 Wrong view .frm created if some field's alias contain \n # @@ -2168,7 +2133,6 @@ select * from v1; drop view v1; drop table t1; - # Bug#14466 lost sort order in GROUP_CONCAT() in a view # create table t1 (f1 int, f2 int); @@ -2180,7 +2144,6 @@ select * from v2; drop view v1,v2; drop table t1; - # # Bug#14026 Crash on second PS execution when using views # @@ -2205,7 +2168,6 @@ execute stmt1 using @parm1; drop view v1; drop table t1,t2,t3,t4; - # # Bug#14540 OPTIMIZE, ANALYZE, REPAIR applied to not a view # @@ -2224,7 +2186,6 @@ REPAIR TABLE v1; DROP VIEW v1; - # # Bug#14719 Views DEFINER grammar is incorrect # @@ -2237,7 +2198,6 @@ create definer = current_user sql security invoker view v1 as select 1; show create view v1; drop view v1; - # # Bug#14816 test_if_order_by_key() expected only Item_fields. # @@ -2248,7 +2208,6 @@ explain select id from v1 order by id; drop view v1; drop table t1; - # # Bug#14850 Item_ref's values wasn't updated # @@ -2260,14 +2219,10 @@ select f1, sum(f2) from v1 group by f1; drop view v1; drop table t1; - # # Bug#14885 incorrect SOURCE in view created in a procedure # TODO: here SOURCE string must be shown when it will be possible # ---disable_warnings -drop procedure if exists p1; ---enable_warnings delimiter //; create procedure p1 () deterministic begin @@ -2280,14 +2235,10 @@ show create view v1; drop view v1; drop procedure p1; - # # Bug#15096 using function with view for view creation # CREATE VIEW v1 AS SELECT 42 AS Meaning; ---disable_warnings -DROP FUNCTION IF EXISTS f1; ---enable_warnings DELIMITER //; --enable_prepare_warnings CREATE FUNCTION f1() RETURNS INTEGER @@ -2304,7 +2255,6 @@ select * from v2; drop view v2,v1; drop function f1; - # # Bug#14861 aliased column names are not preserved. # @@ -2324,7 +2274,6 @@ order by v2.receipt_id; drop view v2, v1; drop table t1; - # # Bug#16016 MIN/MAX optimization for views # @@ -2349,7 +2298,6 @@ EXPLAIN SELECT MIN(a) FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#16382 grouping name is resolved against a view column name # which coincides with a select column name @@ -2367,7 +2315,6 @@ SELECT IF(x IS NULL, 'blank', 'not blank') AS x FROM v1 GROUP BY x; DROP VIEW v1; DROP TABLE t1; - # # Bug#15943 mysql_next_result hangs on invalid SHOW CREATE VIEW # @@ -2385,7 +2332,6 @@ drop view v1; delimiter ;// --enable_ps_protocol - # # Bug#17726 Not checked empty list caused endless loop # @@ -2400,7 +2346,6 @@ select * from v2; drop view v2, v1; drop table t1; - # # Bug#18386 select from view over a table with ORDER BY view_col clause # given view_col is not an image of any column from the base table @@ -2415,7 +2360,6 @@ SELECT my_sqrt FROM v1 ORDER BY my_sqrt; DROP VIEW v1; DROP TABLE t1; - # # Bug#18237 invalid count optimization applied to an outer join with a view # @@ -2437,7 +2381,6 @@ DROP VIEW v2; DROP TABLE t1, t2; - # # Bug#16069 VIEW does return the same results as underlying SELECT # with WHERE condition containing BETWEEN over dates @@ -2459,7 +2402,6 @@ SELECT * FROM v1 WHERE td BETWEEN CAST('2005.01.02' AS DATE) AND CAST('2005.01.0 DROP VIEW v1; DROP TABLE t1; - # # Bug#14308 Recursive view definitions # @@ -2493,7 +2435,6 @@ select * from v1; drop function f1; drop view t1, v1; - # # Bug#15153 CONVERT_TZ() is not allowed in all places in VIEWs # @@ -2521,7 +2462,6 @@ select * from v2; drop view v1, v2; drop table t1; - # # Bug#19490 usage of view specified by a query with GROUP BY # an expression containing non-constant interval @@ -2538,7 +2478,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#19077 A nested materialized view is used before being populated. # @@ -2550,7 +2489,6 @@ SELECT * FROM v2; DROP VIEW v2, v1; DROP TABLE t1; - # # Bug#19573 VIEW with HAVING that refers an alias name # @@ -2578,7 +2516,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1; - # # Bug#19089 wrong inherited dafault values in temp table views # @@ -2610,7 +2547,6 @@ SELECT * FROM t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#16110 insert permitted into view col w/o default value # @@ -2629,7 +2565,6 @@ SELECT * FROM t1; DROP VIEW v1; DROP TABLE t1; - # # Bug#18243 expression over a view column that with the REVERSE function # @@ -2646,7 +2581,6 @@ SELECT CONCAT(LEFT(name,LENGTH(name)-INSTR(REVERSE(name)," ")), DROP VIEW v1; DROP TABLE t1; - # # Bug#19714 wrong type of a view column specified by an expressions over ints # @@ -2660,7 +2594,6 @@ DESCRIBE t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#17526 views with TRIM functions # @@ -2685,7 +2618,6 @@ DROP VIEW v1; DROP TABLE t1; - # # Bug#21080 ALTER VIEW makes user restate SQL SECURITY mode, and ALGORITHM # @@ -2699,7 +2631,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1; - # Bug#21086 server crashes when VIEW defined with a SELECT with COLLATE # clause is called # @@ -2718,7 +2649,6 @@ SELECT s1 FROM t1; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#11551 Asymmetric + undocumented behaviour of DROP VIEW and DROP TABLE # @@ -2737,10 +2667,6 @@ show warnings; SHOW TABLES; DROP TABLE t1; ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - # # Bug#21261 Wrong access rights was required for an insert to a view @@ -2793,19 +2719,12 @@ show create view v1; drop view v1; drop table t1; - # # Test for Bug#16899 Possible buffer overflow in handling of DEFINER-clause. # # Prepare. ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; -DROP VIEW IF EXISTS v2; ---enable_warnings - CREATE TABLE t1(a INT, b INT); --error ER_WRONG_STRING_LENGTH @@ -2820,19 +2739,12 @@ CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890 DROP TABLE t1; - # # Bug#17591 Updatable view not possible with trigger or stored function # # During prelocking phase we didn't update lock type of view tables, # hence READ lock was always requested. # ---disable_warnings -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; ---enable_warnings CREATE TABLE t1 (i INT); @@ -2866,7 +2778,6 @@ DROP FUNCTION f2; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#5500 wrong select_type in EXPLAIN output for queries over views # @@ -2885,7 +2796,6 @@ EXPLAIN SELECT * FROM v1 t WHERE t.s1+1 < (SELECT MAX(t1.s1) FROM t1); DROP VIEW v1; DROP TABLE t1; - # # Bug#5505 Wrong error message on INSERT into a view # @@ -2897,7 +2807,6 @@ update v1 set a = 5; drop view v1; drop table t1; - # # Bug#21646 view qith a subquery in ON expression # @@ -2916,19 +2825,12 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1, t2; - # # Bug#19111 TRIGGERs selecting from a VIEW on the firing base table fail # # Allow to select from a view on a table being modified in a trigger # and stored function, since plain select is allowed there. # ---disable_warnings -DROP FUNCTION IF EXISTS f1; -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; ---enable_warnings - CREATE TABLE t1 (i INT); INSERT INTO t1 VALUES (1); @@ -2949,7 +2851,6 @@ DROP FUNCTION f1; DROP VIEW v1; DROP TABLE t1; - # # Bug#16813 (WITH CHECK OPTION doesn't work with UPDATE) # @@ -2964,7 +2865,6 @@ UPDATE v1 SET val=6 WHERE id=2; DROP VIEW v1; DROP TABLE t1; - # # Bug#22584 last_insert_id not updated after inserting a record # through a updatable view @@ -2973,11 +2873,6 @@ DROP TABLE t1; # not accessible through a view. However, we do not reset the value # of LAST_INSERT_ID, but keep it unchanged. # ---disable_warnings -DROP VIEW IF EXISTS v1, v2; -DROP TABLE IF EXISTS t1; ---enable_warnings - CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT); CREATE VIEW v1 AS SELECT j FROM t1; CREATE VIEW v2 AS SELECT * FROM t1; @@ -3002,7 +2897,6 @@ SELECT * FROM t1; DROP VIEW v1, v2; DROP TABLE t1; - # # Bug#25580 !0 as an operand in a select expression of a view # @@ -3015,15 +2909,9 @@ SELECT * FROM v; DROP VIEW v; - # # Bug#24293 '\Z' token is not handled correctly in views # - ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - CREATE VIEW v1 AS SELECT 'The\ZEnd'; SELECT * FROM v1; @@ -3031,7 +2919,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; - # # Bug#26124 BETWEEN over a view column of the DATETIME type # @@ -3048,7 +2935,6 @@ SELECT * FROM v1 WHERE mydate BETWEEN '2007-01-01' AND '2007-01-31'; DROP VIEW v1; DROP TABLE t1; - # # Bug#25931 update of a multi-table view with check option # @@ -3071,7 +2957,6 @@ SELECT * FROM t2; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#12122 Views with ORDER BY can't be resolved using MERGE algorithm. # @@ -3108,14 +2993,9 @@ SELECT code, COUNT(DISTINCT country) FROM v1 GROUP BY code ORDER BY MAX(id); DROP VIEW v1; DROP TABLE t1; - # # Bug#25897 Some queries are no longer possible after a CREATE VIEW fails # ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - let $query = SELECT * FROM (SELECT 1) AS t into @w; --disable_cursor_protocol @@ -3131,13 +3011,6 @@ eval $query; # # Bug#24532 The return data type of IS TRUE is different from similar operations # - ---disable_warnings -drop view if exists view_24532_a; -drop view if exists view_24532_b; -drop table if exists table_24532; ---enable_warnings - create table table_24532 ( a int, b bigint, @@ -3214,7 +3087,6 @@ drop view view_24532_a; drop view view_24532_b; drop table table_24532; - # # Bug#26560 view using subquery with a reference to an outer alias # @@ -3255,7 +3127,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP table t1,t2; - # # Bug#27786 Inconsistent Operation Performing UNION On View With ORDER BY # @@ -3272,7 +3143,6 @@ EXPLAIN SELECT * FROM t1 UNION SELECT * FROM v1 ORDER BY a; DROP VIEW v1; DROP TABLE t1; - # # Bug#27921 View ignores precision for CAST() # @@ -3285,7 +3155,6 @@ CREATE VIEW v1 AS SELECT CAST(1.23456789 AS DECIMAL(8,0)) AS col; SHOW CREATE VIEW v1; DROP VIEW v1; - # # Bug#28716 CHECK OPTION expression is evaluated over expired record buffers # when VIEW is updated via temporary tables @@ -3302,7 +3171,6 @@ SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#28561 update on multi-table view with CHECK OPTION and a subquery # in WHERE condition @@ -3322,7 +3190,6 @@ UPDATE v1 SET c=1; DROP VIEW v1; DROP TABLE t1,t2; - # # Bug#27827 CHECK OPTION ignores ON conditions when updating # a multi-table view with CHECK OPTION. @@ -3375,7 +3242,6 @@ SELECT * FROM t1; DROP VIEW v1,v2; DROP TABLE t1,t2,t3,t4; - # # Bug#29104 assertion abort for a query with a view column reference # in the GROUP BY list and a condition requiring the value @@ -3387,7 +3253,6 @@ INSERT INTO t1 VALUES (1,2), (2,2), (1,3), (1,2); CREATE VIEW v1 AS SELECT a, b+1 as b FROM t1; - SELECT b, SUM(a) FROM v1 WHERE b=3 GROUP BY b; EXPLAIN SELECT b, SUM(a) FROM v1 WHERE b=3 GROUP BY b; @@ -3400,7 +3265,6 @@ EXPLAIN SELECT a, SUM(b) FROM v1 WHERE a=1 GROUP BY a; DROP VIEW v1; DROP TABLE t1; - # # Bug#29392 SELECT over a multi-table view with ORDER BY # selecting the same view column with two different aliases @@ -3454,7 +3318,6 @@ SELECT t.person_id AS a, t.person_id AS b FROM v1 t WHERE t.person_id=6; DROP VIEW v1; DROP TABLE t1,t2,t3; - # # Bug#30020 Insufficient check led to a wrong info provided by the # information schema table. @@ -3468,7 +3331,6 @@ select table_name, is_updatable from information_schema.views drop view v1; drop table t1; - # # Bug#28701 SELECTs from VIEWs completely ignore USE/FORCE KEY, allowing # invalid statements @@ -3487,7 +3349,6 @@ SELECT * FROM v1 IGNORE KEY(non_existant); DROP VIEW v1; DROP TABLE t1; - # # Bug#28702 VIEWs defined with USE/FORCE KEY ignore that request # @@ -3509,7 +3370,6 @@ DROP VIEW v2; DROP VIEW v3; DROP TABLE t1; - --echo # --echo # Bug#29477 Not all fields of the target table were checked to have --echo # a default value when inserting into a view. @@ -3525,7 +3385,6 @@ set @@sql_mode=@old_mode; drop view v1; drop table t1; - # # Bug#33389 Selecting from a view into a table from within SP or trigger # crashes server @@ -3547,7 +3406,6 @@ execute stmt; drop view v1, v2; drop table t1, t2; - # # Bug#33049 Assert while running test-as3ap test(mysql-bench suite) # @@ -3566,54 +3424,29 @@ DROP TABLE t1; --echo # ----------------------------------------------------------------- --echo # -- Bug#34337 Server crash when Altering a view using a table name. --echo # ----------------------------------------------------------------- ---echo - ---disable_warnings -DROP TABLE IF EXISTS t1; ---enable_warnings - ---echo CREATE TABLE t1(c1 INT); - ---echo - SELECT * FROM t1; --error ER_WRONG_OBJECT ALTER ALGORITHM=TEMPTABLE SQL SECURITY INVOKER VIEW t1 (c2) AS SELECT (1); ---echo - DROP TABLE t1; ---echo ---echo # -- End of test case for Bug#34337. ---echo - ########################################################################### --echo # ----------------------------------------------------------------- --echo # -- Bug#35193 VIEW query is rewritten without "FROM DUAL", --echo # -- causing syntax error --echo # ----------------------------------------------------------------- ---echo CREATE VIEW v1 AS SELECT 1 FROM DUAL WHERE 1; ---echo - SELECT * FROM v1; SHOW CREATE TABLE v1; ---echo - DROP VIEW v1; ---echo ---echo # -- End of test case for Bug#35193. ---echo - ########################################################################### # @@ -3624,7 +3457,6 @@ DROP VIEW v1; CREATE VIEW v1 AS SELECT 1; DROP VIEW v1; - # # Bug#33461 SELECT ... FROM USE INDEX (...) throws an error # @@ -3714,9 +3546,6 @@ DROP TABLE t1; # # Bug#21370 View renaming lacks tablename_to_filename encoding # ---disable_warnings -DROP DATABASE IF EXISTS `d-1`; ---enable_warnings CREATE DATABASE `d-1`; USE `d-1`; CREATE TABLE `t-1` (c1 INT); @@ -3730,125 +3559,64 @@ DROP VIEW `v-2`; DROP DATABASE `d-1`; USE test; ---echo --echo # --echo # Bug#26676 VIEW using old table schema in a session. --echo # ---echo - ---disable_warnings -DROP VIEW IF EXISTS v1; -DROP TABLE IF EXISTS t1; ---enable_warnings CREATE TABLE t1(c1 INT, c2 INT); INSERT INTO t1 VALUES (1, 2), (3, 4); ---echo - SELECT * FROM t1; ---echo - CREATE VIEW v1 AS SELECT * FROM t1; ---echo - SELECT * FROM v1; ---echo - ALTER TABLE t1 ADD COLUMN c3 INT AFTER c2; ---echo - SELECT * FROM t1; ---echo - SELECT * FROM v1; ---echo - SHOW CREATE VIEW v1; ---echo - DROP VIEW v1; DROP TABLE t1; ---echo ---echo # End of test case for Bug#26676. ---echo - ########################################################################### --echo # ----------------------------------------------------------------- --echo # -- Bug#32538 View definition picks up character set, but not collation --echo # ----------------------------------------------------------------- ---echo - ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - ---echo SET collation_connection = latin1_general_ci; CREATE VIEW v1 AS SELECT _latin1 'text1' AS c1, 'text2' AS c2; ---echo - SELECT COLLATION(c1), COLLATION(c2) FROM v1; ---echo - SHOW CREATE VIEW v1; ---echo - --error ER_CANT_AGGREGATE_2COLLATIONS SELECT * FROM v1 WHERE c1 = 'text1'; ---echo - SELECT * FROM v1 WHERE c2 = 'text2'; ---echo - use test; SET names latin1; ---echo - SELECT COLLATION(c1), COLLATION(c2) FROM v1; ---echo - SELECT * FROM v1 WHERE c1 = 'text1'; ---echo - --error ER_CANT_AGGREGATE_2COLLATIONS SELECT * FROM v1 WHERE c2 = 'text2'; ---echo - DROP VIEW v1; ---echo ---echo # -- End of test case for Bug#32538. ---echo - # # Bug#34587 Creating a view inside a stored procedure leads to a server crash # ---disable_warnings -drop view if exists a; -drop procedure if exists p; ---enable_warnings - delimiter |; create procedure p() begin @@ -3863,7 +3631,6 @@ drop procedure p; ########################################################################### - --echo # --echo # Bug #44860: ALTER TABLE on view crashes server --echo # @@ -3879,12 +3646,6 @@ DROP TABLE t1; --echo # view contains function of view --echo # ---disable_warnings -DROP VIEW IF EXISTS v1,v2; -DROP TABLE IF EXISTS t1,t2; -DROP FUNCTION IF EXISTS f1; ---enable_warnings - CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); @@ -3912,7 +3673,6 @@ DROP VIEW v1,v2; DROP TABLE t1,t2; DROP FUNCTION f1; - # # Bug#48294 assertion when creating a view based on some row() construct in select query # @@ -4510,7 +4270,6 @@ SELECT * FROM t3 , t4 WHERE t4.c <= (SELECT t2.e FROM t2 LEFT JOIN t1 ON ( t1.a = t2.d ) WHERE t2.b > t4.b); - EXPLAIN EXTENDED SELECT * FROM t3, v4 WHERE v4.c <= (SELECT t2.e FROM t2 LEFT JOIN t1 ON ( t1.a = t2.d ) @@ -4527,11 +4286,6 @@ DROP TABLE t1,t2,t3,t4; # Bug#9801 (Views: imperfect error message) # ---disable_warnings -drop table if exists t_9801; -drop view if exists v_9801; ---enable_warnings - create table t_9801 (s1 int); --error ER_VIEW_NONUPD_CHECK @@ -4552,11 +4306,6 @@ drop table t_9801; --echo # Bug #47335 assert in get_table_share --echo # ---disable_warnings -DROP TABLE IF EXISTS t1; -DROP VIEW IF EXISTS v1; ---enable_warnings - CREATE TEMPORARY TABLE t1 (id INT); --error ER_NO_SUCH_TABLE ALTER VIEW t1 AS SELECT 1 AS f1; @@ -4569,17 +4318,11 @@ DROP TABLE v1; SELECT * FROM v1; DROP VIEW v1; - --echo # --echo # Bug #47635 assert in start_waiting_global_read_lock --echo # during CREATE VIEW --echo # ---disable_warnings -DROP TABLE IF EXISTS t1, t2; -DROP VIEW IF EXISTS t2; ---enable_warnings - CREATE TABLE t1 (f1 integer); CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); CREATE TEMPORARY TABLE t2 (f1 integer); @@ -4591,17 +4334,11 @@ CREATE VIEW t2 AS SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t1, t2; - --echo # --echo # Bug#48315 Metadata lock is not taken for merged views that --echo # use an INFORMATION_SCHEMA table --echo # ---disable_warnings -DROP VIEW IF EXISTS v1; -DROP PROCEDURE IF EXISTS p1; ---enable_warnings - connect (con2, localhost, root); connect (con3, localhost, root); @@ -4666,19 +4403,14 @@ DROP PROCEDURE p1; disconnect con2; disconnect con3; - --echo # --echo # Bug#12626844: WRONG ERROR MESSAGE WHILE CREATING A VIEW ON A --echo # NON EXISTING DATABASE --echo # ---disable_warnings -DROP DATABASE IF EXISTS nodb; ---enable_warnings --error ER_BAD_DB_ERROR CREATE VIEW nodb.a AS SELECT 1; - --echo # --echo # BUG#14117018 - MYSQL SERVER CREATES INVALID VIEW DEFINITION --echo # BUG#18405221 - SHOW CREATE VIEW OUTPUT INCORRECT @@ -4719,7 +4451,6 @@ SHOW CREATE VIEW v4; DROP VIEW v1, v2, v3, v4, v5; - --echo # --echo # BUG#19886430: VIEW CREATION WITH NAMED COLUMNS, OVER UNION, --echo # IS REJECTED @@ -4762,7 +4493,6 @@ CREATE VIEW v4 AS --echo # Cleanup DROP VIEW v1, v2, v3; - # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc @@ -4962,7 +4692,6 @@ SELECT * FROM ( drop tables t1,t2; - --echo # --echo # MDEV-3876 Wrong result (extra rows) with ALL subquery --echo # from a MERGE view (duplicate of MDEV-3873) @@ -5071,7 +4800,6 @@ insert into t1 values (1),(2); create view v1 (a,r) as select a,rand() from t1; - create table t2 select a, r as r1, r as r2, r as r3 from v1; select a, r1 = r2, r2 = r3 from t2; @@ -5168,7 +4896,6 @@ INSERT INTO t2 VALUES (4),(6); CREATE TABLE t3 (c INT) ENGINE=MyISAM; INSERT INTO t3 VALUES (1),(2); - CREATE ALGORITHM=MERGE VIEW v1 AS SELECT ( SELECT a FROM t1 WHERE ( 1, 1 ) IN ( SELECT b, c FROM t2, t3 HAVING c > 2 ) ) AS field1, @@ -5235,7 +4962,6 @@ DECLARE lResult INTEGER UNSIGNED DEFAULT 0; END| --delimiter ; - SELECT `f1`(1); SELECT `f1`(1); SELECT `f1`(1); @@ -5245,7 +4971,6 @@ DROP FUNCTION f1; DROP VIEW v1; DROP TABLE t1, t2; - create view v1 as select 1; --let SEARCH_FILE= $MYSQLD_DATADIR/test/v1.frm @@ -5356,7 +5081,6 @@ alter table v1 check partition p1; drop view v1; drop table t1; - --echo # --echo # MDEV-10419: crash in mariadb 10.1.16-MariaDB-1~trusty --echo # @@ -5860,7 +5584,6 @@ drop table t1, t2; --error ER_BAD_FIELD_ERROR SELECT 1 FROM (SELECT 1 as a) AS b HAVING (SELECT `SOME_GARBAGE`.b.a)=1; - --echo # --echo # MDEV-10035: DBUG_ASSERT on CREATE VIEW v1 AS SELECT * FROM t1 --echo # FOR UPDATE @@ -6104,7 +5827,6 @@ DROP VIEW v1; DROP TABLE t1; - --echo # --echo # MDEV-9408 CREATE TABLE SELECT MAX(int_column) creates different columns for table vs view --echo # @@ -6347,7 +6069,6 @@ REPLACE INTO v (f1,f2) VALUES (1,1); drop view v; drop table t1,t2,t3; - --echo # --echo # MDEV-12379: Server crashes in TABLE_LIST::is_with_table on --echo # SHOW CREATE VIEW @@ -6547,7 +6268,6 @@ drop view v1; eval CREATE VIEW v1 AS $definition; - drop view v1; --echo # @@ -6613,7 +6333,6 @@ set global table_definition_cache= @tdc, table_open_cache= @tc; --echo # End of 10.2 tests --echo # - --echo # --echo # Start of 10.3 tests --echo # @@ -6682,10 +6401,7 @@ CREATE VIEW v1 as select b''; SELECT * FROM v1; DROP VIEW v1; - ---echo # --echo # End of 10.3 tests ---echo # --echo # --echo # MDEV-25206: view specification contains unknown column reference @@ -6762,7 +6478,6 @@ SHOW CREATE VIEW v1; DROP VIEW v1; DROP TABLE t1; - CREATE TABLE t1 (i INT); CREATE VIEW v1 AS SELECT 1 FROM t1 UNION diff --git a/mysql-test/main/xml.result b/mysql-test/main/xml.result index 7f33997eebb..2fbc8dfc286 100644 --- a/mysql-test/main/xml.result +++ b/mysql-test/main/xml.result @@ -1414,13 +1414,6 @@ ExtractValue(@xml, '/employee/dt3') NULL Warnings: Warning 1525 Incorrect XML value: 'parse error at line 10 pos 27: unexpected END-OF-INPUT' -# -# MDEV-39750 ExtractValue does not control recursion depth. -# -SELECT ExtractValue('', REPEAT('(', 100000)); -ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. -SELECT ExtractValue('', REPEAT('/n', 100000)); -ERROR HY000: Thread stack overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed. Consider increasing the thread_stack system variable. # End of 10.6 tests # Start of 11.4 tests # diff --git a/mysql-test/main/xml.test b/mysql-test/main/xml.test index 2a56a075f33..d596c1b1730 100644 --- a/mysql-test/main/xml.test +++ b/mysql-test/main/xml.test @@ -899,18 +899,6 @@ set @xml= ' SELECT ExtractValue(@xml, '/employee/dt3'); ---echo # ---echo # MDEV-39750 ExtractValue does not control recursion depth. ---echo # - ---replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ ---error ER_STACK_OVERRUN_NEED_MORE -SELECT ExtractValue('', REPEAT('(', 100000)); - ---replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/ ---error ER_STACK_OVERRUN_NEED_MORE -SELECT ExtractValue('', REPEAT('/n', 100000)); - --echo # End of 10.6 tests --echo # Start of 11.4 tests diff --git a/mysql-test/std_data/binlog_invalid_row_v2_tag.001 b/mysql-test/std_data/binlog_invalid_row_v2_tag.001 new file mode 100644 index 00000000000..4b558372ea5 Binary files /dev/null and b/mysql-test/std_data/binlog_invalid_row_v2_tag.001 differ diff --git a/mysql-test/suite/archive/repair_text.result b/mysql-test/suite/archive/repair_text.result new file mode 100644 index 00000000000..67926d923cf --- /dev/null +++ b/mysql-test/suite/archive/repair_text.result @@ -0,0 +1,12 @@ +CREATE TABLE t1( +t TEXT NULL DEFAULT NULL +) COLLATE='utf8_general_ci' ENGINE=archive; +INSERT INTO t1(t) VALUES ('Testtext'); +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT t = 'Testtext' as Expect_1 from t1; +Expect_1 +1 +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/archive/repair_text.test b/mysql-test/suite/archive/repair_text.test new file mode 100644 index 00000000000..842b1edb201 --- /dev/null +++ b/mysql-test/suite/archive/repair_text.test @@ -0,0 +1,10 @@ +--source include/have_archive.inc +CREATE TABLE t1( + t TEXT NULL DEFAULT NULL +) COLLATE='utf8_general_ci' ENGINE=archive; +INSERT INTO t1(t) VALUES ('Testtext'); +REPAIR TABLE t1; +SELECT t = 'Testtext' as Expect_1 from t1; +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/mysql-test/suite/binlog/r/fdle_overflow.result b/mysql-test/suite/binlog/r/fdle_overflow.result new file mode 100644 index 00000000000..3fde805de82 --- /dev/null +++ b/mysql-test/suite/binlog/r/fdle_overflow.result @@ -0,0 +1,10 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= +'+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/mysql-test/suite/binlog/r/unknown_log_event.result b/mysql-test/suite/binlog/r/unknown_log_event.result new file mode 100644 index 00000000000..b3bbc14a6de --- /dev/null +++ b/mysql-test/suite/binlog/r/unknown_log_event.result @@ -0,0 +1,8 @@ +CREATE TABLE t (a INT); +INSERT INTO t VALUES (0); +DROP TABLE t; +FLUSH BINARY LOGS; +#mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql +#mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql diff --git a/mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 b/mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 new file mode 100644 index 00000000000..4b558372ea5 Binary files /dev/null and b/mysql-test/suite/binlog/std_data/invalid_row_v2_tag.001 differ diff --git a/mysql-test/suite/binlog/t/fdle_overflow.test b/mysql-test/suite/binlog/t/fdle_overflow.test new file mode 100644 index 00000000000..abaf143d515 --- /dev/null +++ b/mysql-test/suite/binlog/t/fdle_overflow.test @@ -0,0 +1,38 @@ +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # format-agnostic + +# OOB read on malformed `Format_description_log_event` +# +# Verify that Format Description Events with truncated contents fails gracefully +# rather than underflow the post-header count and lead to buffer over-read. + +SET @saved_dbug= @@GLOBAL.debug_dbug; + + +# MDEV-40366: `used_checksum_alg` + +SET @@GLOBAL.debug_dbug= + '+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + + +# MDEV-40365: `common_header_len` & `post_header_len` + +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + +# Clean-up +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/mysql-test/suite/binlog/t/unknown_log_event.test b/mysql-test/suite/binlog/t/unknown_log_event.test new file mode 100644 index 00000000000..267d107816b --- /dev/null +++ b/mysql-test/suite/binlog/t/unknown_log_event.test @@ -0,0 +1,37 @@ +# MDEV-40674: Test `mariadb-binlog --force`'s Unknown event output +# +# Also: MDEV-40542 +# MSAN use-of-uninitialized-value on Unknown_log_event::read_checksum_alg + +--source include/have_debug.inc +--source include/have_binlog_format_row.inc # Testing with Table Map event + +# Setup +CREATE TABLE t (a INT); +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) + +# Generate an invalid Table Map event followed by an otherwise-valid Rows Event +INSERT INTO t VALUES (0); + +--let $binlog_stop= query_get_value(SHOW BINLOG STATUS, Position, 1) +DROP TABLE t; +FLUSH BINARY LOGS; + + +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/unknown_log_event.sql +--let SEARCH_PATTERN= CRC32 0x[[:xdigit:]]{8}.*\\n# [Uu]nknown [Ee]vent +--let $mariadb_binlog= $MYSQL_BINLOG --result-file=$SEARCH_FILE --verify-binlog-checksum --force-read +--let $binlog_file= `SELECT CONCAT(@@datadir, '/$binlog_file')` + +--echo #mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=$binlog_start --stop-position=$binlog_stop $binlog_file 2>&1 +--source include/search_pattern_in_file.inc + +# Test checksum failure +# +# Only test the first event since the rest of the binlog is unreadable +# without a valid Format Description, which is typically the first event. +--echo #mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 $binlog_file 2>&1 +--source include/search_pattern_in_file.inc diff --git a/mysql-test/suite/binlog_encryption/rpl_corruption.result b/mysql-test/suite/binlog_encryption/rpl_corruption.result index 7c60e15a567..e3501d29a57 100644 --- a/mysql-test/suite/binlog_encryption/rpl_corruption.result +++ b/mysql-test/suite/binlog_encryption/rpl_corruption.result @@ -11,6 +11,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; # 1. Creating test table/data and set corruption position for testing connection master; diff --git a/mysql-test/suite/compat/oracle/r/sp-package-security.result b/mysql-test/suite/compat/oracle/r/sp-package-security.result index c08b78cb8cf..c11589a0ea4 100644 --- a/mysql-test/suite/compat/oracle/r/sp-package-security.result +++ b/mysql-test/suite/compat/oracle/r/sp-package-security.result @@ -320,3 +320,26 @@ SESSION_USER() CURRENT_USER() msg root@localhost root@localhost p1.p1 DROP PACKAGE p1; DROP USER xxx@localhost; +# +# MDEV-40722 DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv +# +CREATE PACKAGE pkg1 AS +FUNCTION f RETURN VARCHAR2(64); +END +$$ +CREATE PACKAGE BODY pkg1 AS +FUNCTION f RETURN VARCHAR2(64) AS +BEGIN RETURN 'f'; +END; +END$$ +CREATE USER reader@localhost; +GRANT EXECUTE ON PACKAGE test.pkg1 TO reader@localhost; +GRANT EXECUTE ON PACKAGE BODY test.pkg1 TO reader@localhost; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +host db user routine_name routine_type proc_priv +localhost test reader pkg1 PACKAGE Execute +localhost test reader pkg1 PACKAGE BODY Execute +DROP PACKAGE pkg1; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +host db user routine_name routine_type proc_priv +DROP USER reader@localhost; diff --git a/mysql-test/suite/compat/oracle/t/sp-package-security.test b/mysql-test/suite/compat/oracle/t/sp-package-security.test index 583f70afe14..20a4fddb5b4 100644 --- a/mysql-test/suite/compat/oracle/t/sp-package-security.test +++ b/mysql-test/suite/compat/oracle/t/sp-package-security.test @@ -330,3 +330,28 @@ DELIMITER ;$$ CALL p1.p1; DROP PACKAGE p1; DROP USER xxx@localhost; + + +--echo # +--echo # MDEV-40722 DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv +--echo # + +DELIMITER $$; +CREATE PACKAGE pkg1 AS + FUNCTION f RETURN VARCHAR2(64); +END +$$ +CREATE PACKAGE BODY pkg1 AS + FUNCTION f RETURN VARCHAR2(64) AS + BEGIN RETURN 'f'; + END; +END$$ +DELIMITER ;$$ + +CREATE USER reader@localhost; +GRANT EXECUTE ON PACKAGE test.pkg1 TO reader@localhost; +GRANT EXECUTE ON PACKAGE BODY test.pkg1 TO reader@localhost; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +DROP PACKAGE pkg1; +SELECT host,db,user,routine_name,routine_type,proc_priv FROM mysql.procs_priv ORDER BY routine_type; +DROP USER reader@localhost; diff --git a/mysql-test/suite/csv/delete.result b/mysql-test/suite/csv/delete.result new file mode 100644 index 00000000000..73dc585fc26 --- /dev/null +++ b/mysql-test/suite/csv/delete.result @@ -0,0 +1,11 @@ +# +# MDEV-40636 CSV crashes on DELETE +# +create table t1 (id int not null, pad char(10) not null) engine=csv; +insert t1 select seq,'AAAAAAAAAA' from seq_1_to_2048; +delete from t1 where id % 2 = 0; +select count(*), sum(id) from t1; +count(*) sum(id) +1024 1048576 +drop table t1; +# End of 10.6 tests diff --git a/mysql-test/suite/csv/delete.test b/mysql-test/suite/csv/delete.test new file mode 100644 index 00000000000..2b4b1eea6c9 --- /dev/null +++ b/mysql-test/suite/csv/delete.test @@ -0,0 +1,13 @@ +--source include/have_csv.inc +--source include/have_sequence.inc + +--echo # +--echo # MDEV-40636 CSV crashes on DELETE +--echo # +create table t1 (id int not null, pad char(10) not null) engine=csv; +insert t1 select seq,'AAAAAAAAAA' from seq_1_to_2048; +delete from t1 where id % 2 = 0; +select count(*), sum(id) from t1; +drop table t1; + +--echo # End of 10.6 tests diff --git a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result index 7e244c2c381..5dcf1857c44 100644 --- a/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result +++ b/mysql-test/suite/encryption/r/filekeys_encfile_badfile.result @@ -1,9 +1,46 @@ -call mtr.add_suppression("File 'bad' not found"); +call mtr.add_suppression("File 'bad.key' not found"); call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); -FOUND 1 /File 'bad' not found/ in mysqld.1.err +FOUND 1 /File 'bad.key' not found/ in mysqld.1.err create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") select plugin_status from information_schema.plugins where plugin_name = 'file_key_management'; plugin_status +install soname 'file_key_management'; +ERROR HY000: Cannot decrypt MTR_SUITE_DIR/t/filekeys-data.enc. Wrong key? +call mtr.add_suppression("Cannot decrypt .*filekeys-data.enc. Wrong key"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /Cannot decrypt .*filekeys-data.enc. Wrong key/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +install soname 'file_key_management'; +ERROR HY000: Cannot read bad.key, the filekey is too long, max secret size is 256B +call mtr.add_suppression("the filekey is too long"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /the filekey is too long/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +# +# MDEV-40658 file_key_management crash on empty FILE: file +# +install soname 'file_key_management'; +ERROR HY000: Cannot decrypt MTR_SUITE_DIR/t/filekeys-data.enc. Wrong key? +call mtr.add_suppression("Cannot decrypt .*filekeys-data.enc. Wrong key"); +call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); +call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); +FOUND 1 /Cannot decrypt .*filekeys-data.enc. Wrong key/ in mysqld.1.err +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") +select plugin_status from information_schema.plugins +where plugin_name = 'file_key_management'; +plugin_status +# End of 10.6 tests diff --git a/mysql-test/suite/encryption/r/filekeys_secret_too_long.result b/mysql-test/suite/encryption/r/filekeys_secret_too_long.result deleted file mode 100644 index bd11e8d925e..00000000000 --- a/mysql-test/suite/encryption/r/filekeys_secret_too_long.result +++ /dev/null @@ -1,10 +0,0 @@ -call mtr.add_suppression("the filekey is too long"); -call mtr.add_suppression("Plugin 'file_key_management' init function returned error"); -call mtr.add_suppression("Plugin 'file_key_management' registration.*failed"); -FOUND 1 /the filekey is too long/ in mysqld.1.err -create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; -ERROR HY000: Can't create table `test`.`t1` (errno: 140 "Wrong create options") -select plugin_status from information_schema.plugins -where plugin_name = 'file_key_management'; -plugin_status -# Test checks if opening an too large secret does not crash the server. diff --git a/mysql-test/suite/encryption/t/filekeys-data-too-long.key b/mysql-test/suite/encryption/t/filekeys-data-too-long.key deleted file mode 100644 index ba1624fb324..00000000000 --- a/mysql-test/suite/encryption/t/filekeys-data-too-long.key +++ /dev/null @@ -1,4 +0,0 @@ -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret -secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret - diff --git a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt index 374a41395b8..713ce507eac 100644 --- a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt +++ b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.opt @@ -1,2 +1,2 @@ ---loose-file-key-management-filekey=FILE:bad +--loose-file-key-management-filekey=FILE:bad.key --loose-file-key-management-filename=$MTR_SUITE_DIR/t/filekeys-data.enc diff --git a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test index e0b07ac62b1..6af2e1c2f01 100644 --- a/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test +++ b/mysql-test/suite/encryption/t/filekeys_encfile_badfile.test @@ -1,2 +1,51 @@ -let SEARCH_PATTERN=File 'bad' not found; -source filekeys_badtest.inc; +# +# first test: no file +# +--let SEARCH_PATTERN=File 'bad.key' not found +--source filekeys_badtest.inc + +# +# second test: wrong key +# +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key +wrong key +EOF +--replace_result $MTR_SUITE_DIR MTR_SUITE_DIR +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=Cannot decrypt .*filekeys-data.enc. Wrong key +--source filekeys_badtest.inc +--remove_file $datadir/bad.key + +# +# third test: too long key +# +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret +EOF +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=the filekey is too long +--source filekeys_badtest.inc +--remove_file $datadir/bad.key + +--echo # +--echo # MDEV-40658 file_key_management crash on empty FILE: file +--echo # +--let $datadir=`select @@datadir` +--write_file $datadir/bad.key + + +EOF +--replace_result $MTR_SUITE_DIR MTR_SUITE_DIR +--error 2 +install soname 'file_key_management'; +--let SEARCH_PATTERN=Cannot decrypt .*filekeys-data.enc. Wrong key +--source filekeys_badtest.inc +--remove_file $datadir/bad.key + +--echo # End of 10.6 tests diff --git a/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt b/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt deleted file mode 100644 index c3f95019f2a..00000000000 --- a/mysql-test/suite/encryption/t/filekeys_secret_too_long.opt +++ /dev/null @@ -1,3 +0,0 @@ ---loose-file-key-management-filekey=FILE:$MTR_SUITE_DIR/t/filekeys-data-too-long.key ---loose-file-key-management-filename=$MTR_SUITE_DIR/t/filekeys-data.enc - diff --git a/mysql-test/suite/encryption/t/filekeys_secret_too_long.test b/mysql-test/suite/encryption/t/filekeys_secret_too_long.test deleted file mode 100644 index 0032e94de37..00000000000 --- a/mysql-test/suite/encryption/t/filekeys_secret_too_long.test +++ /dev/null @@ -1,4 +0,0 @@ -let SEARCH_PATTERN=the filekey is too long; -source filekeys_badtest.inc; - ---echo # Test checks if opening an too large secret does not crash the server. diff --git a/mysql-test/suite/galera/galera_2nodes.cnf b/mysql-test/suite/galera/galera_2nodes.cnf index 072664c4b34..cec70c87fcf 100644 --- a/mysql-test/suite/galera/galera_2nodes.cnf +++ b/mysql-test/suite/galera/galera_2nodes.cnf @@ -34,6 +34,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.2.port wsrep_sst_receive_address='127.0.0.1:@mysqld.2.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_master.cnf b/mysql-test/suite/galera/galera_2nodes_as_master.cnf index 0dd1182bb56..26f9a5c43a8 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_master.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_master.cnf @@ -48,6 +48,8 @@ wsrep-on=OFF server-id=3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf b/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf index 570eeda0bc6..32f02ad0876 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_replica_2primary.cnf @@ -54,6 +54,8 @@ gtid_domain_id=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2nodes_as_slave.cnf b/mysql-test/suite/galera/galera_2nodes_as_slave.cnf index c4a91e4650d..2a10e60afe9 100644 --- a/mysql-test/suite/galera/galera_2nodes_as_slave.cnf +++ b/mysql-test/suite/galera/galera_2nodes_as_slave.cnf @@ -47,6 +47,8 @@ wsrep-on=OFF server-id=3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_2x2nodes.cnf b/mysql-test/suite/galera/galera_2x2nodes.cnf index ac6b08d9bb2..1814afed871 100644 --- a/mysql-test/suite/galera/galera_2x2nodes.cnf +++ b/mysql-test/suite/galera/galera_2x2nodes.cnf @@ -61,6 +61,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.4.port wsrep_sst_receive_address='127.0.0.1:@mysqld.4.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_3nodes_as_slave.cnf b/mysql-test/suite/galera/galera_3nodes_as_slave.cnf index 00bb137f076..cd13c8a5fa0 100644 --- a/mysql-test/suite/galera/galera_3nodes_as_slave.cnf +++ b/mysql-test/suite/galera/galera_3nodes_as_slave.cnf @@ -60,6 +60,8 @@ wsrep-on=OFF server-id=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/galera_4nodes.cnf b/mysql-test/suite/galera/galera_4nodes.cnf index 3d1278a24cf..e8bd79aa6f3 100644 --- a/mysql-test/suite/galera/galera_4nodes.cnf +++ b/mysql-test/suite/galera/galera_4nodes.cnf @@ -61,6 +61,8 @@ wsrep_sst_receive_address='127.0.0.1:@mysqld.4.#sst_port' auto-increment-offset=4 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera/include/have_pkill.inc b/mysql-test/suite/galera/include/have_pkill.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_pkill.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/r/galera_bf_abort_orphan_lock.result b/mysql-test/suite/galera/r/galera_bf_abort_orphan_lock.result index b75ff658e3f..994abfff289 100644 --- a/mysql-test/suite/galera/r/galera_bf_abort_orphan_lock.result +++ b/mysql-test/suite/galera/r/galera_bf_abort_orphan_lock.result @@ -11,7 +11,7 @@ SELECT * FROM t_b WHERE id = 1 FOR UPDATE; id c 1 0 connect node_1_victim, 127.0.0.1, root, , test, $NODE_MYPORT_1; -SET SESSION tx_isolation = 'READ-COMMITTED'; +SET SESSION transaction_isolation = 'READ-COMMITTED'; BEGIN; UPDATE t_a SET c = c + 1 WHERE id > 0; connection node_1; diff --git a/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff b/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff deleted file mode 100644 index 098ce2b28a1..00000000000 --- a/mysql-test/suite/galera/r/galera_bf_kill,debug.rdiff +++ /dev/null @@ -1,37 +0,0 @@ ---- r/galera_bf_kill.result -+++ r/galera_bf_kill,debug.reject -@@ -77,4 +77,34 @@ a b - 5 2 - disconnect node_2a; - connection node_1; -+connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2; -+connection node_2a; -+truncate t1; -+insert into t1 values (7,0); -+connection node_2; -+set wsrep_sync_wait=0; -+begin; -+update t1 set b=2 where a=7; -+connect node_2b, 127.0.0.1, root, , test, $NODE_MYPORT_2; -+set wsrep_sync_wait=0; -+SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb"; -+connection node_1; -+update t1 set b=1 where a=7; -+connection node_2b; -+SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; -+connection node_2; -+connection node_2b; -+SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; -+connection node_2; -+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -+commit; -+select * from t1; -+a b -+7 1 -+connection node_2a; -+SET DEBUG_SYNC= 'RESET'; -+SET GLOBAL debug_dbug = ""; -+disconnect node_2a; -+disconnect node_2b; -+connection node_1; - drop table t1; diff --git a/mysql-test/suite/galera/r/galera_bf_kill.result b/mysql-test/suite/galera/r/galera_bf_kill.result index e6ddf3fc3c5..537a6d48835 100644 --- a/mysql-test/suite/galera/r/galera_bf_kill.result +++ b/mysql-test/suite/galera/r/galera_bf_kill.result @@ -61,9 +61,6 @@ ALTER TABLE t1 ADD UNIQUE KEY b3(b); connection node_2b; SET SESSION wsrep_sync_wait=0; connection node_2a; -select * from t1; -a b -5 2 commit; connection node_2; disconnect node_2a; @@ -77,6 +74,6 @@ connection node_2; select * from t1; a b 5 2 -disconnect node_2a; connection node_1; +disconnect node_2a; drop table t1; diff --git a/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result b/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result index 9d7ea473241..5f63c971ec9 100644 --- a/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result +++ b/mysql-test/suite/galera/r/galera_log_bin_ext_mariabackup.result @@ -58,8 +58,6 @@ SELECT COUNT(*) = 2 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 't1'; COUNT(*) = 2 1 include/show_binlog_events.inc -Log_name Pos Event_type Server_id End_log_pos Info -hostname1-bin.000002 # Binlog_checkpoint # # hostname1-bin.000002 DROP TABLE t1; DROP TABLE t2; #cleanup diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result new file mode 100644 index 00000000000..0424988061e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_missing_ssl.result @@ -0,0 +1,27 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_1; +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +connection node_2; +# Force a fresh SST on node_2 +# Start node_2 with SSL cert/key simulated as missing +# node_2 failed to start, as expected +# Joiner refused the SST instead of running unencrypted +FOUND 1 /ssl-mode is set to .REQUIRED., but no usable SSL/ in mysqld.2.err +# Restart node_2 normally so it rejoins +connection node_2; +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); +connection node_1; diff --git a/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result b/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result new file mode 100644 index 00000000000..771d6e0fd75 --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_rsync_missing_stunnel.result @@ -0,0 +1,27 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_1; +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +connection node_2; +# Force a fresh SST on node_2 +# Start node_2 with stunnel simulated as missing +# node_2 failed to start, as expected +# Joiner refused the SST instead of running unencrypted +FOUND 1 /ssl-mode is set to .REQUIRED., but the .stunnel. binary was not found/ in mysqld.2.err +# Restart node_2 normally so it rejoins +connection node_2; +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); +connection node_1; diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index 612a8bfa595..b4fd4c62f06 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -80,6 +80,10 @@ sub skip_combinations { unless which("lsof") || which("sockstat") || which("ss"); $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" unless which("stunnel"); + # 'mariabackup' combination doesn't need stunnel: only 'rsync' SST goes + # through it. + $skip{'t/galera_sst_cn_injection.combinations'} = [ 'rsync' ] + unless which("stunnel"); $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" unless which("qpress"); $skip{'../encryption/include/have_file_key_management_plugin.combinations'} = [ 'ctr' ] @@ -93,6 +97,9 @@ sub skip_combinations { $skip{'t/galera_sst_cn_injection.test'} = 'does not work with OpenSSL <= 1.1.1' unless $openssl_ver ge "3.0.0"; + $skip{'include/have_pkill.inc'} = "Need 'pkill' utility" + unless which("pkill"); + %skip; } diff --git a/mysql-test/suite/galera/t/galera_bf_abort_orphan_lock.test b/mysql-test/suite/galera/t/galera_bf_abort_orphan_lock.test index 303d272dcbc..65eb4c8bd3c 100644 --- a/mysql-test/suite/galera/t/galera_bf_abort_orphan_lock.test +++ b/mysql-test/suite/galera/t/galera_bf_abort_orphan_lock.test @@ -34,7 +34,7 @@ SELECT * FROM t_b WHERE id = 1 FOR UPDATE; # Victim: holds a granted lock on t_a (makes it a BF target), then locks t_b --connect node_1_victim, 127.0.0.1, root, , test, $NODE_MYPORT_1 -SET SESSION tx_isolation = 'READ-COMMITTED'; +SET SESSION transaction_isolation = 'READ-COMMITTED'; BEGIN; UPDATE t_a SET c = c + 1 WHERE id > 0; diff --git a/mysql-test/suite/galera/t/galera_bf_kill.test b/mysql-test/suite/galera/t/galera_bf_kill.test index b59eef11cd3..5cf98e9fb75 100644 --- a/mysql-test/suite/galera/t/galera_bf_kill.test +++ b/mysql-test/suite/galera/t/galera_bf_kill.test @@ -97,8 +97,8 @@ select * from t1; # # Test case 5: Start a transaction on node_2a with wsrep disabled. -# A conflicting DDL on other transaction can't BF abort -# transaction from node_2a (wsrep disabled). +# A conflicting DDL on other transaction can BF abort +# transaction from node_2a. # --connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 @@ -117,10 +117,6 @@ SET SESSION wsrep_sync_wait=0; --source include/wait_condition.inc --connection node_2a -select * from t1; - -# We expect that ALTER should not be able to BF abort -# this transaction, it must wait for it to finish. # Expect commit to succeed. commit; @@ -153,80 +149,7 @@ update t1 set a =5, b=2; select * from t1; +--connection node_1 --disconnect node_2a ---connection node_1 - -source include/maybe_debug.inc; -if ($have_debug) { -# -# Test case 7: Start a transaction on node_2 and use KILL to abort -# a query in connection node_2a -# During the KILL execution replicate conflicting transaction from node_1 -# to BF abort the transaction executing the KILL -# - ---connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 ---connection node_2a -truncate t1; -insert into t1 values (7,0); - ---connection node_2 -set wsrep_sync_wait=0; - -# get the ID of connection to be later killed ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1 ---source include/wait_condition.inc ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# start a transaction -begin; -update t1 set b=2 where a=7; - -# set sync point for incoming applying ---connect node_2b, 127.0.0.1, root, , test, $NODE_MYPORT_2 -set wsrep_sync_wait=0; - -SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb"; - -# replicate conflicting transaction, should stopp in the sync point ---connection node_1 -update t1 set b=1 where a=7; - -# wait for the applier to reach the sync point ---connection node_2b -SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; - -# issue KILL inside the transacion, implicit commit is expected ---connection node_2 ---disable_query_log ---send_eval KILL QUERY $k_thread ---enable_query_log - -# wait for the KILL processing to be seen in processlist ---connection node_2b ---let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND INFO LIKE 'KILL QUERY%' ---source include/wait_condition.inc - -# resume applying, BF abort should follow -SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; - ---connection node_2 ---error ER_LOCK_DEADLOCK ---reap - -commit; - -select * from t1; - ---connection node_2a -SET DEBUG_SYNC= 'RESET'; -SET GLOBAL debug_dbug = ""; - ---disconnect node_2a ---disconnect node_2b - ---connection node_1 -} - drop table t1; diff --git a/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test b/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test index d9f7bb152fc..fb16ba7f8d3 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test +++ b/mysql-test/suite/galera/t/galera_ist_rsync_verify_ca.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_innodb.inc +--source include/have_stunnel.inc --let $node_1=node_1 --let $node_2=node_2 diff --git a/mysql-test/suite/galera/t/galera_sst_cn_injection.test b/mysql-test/suite/galera/t/galera_sst_cn_injection.test index 9b8058267e8..b970950a0a5 100644 --- a/mysql-test/suite/galera/t/galera_sst_cn_injection.test +++ b/mysql-test/suite/galera/t/galera_sst_cn_injection.test @@ -10,6 +10,7 @@ --source include/have_mariabackup.inc --source include/have_ssl_communication.inc --source include/have_openssl.inc +--source include/have_pkill.inc # This test asserts that the initial cluster-bootstrap SST was SSL-encrypted by # grepping the donor's error log. That is a one-time event, so the cluster must # be freshly bootstrapped on every run (e.g. with --repeat) for node_2 to SST @@ -56,7 +57,8 @@ call mtr.add_suppression("WSREP: Illegal character in variable:"); --echo # cleanup # we have to kill joiner's socat here, because the donor has aborted SST # and joiner's socat will timeout in 5 minutes -# pkill exit code varies by platform 0 or 1 +# pkill below might return 0 = success or 1 in error when it does not find socat +# anymore --error 0,1,15 --exec pkill -f 'socat.*[s]erver-new-cert' --exec echo ssl-cert=$MYSQL_TEST_DIR/std_data/server-cert.pem >> $MYSQLTEST_VARDIR/my.cnf diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf new file mode 100644 index 00000000000..5c9ea2149a3 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.cnf @@ -0,0 +1,11 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +ssl-cert=@ENV.MYSQL_TEST_DIR/std_data/server-cert.pem +ssl-key=@ENV.MYSQL_TEST_DIR/std_data/server-key.pem +ssl-ca=@ENV.MYSQL_TEST_DIR/std_data/cacert.pem + +[sst] +ssl-mode=REQUIRED diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test new file mode 100644 index 00000000000..a636634cd50 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_missing_ssl.test @@ -0,0 +1,69 @@ +# MDEV-28233: with ssl-mode requiring encryption but no usable cert/key, +# wsrep_sst_mariabackup used to fall back to an unencrypted transfer +# WITHOUT any error or warning. Before the fix the joiner only logged the +# informational line: +# WSREP_SST: [INFO] SSL configuration: ... MODE='REQUIRED' ... encrypt='0' +# and then opened a cleartext socket. +# +# Steps: +# 1. Form a 2-node cluster with ssl-mode=REQUIRED and SSL cert/key. +# 2. Shut node_2 down and force a fresh SST. +# 3. Restart node_2 with cert/key simulated as missing +# (MTR_SST_SIMULATE_NO_SSL_CERT=1). +# 4. Check the joiner refuses the SST and logs the error. +# 5. Restart node_2 normally so it rejoins. +# +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +# Save auto_increment_offset values. +--let $node_1=node_1 +--let $node_2=node_2 +--source include/auto_increment_offset_save.inc + +--connection node_1 +# Donor-side noise when the joiner aborts the SST. +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); + +--connection node_2 +--source include/shutdown_mysqld.inc + +--echo # Force a fresh SST on node_2 +--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat + +--echo # Start node_2 with SSL cert/key simulated as missing +# Abort exit code varies by platform. +--error 1,134 +--exec MTR_SST_SIMULATE_NO_SSL_CERT=1 $MYSQLD_LAST_CMD +--echo # node_2 failed to start, as expected + +--echo # Joiner refused the SST instead of running unencrypted +--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let SEARCH_PATTERN = ssl-mode is set to .REQUIRED., but no usable SSL +--source include/search_pattern_in_file.inc + +--echo # Restart node_2 normally so it rejoins +--source include/start_mysqld.inc +--source include/wait_until_connected_again.inc + +--connection node_2 +# Joiner-side noise from the failed SST attempt. +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--source include/auto_increment_offset_restore.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf new file mode 100644 index 00000000000..39fbab6cb5c --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.cnf @@ -0,0 +1,10 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=rsync +ssl-cert=@ENV.MYSQL_TEST_DIR/std_data/server-cert.pem +ssl-key=@ENV.MYSQL_TEST_DIR/std_data/server-key.pem +ssl-ca=@ENV.MYSQL_TEST_DIR/std_data/cacert.pem + +[sst] +ssl-mode=REQUIRED diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test new file mode 100644 index 00000000000..1310c27c6db --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_missing_stunnel.test @@ -0,0 +1,66 @@ +# MDEV-28233: the rsync SST script must NOT silently fall back to an +# unencrypted transfer when encryption is requested (ssl-mode is set to a +# value other than DISABLED) but the stunnel binary is not installed. +# +# Steps: +# 1. Form a 2-node cluster with ssl-mode=REQUIRED (stunnel present). +# 2. Shut node_2 down and force a fresh SST. +# 3. Restart node_2 with stunnel hidden (MTR_SST_SIMULATE_NO_STUNNEL=1). +# 4. Check the joiner refuses the SST and logs the error. +# 5. Restart node_2 normally so it rejoins. +# +--source include/galera_cluster.inc +--source include/have_innodb.inc +# Needed for the initial SST; only simulated as missing below. +--source include/have_stunnel.inc + +# Save auto_increment_offset values. +--let $node_1=node_1 +--let $node_2=node_2 +--source include/auto_increment_offset_save.inc + +--connection node_1 +# Donor-side noise when the joiner aborts the SST. +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); + +--connection node_2 +--source include/shutdown_mysqld.inc + +--echo # Force a fresh SST on node_2 +--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat + +--echo # Start node_2 with stunnel simulated as missing +# Abort exit code +--error 1,134 +--exec MTR_SST_SIMULATE_NO_STUNNEL=1 $MYSQLD_LAST_CMD +--echo # node_2 failed to start, as expected + +--echo # Joiner refused the SST instead of running unencrypted +--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let SEARCH_PATTERN = ssl-mode is set to .REQUIRED., but the .stunnel. binary was not found +--source include/search_pattern_in_file.inc + +--echo # Restart node_2 normally so it rejoins +--source include/start_mysqld.inc +--source include/wait_until_connected_again.inc + +--connection node_2 +# Joiner-side noise from the failed SST attempt. +call mtr.add_suppression("WSREP_SST:"); +call mtr.add_suppression("WSREP: Process completed with error:"); +call mtr.add_suppression("WSREP: Failed to read .ready ."); +call mtr.add_suppression("WSREP: Failed to read uuid:seqno and wsrep_gtid_domain_id from joiner script."); +call mtr.add_suppression("WSREP: Failed to read state from: .*"); +call mtr.add_suppression("WSREP: Failed to prepare for .*"); +call mtr.add_suppression("WSREP: SST failed:.*"); +call mtr.add_suppression("WSREP: SST request callback failed. This is unrecoverable, restart required."); +call mtr.add_suppression("WSREP: .*State transfer to.* failed:"); +call mtr.add_suppression("WSREP: Will never receive state. Need to abort."); +call mtr.add_suppression("WSREP: Requesting state transfer failed:.*"); + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--source include/auto_increment_offset_restore.inc diff --git a/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf b/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf index e63035ac6bf..1f638b2008d 100644 --- a/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf +++ b/mysql-test/suite/galera_3nodes/galera_2x3nodes.cnf @@ -85,6 +85,8 @@ wsrep_node_incoming_address=127.0.0.1:@mysqld.6.port wsrep_sst_receive_address='127.0.0.1:@mysqld.6.#sst_port' [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log [ENV] diff --git a/mysql-test/suite/galera_3nodes/galera_3nodes.cnf b/mysql-test/suite/galera_3nodes/galera_3nodes.cnf index fb6b2b6fc1c..21c36427c54 100644 --- a/mysql-test/suite/galera_3nodes/galera_3nodes.cnf +++ b/mysql-test/suite/galera_3nodes/galera_3nodes.cnf @@ -50,6 +50,8 @@ wsrep_sst_receive_address='127.0.0.1:@mysqld.3.#sst_port' wsrep_node_name=node3 [sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED sst-log-archive-dir=@ENV.MYSQLTEST_VARDIR/log transferfmt=@ENV.MTR_GALERA_TFMT diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result new file mode 100644 index 00000000000..2150b39cdec --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147.result @@ -0,0 +1,52 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +# gtid_strict_mode must be enabled on all nodes +SELECT @@global.gtid_strict_mode AS gtid_strict_mode; +gtid_strict_mode +1 +connection node_1; +connection node_2; +connection node_3; +connection node_3; +connection node_1; +connection node_1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL debug_dbug = '+d,sync.after_mdl_block_ddl'; +connection node_1; +SET DEBUG_SYNC = 'now WAIT_FOR sync.after_mdl_block_ddl_reached'; +connect node_1_freeze, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1_freeze; +SET DEBUG_SYNC = 'commit_before_get_LOCK_commit_ordered SIGNAL t_frozen WAIT_FOR t_go'; +INSERT INTO t1 (val) VALUES (1); +connection node_1; +SET DEBUG_SYNC = 'now WAIT_FOR t_frozen'; +SET DEBUG_SYNC = 'now SIGNAL signal.after_mdl_block_ddl_continue'; +SET DEBUG_SYNC = 'now SIGNAL t_go'; +connection node_1_freeze; +connection node_1; +SET DEBUG_SYNC = 'RESET'; +SET GLOBAL debug_dbug = ''; +connection node_1; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +DROP TABLE t1; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result new file mode 100644 index 00000000000..e62da7e59db --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147_binlogdir.result @@ -0,0 +1,20 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); +connection node_3; +connection node_3; +connection node_1; +connection node_1; +connection node_3; +SELECT COUNT(*) AS rows_on_joiner FROM t1; +rows_on_joiner +3 +connection node_1; +DROP TABLE t1; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-40179.result b/mysql-test/suite/galera_3nodes/r/MDEV-40179.result new file mode 100644 index 00000000000..9d8d43bbd73 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-40179.result @@ -0,0 +1,47 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection n1_load_1; +CALL p_load('t1_1'); +connection n2_load_1; +CALL p_load('t1_5'); +connection n1_load_2; +CALL p_load('t1_2'); +connection n2_load_2; +CALL p_load('t1_6'); +connection n1_load_3; +CALL p_load('t1_3'); +connection n2_load_3; +CALL p_load('t1_7'); +connection n1_load_4; +CALL p_load('t1_4'); +connection n2_load_4; +CALL p_load('t1_8'); +connection node_1; +connection node_2; +connection node_3; +connection node_1; +UPDATE ctrl SET stop = 1 WHERE id = 1; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match gtid_match +1 1 1 +connection node_1; +connection node_2; +connection node_3; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result b/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result new file mode 100644 index 00000000000..6ca4480ce03 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-40179_nobinlog.result @@ -0,0 +1,47 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection n1_load_1; +CALL p_load('t1_1'); +connection n2_load_1; +CALL p_load('t1_5'); +connection n1_load_2; +CALL p_load('t1_2'); +connection n2_load_2; +CALL p_load('t1_6'); +connection n1_load_3; +CALL p_load('t1_3'); +connection n2_load_3; +CALL p_load('t1_7'); +connection n1_load_4; +CALL p_load('t1_4'); +connection n2_load_4; +CALL p_load('t1_8'); +connection node_1; +connection node_2; +connection node_3; +connection node_1; +UPDATE ctrl SET stop = 1 WHERE id = 1; +connection node_1; +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +wsrep_cluster_size +3 +connection node_2; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match +1 1 +connection node_3; +SET SESSION wsrep_sync_wait = 15; +count_match checksum_match +1 1 +connection node_1; +connection node_2; +connection node_3; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf new file mode 100644 index 00000000000..3393252e287 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.cnf @@ -0,0 +1,31 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +gtid_strict_mode=ON +wsrep_gtid_mode=ON +wsrep_gtid_domain_id=100 +gtid_domain_id=10 +log_bin +log_slave_updates=ON +innodb_flush_log_at_trx_commit=1 +sync_binlog=1 +wsrep_sync_wait=6 # allow SHOW to workaround MDEV-39468 and reproduce "error 1950" + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 +# Force node_3 to always SST from node_1 (the node on which we freeze a +# transaction between binary log write and engine commit), so the snapshot +# node_3 receives is the one whose binary log is ahead of its engine checkpoint. +wsrep_sst_donor=node1 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147.test b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test new file mode 100644 index 00000000000..fcfdb025a94 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147.test @@ -0,0 +1,198 @@ +# +# MDEV-38147 - Mariadb error 1950 after SST +# +# Here a single transaction is frozen on the donor in the 2PC window between +# the binary log write (step 2) and the engine commit (step 3), while a +# mariabackup SST to a joiner is paused just before it fixes its redo-log +# copy point. That makes the copied binary log carry a Gtid_list ahead of +# the copied engine snapshot - exactly the condition the bug is about. +# +# The bug: +# +# BACKUP STAGE BLOCK_COMMIT fixes the InnoDB redo copy point but does not stop +# a wsrep transaction from having its GTID written to the binary log before it +# commits in the engine. So the copied binary log can carry a Gtid_list ahead +# of the copied engine snapshot. After the SST the joiner reports the +# (committed, behind) engine position, IST resends the missing transaction, and +# re-binlogging it under gtid_strict_mode=ON collides with the ahead Gtid_list +# -> ER_GTID_STRICT_OUT_OF_ORDER (error 1950), and the joiner never reaches +# synced state. (The same snapshot also captures the transaction in the InnoDB +# XA-prepared state, i.e. the MDEV-40179 condition.) +# +# The fix makes the joiner discard the received binary log and seed its GTID +# position from the engine checkpoint, so re-binlogging over IST stays in +# lockstep with the cluster and node_3 rejoins cleanly. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source include/have_debug_sync.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--echo # gtid_strict_mode must be enabled on all nodes +SELECT @@global.gtid_strict_mode AS gtid_strict_mode; + +--connection node_1 +--disable_query_log +CREATE TABLE t1 (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB; +--enable_query_log + +# Make sure the schema reached all nodes before we purge node_3. +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't1'; +--source include/wait_condition.inc + +# +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST. +# +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# +# Arm the donor-side backup sync point: mariabackup will pause after +# BACKUP STAGE BLOCK_DDL, before BLOCK_COMMIT (which fixes the redo copy point). +# +--connection node_1 +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL debug_dbug = '+d,sync.after_mdl_block_ddl'; + +# +# Start node_3 WITHOUT waiting for it to become ready: it rejoins via a +# mariabackup SST from node_1, whose donor backup pauses at the sync point armed +# above. We must not block on node_3 here, because releasing that pause (and +# everything that lets node_3 finish) happens below on the node_1 connection. +# So just trigger the restart via the expect file and continue. +# +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line restart $_expect_file_name + +--connection node_1 +SET DEBUG_SYNC = 'now WAIT_FOR sync.after_mdl_block_ddl_reached'; + +# +# Freeze one transaction between binary log write and engine commit. +# commit_before_get_LOCK_commit_ordered is reached after the GTID/Xid events +# have been written and fsync'd to the binary log but before the engine commit. +# +--connect node_1_freeze, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1_freeze +SET DEBUG_SYNC = 'commit_before_get_LOCK_commit_ordered SIGNAL t_frozen WAIT_FOR t_go'; +--send INSERT INTO t1 (val) VALUES (1) + +--connection node_1 +SET DEBUG_SYNC = 'now WAIT_FOR t_frozen'; + +# +# Let mariabackup continue. It fixes the redo copy point at BLOCK_COMMIT with +# the frozen transaction still in-doubt (so it is excluded from the engine +# snapshot), then issues "FLUSH BINARY LOGS" while shipping the binary log, +# which blocks on the in-doubt transaction's binary log checkpoint. +# +SET DEBUG_SYNC = 'now SIGNAL signal.after_mdl_block_ddl_continue'; + +--let $wait_condition = SELECT COUNT(*) >= 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE INFO LIKE 'FLUSH BINARY LOGS%' +--source include/wait_condition.inc + +# +# Release the frozen transaction. Its engine commit happens now - after the +# redo copy point was fixed - so it is absent from the copied engine snapshot +# while present in the shipped binary log's Gtid_list. +# +SET DEBUG_SYNC = 'now SIGNAL t_go'; + +--connection node_1_freeze +--reap +--connection node_1 +SET DEBUG_SYNC = 'RESET'; +SET GLOBAL debug_dbug = ''; + +# +# node_3 must rejoin and the cluster must reconverge to three nodes. +# +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect + +--connection node_1 +--source include/galera_wait_ready.inc +--connection node_2 +--source include/galera_wait_ready.inc +--connection node_3 +--source include/galera_wait_ready.inc + +# +# Verify data / GTID consistency across all nodes. node_1 is the origin of the +# highest GTID, so node_2 and node_3 are waited *up* to node_1's position. +# +--connection node_1 +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + +--let $expect_count = `SELECT COUNT(*) FROM t1` +--let $expect_sum = `SELECT COALESCE(SUM(pk), 0) + COALESCE(SUM(val), 0) FROM t1` +# Compare only the wsrep domain (wsrep_gtid_domain_id) of gtid_binlog_pos - that +# is the part the whole cluster shares; other domains are node-local. +--let $wsrep_dom = `SELECT @@global.wsrep_gtid_domain_id` +--let $expect_gtid = `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos, '(?.index' not found (Errcode: 2)". +# +# Here node_3 rejoins via a full mariabackup SST with log_bin relocated to a +# directory that does not exist yet (outside its datadir). The SST script must +# (re)create it so the joiner comes up and rejoins the cluster. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--connection node_1 +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); + +# Make sure the data reached node_3 before we purge it. +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 3 FROM t1 +--source include/wait_condition.inc + +# +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST. +# +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# A binary-log directory that does not exist yet, outside node_3's datadir. +--let $binlog_reloc = $MYSQLTEST_VARDIR/mysqld.3/reloc_binlog +--perl +use File::Path qw(rmtree); +rmtree("$ENV{MYSQLTEST_VARDIR}/mysqld.3/reloc_binlog"); +EOF + +# +# Restart node_3 with the binary log relocated to the (missing) directory. +# Do not block on it: it rejoins via a mariabackup SST which, with the fix, +# creates the directory before mysqld reopens the binary log. +# +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line "restart:--log-bin=$binlog_reloc/mdev-bin --log-bin-index=$binlog_reloc/mdev-bin.index" $_expect_file_name + +--connection node_1 +--let $wait_timeout = 60 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect +--source include/galera_wait_ready.inc + +# The joiner (re)created the relocated binlog directory and opened a fresh +# binary log there. +--file_exists $binlog_reloc/mdev-bin.index +--file_exists $binlog_reloc/mdev-bin.000001 + +# The SST data made it across. +--let $wait_condition = SELECT COUNT(*) = 3 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS rows_on_joiner FROM t1; + +# +# Cleanup. +# +--connection node_1 +DROP TABLE t1; + +# Restore original auto_increment_offset values. +--source ../galera/include/auto_increment_offset_restore.inc + +--source include/galera_end.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf new file mode 100644 index 00000000000..1fd5b6057f6 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.cnf @@ -0,0 +1,30 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +log_bin +log_slave_updates=ON +# The case under test: binary logging ON, but Galera GTID mode OFF. Cluster +# writes are then binlogged under the node's own gtid_domain_id with a locally +# allocated seq_no, unrelated to the wsrep cluster seqno in the SE checkpoint. +wsrep_gtid_mode=OFF +gtid_domain_id=10 +# Distinct from gtid_domain_id so that any (incorrect) seeding from the wsrep +# checkpoint would be visible as a foreign domain/seqno. +wsrep_gtid_domain_id=100 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 +# Force node_3 to always SST from node_1. +wsrep_sst_donor=node1 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test new file mode 100644 index 00000000000..54cbe37754e --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-38147_gtid_off.test @@ -0,0 +1,109 @@ +# +# MDEV-38147 follow-up: binary-log GTID seeding must apply only with +# wsrep_gtid_mode=ON. +# +# After MDEV-38147 a mariabackup-SST joiner starts a fresh binary log and, in +# wsrep_gtid_mode=ON, seeds @@gtid_binlog_pos from the storage-engine checkpoint +# (cluster writes are re-tagged to wsrep_gtid_domain_id and binlogged with the +# cluster seqno, so the checkpoint position maps directly onto the binlog GTID +# state - see MDEV-38147.test for the ON-mode case). +# +# With wsrep_gtid_mode=OFF that mapping does not hold: cluster writes keep the +# node's own gtid_domain_id and are binlogged with a locally allocated seq_no, +# while the cluster seqno lives only in thd->wsrep_current_gtid_seqno and is +# never written to the binlog GTID. Seeding gtid_domain_id from the checkpoint +# seqno would therefore inject a wrong, node-unrelated position. So in OFF mode +# the joiner must seed nothing; a fresh binary log simply resumes its own local +# counter. +# +# This test drives a full mariabackup SST of node_3 in wsrep_gtid_mode=OFF and +# asserts the seeding code path did NOT run on the joiner. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--echo # Precondition: Galera GTID mode is OFF +SELECT @@global.wsrep_gtid_mode AS wsrep_gtid_mode; + +--connection node_1 +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); + +# Make sure the data reached node_3 before we purge it. +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = 5 FROM t1 +--source include/wait_condition.inc + +# +# Stop node_3 and purge its data directory so that rejoining forces a full +# mariabackup SST (which restores the wsrep position into the SE checkpoint). +# +--connection node_3 +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +# Restart node_3 (non-blocking): it rejoins via a mariabackup SST from node_1. +--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.3.expect +--write_line restart $_expect_file_name + +--connection node_1 +--let $wait_timeout = 60 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +# Re-establish the node_3 client connection (it was started without waiting). +--connection node_3 +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect +--source include/galera_wait_ready.inc + +# The SST data made it across. +--let $wait_condition = SELECT COUNT(*) = 5 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS rows_on_joiner FROM t1; + +# +# Regression assertion: in wsrep_gtid_mode=OFF the joiner must NOT seed its +# binary-log GTID state from the wsrep checkpoint. The seeding code path emits +# this exact line only when it runs (it does run in the ON-mode +# MDEV-38147.test); here it must be absent on the joiner. +# +--let $assert_text = joiner did not seed binlog GTID state in wsrep_gtid_mode=OFF +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.3.err +--let $assert_select = seeding binlog GTID state +--let $assert_count = 0 +--source include/assert_grep.inc + +# +# Cleanup. +# +--connection node_1 +DROP TABLE t1; + +# Restore original auto_increment_offset values. +--source ../galera/include/auto_increment_offset_restore.inc + +--source include/galera_end.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf new file mode 100644 index 00000000000..1ce02400920 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179.cnf @@ -0,0 +1,37 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +gtid_strict_mode=ON +wsrep_gtid_mode=ON +wsrep_gtid_domain_id=100 +gtid_domain_id=10 +log_bin +log_slave_updates=ON +# Parallel apply so that prepared transactions can be committed out of order, +# producing a non-contiguous prepared set on the donor. +wsrep_slave_threads=8 +# Slow, durable commits widen the window during which transactions sit in the +# prepared (XA) state of two-phase commit, so the backup's BLOCK_COMMIT +# snapshot is more likely to capture in-doubt transactions. sync_binlog adds an +# fsync inside the 2PC window without exploding the binlog file count, and a +# moderate max_binlog_size keeps the binlog rotating (further widening the +# window) while avoiding the tens of thousands of tiny files that a 4 KB limit +# would create under this load. +innodb_flush_log_at_trx_commit=1 +sync_binlog=1 +max_binlog_size=16384 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc b/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc new file mode 100644 index 00000000000..8e4bcc0b677 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179.inc @@ -0,0 +1,347 @@ +# +# Shared body for the MDEV-40179 tests (sourced by MDEV-40179.test with +# log_bin=ON and MDEV-40179_nobinlog.test with log_bin=OFF). +# +# The bug (reproduced by the log_bin=ON variant): +# +# With log_bin=ON a transaction is committed via two-phase commit (the binary +# log is the second participant), so it passes through the InnoDB XA-prepare +# state. While a donor is held in BLOCK_COMMIT for a mariabackup backup, its +# parallel appliers (wsrep_slave_threads > 1) leave one or more such writesets +# prepared-but-not-yet-committed, and the snapshot captures them. On a freshly +# SST'd joiner nothing resolves these prepared transactions: binlog crash +# recovery does not run (the joiner has no in-use binlog to recover from), and +# the wsrep continuity-based commit is inactive because wsrep_emulate_bin_log +# is FALSE when log_bin is ON. The leftover prepared transactions then abort +# startup with "Found prepared transactions!". Note this does not depend on +# the prepared set being non-contiguous - even a contiguous run aborts, because +# nothing commits or rolls it back. +# +# The log_bin=OFF variant is coverage only: with a single (InnoDB) read-write +# engine and no binary log, commits use one-phase commit, so transactions never +# enter the XA-prepared state and the snapshot has nothing in doubt. It simply +# verifies that mariabackup SST and reconvergence keep working with log_bin=OFF. +# +# To maximize parallel apply on the donor (and thus the chance of catching +# prepared transactions in the snapshot) each client thread writes to its own +# table: there are no certification conflicts between writers, so all of them +# apply concurrently. $writers client threads load on each of node_1 and node_2 +# while node_3 is repeatedly stopped, has its data directory purged and is +# started again, forcing a full mariabackup SST on every rejoin. At the end the +# cluster must reconverge to three nodes and all three nodes must hold identical +# data (and, with log_bin, identical GTID positions). +# +# Parameters set by the including .test: +# $restarts - number of stop/purge/start cycles for node_3 +# $writers - number of concurrent loader threads per node (each gets its +# own table to avoid certification conflicts) +# $check_gtid - 1 to also compare @@global.gtid_binlog_pos across nodes +# (only meaningful with log_bin), 0 otherwise +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +# Save original auto_increment_offset values so that MTR's post-check is +# happy after node_3 has been restarted multiple times. +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +# Total number of data tables: one per writer thread across both nodes. +--let $ntables = `SELECT 2 * $writers` + +# +# Schema: t1_1 .. t1_$ntables hold the load (one table per writer thread), +# ctrl carries the stop flag for the loaders. +# +--connection node_1 +--disable_query_log +CREATE TABLE ctrl (id INT PRIMARY KEY, stop INT) ENGINE=InnoDB; +INSERT INTO ctrl VALUES (1, 0); + +--let $t = 1 +while ($t <= $ntables) +{ + --eval CREATE TABLE t1_$t (pk BIGINT AUTO_INCREMENT PRIMARY KEY, val INT) ENGINE=InnoDB + --inc $t +} + +DELIMITER |; +CREATE PROCEDURE p_load(IN tname VARCHAR(64)) +BEGIN + DECLARE v_stop INT DEFAULT 0; + DECLARE v_i INT; + # Keep the loop alive across transient cluster errors (BF aborts, + # certification failures, donor desync timeouts, ...). + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + END; + SET @ins_sql = CONCAT('INSERT INTO ', tname, ' (pk, val) VALUES (DEFAULT, 1)'); + PREPARE ins FROM @ins_sql; + WHILE v_stop = 0 DO + START TRANSACTION; + SET v_i = 0; + WHILE v_i < 16 DO + EXECUTE ins; + SET v_i = v_i + 1; + END WHILE; + COMMIT; + # Throttle slightly between transactions so that a freshly joined node can + # catch up its replication queue instead of being starved by the load. + DO SLEEP(0.01); + SELECT stop INTO v_stop FROM ctrl WHERE id = 1; + END WHILE; + DEALLOCATE PREPARE ins; +END| +DELIMITER ;| +--enable_query_log + +# Make sure the schema reached the other nodes before starting the load. +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = $ntables FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't1\_%'; +--source include/wait_condition.inc +--connection node_3 +--let $wait_condition = SELECT COUNT(*) = $ntables FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't1\_%'; +--source include/wait_condition.inc + +# +# Start the continuous load: $writers threads on node_1 (tables t1_1..t1_W) +# and $writers threads on node_2 (tables t1_(W+1)..t1_2W). +# +--disable_query_log +--let $w = 1 +while ($w <= $writers) +{ + --connect (n1_load_$w, 127.0.0.1, root, , test, $NODE_MYPORT_1) + --connect (n2_load_$w, 127.0.0.1, root, , test, $NODE_MYPORT_2) + --inc $w +} +--enable_query_log + +--let $w = 1 +--let $n2 = $writers +while ($w <= $writers) +{ + --connection n1_load_$w + --send_eval CALL p_load('t1_$w') + --inc $n2 + --connection n2_load_$w + --send_eval CALL p_load('t1_$n2') + --inc $w +} + +# +# While the load is running, repeatedly stop node_3, purge its data +# directory and start it again. An empty data directory forces a full +# mariabackup SST on every rejoin. +# +--disable_query_log +--let $i = $restarts +while ($i) +{ + --connection node_3 + --source include/shutdown_mysqld.inc + --disable_query_log + + # Wait until node_3 has actually left the cluster. + # (shutdown_mysqld.inc / wait_condition.inc / start_mysqld.inc / + # galera_wait_ready.inc each re-enable the query log, so re-disable it after + # every such include to keep the loop output out of the result file.) + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + # Purge node_3's data directory. + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/test + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mysql + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/performance_schema + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data/mtr + --remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + + # Start node_3 again (rejoins via mariabackup SST). + --connection node_3 + --let $restart_noprint = 2 + --source include/start_mysqld.inc + --disable_query_log + --source include/galera_wait_ready.inc + --disable_query_log + + # Wait until the cluster is back to three nodes before the next cycle. + --connection node_1 + --let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + --source include/wait_condition.inc + --disable_query_log + + --dec $i +} +--enable_query_log + +# +# Make sure the whole cluster is healthy before stopping the load, so that +# any donor that desynced during SST has resynced and the loaders can read +# the stop flag without blocking. +# +--connection node_1 +--source include/galera_wait_ready.inc +--connection node_2 +--source include/galera_wait_ready.inc +--connection node_3 +--source include/galera_wait_ready.inc + +# +# Signal the loaders to stop and collect them. +# +--connection node_1 +UPDATE ctrl SET stop = 1 WHERE id = 1; + +--disable_query_log +--let $w = 1 +while ($w <= $writers) +{ + --connection n1_load_$w + --reap + --connection n2_load_$w + --reap + --inc $w +} +--enable_query_log + +# +# Build the aggregate count / checksum expressions over all data tables. +# +--let $count_expr = 0 +--let $sum_expr = 0 +--let $t = 1 +while ($t <= $ntables) +{ + --let $count_expr = $count_expr + (SELECT COUNT(*) FROM t1_$t) + --let $sum_expr = $sum_expr + (SELECT COALESCE(SUM(pk),0)+COALESCE(SUM(val),0) FROM t1_$t) + --inc $t +} + +# +# Verify reconvergence and data / GTID consistency across all nodes. +# +--connection node_1 +SET SESSION wsrep_sync_wait = 15; +SELECT VARIABLE_VALUE AS wsrep_cluster_size FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; + +# The load has stopped; issue one final transaction from node_1 (sync_wait is +# on, so node_1 first applies everything else). This makes node_1 the origin of +# the cluster's highest GTID, so the checks below can wait for node_2/node_3 to +# converge *up* to node_1's position instead of comparing a single snapshot: +# @@gtid_binlog_pos is a system variable, so reading it is not covered by +# wsrep_sync_wait and a plain read can otherwise sample a position before the +# node has finished applying (a race that grows with accumulated load, e.g. +# under --repeat). +--disable_query_log +UPDATE ctrl SET stop = 2 WHERE id = 1; +--enable_query_log + +--let $expect_count = `SELECT $count_expr` +--let $expect_sum = `SELECT $sum_expr` +if ($check_gtid) +{ + # Compare only the wsrep domain (wsrep_gtid_domain_id) of gtid_binlog_pos. + # That is the part the whole cluster shares. Other domains in the position + # are node-local and legitimately differ: e.g. CALL mtr.add_suppression() + # below writes to the non-replicated 'mtr' database, which each node binlogs + # under its own gtid_domain_id/server_id - so those entries accumulate + # per-node across runs (visible under --repeat) and must not be compared. + --let $wsrep_dom = `SELECT @@global.wsrep_gtid_domain_id` + --let $expect_gtid = `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos, '(? prepared transactions!". (gtid_strict_mode is enabled so any +# binlog/engine position inconsistency would also be caught.) +# +# See MDEV-40179.inc for the shared test body. +# + +--let $restarts = 8 +--let $writers = 4 +--let $check_gtid = 1 +--source MDEV-40179.inc diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf new file mode 100644 index 00000000000..38740c2ec87 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.cnf @@ -0,0 +1,27 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +# No log_bin: Galera uses its emulated binlog (wsrep_emulate_bin_log), so the +# wsrep XID continuity check is what resolves prepared transactions on a joiner. +# Parallel apply so that prepared transactions can be committed out of order, +# producing a non-contiguous prepared set on the donor. +wsrep_slave_threads=8 +# Slow, durable commits widen the window during which transactions sit in the +# prepared state, so the backup's BLOCK_COMMIT snapshot is more likely to +# capture in-doubt transactions. +innodb_flush_log_at_trx_commit=1 + +[mysqld.1] +server_id=11 + +[mysqld.2] +server_id=12 + +[mysqld.3] +server_id=13 + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test new file mode 100644 index 00000000000..3eb96d3defd --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/MDEV-40179_nobinlog.test @@ -0,0 +1,16 @@ +# +# MDEV-40179 - prepared transactions left behind by a mariabackup SST. +# +# log_bin=OFF variant: coverage only. With a single InnoDB read-write engine +# and no binary log, commits use one-phase commit, so transactions never enter +# the XA-prepared state and a mariabackup snapshot has nothing in doubt - the +# bug cannot occur here. This variant just exercises the same load and repeated +# mariabackup SST with log_bin=OFF and checks the cluster reconverges. +# +# See MDEV-40179.inc for the shared test body. +# + +--let $restarts = 8 +--let $writers = 4 +--let $check_gtid = 0 +--source MDEV-40179.inc diff --git a/mysql-test/suite/gcol/r/gcol_bugfixes.result b/mysql-test/suite/gcol/r/gcol_bugfixes.result index f26b0ad3795..1b218eb3a3a 100644 --- a/mysql-test/suite/gcol/r/gcol_bugfixes.result +++ b/mysql-test/suite/gcol/r/gcol_bugfixes.result @@ -782,3 +782,22 @@ a c1 Warnings: Warning 1292 Incorrect datetime value: '0' DROP TABLE t1; +# +# MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +# BEFORE INSERT trigger changes its base column +# +create table t ( +id int primary key, +v int not null, +g int generated always as (v * 2) stored, +note varchar(20) +) engine=innodb; +create trigger t_bi before insert on t for each row set new.v = 20; +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); +# The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; +id v g note +2 20 40 new +drop table t; +# End of 10.11 tests diff --git a/mysql-test/suite/gcol/t/gcol_bugfixes.test b/mysql-test/suite/gcol/t/gcol_bugfixes.test index c4beb69058a..0ff5c1a1aad 100644 --- a/mysql-test/suite/gcol/t/gcol_bugfixes.test +++ b/mysql-test/suite/gcol/t/gcol_bugfixes.test @@ -753,3 +753,30 @@ UPDATE t1 SET a=2; UPDATE t1 SET c1=1; SELECT * FROM t1; DROP TABLE t1; + + +--echo # +--echo # MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +--echo # BEFORE INSERT trigger changes its base column +--echo # + +create table t ( + id int primary key, + v int not null, + g int generated always as (v * 2) stored, + note varchar(20) +) engine=innodb; + +create trigger t_bi before insert on t for each row set new.v = 20; + +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); + +--echo # The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; + +drop table t; +--let $datadir= `select @@datadir` +--remove_file $datadir/test/load_40480 + +--echo # End of 10.11 tests diff --git a/mysql-test/suite/handler/heap.result b/mysql-test/suite/handler/heap.result index 17b4ffd5f73..b82e07a6234 100644 --- a/mysql-test/suite/handler/heap.result +++ b/mysql-test/suite/handler/heap.result @@ -1702,3 +1702,36 @@ HANDLER m READ `ba`= (30); ERROR HY000: HASH index `ba` does not support this operation HANDLER m CLOSE; DROP TABLE t1; +# +# MDEV-39821 heap-use-after-free in heap_rnext with tree indexes +# +CREATE TABLE t1 ( +k INT, +pad VARCHAR(200), +INDEX idx USING BTREE (k) +) ENGINE=MEMORY; +INSERT t1 VALUES (10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e'), +(60,'f'),(70,'g'),(80,'h'),(90,'i'),(100,'j'), +(110,'k'),(120,'l'),(130,'m'),(140,'n'),(150,'o'), +(160,'p'),(170,'q'),(180,'r'),(190,'s'),(200,'t'); +HANDLER t1 OPEN; +HANDLER t1 READ idx FIRST; +k pad +10 a +HANDLER t1 READ idx NEXT; +k pad +20 b +HANDLER t1 READ idx NEXT; +k pad +30 c +HANDLER t1 READ idx NEXT; +k pad +40 d +HANDLER t1 READ idx NEXT; +k pad +50 e +UPDATE t1 SET k = k + 5000 WHERE k BETWEEN 20 AND 180; +HANDLER t1 READ idx NEXT; +ERROR HY000: Record has changed since last read in table 't1'; try restarting transaction +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/handler/heap.test b/mysql-test/suite/handler/heap.test index d60f92daa76..175044d392e 100644 --- a/mysql-test/suite/handler/heap.test +++ b/mysql-test/suite/handler/heap.test @@ -103,3 +103,29 @@ HANDLER m READ `ba`= (30); HANDLER m CLOSE; DROP TABLE t1; +--echo # +--echo # MDEV-39821 heap-use-after-free in heap_rnext with tree indexes +--echo # +CREATE TABLE t1 ( + k INT, + pad VARCHAR(200), + INDEX idx USING BTREE (k) +) ENGINE=MEMORY; + +INSERT t1 VALUES (10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e'), + (60,'f'),(70,'g'),(80,'h'),(90,'i'),(100,'j'), + (110,'k'),(120,'l'),(130,'m'),(140,'n'),(150,'o'), + (160,'p'),(170,'q'),(180,'r'),(190,'s'),(200,'t'); + +HANDLER t1 OPEN; +HANDLER t1 READ idx FIRST; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +HANDLER t1 READ idx NEXT; +UPDATE t1 SET k = k + 5000 WHERE k BETWEEN 20 AND 180; +--error ER_CHECKREAD +HANDLER t1 READ idx NEXT; +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/mysql-test/suite/heap/heap_btree.result b/mysql-test/suite/heap/heap_btree.result index 3eb618a0680..605213d42b4 100644 --- a/mysql-test/suite/heap/heap_btree.result +++ b/mysql-test/suite/heap/heap_btree.result @@ -1,4 +1,3 @@ -drop table if exists t1; create table t1 (a int not null,b int not null, primary key using BTREE (a)) engine=heap comment="testing heaps" avg_row_length=100 min_rows=1 max_rows=100; insert into t1 values(1,1),(2,2),(3,3),(4,4); delete from t1 where a=1 or a=0; @@ -321,7 +320,7 @@ SELECT * FROM t1; a 1 DROP TABLE t1; -End of 4.1 tests +# End of 4.1 tests CREATE TABLE t1(val INT, KEY USING BTREE(val)) ENGINE=memory; INSERT INTO t1 VALUES(0); SELECT INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='t1'; @@ -350,7 +349,7 @@ CREATE TABLE t1(a INT, KEY USING BTREE (a)) ENGINE=MEMORY; INSERT INTO t1 VALUES(1),(1); DELETE a1 FROM t1 AS a1, t1 AS a2 WHERE a1.a=a2.a; DROP TABLE t1; -End of 5.0 tests +# End of 5.0 tests # bit index in heap tables create table t1 (a bit(63) not null) engine=heap; insert into t1 values (869751),(736494),(226312),(802616),(728912); @@ -386,8 +385,64 @@ explain select 0+a from t1 where a in (869751,736494,226312,802616); id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range uniq_id uniq_id 8 NULL 4 Using where drop table t1; -End of 5.3 tests +# End of 5.3 tests create table t1 (id int, a varchar(300) not null, key using btree(a)) engine=heap; insert t1 values (1, repeat('a', 300)); drop table t1; -End of 5.5 tests +# End of 5.5 tests +# +# MDEV-40186 MEMORY tables incorrectly restart index scan on DELETE +# +create table t1 (a int, b int, c int, key (a, b) using btree) engine=heap; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c>60; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_NEXT 8 +select * from t1 where a=3; +a b c +3 3 3 +3 13 13 +3 23 23 +3 33 33 +3 43 43 +3 53 53 +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_NEXT 8 +select * from t1 where a=3; +a b c +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c<20 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_PREV 8 +select * from t1 where a=3; +a b c +3 23 23 +3 33 33 +3 43 43 +3 53 53 +3 63 63 +3 73 73 +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +VARIABLE_NAME VARIABLE_VALUE +HANDLER_READ_KEY 1 +HANDLER_READ_PREV 8 +select * from t1 where a=3; +a b c +drop table t1; diff --git a/mysql-test/suite/heap/heap_btree.test b/mysql-test/suite/heap/heap_btree.test index e8f7c02c6f3..a05f2c80336 100644 --- a/mysql-test/suite/heap/heap_btree.test +++ b/mysql-test/suite/heap/heap_btree.test @@ -4,10 +4,6 @@ # Test of heap tables. # ---disable_warnings -drop table if exists t1; ---enable_warnings - create table t1 (a int not null,b int not null, primary key using BTREE (a)) engine=heap comment="testing heaps" avg_row_length=100 min_rows=1 max_rows=100; insert into t1 values(1,1),(2,2),(3,3),(4,4); delete from t1 where a=1 or a=0; @@ -229,7 +225,7 @@ DELETE FROM t1 WHERE a=2; SELECT * FROM t1; DROP TABLE t1; ---echo End of 4.1 tests +--echo # End of 4.1 tests # # BUG#18160 - Memory-/HEAP Table endless growing indexes @@ -268,7 +264,7 @@ CREATE TABLE t1(a INT, KEY USING BTREE (a)) ENGINE=MEMORY; INSERT INTO t1 VALUES(1),(1); DELETE a1 FROM t1 AS a1, t1 AS a2 WHERE a1.a=a2.a; DROP TABLE t1; ---echo End of 5.0 tests +--echo # End of 5.0 tests -- echo # bit index in heap tables @@ -287,7 +283,7 @@ select 0+a from t1 where a in (869751,736494,226312,802616); explain select 0+a from t1 where a in (869751,736494,226312,802616); drop table t1; ---echo End of 5.3 tests +--echo # End of 5.3 tests # # Bug#27799513: POTENTIAL DOUBLE FREE OR CORRUPTION OF HEAP INFO (HP_INFO) @@ -296,4 +292,41 @@ create table t1 (id int, a varchar(300) not null, key using btree(a)) engine=hea insert t1 values (1, repeat('a', 300)); drop table t1; ---echo End of 5.5 tests +--echo # End of 5.5 tests + +--echo # +--echo # MDEV-40186 MEMORY tables incorrectly restart index scan on DELETE +--echo # + +--disable_ps2_protocol +create table t1 (a int, b int, c int, key (a, b) using btree) engine=heap; + +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c>60; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 and c<20 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +delete from t1; +insert t1 select seq % 10, seq, seq from seq_1_to_80; +flush status; +delete from t1 where a=3 order by a desc, b desc limit 10; +select * from information_schema.session_status where variable_name like 'handler_r%' and variable_value>0; +select * from t1 where a=3; + +drop table t1; +--enable_ps2_protocol diff --git a/mysql-test/suite/mariabackup/innodb_redo_overwrite.result b/mysql-test/suite/mariabackup/innodb_redo_overwrite.result index bab5d4331e0..cbc7f44cab3 100644 --- a/mysql-test/suite/mariabackup/innodb_redo_overwrite.result +++ b/mysql-test/suite/mariabackup/innodb_redo_overwrite.result @@ -1,5 +1,5 @@ CREATE TABLE t ENGINE=INNODB SELECT seq%10 i FROM seq_0_to_204796; # xtrabackup backup -FOUND 1 /Was only able to copy log from \d+ to \d+, not \d+; try increasing innodb_log_file_size\b/ in backup.log +FOUND 1 /mariabackup: Try increasing the innodb_log_file_size./ in backup.log NOT FOUND /failed: redo log block checksum does not match/ in backup.log DROP TABLE t; diff --git a/mysql-test/suite/mariabackup/innodb_redo_overwrite.test b/mysql-test/suite/mariabackup/innodb_redo_overwrite.test index ab5993d232c..8c26acf4141 100644 --- a/mysql-test/suite/mariabackup/innodb_redo_overwrite.test +++ b/mysql-test/suite/mariabackup/innodb_redo_overwrite.test @@ -17,7 +17,7 @@ CREATE TABLE t ENGINE=INNODB SELECT seq%10 i FROM seq_0_to_204796; --exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir --dbug=+d,mariabackup_events > $backuplog --enable_result_log ---let SEARCH_PATTERN=Was only able to copy log from \\d+ to \\d+, not \\d+; try increasing innodb_log_file_size\\b +--let SEARCH_PATTERN=mariabackup: Try increasing the innodb_log_file_size. --let SEARCH_FILE=$backuplog --source include/search_pattern_in_file.inc --remove_file $backuplog diff --git a/mysql-test/suite/plugins/r/qc_info.result b/mysql-test/suite/plugins/r/qc_info.result index afab7671b5c..5ad4e13f1e1 100644 Binary files a/mysql-test/suite/plugins/r/qc_info.result and b/mysql-test/suite/plugins/r/qc_info.result differ diff --git a/mysql-test/suite/plugins/t/multiauth.test b/mysql-test/suite/plugins/t/multiauth.test index 6ae50b2b3ba..fa902ab3b2c 100644 --- a/mysql-test/suite/plugins/t/multiauth.test +++ b/mysql-test/suite/plugins/t/multiauth.test @@ -1,5 +1,3 @@ ---source include/not_ubsan.inc - let $REGEX_VERSION_ID=/$mysql_get_server_version/VERSION_ID/; let $REGEX_PASSWORD_LAST_CHANGED=/password_last_changed": [0-9]*/password_last_changed": #/; let $REGEX_GLOBAL_PRIV=$REGEX_PASSWORD_LAST_CHANGED $REGEX_VERSION_ID; diff --git a/mysql-test/suite/plugins/t/qc_info.test b/mysql-test/suite/plugins/t/qc_info.test index 5345bac12ff..0bccd7f6672 100644 Binary files a/mysql-test/suite/plugins/t/qc_info.test and b/mysql-test/suite/plugins/t/qc_info.test differ diff --git a/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result b/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result new file mode 100644 index 00000000000..6107508c9fc --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_compressed_log_event_overflow.result @@ -0,0 +1,78 @@ +include/master-slave.inc +[connection master] +connection slave; +include/stop_slave.inc +CHANGE MASTER TO master_use_gtid= slave_pos; +include/start_slave.inc +call mtr.add_suppression("Uncompressed data size too large"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +create table t2 (a longtext); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +# +# Test Case 1: a compressed Query event +# +connection master; +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +connection slave; +# Waiting for the IO thread to reject the compressed Query event +include/wait_for_slave_io_error.inc [errno=1256, 1595] +# Ensure the replica rejected the event on its recorded length +include/assert_grep.inc [The IO thread reported an oversized uncompressed length] +# Move the replica past the corrupt event +include/stop_slave_sql.inc +include/start_slave.inc +# Ensure replication resumes +connection master; +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Test Case 2: a compressed Write_rows event +# +connection master; +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= ROW; +insert into t2 values (repeat('a', 65536)); +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +connection slave; +# Waiting for the IO thread to reject the compressed Write_rows event +include/wait_for_slave_io_error.inc [errno=1256, 1595] +# Ensure the replica rejected the event on its recorded length +include/assert_grep.inc [The IO thread reported an oversized uncompressed length] +# Move the replica past the corrupt event +include/stop_slave_sql.inc +include/start_slave.inc +# Ensure replication resumes +connection master; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1, t2; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_compressed_log_event_overflow.test diff --git a/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result b/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result new file mode 100644 index 00000000000..b56808e1128 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_corrupted_decimal_precision.result @@ -0,0 +1,70 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Slave SQL: Invalid variable length at User var event"); +connection master; +create table t1 (a decimal(10,5)); +insert into t1 values (0.5); +connection slave; +# +# Write a User_var_log_event whose DECIMAL precision and scale do not +# match the length of the packed value +# +connection master; +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +set @a= 1.5; +insert into t1 values (@a); +set @@session.debug_dbug= ""; +# +# SHOW BINLOG EVENTS must not crash in User_var_log_event::pack_info() +# +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a decimal(10,5)) +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0.5) +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # User var # # NULL +master-bin.000001 # Query # # use `test`; insert into t1 values (@a) +master-bin.000001 # Query # # COMMIT +set @@session.debug_dbug= ""; +# +# mysqlbinlog must not crash in User_var_log_event::print() +# +# +# The slave rejects the event instead of crashing on it +# +connection slave; +include/wait_for_slave_sql_error.inc [errno=1593] +# +# Replication continues once the corrupted event group is skipped +# +set global sql_slave_skip_counter= 1; +include/start_slave.inc +connection master; +insert into t1 values (3.5); +connection slave; +# Only the skipped row (1.50000) is missing on the slave, every later +# event is applied normally +connection master; +select * from t1 order by a; +a +0.50000 +1.50000 +3.50000 +connection slave; +select * from t1 order by a; +a +0.50000 +3.50000 +# +# Cleanup +# +connection master; +drop table t1; +connection slave; +include/rpl_end.inc +# End of rpl_corrupted_decimal_precision.test diff --git a/mysql-test/suite/rpl/r/rpl_corruption.result b/mysql-test/suite/rpl/r/rpl_corruption.result index 7c60e15a567..e3501d29a57 100644 --- a/mysql-test/suite/rpl/r/rpl_corruption.result +++ b/mysql-test/suite/rpl/r/rpl_corruption.result @@ -11,6 +11,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; # 1. Creating test table/data and set corruption position for testing connection master; diff --git a/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result b/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result new file mode 100644 index 00000000000..9fc0267cc11 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_heartbeat_log_event_underflow.result @@ -0,0 +1,76 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Unexpected master's heartbeat data"); +call mtr.add_suppression("Event from master is shorter than its common header"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +CHANGE MASTER TO MASTER_HEARTBEAT_PERIOD= 2; +include/start_slave.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case 1: a heartbeat that leaves no room for a file name +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_truncated_heartbeat"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +# Waiting for the IO thread to reject the truncated heartbeat +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure heartbeat validation rejected the event +include/assert_grep.inc [The IO thread reported an inconsistent heartbeat event] +include/assert_grep.inc [The heartbeat error should report an empty log_file_name] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 2: a heartbeat shorter than the common header +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_undersized_heartbeat"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +# Waiting for the IO thread to reject the undersized heartbeat +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before its header was read +include/assert_grep.inc [The IO thread reported an event shorter than the common header] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Ensure replication works after the invalid heartbeat events +# +connection master; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_heartbeat_log_event_underflow.test diff --git a/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result new file mode 100644 index 00000000000..bcb128ebc94 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result @@ -0,0 +1,122 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Event from master declares a length that does not match"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +change master to master_use_gtid=no; +include/start_slave.inc +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case 1: a Rotate event declaring fewer bytes than its packet +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_short_event_len"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +FLUSH LOGS; +# Waiting for the IO thread to reject the mismatched Rotate event +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the event was rejected before the replica relay logged the event +include/assert_grep.inc [The IO thread reported a Rotate event declaring 8 bytes fewer than its packet] +# Ensure the SQL thread read everything the IO thread relay logged +include/sync_slave_sql_with_io.inc +include/assert.inc [The SQL thread reported no relay log read failure] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +connection master; +include/sync_slave_io_with_master.inc +# +# Test Case 2: a Rotate event declaring more bytes than its packet +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_long_event_len"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +FLUSH LOGS; +# Waiting for the IO thread to reject the mismatched Rotate event +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the event was rejected before the replica relay logged the event +include/assert_grep.inc [The IO thread reported a Rotate event declaring 8 bytes more than its packet] +# Ensure the SQL thread read everything the IO thread relay logged +include/sync_slave_sql_with_io.inc +include/assert.inc [The SQL thread reported no relay log read failure] +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 3: a packet carrying a second event behind the first +# +connection slave; +include/stop_slave.inc +connection master; +set @saved_checksum= @@global.binlog_checksum; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_append_extra_event"; +connection slave; +include/start_slave.inc +connection master; +set @saved_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +insert into t1 values (2); +set @@session.binlog_format= @saved_format; +# Waiting for the IO thread to reject the doubled packet +connection slave; +include/wait_for_slave_io_error.inc [errno=1593, 1595] +# Ensure the rejected packet delivered nothing to the replica +include/assert.inc [The replica has not applied the insert the packet carried] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +# Ensure old relay logs are purged, use GTID +change master to master_use_gtid=slave_pos; +include/start_slave.inc +# Ensure the replica holds the row once the master resends the insert +connection master; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Ensure replication works after the mismatched events +# +connection master; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_queue_event_length_mismatch.test diff --git a/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result new file mode 100644 index 00000000000..7c218054950 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result @@ -0,0 +1,19 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +include/master-slave.inc +[connection master] +FLUSH BINARY LOGS; +CALL mtr.add_suppression('Found invalid event in binary log'); +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +connection slave; +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +include/wait_for_slave_io_error.inc [errno=1595] +connection master; +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; +connection slave; +CHANGE MASTER TO master_use_gtid=SLAVE_POS; +SET @@GLOBAL.gtid_slave_pos=''; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result b/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result new file mode 100644 index 00000000000..b8c921980a8 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_log_event_overflow.result @@ -0,0 +1,92 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Rotate event from master names a binary log file longer"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +change master to master_use_gtid=no; +include/start_slave.inc +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +set @saved_checksum= @@global.binlog_checksum; +# +# Test Case 1: the master adds no checksum and the relay log carries one +# +connection master; +set @@global.binlog_checksum= CRC32; +insert into t1 values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +connection master; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; +connection slave; +START SLAVE; +# Waiting for the IO thread to reject the oversized Rotate event +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before the replica copied it +include/assert_grep.inc [The IO thread reported an oversized Rotate event] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Test Case 2: the master adds a checksum and the relay log carries none +# +connection master; +set @@global.binlog_checksum= NONE; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +connection master; +set @@global.binlog_checksum= CRC32; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; +connection slave; +START SLAVE; +# Waiting for the IO thread to reject the oversized Rotate event +include/wait_for_slave_io_error.inc [errno=1595] +Last_IO_Error = 'Relay log write failure: could not queue event from master' +# Ensure the event was rejected before the replica copied it +include/assert_grep.inc [The IO thread reported an oversized Rotate event] +connection master; +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; +connection slave; +include/stop_slave_sql.inc +include/start_slave.inc +# +# Ensure replication works after the oversized Rotate events +# +connection master; +insert into t1 values (4); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_rotate_log_event_overflow.test diff --git a/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result b/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result new file mode 100644 index 00000000000..0023b572b7e --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_table_map_metadata_underflow.result @@ -0,0 +1,62 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Found invalid event in binary log"); +call mtr.add_suppression("Relay log read failure: Could not parse relay log event entry"); +# +# Initialize test data +# +connection slave; +include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +include/start_slave.inc +connection master; +create table t1 (c1 int,c2 int,c3 int,c4 int,c5 int,c6 int,c7 int,c8 int,c9 int,c10 int,c11 int,c12 int,c13 int,c14 int,c15 int,c16 int,c17 int,c18 int,c19 int,c20 int,c21 int,c22 int,c23 int,c24 int); +insert into t1 (c1) values (0); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case: a Table_map declaring more metadata than it carries +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_undersized_table_map_metadata"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +insert into t1 (c1) values (1); +connection slave; +# Waiting for the SQL thread to reject the malformed Table_map +include/wait_for_slave_sql_error.inc [errno=1594] +# Ensure the event was rejected before its metadata was decoded +include/assert_grep.inc [The SQL thread reported an invalid event] +# +# Recover: discard the poisoned relay log and re-fetch a clean copy +# +connection master; +set @@global.debug_dbug= @saved_dbug; +connection slave; +include/stop_slave_io.inc +include/start_slave.inc +# +# Ensure replication works after the invalid event +# +connection master; +insert into t1 (c1) values (2); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_table_map_metadata_underflow.test diff --git a/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result b/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result new file mode 100644 index 00000000000..5b8c22ff498 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_table_map_name_overflow.result @@ -0,0 +1,68 @@ +include/master-slave.inc +[connection master] +connection slave; +call mtr.add_suppression("Error in Log_event::read_log_event.*Found invalid event"); +call mtr.add_suppression("Relay log read failure"); +# +# Initialize test data +# +connection master; +create table t1 (a int); +insert into t1 values (1); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +include/start_slave.inc +connection master; +set @saved_dbug= @@global.debug_dbug; +# +# Test Case: a Table_map event naming a table with an oversized name +# +connection master; +set @@global.debug_dbug= "+d,binlog_sender_oversized_table_name"; +connection slave; +include/stop_slave.inc +include/start_slave.inc +connection master; +insert into t1 values (2); +# Waiting for the SQL thread to reject the oversized Table_map event +connection slave; +include/wait_for_slave_sql_error.inc [errno=1594] +# Ensure the SQL thread rejected the oversized Table_map event +include/assert_grep.inc [The SQL thread reported the oversized Table_map event as invalid] +# Ensure the rejected event delivered nothing to the replica +include/assert.inc [The replica has not applied the insert the event carried] +connection master; +set @@global.debug_dbug= @saved_dbug; +# +# Ensure the replica holds the row once it refetches the relay log +# +connection slave; +include/stop_slave_io.inc +include/start_slave.inc +connection master; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Ensure replication works after the rejected event +# +connection master; +insert into t1 values (3); +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1,slave:t1] +# +# Cleanup +# +connection master; +drop table t1; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/rpl_end.inc +# End of rpl_table_map_name_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt new file mode 100644 index 00000000000..c949694b21b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow-master.opt @@ -0,0 +1 @@ +--log-bin-compress=1 diff --git a/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test new file mode 100644 index 00000000000..29d51a823a7 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_compressed_log_event_overflow.test @@ -0,0 +1,160 @@ +# +# This test verifies that a replica rejects a compressed event whose +# recorded uncompressed length is larger than the biggest packet the +# server accepts. A compressed Query event and a compressed Write_rows +# event are both covered, because the IO thread uncompresses each with +# its own function. MDEV-39762 reported that the compressed Query event +# crashed the replica. +# +# A debug injection makes the master write each of those events with +# a corrupted length field. The event stays in the master's binary log +# and is resent as it is, so each case moves gtid_slave_pos past it +# before checking that replication resumes. The sibling .opt file turns +# on log_bin_compress, without which the master writes no compressed +# events at all. +# +# References: +# +# * MDEV-39762: Slave Overflow on Malformed Query_compressed_log_event +# + +--source include/have_debug.inc +# Each test case sets the binlog format that its event type needs +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +# Test requires slave_pos, as each case recovers by moving gtid_slave_pos +# past the corrupt event. +--connection slave +--source include/stop_slave.inc +CHANGE MASTER TO master_use_gtid= slave_pos; +--source include/start_slave.inc + +call mtr.add_suppression("Uncompressed data size too large"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +# t1 tracks what replicates. t2 takes the corrupt events, and the replica +# never receives its rows. +create table t1 (a int); +create table t2 (a longtext); + +# The content must uncompress to far more than the 4096 byte stack buffer +# that queue_event() passes to the uncompress functions. +--let $long_value= `SELECT REPEAT('a', 65536)` +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--echo # +--echo # Test Case 1: a compressed Query event +--echo # +--connection master +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +# In statement format the compressed content is the query text itself, so +# the value has to appear in the statement rather than come from repeat(). +--disable_query_log +--eval insert into t2 values ('$long_value') +--enable_query_log +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +--let $corrupt_gtid_pos= `SELECT @@gtid_binlog_pos` + +--connection slave +--echo # Waiting for the IO thread to reject the compressed Query event +# The IO thread reports ER_TOO_BIG_FOR_UNCOMPRESS (1256) and then +# overwrites it with ER_SLAVE_RELAY_LOG_WRITE_FAILURE (1595), so the +# wait can sample either errno. +--let $slave_io_errno= 1256, 1595 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the replica rejected the event on its recorded length +--let $assert_text= The IO thread reported an oversized uncompressed length +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Uncompressed data size too large +--let $assert_match= Uncompressed data size too large; the maximum size is 1073741824 +--source include/assert_grep.inc + +--echo # Move the replica past the corrupt event +--source include/stop_slave_sql.inc +--disable_query_log +--eval SET GLOBAL gtid_slave_pos= '$corrupt_gtid_pos' +--enable_query_log +--source include/start_slave.inc + +--echo # Ensure replication resumes +--connection master +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables= master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Test Case 2: a compressed Write_rows event +--echo # +--connection master +set @saved_dbug= @@session.debug_dbug; +set @@session.debug_dbug= "+d,binlog_compress_corrupt_len"; +set @saved_binlog_format= @@session.binlog_format; +set @@session.binlog_format= ROW; +# In row format the compressed content is the record, so the statement +# that writes it can stay short. +insert into t2 values (repeat('a', 65536)); +set @@session.binlog_format= @saved_binlog_format; +set @@session.debug_dbug= @saved_dbug; +--let $corrupt_gtid_pos= `SELECT @@gtid_binlog_pos` + +--connection slave +--echo # Waiting for the IO thread to reject the compressed Write_rows event +# The IO thread reports ER_TOO_BIG_FOR_UNCOMPRESS (1256) and then +# overwrites it with ER_SLAVE_RELAY_LOG_WRITE_FAILURE (1595), so the +# wait can sample either errno. +--let $slave_io_errno= 1256, 1595 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the replica rejected the event on its recorded length +--let $assert_text= The IO thread reported an oversized uncompressed length +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Uncompressed data size too large +--let $assert_match= Uncompressed data size too large; the maximum size is 1073741824 +--source include/assert_grep.inc + +--echo # Move the replica past the corrupt event +--source include/stop_slave_sql.inc +--disable_query_log +--eval SET GLOBAL gtid_slave_pos= '$corrupt_gtid_pos' +--enable_query_log +--source include/start_slave.inc + +--echo # Ensure replication resumes +--connection master +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables= master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1, t2; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_compressed_log_event_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test b/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test new file mode 100644 index 00000000000..36058770ff3 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_corrupted_decimal_precision.test @@ -0,0 +1,82 @@ +# +# MDEV-40645 Slave crash on malformed User_var_log_event +# +# A User_var_log_event of type DECIMAL_RESULT carries the precision and the +# scale of the value in the first two bytes of the packed value. Nothing ties +# them to the length of the event, so a crafted event makes bin2decimal() +# alloca() and copy a size taken entirely from those two bytes. +# +--source include/have_debug.inc +# A User_var_log_event is only written when the statement itself is binlogged, +# i.e. never for row based replication. +--source include/have_binlog_format_mixed_or_statement.inc +--source include/master-slave.inc + +# The master intentionally writes a corrupted event, the slave has to reject it +--connection slave +call mtr.add_suppression("Slave SQL: Invalid variable length at User var event"); + +--connection master +create table t1 (a decimal(10,5)); +insert into t1 values (0.5); +--sync_slave_with_master + +--echo # +--echo # Write a User_var_log_event whose DECIMAL precision and scale do not +--echo # match the length of the packed value +--echo # +--connection master +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +set @a= 1.5; +insert into t1 values (@a); +set @@session.debug_dbug= ""; +--let $corrupt_file= query_get_value(SHOW MASTER STATUS, File, 1) + +--echo # +--echo # SHOW BINLOG EVENTS must not crash in User_var_log_event::pack_info() +--echo # +set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision"; +--source include/show_binlog_events.inc +set @@session.debug_dbug= ""; + +--echo # +--echo # mysqlbinlog must not crash in User_var_log_event::print() +--echo # +--let $datadir= `select @@datadir` +# The corrupted event is rejected, only the exit code matters here +--error 1 +--exec $MYSQL_BINLOG $datadir/$corrupt_file >/dev/null 2>&1 + +--echo # +--echo # The slave rejects the event instead of crashing on it +--echo # +--connection slave +--let $slave_sql_errno= 1593 +--source include/wait_for_slave_sql_error.inc + +--echo # +--echo # Replication continues once the corrupted event group is skipped +--echo # +set global sql_slave_skip_counter= 1; +--source include/start_slave.inc + +--connection master +insert into t1 values (3.5); +--sync_slave_with_master + +--echo # Only the skipped row (1.50000) is missing on the slave, every later +--echo # event is applied normally +--connection master +select * from t1 order by a; +--connection slave +select * from t1 order by a; + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--sync_slave_with_master + +--source include/rpl_end.inc +--echo # End of rpl_corrupted_decimal_precision.test diff --git a/mysql-test/suite/rpl/t/rpl_corruption.test b/mysql-test/suite/rpl/t/rpl_corruption.test index 16270fa4adc..7e17dac11e2 100644 --- a/mysql-test/suite/rpl/t/rpl_corruption.test +++ b/mysql-test/suite/rpl/t/rpl_corruption.test @@ -37,6 +37,7 @@ call mtr.add_suppression('event read from binlog did not pass crc check'); call mtr.add_suppression('Replication event checksum verification failed'); call mtr.add_suppression('Event crc check failed! Most likely there is event corruption'); call mtr.add_suppression('Slave SQL: Error initializing relay log position: I/O error reading event at position .*, error.* 1593'); +call mtr.add_suppression('Slave I/O: Fatal error: Event from master declares a length that does not match the packet the event arrived in;'); SET @old_master_verify_checksum = @@master_verify_checksum; diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt new file mode 100644 index 00000000000..a6e99a9fd5a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow-master.opt @@ -0,0 +1 @@ +--binlog-checksum=CRC32 diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test new file mode 100644 index 00000000000..2dc188c22c3 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_heartbeat_log_event_underflow.test @@ -0,0 +1,147 @@ +# +# This test verifies that a replica rejects a heartbeat event that is +# too short to carry the log file name it must hold. Two lengths are +# covered. An event that reaches the heartbeat parser but leaves no room +# for a file name must stop the IO thread with an error reporting an +# empty file name. An event shorter than the common header must be +# rejected before any parser reads that header. Replication must be able +# to resume after each. +# +# The debug injections send heartbeats of 17 and 13 bytes. MDEV-40643 +# reported that the 17-byte case underflowed the replica's computed name +# length and crashed the replica. +# +# Note rpl_heartbeat_log_event_underflow-master.opt sets +# binlog_checksum=CRC32 on the master for Test Case 1. That is, those four +# checksum bytes carry the 17-byte heartbeat over queue_event()'s 19-byte +# minimum length. +# +# References: +# * MDEV-40643: Corrupt Heartbeat Log Event can Crash Slave +# * Bug#39377010: Improve malformed heartbeat handling +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Unexpected master's heartbeat data"); +call mtr.add_suppression("Event from master is shorter than its common header"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +# Both cases need a heartbeat to arrive promptly. The default period is +# slave_net_timeout/2. The CHANGE MASTER that rpl_end.inc runs on every +# server restores it. +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_HEARTBEAT_PERIOD= 2; +--source include/start_slave.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case 1: a heartbeat that leaves no room for a file name +--echo # + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_truncated_heartbeat"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--echo # Waiting for the IO thread to reject the truncated heartbeat +# queue_event() reports 1623, then handle_slave_io() overwrites it with 1595 +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure heartbeat validation rejected the event +--let $assert_text= The IO thread reported an inconsistent heartbeat event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Unexpected master.s heartbeat data: inconsistent heartbeat event content +--let $assert_count= 1 +--source include/assert_grep.inc + +# An empty log_file_name shows ident_len was bounded. Reaching this assertion +# at all is the other half of the evidence. +--let $assert_text= The heartbeat error should report an empty log_file_name +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= heartbeat event content;the event.s data: log_file_name log_pos [0-9]+ +--let $assert_count= 1 +--source include/assert_grep.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 2: a heartbeat shorter than the common header +--echo # +--connection master +set @@global.debug_dbug= "+d,binlog_sender_undersized_heartbeat"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--echo # Waiting for the IO thread to reject the undersized heartbeat +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +--source include/wait_for_slave_io_error.inc + +--echo # Ensure the event was rejected before its header was read +--let $assert_text= The IO thread reported an event shorter than the common header +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master is shorter than its common header; the event.s length: [0-9]+ +--let $assert_count= 1 +--source include/assert_grep.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the invalid heartbeat events +--echo # +--connection master +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_heartbeat_log_event_underflow.test diff --git a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt new file mode 100644 index 00000000000..a6e99a9fd5a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch-master.opt @@ -0,0 +1 @@ +--binlog-checksum=CRC32 diff --git a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test new file mode 100644 index 00000000000..f2d5015b082 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test @@ -0,0 +1,260 @@ +# +# This test verifies that a replica rejects an event whose header +# declares a length other than the number of bytes the packet holds. +# Three shapes are covered: a header declaring fewer bytes than the +# packet holds, a header declaring more, and a packet holding a second +# complete event behind the first. Each must stop the IO thread with an +# error, and replication must be able to resume after each. +# +# A replica relay logs the packet as received, while every reader of +# that relay log frames each event by the length the event's own header +# declares. An accepted event of any of these shapes therefore leaves +# the SQL thread beginning its next read at the wrong offset. Where the +# bytes at that offset are themselves an event, the replica applies that +# event, and the replica's data ends up holding what the master's binary +# log does not. MDEV-40648 reported that a replica never compared the +# two lengths. +# +# Note rpl_queue_event_length_mismatch-master.opt sets +# binlog_checksum=CRC32 on the master, so the event lengths Test Cases 1 +# and 2 pin include those four checksum bytes. +# +# References: +# * MDEV-40648: Replication Undefined Behavior on Malformed Rotate Log Event +# * Bug#39254914: Improve directly queued event validation +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Event from master declares a length that does not match"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +--source include/stop_slave.inc +change master to master_use_gtid=no; +--source include/start_slave.inc + +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case 1: a Rotate event declaring fewer bytes than its packet +--echo # + +# The dump thread reads the injection when the replica reconnects below. +# The injection reaches real Rotate events only, so FLUSH LOGS is what +# produces the event. The fake Rotate that opens a connection is written +# by fake_rotate_event(), which the injection never runs in. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_short_event_len"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +FLUSH LOGS; + +--echo # Waiting for the IO thread to reject the mismatched Rotate event +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The Rotate event names master-bin.000002, so the packet holds 48 bytes: +# the two headers, the 17 byte file name, and the checksum. +--echo # Ensure the event was rejected before the replica relay logged the event +--let $assert_text= The IO thread reported a Rotate event declaring 8 bytes fewer than its packet +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master declares a length that does not match the packet the event arrived in; the declared length: 40, the packet.s length: 48 +--let $assert_count= 1 +--source include/assert_grep.inc + +# A relay logged mismatch stops the SQL thread, which frames the relay log +# by the lengths the event headers declare. The sample below must wait for +# the SQL thread to read up to the IO thread's stop position: an earlier +# sample reads 0 whether or not a mismatch reached the relay log. +--echo # Ensure the SQL thread read everything the IO thread relay logged +--source include/sync_slave_sql_with_io.inc +--let $sql_errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1) +--let $assert_text= The SQL thread reported no relay log read failure +--let $assert_cond= "$sql_errno" = "0" +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--connection master +--source include/sync_slave_io_with_master.inc + +--echo # +--echo # Test Case 2: a Rotate event declaring more bytes than its packet +--echo # + +--connection master +set @@global.debug_dbug= "+d,binlog_sender_long_event_len"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +FLUSH LOGS; + +--echo # Waiting for the IO thread to reject the mismatched Rotate event +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +--echo # Ensure the event was rejected before the replica relay logged the event +--let $assert_text= The IO thread reported a Rotate event declaring 8 bytes more than its packet +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Event from master declares a length that does not match the packet the event arrived in; the declared length: 56, the packet.s length: 48 +--let $assert_count= 1 +--source include/assert_grep.inc + +# A relay logged mismatch stops the SQL thread, which frames the relay log +# by the lengths the event headers declare. The sample below must wait for +# the SQL thread to read up to the IO thread's stop position: an earlier +# sample reads 0 whether or not a mismatch reached the relay log. +--echo # Ensure the SQL thread read everything the IO thread relay logged +--source include/sync_slave_sql_with_io.inc +--let $sql_errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1) +--let $assert_text= The SQL thread reported no relay log read failure +--let $assert_cond= "$sql_errno" = "0" +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 3: a packet carrying a second event behind the first +--echo # + +# Note that this case does not assert whether the appended event executes. +# Execution state is not tracked the same way before and after the fix: the +# fix rejects the packet before either copy is queued, so the assertions +# below are never reached without the fix. Testing the regression before +# the fix means observing the replica's t1 by hand, for example after a +# sleep. + +# The master logs no checksum for this case. The packet ends in the checksum +# bytes the trailing copy carries, and those bytes cover the copy rather +# than the packet, so a replica checking the packet's own checksum would +# reject the event as corrupt before reaching the length comparison. +--connection slave +--source include/stop_slave.inc + +--connection master +set @saved_checksum= @@global.binlog_checksum; +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_append_extra_event"; + +--connection slave +--source include/start_slave.inc + +# The statement's own Query event carries the injection, so the insert is +# logged as a statement. Note that we switch to STATEMENT mode temporarily +# because a row event cannot carry the injection: the trailing copy reaches +# the applier after the leading copy's STMT_END_F has cleared the table map, +# and the applier skips a row event whose table id is no longer mapped. +--connection master +set @saved_format= @@session.binlog_format; +set @@session.binlog_format= STATEMENT; +insert into t1 values (2); +set @@session.binlog_format= @saved_format; + +--echo # Waiting for the IO thread to reject the doubled packet +--connection slave +--let $slave_io_errno= 1593, 1595 +# A master that leaves the two lengths agreeing shows up here as a timeout. +# Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The packet held the insert twice. A replica reading its relay log by the +# event headers applies the leading copy and then the trailing one, landing +# the row twice. Rejecting the packet keeps both copies out, so the table +# still holds only the row the initialization above inserted. +--echo # Ensure the rejected packet delivered nothing to the replica +--let $slave_row_count= query_get_value(SELECT COUNT(*) AS c FROM t1, c, 1) +--let $assert_text= The replica has not applied the insert the packet carried +--let $assert_cond= $slave_row_count = 1 +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc + +--echo # Ensure old relay logs are purged, use GTID +change master to master_use_gtid=slave_pos; +--source include/start_slave.inc + +--echo # Ensure the replica holds the row once the master resends the insert +--connection master +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Ensure replication works after the mismatched events +--echo # +--connection master +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_queue_event_length_mismatch.test diff --git a/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test new file mode 100644 index 00000000000..579d7b4c420 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test @@ -0,0 +1,51 @@ +# MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events + +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # should be format-agnostic + +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +--let $rpl_skip_start_slave= 1 +# This setup will also RESET MASTER ... +--source include/master-slave.inc +# ... which means the binlog file is consistent. +--let $binlog_file= master-bin.000001 + +# Use a specific binlog position to avoid the +# test failing early from other metadata events +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) +# Generate the Rotate Event +FLUSH BINARY LOGS; + + +# Control: SHOW BINLOG EVENTS fails as expected. +CALL mtr.add_suppression('Found invalid event in binary log'); +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' FROM $binlog_start +--enable_query_log + +--connection slave +--disable_query_log + eval CHANGE MASTER TO master_use_gtid=NO, + master_log_file='$binlog_file', master_log_pos=$binlog_start; +--enable_query_log +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +# ER_SLAVE_RELAY_LOG_WRITE_FAILURE +--let $slave_io_errno= 1595 +# Experiment: The IO thread should fail. +--source include/wait_for_slave_io_error.inc + +# Clean-up +--connection master +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; + +--connection slave +CHANGE MASTER TO master_use_gtid=SLAVE_POS; # restore the default +--disable_warnings + SET @@GLOBAL.gtid_slave_pos=''; # for good measure +--enable_warnings +--source include/start_slave.inc +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test b/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test new file mode 100644 index 00000000000..d55e4aac81b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_log_event_overflow.test @@ -0,0 +1,188 @@ +# +# This test verifies that a replica rejects a fake Rotate event that names +# a binary log file longer than the longest file name a replica can hold. The +# replica rewrites the checksum of that event through a buffer of its own +# whenever the master's binlog_checksum setting differs from the checksum its +# relay log carries. Both directions of that difference are covered, and +# replication must be able to resume after each. The replica connects by +# binlog file position, so each resume also verifies the rejected event left +# those coordinates untouched. +# +# MDEV-40492 reported that the replica sized the copy into that buffer from +# the length the master sent, and overran it. +# +# Note the relay log takes its checksum from the last format description +# event the replica queued, so each case changes binlog_checksum on the master +# while the IO thread is stopped. That is, the fake Rotate of the next +# connection arrives ahead of the format description event that settles the +# difference again. +# +# References: +# * MDEV-40492: queue_event: fake ROTATE checksum adjust copies into fixed +# rot_buf without bounding event_len (stack OOB) +# * MDEV-39689: Slave Overflow on Malformed Table_map_log_event +# + +--source include/have_debug.inc +# Test is format independent +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Rotate event from master names a binary log file longer"); +call mtr.add_suppression("Relay log write failure: could not queue event from master"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +--source include/stop_slave.inc +change master to master_use_gtid=no; +--source include/start_slave.inc + +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; +set @saved_checksum= @@global.binlog_checksum; + +--echo # +--echo # Test Case 1: the master adds no checksum and the relay log carries one +--echo # + +# Replicating from a master that adds a checksum gives the relay log one. +--connection master +set @@global.binlog_checksum= CRC32; +insert into t1 values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--source include/stop_slave.inc + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.binlog_checksum= NONE; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; + +# The IO thread can reject the event before include/start_slave.inc observes +# the thread running, and the include reports a nonzero Last_IO_Errno as a +# failure of its own. +--connection slave +START SLAVE; + +--echo # Waiting for the IO thread to reject the oversized Rotate event +# queue_event() reports 1593, then handle_slave_io() overwrites it with 1595 +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +# A setup that leaves the master and the relay log agreeing shows up here as +# a timeout. Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The reported maximum bounds a name of FN_REFLEN bytes behind the same headers +--echo # Ensure the event was rejected before the replica copied it +--let $assert_text= The IO thread reported an oversized Rotate event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Rotate event from master names a binary log file longer than the maximum file name length;.*the maximum: 539 +--let $assert_count= 1 +--source include/assert_grep.inc + +# Restore the master's checksum before the IO thread reconnects. The relay log +# takes its checksum from the next format description event, so the reconnect +# leaves the master and the relay log agreeing again. +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Test Case 2: the master adds a checksum and the relay log carries none +--echo # + +# Replicating from a master that adds no checksum leaves the relay log +# without one. +--connection master +set @@global.binlog_checksum= NONE; +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--source include/stop_slave.inc + +# The dump thread reads the injection when the replica reconnects below. +--connection master +set @@global.binlog_checksum= CRC32; +set @@global.debug_dbug= "+d,binlog_sender_oversized_fake_rotate"; + +# The IO thread can reject the event before include/start_slave.inc observes +# the thread running, and the include reports a nonzero Last_IO_Errno as a +# failure of its own. +--connection slave +START SLAVE; + +--echo # Waiting for the IO thread to reject the oversized Rotate event +--let $slave_io_errno= 1595 +--let $show_slave_io_error= 1 +# A setup that leaves the master and the relay log agreeing shows up here as +# a timeout. Keep that timeout short rather than the default of five minutes. +--let $slave_timeout= 30 +--source include/wait_for_slave_io_error.inc +--let $slave_timeout= + +# The reported maximum bounds a name of FN_REFLEN bytes behind the same +# headers, plus that checksum. +--echo # Ensure the event was rejected before the replica copied it +--let $assert_text= The IO thread reported an oversized Rotate event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Rotate event from master names a binary log file longer than the maximum file name length;.*the maximum: 543 +--let $assert_count= 1 +--source include/assert_grep.inc + +# Restore the master's checksum before the IO thread reconnects. The relay log +# takes its checksum from the next format description event, so the reconnect +# leaves the master and the relay log agreeing again. +--connection master +set @@global.debug_dbug= @saved_dbug; +set @@global.binlog_checksum= @saved_checksum; + +--connection slave +--source include/stop_slave_sql.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the oversized Rotate events +--echo # +--connection master +insert into t1 values (4); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_rotate_log_event_overflow.test diff --git a/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test b/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test new file mode 100644 index 00000000000..2f081f062d2 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_metadata_underflow.test @@ -0,0 +1,114 @@ +# +# This test verifies that a replica rejects a Table_map event whose field +# metadata is shorter than the metadata the column types it declares require. +# The replica must stop the SQL thread with an error rather than decode the +# metadata past the end of what the event carries, and replication must resume +# once a well formed copy of the event arrives. +# +# MDEV-40646 reported that such an event read past the metadata buffer. A +# build with AddressSanitizer is the reliable witness of that read, aborting +# at the first byte past the allocation. A regular debug build does not fault +# on the read: it decodes adjacent heap and stops the SQL thread with a +# conversion error whose reported type is rendered from those bytes. This test +# asserts the rejection the fix adds ahead of the read, which holds on every +# build. +# +# References: +# * MDEV-40646: Slave Crashes in table_def::table_def() on Malformed +# Table_map +# + +--source include/have_debug.inc +# A Table_map event is only written under row based binary logging. +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Found invalid event in binary log"); +call mtr.add_suppression("Relay log read failure: Could not parse relay log event entry"); + +--echo # +--echo # Initialize test data +--echo # + +--connection slave +# Use slave_pos so we can easily reset the relay log after it receives a +# corrupted event +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +--source include/start_slave.inc + +--connection master +# With 24 columns, the metadata reads run well past the end of the replica's +# allocation once the event declares a single metadata byte. +create table t1 (c1 int,c2 int,c3 int,c4 int,c5 int,c6 int,c7 int,c8 int,c9 int,c10 int,c11 int,c12 int,c13 int,c14 int,c15 int,c16 int,c17 int,c18 int,c19 int,c20 int,c21 int,c22 int,c23 int,c24 int); +insert into t1 (c1) values (0); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case: a Table_map declaring more metadata than it carries +--echo # + +# The reconnect below starts a dump thread that inherits the injection. +--connection master +set @@global.debug_dbug= "+d,binlog_sender_undersized_table_map_metadata"; + +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +insert into t1 (c1) values (1); + +--connection slave +--echo # Waiting for the SQL thread to reject the malformed Table_map +--let $slave_sql_errno= 1594 +--source include/wait_for_slave_sql_error.inc + +--echo # Ensure the event was rejected before its metadata was decoded +--let $assert_text= The SQL thread reported an invalid event +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Found invalid event in binary log +--let $assert_count= 1 +--source include/assert_grep.inc + +--echo # +--echo # Recover: discard the poisoned relay log and re-fetch a clean copy +--echo # +--connection master +set @@global.debug_dbug= @saved_dbug; + +--connection slave +--source include/stop_slave_io.inc +--source include/start_slave.inc + +--echo # +--echo # Ensure replication works after the invalid event +--echo # +--connection master +insert into t1 (c1) values (2); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_table_map_metadata_underflow.test diff --git a/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt new file mode 100644 index 00000000000..dc66bb4c46a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow-master.opt @@ -0,0 +1 @@ +--binlog-checksum=NONE diff --git a/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test new file mode 100644 index 00000000000..b92f31123fb --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_table_map_name_overflow.test @@ -0,0 +1,127 @@ +# +# This test verifies that a replica rejects a Table_map event whose +# database or table name is longer than an identifier can be, when the SQL +# thread reads the event from the relay log and constructs it. +# +# A replica copies each name into a NAME_LEN+1 byte buffer with strmov(), +# which stops only at a terminating null, so without the bound a 255 byte +# name overruns that buffer as the SQL thread applies the event. The event's +# constructor rejects the oversized name before that copy, so the SQL thread +# stops with ER_SLAVE_RELAY_LOG_READ_FAILURE instead. +# +# Note rpl_table_map_name_overflow-master.opt sets binlog_checksum=NONE on +# the master to simplify creating a corrupted event to send to the slave. +# +# References: +# * MDEV-40644: Slave SQL Thread Overflow on Malformed Table_map_log_event +# + +--source include/have_debug.inc +# A Table_map event is only produced in row format. +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--connection slave +call mtr.add_suppression("Error in Log_event::read_log_event.*Found invalid event"); +call mtr.add_suppression("Relay log read failure"); + +--echo # +--echo # Initialize test data +--echo # +--connection master +create table t1 (a int); +insert into t1 values (1); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +# Use slave_pos so we can easily reset the relay log after it receives a +# corrupted event +--source include/stop_slave.inc +CHANGE MASTER TO MASTER_USE_GTID=slave_pos; +--source include/start_slave.inc + +--connection master +set @saved_dbug= @@global.debug_dbug; + +--echo # +--echo # Test Case: a Table_map event naming a table with an oversized name +--echo # +--connection master +set @@global.debug_dbug= "+d,binlog_sender_oversized_table_name"; + +# The dump thread reads the global setting when the replica reconnects, so +# restart the replica to open a fresh dump thread that carries the injection. +--connection slave +--source include/stop_slave.inc +--source include/start_slave.inc + +--connection master +insert into t1 values (2); + +--echo # Waiting for the SQL thread to reject the oversized Table_map event +--connection slave +--let $slave_sql_errno= 1594 +--source include/wait_for_slave_sql_error.inc + +# The injection grows the table name to 255 bytes and leaves the database +# name "test" at its four bytes. The constructor rejects the oversized name, +# so read_log_event() reports the event as invalid. +--echo # Ensure the SQL thread rejected the oversized Table_map event +--let $assert_text= The SQL thread reported the oversized Table_map event as invalid +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_select= Found invalid event in binary log +--let $assert_count= 1 +--source include/assert_grep.inc + +# The constructor rejects the event before do_apply_event() copies the name, +# so the SQL thread stops without applying the row the event carried. +--echo # Ensure the rejected event delivered nothing to the replica +--let $slave_row_count= query_get_value(SELECT COUNT(*) AS c FROM t1, c, 1) +--let $assert_text= The replica has not applied the insert the event carried +--let $assert_cond= $slave_row_count = 1 +--source include/assert.inc + +--connection master +set @@global.debug_dbug= @saved_dbug; + +--echo # +--echo # Ensure the replica holds the row once it refetches the relay log +--echo # +--connection slave +--source include/stop_slave_io.inc +--source include/start_slave.inc + +--connection master +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Ensure replication works after the rejected event +--echo # +--connection master +insert into t1 values (3); +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:t1,slave:t1 +--source include/diff_tables.inc + +--echo # +--echo # Cleanup +--echo # +--connection master +drop table t1; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc + +--source include/rpl_end.inc +--echo # End of rpl_table_map_name_overflow.test diff --git a/mysql-test/suite/s3/clone.result b/mysql-test/suite/s3/clone.result index a33c8c4259a..308b0a80923 100644 --- a/mysql-test/suite/s3/clone.result +++ b/mysql-test/suite/s3/clone.result @@ -1,12 +1,10 @@ # # SELECT using ror_merged scan fails with s3 tables # -DROP TABLE IF EXISTS t1; -Warnings: -Note 1051 Unknown table 'test.t1' CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; INSERT INTO t1 VALUES (0,0),(0,10),(3,10); ALTER TABLE t1 ENGINE=S3; SELECT * FROM t1 WHERE a = 99 OR b = 2; a b DROP TABLE t1; +End of 10.11 tests diff --git a/mysql-test/suite/s3/clone.test b/mysql-test/suite/s3/clone.test index 5695257c404..304671654e6 100644 --- a/mysql-test/suite/s3/clone.test +++ b/mysql-test/suite/s3/clone.test @@ -2,13 +2,21 @@ --source include/have_sequence.inc --source include/have_innodb.inc +# +# Create unique database for running the tests +# +--source create_database.inc + --echo # --echo # SELECT using ror_merged scan fails with s3 tables --echo # -DROP TABLE IF EXISTS t1; CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=Aria; INSERT INTO t1 VALUES (0,0),(0,10),(3,10); ALTER TABLE t1 ENGINE=S3; SELECT * FROM t1 WHERE a = 99 OR b = 2; DROP TABLE t1; + +--source drop_database.inc + +--echo End of 10.11 tests diff --git a/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff b/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff new file mode 100644 index 00000000000..44674696c9c --- /dev/null +++ b/mysql-test/suite/sys_vars/r/secure_file_priv,win.rdiff @@ -0,0 +1,10 @@ +--- secure_file_priv.result ++++ secure_file_priv.reject +@@ -30,6 +30,6 @@ + # + CREATE TABLE t1 (c1 VARCHAR(50)); + LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +-ERROR HY000: The MariaDB server is running with the --secure-file-priv option so it cannot execute this statement ++ERROR HY000: File 'C:\proc\cpuinfo' not found (Errcode: 2 "No such file or directory") + DROP TABLE t1; + # End of 10.6 tests diff --git a/mysql-test/suite/sys_vars/r/secure_file_priv.result b/mysql-test/suite/sys_vars/r/secure_file_priv.result index eeeb9a58c0f..06968a185e7 100644 --- a/mysql-test/suite/sys_vars/r/secure_file_priv.result +++ b/mysql-test/suite/sys_vars/r/secure_file_priv.result @@ -6,12 +6,17 @@ INSERT INTO t1 VALUES ("one"),("two"),("three"),("four"),("five"); SHOW VARIABLES LIKE 'secure_file_priv'; Variable_name Value secure_file_priv +SELECT * INTO OUTFILE '$MYSQLTEST_VARDIR/../bug50373.txt' FROM t1; +DELETE FROM t1; +LOAD DATA INFILE '$MYSQLTEST_VARDIR/../bug50373.txt' INTO TABLE t1; +SELECT * FROM t1; c1 one two three four five +SELECT load_file('$MYSQLTEST_VARDIR/../bug50373.txt') AS loaded_file; loaded_file one two @@ -20,3 +25,11 @@ four five DROP TABLE t1; +# +# MDEV-40589 default exclude list for secure-file-priv +# +CREATE TABLE t1 (c1 VARCHAR(50)); +LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +ERROR HY000: The MariaDB server is running with the --secure-file-priv option so it cannot execute this statement +DROP TABLE t1; +# End of 10.6 tests diff --git a/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result new file mode 100644 index 00000000000..7badd1cb5f0 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/sysvars_readonly_debug.result @@ -0,0 +1,18 @@ +# +# MDEV-40341 store read-only sysvars in a read-only root and a read-only segment +# +call mtr.add_suppression('mariadbd(.exe)? got (exception 0xc0000005|signal 11)'); +select @@global.skip_name_resolve; +@@global.skip_name_resolve +0 +set @@global.skip_name_resolve=0; +ERROR HY000: Variable 'skip_name_resolve' is a read only variable +set @@debug_dbug='d,set_skip_name_resolve'; +Got one of the listed errors +select @@global.version; +@@global.version +12.11.10-fake-version-for-the-test +set @@global.version=0; +ERROR HY000: Variable 'version' is a read only variable +set @@debug_dbug='d,set_version_buf'; +Got one of the listed errors diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 0af805506b1..114eff7521d 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3355,7 +3355,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SECURE_FILE_PRIV VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR -VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory +VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff index acee8a39b43..77c7c9cf941 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded,win.rdiff @@ -1,6 +1,6 @@ ---- suite/sys_vars/r/sysvars_server_notembedded.result -+++ suite/sys_vars/r/sysvars_server_notembedded.reject -@@ -34,7 +34,7 @@ +--- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result ++++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +@@ -44,7 +44,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_BLOCK_SIZE VARIABLE_SCOPE GLOBAL @@ -9,7 +9,7 @@ VARIABLE_COMMENT Block size to be used for Aria index pages. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 32768 -@@ -44,7 +44,7 @@ +@@ -54,7 +54,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_CHECKPOINT_INTERVAL VARIABLE_SCOPE GLOBAL @@ -18,7 +18,7 @@ VARIABLE_COMMENT Interval between tries to do an automatic checkpoints. In seconds; 0 means 'no automatic checkpoints' which makes sense only for testing. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -54,7 +54,7 @@ +@@ -64,7 +64,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_CHECKPOINT_LOG_ACTIVITY VARIABLE_SCOPE GLOBAL @@ -27,7 +27,7 @@ VARIABLE_COMMENT Number of bytes that the transaction log has to grow between checkpoints before a new checkpoint is written to the log. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -74,7 +74,7 @@ +@@ -84,7 +84,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ARIA_FORCE_START_AFTER_RECOVERY_FAILURES VARIABLE_SCOPE GLOBAL @@ -36,7 +36,7 @@ VARIABLE_COMMENT Number of consecutive log recovery failures after which logs will be automatically deleted to cure the problem; 0 (the default) disables the feature. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -94,7 +94,7 @@ +@@ -104,7 +104,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_GROUP_COMMIT_INTERVAL VARIABLE_SCOPE GLOBAL @@ -45,7 +45,7 @@ VARIABLE_COMMENT Interval between commits in microseconds (1/1000000 sec). 0 stands for no waiting for other threads to come and do a commit in "hard" mode and no sync()/commit at all in "soft" mode. Option has only an effect if aria_group_commit is used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -114,7 +114,7 @@ +@@ -124,7 +124,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_LOG_FILE_SIZE VARIABLE_SCOPE GLOBAL @@ -54,7 +54,7 @@ VARIABLE_COMMENT Limit for transaction log size NUMERIC_MIN_VALUE 8388608 NUMERIC_MAX_VALUE 4294967295 -@@ -144,10 +144,10 @@ +@@ -154,10 +154,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_AGE_THRESHOLD VARIABLE_SCOPE GLOBAL @@ -67,7 +67,7 @@ NUMERIC_BLOCK_SIZE 100 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -164,7 +164,7 @@ +@@ -174,7 +174,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_DIVISION_LIMIT VARIABLE_SCOPE GLOBAL @@ -76,7 +76,7 @@ VARIABLE_COMMENT The minimum percentage of warm blocks in key cache NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100 -@@ -174,7 +174,7 @@ +@@ -184,7 +184,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME ARIA_PAGECACHE_FILE_HASH_SIZE VARIABLE_SCOPE GLOBAL @@ -85,7 +85,7 @@ VARIABLE_COMMENT Number of hash buckets for open and changed files. If you have a lot of Aria files open you should increase this for faster flush of changes. A good value is probably 1/10 of number of possible open Aria files. NUMERIC_MIN_VALUE 128 NUMERIC_MAX_VALUE 16384 -@@ -204,7 +204,7 @@ +@@ -214,7 +214,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ARIA_REPAIR_THREADS VARIABLE_SCOPE SESSION @@ -94,7 +94,7 @@ VARIABLE_COMMENT Number of threads to use when repairing Aria tables. The value of 1 disables parallel repair. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 128 -@@ -274,7 +274,7 @@ +@@ -284,7 +284,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME AUTO_INCREMENT_INCREMENT VARIABLE_SCOPE SESSION @@ -103,7 +103,7 @@ VARIABLE_COMMENT Auto-increment columns are incremented by this NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 65535 -@@ -284,7 +284,7 @@ +@@ -294,7 +294,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME AUTO_INCREMENT_OFFSET VARIABLE_SCOPE SESSION @@ -112,7 +112,7 @@ VARIABLE_COMMENT Offset added to Auto-increment columns. Used when auto-increment-increment != 1 NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 65535 -@@ -294,7 +294,7 @@ +@@ -304,7 +304,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME BACK_LOG VARIABLE_SCOPE GLOBAL @@ -121,7 +121,7 @@ VARIABLE_COMMENT The number of outstanding connection requests MariaDB can have. This comes into play when the main MariaDB thread gets very many connection requests in a very short time NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65535 -@@ -364,20 +364,20 @@ +@@ -374,20 +374,20 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME BINLOG_COMMIT_WAIT_COUNT VARIABLE_SCOPE GLOBAL @@ -146,7 +146,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -394,7 +394,7 @@ +@@ -404,7 +404,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME BINLOG_EXPIRE_LOGS_SECONDS VARIABLE_SCOPE GLOBAL @@ -155,7 +155,7 @@ VARIABLE_COMMENT If non-zero, binary logs will be purged after binlog_expire_logs_seconds seconds; It and expire_logs_days are linked, such that changes in one are converted into the other. Possible purges happen at startup and at binary log rotation. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 8553600 -@@ -654,7 +654,7 @@ +@@ -664,7 +664,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME CONNECT_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -164,7 +164,7 @@ VARIABLE_COMMENT The number of seconds the mysqld server is waiting for a connect packet before responding with 'Bad handshake' NUMERIC_MIN_VALUE 2 NUMERIC_MAX_VALUE 31536000 -@@ -704,7 +704,7 @@ +@@ -714,7 +714,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_SEARCH_DEPTH_LONG VARIABLE_SCOPE SESSION @@ -173,7 +173,7 @@ VARIABLE_COMMENT Long search depth for the two-step deadlock detection NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 33 -@@ -714,7 +714,7 @@ +@@ -724,7 +724,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_SEARCH_DEPTH_SHORT VARIABLE_SCOPE SESSION @@ -182,7 +182,7 @@ VARIABLE_COMMENT Short search depth for the two-step deadlock detection NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 32 -@@ -724,7 +724,7 @@ +@@ -734,7 +734,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_TIMEOUT_LONG VARIABLE_SCOPE SESSION @@ -191,7 +191,7 @@ VARIABLE_COMMENT Long timeout for the two-step deadlock detection (in microseconds) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -734,7 +734,7 @@ +@@ -744,7 +744,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DEADLOCK_TIMEOUT_SHORT VARIABLE_SCOPE SESSION @@ -200,7 +200,7 @@ VARIABLE_COMMENT Short timeout for the two-step deadlock detection (in microseconds) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -794,7 +794,7 @@ +@@ -804,7 +804,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME DEFAULT_WEEK_FORMAT VARIABLE_SCOPE SESSION @@ -209,7 +209,7 @@ VARIABLE_COMMENT The default week format used by WEEK() functions NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 7 -@@ -804,7 +804,7 @@ +@@ -814,7 +814,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_INSERT_LIMIT VARIABLE_SCOPE GLOBAL @@ -218,7 +218,7 @@ VARIABLE_COMMENT After inserting delayed_insert_limit rows, the INSERT DELAYED handler will check if there are any SELECT statements pending. If so, it allows these to execute before continuing. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -814,7 +814,7 @@ +@@ -824,7 +824,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_INSERT_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -227,7 +227,7 @@ VARIABLE_COMMENT How long a INSERT DELAYED thread should wait for INSERT statements before terminating NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -824,7 +824,7 @@ +@@ -834,7 +834,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME DELAYED_QUEUE_SIZE VARIABLE_SCOPE GLOBAL @@ -236,7 +236,7 @@ VARIABLE_COMMENT What size queue (in rows) should be allocated for handling INSERT DELAYED. If the queue becomes full, any client that does INSERT DELAYED will wait until there is room in the queue again NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -854,7 +854,7 @@ +@@ -864,7 +864,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME DIV_PRECISION_INCREMENT VARIABLE_SCOPE SESSION @@ -245,7 +245,7 @@ VARIABLE_COMMENT Precision of the result of '/' operator will be increased on that value NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 38 -@@ -974,7 +974,7 @@ +@@ -984,7 +984,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME EXTRA_MAX_CONNECTIONS VARIABLE_SCOPE GLOBAL @@ -254,7 +254,7 @@ VARIABLE_COMMENT The number of connections on extra-port NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100000 -@@ -1004,7 +1004,7 @@ +@@ -1014,7 +1014,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME FLUSH_TIME VARIABLE_SCOPE GLOBAL @@ -263,7 +263,7 @@ VARIABLE_COMMENT A dedicated thread is created to flush all tables at the given interval NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -1034,7 +1034,7 @@ +@@ -1044,7 +1044,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_MAX_WORD_LEN VARIABLE_SCOPE GLOBAL @@ -272,7 +272,7 @@ VARIABLE_COMMENT The maximum length of the word to be included in a FULLTEXT index. Note: FULLTEXT indexes must be rebuilt after changing this variable NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 84 -@@ -1044,7 +1044,7 @@ +@@ -1054,7 +1054,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_MIN_WORD_LEN VARIABLE_SCOPE GLOBAL @@ -281,7 +281,7 @@ VARIABLE_COMMENT The minimum length of the word to be included in a FULLTEXT index. Note: FULLTEXT indexes must be rebuilt after changing this variable NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 84 -@@ -1054,7 +1054,7 @@ +@@ -1064,7 +1064,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME FT_QUERY_EXPANSION_LIMIT VARIABLE_SCOPE GLOBAL @@ -290,7 +290,7 @@ VARIABLE_COMMENT Number of best matches to use for query expansion NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1000 -@@ -1304,7 +1304,7 @@ +@@ -1314,7 +1314,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME HISTOGRAM_SIZE VARIABLE_SCOPE SESSION @@ -299,7 +299,7 @@ VARIABLE_COMMENT Number of bytes used for a histogram. If set to 0, no histograms are created by ANALYZE. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -1334,7 +1334,7 @@ +@@ -1344,7 +1344,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME HOST_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -308,7 +308,7 @@ VARIABLE_COMMENT How many host names should be cached to avoid resolving. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65536 -@@ -1357,7 +1357,7 @@ +@@ -1367,7 +1367,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for read-only idle transaction NUMERIC_MIN_VALUE 0 @@ -317,7 +317,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1367,7 +1367,7 @@ +@@ -1377,7 +1377,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for idle transaction NUMERIC_MIN_VALUE 0 @@ -326,7 +326,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1377,7 +1377,7 @@ +@@ -1387,7 +1387,7 @@ VARIABLE_SCOPE SESSION VARIABLE_TYPE INT UNSIGNED VARIABLE_COMMENT The number of seconds the server waits for write idle transaction NUMERIC_MIN_VALUE 0 @@ -335,7 +335,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1444,7 +1444,7 @@ +@@ -1454,7 +1454,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME INTERACTIVE_TIMEOUT VARIABLE_SCOPE SESSION @@ -344,7 +344,7 @@ VARIABLE_COMMENT The number of seconds the server waits for activity on an interactive connection before closing it NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -1494,7 +1494,7 @@ +@@ -1504,7 +1504,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME JOIN_CACHE_LEVEL VARIABLE_SCOPE SESSION @@ -353,7 +353,7 @@ VARIABLE_COMMENT Controls what join operations can be executed with join buffers. Odd numbers are used for plain join buffers while even numbers are used for linked buffers NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 8 -@@ -1672,19 +1672,9 @@ +@@ -1682,19 +1682,9 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL @@ -374,7 +374,7 @@ VARIABLE_COMMENT Timeout in seconds to wait for a lock before returning an error. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -1834,7 +1824,7 @@ +@@ -1844,7 +1834,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_SLOW_MAX_WARNINGS VARIABLE_SCOPE SESSION @@ -383,7 +383,7 @@ VARIABLE_COMMENT Max numbers of warnings printed to slow query log per statement NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1000 -@@ -1844,7 +1834,7 @@ +@@ -1854,7 +1844,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_SLOW_RATE_LIMIT VARIABLE_SCOPE SESSION @@ -392,7 +392,7 @@ VARIABLE_COMMENT Write to slow log every #th slow query. Set to 1 to log everything. Increase it to reduce the size of the slow or the performance impact of slow logging NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -1874,7 +1864,7 @@ +@@ -1884,7 +1874,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME LOG_WARNINGS VARIABLE_SCOPE SESSION @@ -401,7 +401,7 @@ VARIABLE_COMMENT Log some non critical warnings to the error log.Value can be between 0 and 11. Higher values mean more verbosity NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -1934,7 +1924,7 @@ +@@ -1944,7 +1934,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME MAX_ALLOWED_PACKET VARIABLE_SCOPE SESSION @@ -410,7 +410,7 @@ VARIABLE_COMMENT Max packet length to send to or receive from the server NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -1954,7 +1944,7 @@ +@@ -1964,7 +1954,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_BINLOG_SIZE VARIABLE_SCOPE GLOBAL @@ -419,7 +419,7 @@ VARIABLE_COMMENT Binary log will be rotated automatically when the size exceeds this value. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 1073741824 -@@ -1974,7 +1964,7 @@ +@@ -1984,7 +1974,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_CONNECTIONS VARIABLE_SCOPE GLOBAL @@ -428,7 +428,7 @@ VARIABLE_COMMENT The number of simultaneous clients allowed NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 100000 -@@ -1984,7 +1974,7 @@ +@@ -1994,7 +1984,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_CONNECT_ERRORS VARIABLE_SCOPE GLOBAL @@ -437,7 +437,7 @@ VARIABLE_COMMENT If there is more than this number of interrupted connections from a host this host will be blocked from further connections NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -1994,7 +1984,7 @@ +@@ -2004,7 +1994,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_DELAYED_THREADS VARIABLE_SCOPE SESSION @@ -446,7 +446,7 @@ VARIABLE_COMMENT Don't start more than this number of threads to handle INSERT DELAYED statements. If set to zero INSERT DELAYED will be not used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -2014,7 +2004,7 @@ +@@ -2024,7 +2014,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_ERROR_COUNT VARIABLE_SCOPE SESSION @@ -455,7 +455,7 @@ VARIABLE_COMMENT Max number of errors/warnings to store for a statement NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 65535 -@@ -2034,7 +2024,7 @@ +@@ -2044,7 +2034,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_INSERT_DELAYED_THREADS VARIABLE_SCOPE SESSION @@ -464,7 +464,7 @@ VARIABLE_COMMENT Don't start more than this number of threads to handle INSERT DELAYED statements. If set to zero INSERT DELAYED will be not used NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -2054,7 +2044,7 @@ +@@ -2064,7 +2054,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_LENGTH_FOR_SORT_DATA VARIABLE_SCOPE SESSION @@ -473,7 +473,7 @@ VARIABLE_COMMENT Max number of bytes in sorted records NUMERIC_MIN_VALUE 4 NUMERIC_MAX_VALUE 8388608 -@@ -2084,7 +2074,7 @@ +@@ -2094,7 +2084,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_RECURSIVE_ITERATIONS VARIABLE_SCOPE SESSION @@ -482,7 +482,7 @@ VARIABLE_COMMENT Maximum number of iterations when executing recursive queries NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2114,7 +2104,7 @@ +@@ -2124,7 +2114,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SEEKS_FOR_KEY VARIABLE_SCOPE SESSION @@ -491,7 +491,7 @@ VARIABLE_COMMENT Limit assumed max number of seeks when looking up rows based on a key NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2134,7 +2124,7 @@ +@@ -2144,7 +2134,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SORT_LENGTH VARIABLE_SCOPE SESSION @@ -500,7 +500,7 @@ VARIABLE_COMMENT The number of bytes to use when sorting BLOB or TEXT values (only the first max_sort_length bytes of each value are used; the rest are ignored) NUMERIC_MIN_VALUE 64 NUMERIC_MAX_VALUE 8388608 -@@ -2144,7 +2134,7 @@ +@@ -2154,7 +2144,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_SP_RECURSION_DEPTH VARIABLE_SCOPE SESSION @@ -509,7 +509,7 @@ VARIABLE_COMMENT Maximum stored procedure recursion depth NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 255 -@@ -2164,7 +2154,7 @@ +@@ -2174,7 +2164,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_TMP_TABLES VARIABLE_SCOPE SESSION @@ -518,7 +518,7 @@ VARIABLE_COMMENT Unused, will be removed. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2184,7 +2174,7 @@ +@@ -2194,7 +2184,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MAX_WRITE_LOCK_COUNT VARIABLE_SCOPE GLOBAL @@ -527,7 +527,7 @@ VARIABLE_COMMENT After this many write locks, allow some read locks to run in between NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2194,7 +2184,7 @@ +@@ -2204,7 +2194,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME METADATA_LOCKS_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -536,7 +536,7 @@ VARIABLE_COMMENT Unused NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1048576 -@@ -2204,7 +2194,7 @@ +@@ -2214,7 +2204,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME METADATA_LOCKS_HASH_INSTANCES VARIABLE_SCOPE GLOBAL @@ -545,7 +545,7 @@ VARIABLE_COMMENT Unused NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1024 -@@ -2214,7 +2204,7 @@ +@@ -2224,7 +2214,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MIN_EXAMINED_ROW_LIMIT VARIABLE_SCOPE SESSION @@ -554,7 +554,7 @@ VARIABLE_COMMENT Don't write queries to slow log that examine fewer rows than that NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2224,7 +2214,7 @@ +@@ -2234,7 +2224,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MRR_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -563,7 +563,7 @@ VARIABLE_COMMENT Size of buffer to use when using MRR with range access NUMERIC_MIN_VALUE 8192 NUMERIC_MAX_VALUE 2147483647 -@@ -2234,7 +2224,7 @@ +@@ -2244,7 +2234,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MYISAM_BLOCK_SIZE VARIABLE_SCOPE GLOBAL @@ -572,7 +572,7 @@ VARIABLE_COMMENT Block size to be used for MyISAM index pages NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 16384 -@@ -2244,7 +2234,7 @@ +@@ -2254,7 +2244,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME MYISAM_DATA_POINTER_SIZE VARIABLE_SCOPE GLOBAL @@ -581,7 +581,7 @@ VARIABLE_COMMENT Default pointer size to be used for MyISAM tables NUMERIC_MIN_VALUE 2 NUMERIC_MAX_VALUE 7 -@@ -2284,10 +2274,10 @@ +@@ -2294,10 +2284,10 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME MYISAM_REPAIR_THREADS VARIABLE_SCOPE SESSION @@ -594,7 +594,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -2332,9 +2322,19 @@ +@@ -2342,9 +2332,19 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL @@ -615,7 +615,7 @@ VARIABLE_COMMENT Buffer length for TCP/IP and socket communication NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1048576 -@@ -2344,7 +2344,7 @@ +@@ -2354,7 +2354,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_READ_TIMEOUT VARIABLE_SCOPE SESSION @@ -624,7 +624,7 @@ VARIABLE_COMMENT Number of seconds to wait for more data from a connection before aborting the read NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -2354,7 +2354,7 @@ +@@ -2364,7 +2364,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_RETRY_COUNT VARIABLE_SCOPE SESSION @@ -633,7 +633,7 @@ VARIABLE_COMMENT If a read on a communication port is interrupted, retry this many times before giving up NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -2364,7 +2364,7 @@ +@@ -2374,7 +2374,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME NET_WRITE_TIMEOUT VARIABLE_SCOPE SESSION @@ -642,7 +642,7 @@ VARIABLE_COMMENT Number of seconds to wait for a block to be written to a connection before aborting the write NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 31536000 -@@ -2424,7 +2424,7 @@ +@@ -2434,7 +2434,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME OPEN_FILES_LIMIT VARIABLE_SCOPE GLOBAL @@ -651,16 +651,7 @@ VARIABLE_COMMENT If this is not 0, then mysqld will use this value to reserve file descriptors to use with setrlimit(). If this value is 0 or autoset then mysqld will reserve max_connections*5 or max_connections + table_cache*2 (whichever is larger) number of file descriptors NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2434,7 +2434,7 @@ - COMMAND_LINE_ARGUMENT REQUIRED - VARIABLE_NAME OPTIMIZER_ADJUST_SECONDARY_KEY_COSTS - VARIABLE_SCOPE SESSION --VARIABLE_TYPE BIGINT UNSIGNED -+VARIABLE_TYPE INT UNSIGNED - VARIABLE_COMMENT 0 = No changes. 1 = Update secondary key costs for ranges to be at least 5x of clustered primary key costs. 2 = Remove 'max_seek optimization' for secondary keys and slight adjustment of filter cost. This option will be deleted in MariaDB 11.0 as it is not needed with the new 11.0 optimizer. - NUMERIC_MIN_VALUE 0 - NUMERIC_MAX_VALUE 2 -@@ -2444,7 +2444,7 @@ +@@ -2464,7 +2464,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_MAX_SEL_ARGS VARIABLE_SCOPE SESSION @@ -669,7 +660,7 @@ VARIABLE_COMMENT The maximum number of SEL_ARG objects created when optimizing a range. If more objects would be needed, the range will not be used by the optimizer. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2454,7 +2454,7 @@ +@@ -2474,7 +2474,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_MAX_SEL_ARG_WEIGHT VARIABLE_SCOPE SESSION @@ -678,7 +669,7 @@ VARIABLE_COMMENT The maximum weight of the SEL_ARG graph. Set to 0 for no limit NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -2464,7 +2464,7 @@ +@@ -2484,7 +2484,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_PRUNE_LEVEL VARIABLE_SCOPE SESSION @@ -687,7 +678,7 @@ VARIABLE_COMMENT Controls the heuristic(s) applied during query optimization to prune less-promising partial plans from the optimizer search space. Meaning: 0 - do not apply any heuristic, thus perform exhaustive search; 1 - prune plans based on number of retrieved rows NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1 -@@ -2474,7 +2474,7 @@ +@@ -2494,7 +2494,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_SEARCH_DEPTH VARIABLE_SCOPE SESSION @@ -696,7 +687,7 @@ VARIABLE_COMMENT Maximum depth of search performed by the query optimizer. Values larger than the number of relations in a query result in better query plans, but take longer to compile a query. Values smaller than the number of tables in a relation result in faster optimization, but may produce very bad query plans. If set to 0, the system will automatically pick a reasonable value. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 62 -@@ -2484,7 +2484,7 @@ +@@ -2504,7 +2504,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_SELECTIVITY_SAMPLING_LIMIT VARIABLE_SCOPE SESSION @@ -705,7 +696,7 @@ VARIABLE_COMMENT Controls number of record samples to check condition selectivity NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 4294967295 -@@ -2514,17 +2514,17 @@ +@@ -2534,17 +2534,17 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OPTIMIZER_TRACE_MAX_MEM_SIZE VARIABLE_SCOPE SESSION @@ -726,7 +717,7 @@ VARIABLE_COMMENT Controls selectivity of which conditions the optimizer takes into account to calculate cardinality of a partial join when it searches for the best execution plan Meaning: 1 - use selectivity of index backed range conditions to calculate the cardinality of a partial join if the last joined table is accessed by full table scan or an index scan, 2 - use selectivity of index backed range conditions to calculate the cardinality of a partial join in any case, 3 - additionally always use selectivity of range conditions that are not backed by any index to calculate the cardinality of a partial join, 4 - use histograms to calculate selectivity of range conditions that are not backed by any index to calculate the cardinality of a partial join.5 - additionally use selectivity of certain non-range predicates calculated on record samples NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 5 -@@ -2544,7 +2544,7 @@ +@@ -2564,7 +2564,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME PERFORMANCE_SCHEMA_ACCOUNTS_SIZE VARIABLE_SCOPE GLOBAL @@ -735,7 +726,7 @@ VARIABLE_COMMENT Maximum number of instrumented user@host accounts. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2554,7 +2554,7 @@ +@@ -2574,7 +2574,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_DIGESTS_SIZE VARIABLE_SCOPE GLOBAL @@ -744,7 +735,7 @@ VARIABLE_COMMENT Size of the statement digest. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2564,7 +2564,7 @@ +@@ -2584,7 +2584,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STAGES_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -753,7 +744,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_STAGES_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2574,7 +2574,7 @@ +@@ -2594,7 +2594,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STAGES_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -762,7 +753,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STAGES_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2584,7 +2584,7 @@ +@@ -2604,7 +2604,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STATEMENTS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -771,7 +762,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_STATEMENTS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2594,7 +2594,7 @@ +@@ -2614,7 +2614,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_STATEMENTS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -780,7 +771,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STATEMENTS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2604,7 +2604,7 @@ +@@ -2624,7 +2624,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_TRANSACTIONS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -789,7 +780,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_TRANSACTIONS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2614,7 +2614,7 @@ +@@ -2634,7 +2634,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_TRANSACTIONS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -798,7 +789,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_TRANSACTIONS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2624,7 +2624,7 @@ +@@ -2644,7 +2644,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_WAITS_HISTORY_LONG_SIZE VARIABLE_SCOPE GLOBAL @@ -807,7 +798,7 @@ VARIABLE_COMMENT Number of rows in EVENTS_WAITS_HISTORY_LONG. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2634,7 +2634,7 @@ +@@ -2654,7 +2654,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_EVENTS_WAITS_HISTORY_SIZE VARIABLE_SCOPE GLOBAL @@ -816,7 +807,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_WAITS_HISTORY. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2644,7 +2644,7 @@ +@@ -2664,7 +2664,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_HOSTS_SIZE VARIABLE_SCOPE GLOBAL @@ -825,7 +816,7 @@ VARIABLE_COMMENT Maximum number of instrumented hosts. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2654,7 +2654,7 @@ +@@ -2674,7 +2674,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_COND_CLASSES VARIABLE_SCOPE GLOBAL @@ -834,7 +825,7 @@ VARIABLE_COMMENT Maximum number of condition instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2664,7 +2664,7 @@ +@@ -2684,7 +2684,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_COND_INSTANCES VARIABLE_SCOPE GLOBAL @@ -843,7 +834,7 @@ VARIABLE_COMMENT Maximum number of instrumented condition objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2674,7 +2674,7 @@ +@@ -2694,7 +2694,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_DIGEST_LENGTH VARIABLE_SCOPE GLOBAL @@ -852,7 +843,7 @@ VARIABLE_COMMENT Maximum length considered for digest text, when stored in performance_schema tables. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2684,7 +2684,7 @@ +@@ -2704,7 +2704,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_CLASSES VARIABLE_SCOPE GLOBAL @@ -861,7 +852,7 @@ VARIABLE_COMMENT Maximum number of file instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2694,7 +2694,7 @@ +@@ -2714,7 +2714,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_HANDLES VARIABLE_SCOPE GLOBAL @@ -870,7 +861,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented files. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2704,7 +2704,7 @@ +@@ -2724,7 +2724,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_FILE_INSTANCES VARIABLE_SCOPE GLOBAL @@ -879,7 +870,7 @@ VARIABLE_COMMENT Maximum number of instrumented files. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2714,7 +2714,7 @@ +@@ -2734,7 +2734,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_INDEX_STAT VARIABLE_SCOPE GLOBAL @@ -888,7 +879,7 @@ VARIABLE_COMMENT Maximum number of index statistics for instrumented tables. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2724,7 +2724,7 @@ +@@ -2744,7 +2744,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MEMORY_CLASSES VARIABLE_SCOPE GLOBAL @@ -897,7 +888,7 @@ VARIABLE_COMMENT Maximum number of memory pool instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1024 -@@ -2734,7 +2734,7 @@ +@@ -2754,7 +2754,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_METADATA_LOCKS VARIABLE_SCOPE GLOBAL @@ -906,7 +897,7 @@ VARIABLE_COMMENT Maximum number of metadata locks. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2744,7 +2744,7 @@ +@@ -2764,7 +2764,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MUTEX_CLASSES VARIABLE_SCOPE GLOBAL @@ -915,7 +906,7 @@ VARIABLE_COMMENT Maximum number of mutex instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2754,7 +2754,7 @@ +@@ -2774,7 +2774,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_MUTEX_INSTANCES VARIABLE_SCOPE GLOBAL @@ -924,7 +915,7 @@ VARIABLE_COMMENT Maximum number of instrumented MUTEX objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2764,7 +2764,7 @@ +@@ -2784,7 +2784,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_PREPARED_STATEMENTS_INSTANCES VARIABLE_SCOPE GLOBAL @@ -933,7 +924,7 @@ VARIABLE_COMMENT Maximum number of instrumented prepared statements. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2774,7 +2774,7 @@ +@@ -2794,7 +2794,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_PROGRAM_INSTANCES VARIABLE_SCOPE GLOBAL @@ -942,7 +933,7 @@ VARIABLE_COMMENT Maximum number of instrumented programs. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2784,7 +2784,7 @@ +@@ -2804,7 +2804,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_RWLOCK_CLASSES VARIABLE_SCOPE GLOBAL @@ -951,7 +942,7 @@ VARIABLE_COMMENT Maximum number of rwlock instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2794,7 +2794,7 @@ +@@ -2814,7 +2814,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_RWLOCK_INSTANCES VARIABLE_SCOPE GLOBAL @@ -960,7 +951,7 @@ VARIABLE_COMMENT Maximum number of instrumented RWLOCK objects. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 104857600 -@@ -2804,7 +2804,7 @@ +@@ -2824,7 +2824,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SOCKET_CLASSES VARIABLE_SCOPE GLOBAL @@ -969,7 +960,7 @@ VARIABLE_COMMENT Maximum number of socket instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2814,7 +2814,7 @@ +@@ -2834,7 +2834,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SOCKET_INSTANCES VARIABLE_SCOPE GLOBAL @@ -978,7 +969,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented sockets. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2824,7 +2824,7 @@ +@@ -2844,7 +2844,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_SQL_TEXT_LENGTH VARIABLE_SCOPE GLOBAL @@ -987,7 +978,7 @@ VARIABLE_COMMENT Maximum length of displayed sql text. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 1048576 -@@ -2834,7 +2834,7 @@ +@@ -2854,7 +2854,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STAGE_CLASSES VARIABLE_SCOPE GLOBAL @@ -996,7 +987,7 @@ VARIABLE_COMMENT Maximum number of stage instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2844,7 +2844,7 @@ +@@ -2864,7 +2864,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STATEMENT_CLASSES VARIABLE_SCOPE GLOBAL @@ -1005,7 +996,7 @@ VARIABLE_COMMENT Maximum number of statement instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2854,7 +2854,7 @@ +@@ -2874,7 +2874,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_STATEMENT_STACK VARIABLE_SCOPE GLOBAL @@ -1014,7 +1005,7 @@ VARIABLE_COMMENT Number of rows per thread in EVENTS_STATEMENTS_CURRENT. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 256 -@@ -2864,7 +2864,7 @@ +@@ -2884,7 +2884,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_HANDLES VARIABLE_SCOPE GLOBAL @@ -1023,7 +1014,7 @@ VARIABLE_COMMENT Maximum number of opened instrumented tables. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2874,7 +2874,7 @@ +@@ -2894,7 +2894,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_INSTANCES VARIABLE_SCOPE GLOBAL @@ -1032,7 +1023,7 @@ VARIABLE_COMMENT Maximum number of instrumented tables. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2884,7 +2884,7 @@ +@@ -2904,7 +2904,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_TABLE_LOCK_STAT VARIABLE_SCOPE GLOBAL @@ -1041,7 +1032,7 @@ VARIABLE_COMMENT Maximum number of lock statistics for instrumented tables. Use 0 to disable, -1 for automated scaling. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2894,7 +2894,7 @@ +@@ -2914,7 +2914,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_THREAD_CLASSES VARIABLE_SCOPE GLOBAL @@ -1050,7 +1041,7 @@ VARIABLE_COMMENT Maximum number of thread instruments. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 256 -@@ -2904,7 +2904,7 @@ +@@ -2924,7 +2924,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_MAX_THREAD_INSTANCES VARIABLE_SCOPE GLOBAL @@ -1059,7 +1050,7 @@ VARIABLE_COMMENT Maximum number of instrumented threads. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2914,7 +2914,7 @@ +@@ -2934,7 +2934,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SESSION_CONNECT_ATTRS_SIZE VARIABLE_SCOPE GLOBAL @@ -1068,7 +1059,7 @@ VARIABLE_COMMENT Size of session attribute string buffer per thread. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2924,7 +2924,7 @@ +@@ -2944,7 +2944,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SETUP_ACTORS_SIZE VARIABLE_SCOPE GLOBAL @@ -1077,7 +1068,7 @@ VARIABLE_COMMENT Maximum number of rows in SETUP_ACTORS. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1024 -@@ -2934,7 +2934,7 @@ +@@ -2954,7 +2954,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_SETUP_OBJECTS_SIZE VARIABLE_SCOPE GLOBAL @@ -1086,7 +1077,7 @@ VARIABLE_COMMENT Maximum number of rows in SETUP_OBJECTS. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2944,7 +2944,7 @@ +@@ -2964,7 +2964,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PERFORMANCE_SCHEMA_USERS_SIZE VARIABLE_SCOPE GLOBAL @@ -1095,7 +1086,7 @@ VARIABLE_COMMENT Maximum number of instrumented users. Use 0 to disable, -1 for automated sizing. NUMERIC_MIN_VALUE -1 NUMERIC_MAX_VALUE 1048576 -@@ -2994,7 +2994,7 @@ +@@ -3014,7 +3014,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PRELOAD_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -1104,7 +1095,7 @@ VARIABLE_COMMENT The size of the buffer that is allocated when preloading indexes NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -3014,7 +3014,7 @@ +@@ -3034,7 +3034,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME PROFILING_HISTORY_SIZE VARIABLE_SCOPE SESSION @@ -1113,7 +1104,7 @@ VARIABLE_COMMENT Number of statements about which profiling information is maintained. If set to 0, no profiles are stored. See SHOW PROFILES. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -3024,7 +3024,7 @@ +@@ -3044,7 +3044,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME PROGRESS_REPORT_TIME VARIABLE_SCOPE SESSION @@ -1122,7 +1113,7 @@ VARIABLE_COMMENT Seconds between sending progress reports to the client for time-consuming statements. Set to 0 to disable progress reporting. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3084,7 +3084,7 @@ +@@ -3104,7 +3104,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME QUERY_ALLOC_BLOCK_SIZE VARIABLE_SCOPE SESSION @@ -1131,7 +1122,7 @@ VARIABLE_COMMENT Allocation block size for query parsing and execution NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 4294967295 -@@ -3094,7 +3094,7 @@ +@@ -3114,7 +3114,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME QUERY_CACHE_LIMIT VARIABLE_SCOPE GLOBAL @@ -1140,7 +1131,7 @@ VARIABLE_COMMENT Don't cache results that are bigger than this NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3104,7 +3104,7 @@ +@@ -3124,7 +3124,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME QUERY_CACHE_MIN_RES_UNIT VARIABLE_SCOPE GLOBAL @@ -1149,7 +1140,7 @@ VARIABLE_COMMENT The minimum size for blocks allocated by the query cache NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3117,7 +3117,7 @@ +@@ -3137,7 +3137,7 @@ VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT The memory allocated to store results from old queries NUMERIC_MIN_VALUE 0 @@ -1158,7 +1149,7 @@ NUMERIC_BLOCK_SIZE 1024 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3154,7 +3154,7 @@ +@@ -3174,7 +3174,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME QUERY_PREALLOC_SIZE VARIABLE_SCOPE SESSION @@ -1167,7 +1158,7 @@ VARIABLE_COMMENT Persistent buffer for query parsing and execution NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 4294967295 -@@ -3167,7 +3167,7 @@ +@@ -3187,7 +3187,7 @@ VARIABLE_SCOPE SESSION ONLY VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Sets the internal state of the RAND() generator for replication purposes NUMERIC_MIN_VALUE 0 @@ -1176,7 +1167,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3177,14 +3177,14 @@ +@@ -3197,14 +3197,14 @@ VARIABLE_SCOPE SESSION ONLY VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Sets the internal state of the RAND() generator for replication purposes NUMERIC_MIN_VALUE 0 @@ -1193,7 +1184,7 @@ VARIABLE_COMMENT Allocation block size for storing ranges during optimization NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 4294967295 -@@ -3197,14 +3197,14 @@ +@@ -3217,14 +3217,14 @@ VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED VARIABLE_COMMENT Maximum speed(KB/s) to read binlog from master (0 = no limit) NUMERIC_MIN_VALUE 0 @@ -1210,7 +1201,7 @@ VARIABLE_COMMENT Each thread that does a sequential scan allocates a buffer of this size for each table it scans. If you do many sequential scans, you may want to increase this value NUMERIC_MIN_VALUE 8192 NUMERIC_MAX_VALUE 2147483647 -@@ -3224,7 +3224,7 @@ +@@ -3244,7 +3244,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME READ_RND_BUFFER_SIZE VARIABLE_SCOPE SESSION @@ -1219,7 +1210,7 @@ VARIABLE_COMMENT When reading rows in sorted order after a sort, the rows are read through this buffer to avoid a disk seeks NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 2147483647 -@@ -3434,10 +3434,10 @@ +@@ -3454,10 +3454,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME ROWID_MERGE_BUFF_SIZE VARIABLE_SCOPE SESSION @@ -1232,7 +1223,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3454,20 +3454,20 @@ +@@ -3474,20 +3474,20 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME RPL_SEMI_SYNC_MASTER_TIMEOUT VARIABLE_SCOPE GLOBAL @@ -1257,7 +1248,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3524,10 +3524,10 @@ +@@ -3544,10 +3544,10 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME RPL_SEMI_SYNC_SLAVE_TRACE_LEVEL VARIABLE_SCOPE GLOBAL @@ -1270,7 +1261,16 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -3564,7 +3564,7 @@ +@@ -3565,7 +3565,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL + VARIABLE_NAME SECURE_FILE_PRIV + VARIABLE_SCOPE GLOBAL + VARIABLE_TYPE VARCHAR +-VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc ++VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. + NUMERIC_MIN_VALUE NULL + NUMERIC_MAX_VALUE NULL + NUMERIC_BLOCK_SIZE NULL +@@ -3584,7 +3584,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SERVER_ID VARIABLE_SCOPE SESSION @@ -1279,7 +1279,7 @@ VARIABLE_COMMENT Uniquely identifies the server instance in the community of replication partners NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -3694,7 +3694,7 @@ +@@ -3724,7 +3724,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_DOMAIN_PARALLEL_THREADS VARIABLE_SCOPE GLOBAL @@ -1288,7 +1288,7 @@ VARIABLE_COMMENT Maximum number of parallel threads to use on slave for events in a single replication domain. When using multiple domains, this can be used to limit a single domain from grabbing all threads and thus stalling other domains. The default of 0 means to allow a domain to grab as many threads as it wants, up to the value of slave_parallel_threads. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3724,7 +3724,7 @@ +@@ -3754,7 +3754,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_MAX_ALLOWED_PACKET VARIABLE_SCOPE GLOBAL @@ -1297,7 +1297,7 @@ VARIABLE_COMMENT The maximum packet length to sent successfully from the master to slave. NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 1073741824 -@@ -3744,7 +3744,7 @@ +@@ -3774,7 +3774,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_PARALLEL_MAX_QUEUED VARIABLE_SCOPE GLOBAL @@ -1306,7 +1306,7 @@ VARIABLE_COMMENT Limit on how much memory SQL threads should use per parallel replication thread when reading ahead in the relay log looking for opportunities for parallel replication. Only used when --slave-parallel-threads > 0. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2147483647 -@@ -3764,7 +3764,7 @@ +@@ -3794,7 +3794,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME SLAVE_PARALLEL_THREADS VARIABLE_SCOPE GLOBAL @@ -1315,7 +1315,7 @@ VARIABLE_COMMENT If non-zero, number of threads to spawn to apply in parallel events on the slave that were group-committed on the master or were logged with GTID in different replication domains. Note that these threads are in addition to the IO and SQL threads, which are always created by a replication slave NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3774,7 +3774,7 @@ +@@ -3804,7 +3804,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_PARALLEL_WORKERS VARIABLE_SCOPE GLOBAL @@ -1324,7 +1324,7 @@ VARIABLE_COMMENT Alias for slave_parallel_threads NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16383 -@@ -3814,7 +3814,7 @@ +@@ -3844,7 +3844,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SLAVE_TRANSACTION_RETRIES VARIABLE_SCOPE GLOBAL @@ -1333,7 +1333,7 @@ VARIABLE_COMMENT Number of times the slave SQL thread will retry a transaction in case it failed with a deadlock, elapsed lock wait timeout or listed in slave_transaction_retry_errors, before giving up and stopping NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 -@@ -3834,7 +3834,7 @@ +@@ -3864,7 +3864,7 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLAVE_TRANSACTION_RETRY_INTERVAL VARIABLE_SCOPE GLOBAL @@ -1342,7 +1342,7 @@ VARIABLE_COMMENT Interval of the slave SQL thread will retry a transaction in case it failed with a deadlock or elapsed lock wait timeout or listed in slave_transaction_retry_errors NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 3600 -@@ -3854,7 +3854,7 @@ +@@ -3884,7 +3884,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SLOW_LAUNCH_TIME VARIABLE_SCOPE GLOBAL @@ -1351,7 +1351,7 @@ VARIABLE_COMMENT If creating the thread takes longer than this value (in seconds), the Slow_launch_threads counter will be incremented NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 31536000 -@@ -4124,7 +4124,7 @@ +@@ -4154,7 +4154,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME STORED_PROGRAM_CACHE VARIABLE_SCOPE GLOBAL @@ -1360,7 +1360,7 @@ VARIABLE_COMMENT The soft upper limit for number of cached stored routines for one connection. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 524288 -@@ -4224,7 +4224,7 @@ +@@ -4254,7 +4254,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME TABLE_DEFINITION_CACHE VARIABLE_SCOPE GLOBAL @@ -1369,7 +1369,7 @@ VARIABLE_COMMENT The number of cached table definitions NUMERIC_MIN_VALUE 400 NUMERIC_MAX_VALUE 2097152 -@@ -4234,7 +4234,7 @@ +@@ -4264,7 +4264,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TABLE_OPEN_CACHE VARIABLE_SCOPE GLOBAL @@ -1378,7 +1378,7 @@ VARIABLE_COMMENT The number of cached open tables NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 1048576 -@@ -4294,7 +4294,7 @@ +@@ -4324,7 +4324,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME THREAD_CACHE_SIZE VARIABLE_SCOPE GLOBAL @@ -1387,7 +1387,7 @@ VARIABLE_COMMENT How many threads we should keep in a cache for reuse. These are freed after 5 minutes of idle time NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16384 -@@ -4352,6 +4352,26 @@ +@@ -4382,6 +4382,26 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED @@ -1404,7 +1404,7 @@ +VARIABLE_NAME THREAD_POOL_MODE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE ENUM -+VARIABLE_COMMENT Chose implementation of the threadpool ++VARIABLE_COMMENT Chose implementation of the threadpool. Use 'windows' unless you have a workload with a lot of concurrent connections and minimal contention +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL @@ -1414,7 +1414,7 @@ VARIABLE_NAME THREAD_POOL_OVERSUBSCRIBE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED -@@ -4455,7 +4475,7 @@ +@@ -4485,7 +4505,7 @@ COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TMPDIR VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR @@ -1423,7 +1423,7 @@ NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -@@ -4494,7 +4514,7 @@ +@@ -4524,7 +4544,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TRANSACTION_ALLOC_BLOCK_SIZE VARIABLE_SCOPE SESSION @@ -1432,7 +1432,7 @@ VARIABLE_COMMENT Allocation block size for transactions to be stored in binary log NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 134217728 -@@ -4504,7 +4524,7 @@ +@@ -4534,7 +4554,7 @@ READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME TRANSACTION_PREALLOC_SIZE VARIABLE_SCOPE SESSION @@ -1441,7 +1441,7 @@ VARIABLE_COMMENT Persistent buffer for transactions to be stored in binary log NUMERIC_MIN_VALUE 1024 NUMERIC_MAX_VALUE 134217728 -@@ -4644,10 +4664,10 @@ +@@ -4674,10 +4694,10 @@ READ_ONLY YES COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME WAIT_TIMEOUT VARIABLE_SCOPE SESSION @@ -1454,7 +1454,7 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -4671,7 +4691,7 @@ +@@ -4701,7 +4721,7 @@ order by variable_name; VARIABLE_NAME LOG_TC_SIZE GLOBAL_VALUE_ORIGIN AUTO VARIABLE_SCOPE GLOBAL diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 5bc9e5ecf3e..12d884d1258 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3875,7 +3875,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SECURE_FILE_PRIV VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR -VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory +VARIABLE_COMMENT Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory. Empty value means no limits except /proc NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL diff --git a/mysql-test/suite/sys_vars/t/secure_file_priv-master.opt b/mysql-test/suite/sys_vars/t/secure_file_priv.opt similarity index 100% rename from mysql-test/suite/sys_vars/t/secure_file_priv-master.opt rename to mysql-test/suite/sys_vars/t/secure_file_priv.opt diff --git a/mysql-test/suite/sys_vars/t/secure_file_priv.test b/mysql-test/suite/sys_vars/t/secure_file_priv.test index bbbf01aae89..f1bc84576b5 100644 --- a/mysql-test/suite/sys_vars/t/secure_file_priv.test +++ b/mysql-test/suite/sys_vars/t/secure_file_priv.test @@ -1,10 +1,10 @@ +--source include/platform.inc --echo # --echo # Bug50373 --secure-file-priv="" --echo # CREATE TABLE t1 (c1 VARCHAR(50)); INSERT INTO t1 VALUES ("one"),("two"),("three"),("four"),("five"); SHOW VARIABLES LIKE 'secure_file_priv'; ---disable_query_log # Atempt to create a file where we normally aren't allowed to create one. # @@ -17,35 +17,23 @@ SHOW VARIABLES LIKE 'secure_file_priv'; # If we run tests with --mem, it will be /dev/shm. # If we run tests with --parallel, it will be mysql-test/var # (because MYSQLTEST_VARDIR in this case is mysql-test/var/N). ---disable_cursor_protocol - ---perl -use File::Basename; -my $protected_file= dirname($ENV{MYSQLTEST_VARDIR}).'/bug50373.txt'; -# Ensure bug50373.txt does not exist (e.g. leftover from previous -# test runs). -unlink $protected_file; -open(FILE, ">", "$ENV{MYSQL_TMP_DIR}/bug50373.inc") or die; -print FILE "SELECT * FROM t1 INTO OUTFILE '".$protected_file."';\n"; -print FILE "DELETE FROM t1;\n"; -print FILE "LOAD DATA INFILE '".$protected_file."' INTO TABLE t1;\n"; -print FILE "SELECT * FROM t1;\n"; -print FILE "SELECT load_file('",$protected_file,"') AS loaded_file;\n"; -close(FILE); -EOF - ---enable_prepare_warnings ---source $MYSQL_TMP_DIR/bug50373.inc ---disable_prepare_warnings ---remove_file $MYSQL_TMP_DIR/bug50373.inc ---enable_query_log - ---enable_cursor_protocol +evalp SELECT * INTO OUTFILE '$MYSQLTEST_VARDIR/../bug50373.txt' FROM t1; +DELETE FROM t1; +evalp LOAD DATA INFILE '$MYSQLTEST_VARDIR/../bug50373.txt' INTO TABLE t1; +SELECT * FROM t1; +evalp SELECT load_file('$MYSQLTEST_VARDIR/../bug50373.txt') AS loaded_file; DROP TABLE t1; ---perl -use File::Basename; -unlink dirname($ENV{MYSQLTEST_VARDIR}).'/bug50373.txt'; -EOF +--remove_file $MYSQLTEST_VARDIR/../bug50373.txt +--echo # +--echo # MDEV-40589 default exclude list for secure-file-priv +--echo # +CREATE TABLE t1 (c1 VARCHAR(50)); +--disable_abort_on_error +LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1; +--enable_abort_on_error +DROP TABLE t1; + +--echo # End of 10.6 tests diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt new file mode 100644 index 00000000000..a5812bb7aa0 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.opt @@ -0,0 +1,2 @@ +--loose-skip-stack-trace --skip-core-file +--version=12.11.10-fake-version-for-the-test diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test new file mode 100644 index 00000000000..192c481b4ce --- /dev/null +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -0,0 +1,27 @@ +--source include/have_debug.inc +--source include/not_asan.inc +--source include/not_embedded.inc +--source include/not_valgrind.inc + +--echo # +--echo # MDEV-40341 store read-only sysvars in a read-only root and a read-only segment +--echo # +call mtr.add_suppression('mariadbd(.exe)? got (exception 0xc0000005|signal 11)'); + +select @@global.skip_name_resolve; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@global.skip_name_resolve=0; +--write_line restart $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--error 2013,2026 +set @@debug_dbug='d,set_skip_name_resolve'; +--enable_reconnect +--source include/wait_until_connected_again.inc + +select @@global.version; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@global.version=0; +--write_line restart $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--error 2013,2026 +set @@debug_dbug='d,set_version_buf'; +--enable_reconnect +--source include/wait_until_connected_again.inc diff --git a/mysql-test/suite/sys_vars/t/thread_stack_basic.test b/mysql-test/suite/sys_vars/t/thread_stack_basic.test index a1f52576d15..5efc42d0a83 100644 --- a/mysql-test/suite/sys_vars/t/thread_stack_basic.test +++ b/mysql-test/suite/sys_vars/t/thread_stack_basic.test @@ -1,20 +1,18 @@ # # only global # ---source include/not_asan.inc ---source include/not_ubsan.inc ---source include/not_msan.inc ---replace_result 392192 299008 +# sizes per DEFAULT_THREAD_STACK in include/my_pthread.h +--replace_result 11534336 299008 2097152 299008 select @@global.thread_stack; --error ER_INCORRECT_GLOBAL_LOCAL_VAR select @@session.thread_stack; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 show global variables like 'thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 show session variables like 'thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 select * from information_schema.global_variables where variable_name='thread_stack'; ---replace_result 392192 299008 +--replace_result 11534336 299008 2097152 299008 select * from information_schema.session_variables where variable_name='thread_stack'; # diff --git a/mysql-test/suite/wsrep/r/wsrep_provider_plugin.result b/mysql-test/suite/wsrep/r/wsrep_provider_plugin.result index abcc15b5c41..486b91e70cc 100644 --- a/mysql-test/suite/wsrep/r/wsrep_provider_plugin.result +++ b/mysql-test/suite/wsrep/r/wsrep_provider_plugin.result @@ -12,7 +12,7 @@ Variable_name Value wsrep_provider_repl_max_ws_size 2147483647 INSERT INTO t1 VALUES (1); SET GLOBAL wsrep_provider_options='repl.max_ws_size=1'; -ERROR HY000: Variable 'wsrep_provider_options' is a read only variable +ERROR HY000: wsrep_provider_options cannot be changed while the wsrep-provider plugin is loaded INSERT INTO t1 VALUES (2); SET GLOBAL wsrep_provider='none'; ERROR HY000: Variable 'wsrep_provider' is a read only variable @@ -21,4 +21,4 @@ CALL mtr.add_suppression("transaction size limit"); CALL mtr.add_suppression("rbr write fail"); SELECT VARIABLE_NAME,READ_ONLY FROM information_schema.system_variables where VARIABLE_NAME like '%wsrep_provider_options%'; VARIABLE_NAME READ_ONLY -WSREP_PROVIDER_OPTIONS YES +WSREP_PROVIDER_OPTIONS NO diff --git a/mysql-test/suite/wsrep/t/wsrep_provider_plugin.test b/mysql-test/suite/wsrep/t/wsrep_provider_plugin.test index de4533f879c..3c4fa621fff 100644 --- a/mysql-test/suite/wsrep/t/wsrep_provider_plugin.test +++ b/mysql-test/suite/wsrep/t/wsrep_provider_plugin.test @@ -22,8 +22,8 @@ SHOW VARIABLES LIKE 'wsrep_provider_repl_max_ws_size'; INSERT INTO t1 VALUES (1); -# Variable should be read only, must not take effect ---error ER_INCORRECT_GLOBAL_LOCAL_VAR +# wsrep-provider plugin is loaded, must not take effect +--error ER_WRONG_ARGUMENTS SET GLOBAL wsrep_provider_options='repl.max_ws_size=1'; INSERT INTO t1 VALUES (2); @@ -36,6 +36,8 @@ CALL mtr.add_suppression("transaction size limit"); CALL mtr.add_suppression("rbr write fail"); # -# MDEV-30120 :Update the wsrep_provider_options read_only value in the system_variables table. +# MDEV-30120: wsrep_provider_options is not flagged READ_ONLY in +# system_variables, even though SET on it is rejected above while the +# wsrep-provider plugin is loaded. # SELECT VARIABLE_NAME,READ_ONLY FROM information_schema.system_variables where VARIABLE_NAME like '%wsrep_provider_options%'; diff --git a/mysys/charset.c b/mysys/charset.c index 6b34fd04638..c277cb442ef 100644 --- a/mysys/charset.c +++ b/mysys/charset.c @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates - Copyright (c) 2009, 2020, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -525,7 +525,7 @@ my_charset_loader_init_mysys(MY_CHARSET_LOADER *loader) #define MY_MAX_ALLOWED_BUF 1024*1024 #define MY_CHARSET_INDEX "Index.xml" -const char *charsets_dir= NULL; +READ_ONLY_SYSVAR const char *charsets_dir= NULL; static my_bool diff --git a/mysys/my_alloc.c b/mysys/my_alloc.c index 24a2bab0235..0086ffa37f0 100644 --- a/mysys/my_alloc.c +++ b/mysys/my_alloc.c @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2010, Oracle and/or its affiliates - Copyright (c) 2010, 2020, MariaDB + Copyright (c) 2010, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,15 +21,13 @@ #include #include #include -#ifdef HAVE_SYS_MMAN_H -#include -#endif +#include #undef EXTRA_DEBUG #define EXTRA_DEBUG #define ROOT_FLAG_THREAD_SPECIFIC 1 -#define ROOT_FLAG_MPROTECT 2 +#define ROOT_FLAG_VMEM 2 #define ROOT_FLAG_READ_ONLY 4 /* data packed in MEM_ROOT -> min_malloc */ @@ -43,37 +41,35 @@ /* - Alloc memory through either my_malloc or mmap() + Alloc memory through either my_malloc() or my_virtual_mem_commit() */ static void *root_alloc(MEM_ROOT *root, size_t size, size_t *alloced_size, myf my_flags) { *alloced_size= size; -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) - if (root->flags & ROOT_FLAG_MPROTECT) + if (root->flags & ROOT_FLAG_VMEM) { - void *res; + void *ptr; *alloced_size= MY_ALIGN(size, my_system_page_size); - res= my_mmap(0, *alloced_size, PROT_READ | PROT_WRITE, - MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (res == MAP_FAILED) - res= 0; - return res; + if ((ptr= my_virtual_mem_commit(NULL, *alloced_size))) + update_malloc_size(*alloced_size, + MY_TEST(root->flags & ROOT_FLAG_THREAD_SPECIFIC)); + return ptr; } -#endif /* HAVE_MMAP */ - return my_malloc(root->psi_key, size, - my_flags | MALLOC_FLAG(root)); + return my_malloc(root->psi_key, size, my_flags | MALLOC_FLAG(root)); } static void root_free(MEM_ROOT *root, void *ptr, size_t size) { -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) - if (root->flags & ROOT_FLAG_MPROTECT) - my_munmap(ptr, size); + if (root->flags & ROOT_FLAG_VMEM) + { + update_malloc_size(-(longlong) size, + MY_TEST(root->flags & ROOT_FLAG_THREAD_SPECIFIC)); + my_virtual_mem_release(ptr, size); + } else -#endif my_free(ptr); } @@ -93,7 +89,7 @@ static void calculate_block_sizes(MEM_ROOT *mem_root, size_t block_size, { size_t pre_alloc= *pre_alloc_size; - if (mem_root->flags & ROOT_FLAG_MPROTECT) + if (mem_root->flags & ROOT_FLAG_VMEM) { mem_root->block_size= MY_ALIGN(block_size, my_system_page_size); if (pre_alloc) @@ -132,7 +128,7 @@ static void calculate_block_sizes(MEM_ROOT *mem_root, size_t block_size, pre_alloc_size - if non-0, then size of block that should be pre-allocated during memory root initialization. my_flags MY_THREAD_SPECIFIC flag for my_malloc - MY_RROOT_USE_MPROTECT for read only protected memory + MY_ROOT_USE_VMEM to use anonymos mmap instead of malloc DESCRIPTION This function prepares memory root for further use, sets initial size of @@ -157,11 +153,11 @@ void init_alloc_root(PSI_memory_key key, MEM_ROOT *mem_root, size_t block_size, mem_root->flags= 0; DBUG_ASSERT(!test_all_bits(mem_root->flags, - (MY_THREAD_SPECIFIC | MY_ROOT_USE_MPROTECT))); + (MY_THREAD_SPECIFIC | MY_ROOT_USE_VMEM))); if (my_flags & MY_THREAD_SPECIFIC) mem_root->flags|= ROOT_FLAG_THREAD_SPECIFIC; - if (my_flags & MY_ROOT_USE_MPROTECT) - mem_root->flags|= ROOT_FLAG_MPROTECT; + if (my_flags & MY_ROOT_USE_VMEM) + mem_root->flags|= ROOT_FLAG_VMEM; calculate_block_sizes(mem_root, block_size, &pre_alloc_size); @@ -286,7 +282,7 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) }); #if defined(HAVE_valgrind) && defined(EXTRA_DEBUG) - if (!(mem_root->flags & ROOT_FLAG_MPROTECT)) + if (!(mem_root->flags & ROOT_FLAG_VMEM)) { length+= ALIGN_SIZE(sizeof(USED_MEM)); if (!(next = (USED_MEM*) my_malloc(mem_root->psi_key, length, @@ -621,32 +617,20 @@ void free_all_new_blocks(MEM_ROOT *root, USED_MEM *last_block) Change protection for all blocks in the mem root */ -#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) void protect_root(MEM_ROOT *root, int prot) { - reg1 USED_MEM *next,*old; + USED_MEM *next; DBUG_ENTER("protect_root"); DBUG_PRINT("enter",("root: %p prot: %d", root, prot)); - DBUG_ASSERT(root->flags & ROOT_FLAG_MPROTECT); + DBUG_ASSERT(root->flags & ROOT_FLAG_VMEM); - for (next= root->used; next ;) - { - old= next; next= next->next ; - mprotect(old, old->size, prot); - } - for (next= root->free; next ;) - { - old= next; next= next->next ; - mprotect(old, old->size, prot); - } + for (next= root->used; next; next= next->next) + my_virtual_mem_protect(next, next->size, prot); + for (next= root->free; next; next= next->next) + my_virtual_mem_protect(next, next->size, prot); DBUG_VOID_RETURN; } -#else -void protect_root(MEM_ROOT *root, int prot) -{ -} -#endif /* defined(HAVE_MMAP) && ... */ char *strdup_root(MEM_ROOT *root, const char *str) diff --git a/mysys/my_init.c b/mysys/my_init.c index 9c0d1f9957b..dc4a6766c70 100644 --- a/mysys/my_init.c +++ b/mysys/my_init.c @@ -1,6 +1,7 @@ /* Copyright (c) 2000, 2012, Oracle and/or its affiliates Copyright (c) 2009, 2011, Monty Program Ab + Copyright (c) 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,7 +60,7 @@ my_bool my_init_done= 0; uint mysys_usage_id= 0; /* Incremented for each my_init() */ size_t my_system_page_size= 8192; /* Default if no sysconf() */ -ulonglong my_thread_stack_size= (sizeof(void*) <= 4)? 65536: ((256-16)*1024); +READ_ONLY_SYSVAR ulonglong my_thread_stack_size= (sizeof(void*) <= 4)? 65536: ((256-16)*1024); static mode_t atoi_octal(const char *str) { diff --git a/mysys/my_static.c b/mysys/my_static.c index bc90975f632..858e2f26605 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2009, 2019, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -119,8 +119,8 @@ void (*proc_info_hook)(void *, const PSI_stage_info *, PSI_stage_info *, const char *, const char *, const unsigned int)= proc_info_dummy; void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t)= 0; - /* How to disable options */ -my_bool my_disable_locking=0; +/* How to disable options */ +READ_ONLY_SYSVAR my_bool my_disable_locking=0; my_bool my_disable_sync=0; my_bool my_disable_async_io=0; my_bool my_disable_flush_key_blocks=0; diff --git a/mysys/my_virtual_mem.c b/mysys/my_virtual_mem.c index 7157dd7ae06..acd63a9b0a8 100644 --- a/mysys/my_virtual_mem.c +++ b/mysys/my_virtual_mem.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2025, MariaDB +/* Copyright (c) 2025, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,19 +24,24 @@ /* Functionality for handling virtual memory - - reserve range, - - commit memory (within reserved range) - - decommit previously commited memory - - release range + - reserve range - memory is reserved, not allocated. + - commit memory (within reserved range) - memory is allocated + - decommit previously commited memory - memory is deallocated, stays reserved + - release range - reserved range is released to OS Not every OS has a "reserve" functionality, i.e it is not always possible to reserve memory larger than swap or RAM for example. - We try to respect use_large_pages setting, on Windows and Linux + The unfortunate terminology comes from WinAPI, see MEM_* flags below. + It's rather confusing in the context of a database (and specifically, + InnoDB, the only user of this functionality), but it's the established + terminology. + + We try to respect use_large_pages setting, both on Windows and Linux */ -#ifdef _WIN32 char *my_virtual_mem_reserve(size_t *size) { +#ifdef _WIN32 DWORD flags= my_use_large_pages ? MEM_LARGE_PAGES | MEM_RESERVE | MEM_COMMIT : MEM_RESERVE; @@ -49,8 +54,10 @@ char *my_virtual_mem_reserve(size_t *size) my_error(EE_OUTOFMEMORY, MYF(ME_BELL + ME_ERROR_LOG), *size); } return ptr; -} +#else + return my_large_virtual_alloc(size); #endif +} #if defined _WIN32 && !defined DBUG_OFF static my_bool is_memory_committed(char *ptr, size_t size) @@ -62,10 +69,18 @@ static my_bool is_memory_committed(char *ptr, size_t size) } #endif +/* + "commit" - in other words, allocate - memory. + ptr must lie within a previously reserved range or be NULL. + in the latter case new memory is reserved and committed. + + This is compatible with the mmap / VirtualAlloc semantics. +*/ char *my_virtual_mem_commit(char *ptr, size_t size) { - DBUG_ASSERT(ptr); #ifdef _WIN32 + if (!ptr) + return VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (my_use_large_pages) { DBUG_ASSERT(is_memory_committed(ptr, size)); @@ -81,6 +96,12 @@ char *my_virtual_mem_commit(char *ptr, size_t size) } } #else + if (!ptr) + { + void *p= mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + return p == MAP_FAILED ? NULL : p; + } if (my_use_large_pages) /* my_large_virtual_alloc() already created a read/write mapping. */; else @@ -165,7 +186,6 @@ void my_virtual_mem_decommit(char *ptr, size_t size) void my_virtual_mem_release(char *ptr, size_t size) { #ifdef _WIN32 - DBUG_ASSERT(my_use_large_pages || !is_memory_committed(ptr, size)); if (!VirtualFree(ptr, 0, MEM_RELEASE)) { my_error(EE_BADMEMORYRELEASE, MYF(ME_ERROR_LOG_ONLY), ptr, size, @@ -180,3 +200,16 @@ void my_virtual_mem_release(char *ptr, size_t size) } #endif } + + +void my_virtual_mem_protect(void *ptr, size_t size, enum my_vmem_prot prot) +{ + int res __attribute__((unused)); +#ifdef _WIN32 + DWORD old_prot; + res= !VirtualProtect(ptr, size, prot ? PAGE_READWRITE : PAGE_READONLY, &old_prot); +#else + res= mprotect(ptr, size, prot ? PROT_READ | PROT_WRITE : PROT_READ); +#endif + DBUG_ASSERT(res == 0); // bad ptr or size alignment, neither should be possible +} diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index de9828069ef..0fa20600a9c 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -1605,7 +1605,6 @@ int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test), static mysql_cond_t COND_thread_count; static mysql_mutex_t LOCK_thread_count; static uint thread_count; -static ulong sum=0; #define MAX_LOCK_COUNT 8 #define TEST_TIMEOUT 100000 @@ -1632,6 +1631,7 @@ static my_bool test_check_status(void* param __attribute__((unused))) return 0; } +#include "my_cpu.h" static void *test_thread(void *arg) { @@ -1665,7 +1665,7 @@ static void *test_thread(void *arg) { ulong k; for (k=0 ; k < (ulong) (tmp-2)*100000L ; k++) - sum+=k; + MY_RELAX_CPU(); } } mysql_mutex_unlock(&LOCK_thread_count); diff --git a/mysys/thr_mutex.c b/mysys/thr_mutex.c index 550bc83e2ab..28174a26577 100644 --- a/mysys/thr_mutex.c +++ b/mysys/thr_mutex.c @@ -1,6 +1,7 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. Copyright (c) 2010, 2011, Monty Program Ab + Copyright (c) 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,7 +57,7 @@ pthread_mutexattr_t my_errorcheck_mutexattr; static pthread_mutex_t THR_LOCK_mutex; static ulong safe_mutex_count= 0; /* Number of mutexes created */ static ulong safe_mutex_id= 0; -my_bool safe_mutex_deadlock_detector= 1; /* On by default */ +READ_ONLY_SYSVAR my_bool safe_mutex_deadlock_detector= 1; /* On by default */ #ifdef SAFE_MUTEX_DETECT_DESTROY static struct st_safe_mutex_create_info_t *safe_mutex_create_root= NULL; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result index b46d470c349..42964a7d8d2 100644 --- a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result @@ -23,7 +23,7 @@ test2@localhost test2@% 1 # mysql -utest2 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()" ERROR 1045 (28000): Access denied for user 'test2'@'localhost' (using password: YES) # mysql -utest1 -ppwd --ssl-verify-server-cert -e "call test.checkme()" -ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure +ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate drop procedure checkme; drop user test1@'%'; drop user test2@'%'; diff --git a/plugin/file_key_management/parser.cc b/plugin/file_key_management/parser.cc index a7b39f0ad16..804cded05c3 100644 --- a/plugin/file_key_management/parser.cc +++ b/plugin/file_key_management/parser.cc @@ -175,7 +175,7 @@ bool Parser::read_filekey(const char *filekey, char *secret) } close(f); - while (secret[len - 1] == '\r' || secret[len - 1] == '\n') len--; + while (len && (secret[len - 1] == '\r' || secret[len - 1] == '\n')) len--; if (len > MAX_SECRET_SIZE) { my_printf_error(EE_READ, diff --git a/plugin/qc_info/qc_info.cc b/plugin/qc_info/qc_info.cc index a73bc8ea9ec..0e1d86550f3 100644 --- a/plugin/qc_info/qc_info.cc +++ b/plugin/qc_info/qc_info.cc @@ -233,6 +233,7 @@ static int qc_info_fill_table(THD *thd, TABLE_LIST *tables, compile_time_assert(QUERY_CACHE_DB_LENGTH_SIZE == 2); db= key + statement_text_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE; db_length= uint2korr(db - QUERY_CACHE_DB_LENGTH_SIZE); + set_if_smaller(db_length, (size_t)(key + key_length - QUERY_CACHE_FLAGS_SIZE - db)); table->field[COLUMN_STATEMENT_SCHEMA]->store(db, db_length, scs); diff --git a/plugin/type_mysql_json/mysql_json.cc b/plugin/type_mysql_json/mysql_json.cc index c28a403a603..6b267d404bd 100644 --- a/plugin/type_mysql_json/mysql_json.cc +++ b/plugin/type_mysql_json/mysql_json.cc @@ -373,7 +373,8 @@ static bool parse_mysql_scalar_or_value(String *buffer, const uchar *data, if (type_is_stored_inline(value_type, large)) { const size_t value_start = value_type_offset + 1; - if (parse_mysql_scalar(buffer, value_type, data + value_start, + if (value_start >= len || + parse_mysql_scalar(buffer, value_type, data + value_start, len - value_start)) return true; } @@ -383,7 +384,8 @@ static bool parse_mysql_scalar_or_value(String *buffer, const uchar *data, of the Object / Array */ const size_t value_start= read_offset_or_size( data + value_type_offset + 1, large); - if (parse_mysql_json_value(buffer, value_type, data + value_start, + if (value_start >= len || + parse_mysql_json_value(buffer, value_type, data + value_start, len - value_start, depth)) return true; } @@ -453,7 +455,7 @@ static bool parse_array_or_object(String *buffer, const uchar *data, size_t len, i * value_size(large)); /* First print the key. */ - if (buffer->append('"') || + if (key_start + key_len > bytes || buffer->append('"') || append_string_json(buffer, data + key_start, key_len) || buffer->append(STRING_WITH_LEN("\": "))) { diff --git a/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf b/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf index 5adf6313843..988096e134a 100644 --- a/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf +++ b/plugin/wsrep_info/mysql-test/wsrep_info/my.cnf @@ -26,6 +26,10 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port' wsrep_sst_receive_address='127.0.0.1:@mysqld.2.#sst_port' wsrep_node_name=test-node-2 +[sst] +# SSL mode is disabled for SST unless tests specifically enable it. +ssl-mode=DISABLED + [ENV] NODE_MYPORT_1= @mysqld.1.port NODE_MYSOCK_1= @mysqld.1.socket diff --git a/scripts/galera_new_cluster.sh b/scripts/galera_new_cluster.sh index 4d27ff94e2a..98d23dd82f4 100755 --- a/scripts/galera_new_cluster.sh +++ b/scripts/galera_new_cluster.sh @@ -21,11 +21,17 @@ EOF exit 0 fi -echo _WSREP_NEW_CLUSTER='--wsrep-new-cluster' > "@INSTALL_RUNDATADIR@/wsrep-new-cluster" && \ +# INSTALL_RUNDIR, not the socket directory - the latter is writable by +# mariadbd, which could then inject arbitrary variables into the service +# environment. Removing the file here and not in the service file, +# because that runs as mysql and cannot unlink in /run. +new_cluster=@INSTALL_RUNDIR@/mariadb-wsrep-new-cluster +trap 'rm -f "$new_cluster"' EXIT +trap 'exit 1' HUP INT TERM + +echo _WSREP_NEW_CLUSTER='--wsrep-new-cluster' > "$new_cluster" && \ systemctl restart "${1:-mariadb.service}" extcode=$? -rm -f "@INSTALL_RUNDATADIR@/wsrep-new-cluster" - exit $extcode diff --git a/scripts/mysqld_multi.sh b/scripts/mysqld_multi.sh index 316cc59536d..fbcf47362d4 100644 --- a/scripts/mysqld_multi.sh +++ b/scripts/mysqld_multi.sh @@ -622,7 +622,8 @@ sub find_groups my ($raw_gids) = @_; my %gids; - my %groups; + my %seen_group; + my @groups; if (defined($raw_gids)) { @@ -661,7 +662,8 @@ sub find_groups # Use $2 + 0 to normalize numbers (002 + 0 -> 2) if (not defined($raw_gids) or $gids{$2 + 0}) { - $groups{"$1$2"} = 1; + push @groups, "$1$2" unless $seen_group{"$1$2"}; + $seen_group{"$1$2"}=1; } } elsif (/^\s*!include\s+(\S.*?)\s*$/) @@ -676,7 +678,7 @@ sub find_groups close CONF; } - return keys %groups; + return @groups; } #### diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 5642c1b8bda..73cd5251871 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -571,6 +571,11 @@ read_cnf() if [ "$tmode" != 'DISABLED' -o $encrypt -ge 2 ]; then check_server_ssl_config fi + # MTR test hook: simulate a missing SSL certificate and key. + if [ -n "${MTR_SST_SIMULATE_NO_SSL_CERT:-}" ]; then + tpem="" + tkey="" + fi if [ "$tmode" != 'DISABLED' ]; then if [ 0 -eq $encrypt -a -n "$tpem" -a -n "$tkey" ] then @@ -599,6 +604,16 @@ read_cnf() "CERT='$tpem', KEY='$tkey', MODE='$tmode'," \ "encrypt='$encrypt'" + # ssl-mode requires encryption but none could be set up (no usable + # cert/key): abort instead of silently transferring in cleartext. + if [ "$tmode" != 'DISABLED' -a $encrypt -eq 0 ]; then + wsrep_log_error "ssl-mode is set to '$tmode', but no usable SSL" \ + "certificate and key were found. Cannot perform an" \ + "encrypted transfer. Please configure ssl-cert and" \ + "ssl-key, or set ssl-mode to DISABLED." + exit 22 # EINVAL + fi + if [ $encrypt -ge 2 ]; then ssl_dhparams=$(parse_cnf "$encgroups" 'ssl-dhparams') fi @@ -1515,38 +1530,52 @@ else # joiner if [ -n "$WSREP_SST_OPT_BINLOG" ]; then cd "$DATA" - binlogs="" + # + # MDEV-38147: do NOT move the donor's binary log into + # place on the joiner. + # + # The donor still ships its (freshly rotated) current binary log so + # that an old joiner keeps working and a new joiner can identify + # exactly which file was sent. That file, however, only carries a + # Gtid_list, and its position can be ahead of the engine snapshot + # (BACKUP STAGE BLOCK_COMMIT does not pause commits if mariabackup + # is used for SST). With gtid_strict_mode=ON that ahead position + # makes the joiner raise error 1950 when it re-binlogs transactions + # during IST, and keeping the file could also collide with the + # joiner's own binary log numbering. + # + # Therefore the joiner removes the received binary log file(s) and + # starts a fresh binary log, seeding its GTID position from the + # storage-engine checkpoint during recovery (see + # wsrep_seed_binlog_gtid_state() in sql/log.cc) - the exact position + # from which IST resumes, which keeps the joiner's binary log in + # lockstep with the rest of the cluster. + # + binlogs=() if [ -f 'mariadb_backup_binlog_info' ]; then - NL=$'\n' while read bin_string || [ -n "$bin_string" ]; do bin_file=$(echo "$bin_string" | cut -f1) - if [ -f "$bin_file" ]; then - binlogs="$binlogs${binlogs:+$NL}$bin_file" + if [ -n "$bin_file" -a -f "$bin_file" ]; then + binlogs+=("$bin_file") fi done < 'mariadb_backup_binlog_info' else - binlogs=$(ls -d -1 "$binlog_base".[0-9]* 2>/dev/null || :) - fi - cd "$DATA_DIR" - if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ - "$binlog_dir" != "$DATA_DIR" ] - then - [ ! -d "$binlog_dir" ] && mkdir -p "$binlog_dir" - fi - index_dir=$(dirname "$binlog_index"); - if [ -n "$index_dir" -a "$index_dir" != '.' -a \ - "$index_dir" != "$DATA_DIR" ] - then - [ ! -d "$index_dir" ] && mkdir -p "$index_dir" - fi - if [ -n "$binlogs" ]; then - wsrep_log_info "Moving binary logs to $binlog_dir" - echo "$binlogs" | \ - while read bin_file || [ -n "$bin_file" ]; do - mv "$DATA/$bin_file" "$binlog_dir" - echo "$binlog_dir${binlog_dir:+/}$bin_file" >> "$binlog_index" + for bin_file in "$binlog_base".[0-9]*; do + [ -f "$bin_file" ] && binlogs+=("$bin_file") done fi + if [ ${#binlogs[@]} -ne 0 ]; then + wsrep_log_info "Removing received binary log(s) so the joiner" \ + "starts a fresh binary log seeded from the" \ + "storage-engine checkpoint" + for bin_file in "${binlogs[@]}"; do + rm -f "$DATA/$bin_file" + done + else + wsrep_log_info "No binary log received from donor; the joiner" \ + "will start a fresh binary log seeded from the" \ + "storage-engine checkpoint" + fi cd "$OLD_PWD" fi @@ -1565,6 +1594,38 @@ else # joiner exit 22 fi + # + # The joiner starts a fresh binary log after SST (see the MDEV-38147 + # note above), but the server does not create the log-bin directory + # itself - it must exist before mysqld reopens the binary log. When the + # binary log lives outside the datadir it may not exist yet on a freshly + # provisioned joiner, and when it lives in a subdirectory of the datadir + # the cleanup above removed it and the move stage did not restore it + # (binary logs are not part of the backup). Recreate the binary log and + # index directories here, after the move so they are not wiped again. + # Relative paths are resolved against the datadir, matching the server. + # + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + cd "$DATA_DIR" + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ + "$binlog_dir" != "$DATA_DIR" -a ! -d "$binlog_dir" ] + then + wsrep_log_info "Creating the binlog directory '$binlog_dir'" + mkdir -p "$binlog_dir" + fi + if [ -n "$binlog_index" ]; then + index_dir=$(dirname "$binlog_index") + if [ -n "$index_dir" -a "$index_dir" != '.' -a \ + "$index_dir" != "$DATA_DIR" -a ! -d "$index_dir" ] + then + wsrep_log_info \ + "Creating the binlog index directory '$index_dir'" + mkdir -p "$index_dir" + fi + fi + cd "$OLD_PWD" + fi + else wsrep_log_info "'$IST_FILE' received from donor: Running IST" diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index a559c6749e9..faa8e592d65 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -197,14 +197,11 @@ SSTCAP="$tcap" SSLMODE=$(parse_cnf "$encgroups" 'ssl-mode' | tr '[[:lower:]]' '[[:upper:]]') if [ -z "$SSLMODE" ]; then - # Implicit verification if CA is set and the SSL mode - # is not specified by user: + # ssl-mode not set: derive it from the SSL config. Set it even when + # stunnel is absent, so the check below aborts instead of silently + # falling back to an unencrypted transfer. if [ -n "$SSTCA$SSTCAP" ]; then - STUNNEL_BIN=$(commandex 'stunnel') - if [ -n "$STUNNEL_BIN" ]; then - SSLMODE='VERIFY_CA' - fi - # Require SSL by default if SSL key and cert are present: + SSLMODE='VERIFY_CA' elif [ -n "$SSTKEY" -a -n "$SSTCERT" ]; then SSLMODE='REQUIRED' fi @@ -279,10 +276,22 @@ if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then if [ -z "${STUNNEL_BIN+x}" ]; then STUNNEL_BIN=$(commandex 'stunnel') fi + # MTR test hook: simulate a missing stunnel binary. + if [ -n "${MTR_SST_SIMULATE_NO_STUNNEL:-}" ]; then + STUNNEL_BIN="" + fi if [ -n "$STUNNEL_BIN" ]; then wsrep_log_info "Using stunnel for SSL encryption: CA: '$SSTCA'," \ "CAPATH='$SSTCAP', ssl-mode='$SSLMODE'" STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + else + # Encryption required but stunnel missing: abort instead of + # silently falling back to an unencrypted transfer. + wsrep_log_error "ssl-mode is set to '$SSLMODE', but the 'stunnel'" \ + "binary was not found in the path. Cannot perform" \ + "an encrypted transfer. Please install stunnel or" \ + "set ssl-mode to DISABLED." + exit 2 # ENOENT fi fi diff --git a/sql-common/client.c b/sql-common/client.c index 49b300de7a4..fc5afecd2c0 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -1,5 +1,5 @@ /* Copyright (c) 2003, 2016, Oracle and/or its affiliates. - Copyright (c) 2009, 2020, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1699,7 +1699,7 @@ mysql_set_character_set_with_default_collation(MYSQL *mysql) { const char *save= charsets_dir; if (mysql->options.charset_dir) - charsets_dir=mysql->options.charset_dir; + charsets_dir= mysql->options.charset_dir; if ((mysql->charset= get_charset_by_csname(mysql->options.charset_name, MY_CS_PRIMARY, MYF(MY_WME| @@ -1723,7 +1723,8 @@ mysql_set_character_set_with_default_collation(MYSQL *mysql) */ } } - charsets_dir= save; + if (mysql->options.charset_dir) + charsets_dir= save; } @@ -3835,10 +3836,12 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) my_free(mysql->options.my_cnf_group); mysql->options.my_cnf_group= opt_strdup(arg,MYF(MY_WME)); break; +#ifdef EMBEDDED_LIBRARY case MYSQL_SET_CHARSET_DIR: my_free(mysql->options.charset_dir); mysql->options.charset_dir= opt_strdup(arg,MYF(MY_WME)); break; +#endif case MYSQL_SET_CHARSET_NAME: my_free(mysql->options.charset_name); mysql->options.charset_name= opt_strdup(arg,MYF(MY_WME)); @@ -4154,7 +4157,8 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) (cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(MY_UTF8_IS_UTF8MB3)))) { char buff[MY_CS_CHARACTER_SET_NAME_SIZE + 10]; - charsets_dir= save_csdir; + if (mysql->options.charset_dir) + charsets_dir= save_csdir; /* Skip execution of "SET NAMES" for pre-4.1 servers */ if (mysql_get_server_version(mysql) < 40100) return 0; @@ -4171,7 +4175,8 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) set_mysql_extended_error(mysql, CR_CANT_READ_CHARSET, unknown_sqlstate, ER(CR_CANT_READ_CHARSET), cs_name, cs_dir_name); } - charsets_dir= save_csdir; + if (mysql->options.charset_dir) + charsets_dir= save_csdir; return mysql->net.last_errno; } diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 1fc81dbe0ae..83e64f8c1a5 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (c) 2006, 2014, Oracle and/or its affiliates. -# Copyright (c) 2010, 2022, MariaDB Corporation. +# Copyright (c) 2010, 2026, MariaDB plc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -349,6 +349,16 @@ ENDIF() DTRACE_INSTRUMENT_STATIC_LIBS(mariadbd "sql;mysys;mysys_ssl;${MYSQLD_STATIC_PLUGIN_LIBS}") +IF(HAVE_RO_AFTER_INIT) + GET_TARGET_PROPERTY(RO_AFTER_INIT_LDFLAGS mariadbd LINK_FLAGS) + IF(NOT RO_AFTER_INIT_LDFLAGS) + SET(RO_AFTER_INIT_LDFLAGS) + ENDIF() + SET_TARGET_PROPERTIES(mariadbd PROPERTIES LINK_FLAGS + "${RO_AFTER_INIT_LDFLAGS} -Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/mysqld_ro.lds") + SET_TARGET_PROPERTIES(mariadbd PROPERTIES + LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mysqld_ro.lds) +ENDIF() SET(WITH_MYSQLD_LDFLAGS "" CACHE STRING "Additional linker flags for mysqld") MARK_AS_ADVANCED(WITH_MYSQLD_LDFLAGS) diff --git a/sql/field.h b/sql/field.h index cc10f766262..341424e19f9 100644 --- a/sql/field.h +++ b/sql/field.h @@ -5947,6 +5947,7 @@ bool check_expression(Virtual_column_info *vcol, const LEX_CSTRING *name, #define FIELDFLAG_GEOM 2048U // mangled with decimals! #define FIELDFLAG_TREAT_BIT_AS_CHAR 4096U /* use Field_bit_as_char */ +#define FIELDFLAG_FRM_HEX_ENCODED_TYPELIB 4096U // mangled with *_BIT_AS_CHAR #define FIELDFLAG_LONG_DECIMAL 8192U #define FIELDFLAG_NO_DEFAULT 16384U /* sql */ #define FIELDFLAG_MAYBE_NULL 32768U // sql diff --git a/sql/handler.cc b/sql/handler.cc index 768be19d921..666bc02bb55 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2597,7 +2597,7 @@ static my_xid wsrep_order_and_check_continuity(XID *list, int len) if (!wsrep_is_wsrep_xid(list + i) || wsrep_xid_seqno(list + i) != cur_seqno + 1) { - WSREP_WARN("Discovered discontinuity in recovered wsrep " + WSREP_INFO("Discovered discontinuity in recovered wsrep " "transaction XIDs. Truncating the recovery list to " "%d entries", i); break; @@ -2884,6 +2884,53 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, x <= wsrep_limit) && info->dry_run, info->dry_run)) { +#ifdef WITH_WSREP + /* + MDEV-40179: a wsrep transaction still in the prepared state at the + final recovery pass (the dry run, commit_list == 0) but they don't + have corresponding binlog events because they are no longer part of + SST. With log_bin=ON they can't be committed. + After recovering from SST without binlogs in place the joiner runs + no binlog XA recovery to commit or roll back such transactions, so + without binlog events they would abort startup with "Found N prepared + transactions!". Roll them back here; the node re-receives them + from the donor via IST. Non-wsrep (e.g. user XA) prepared transactions + are left untouched and still reported. + + Notice that in the wsrep_emulate_bin_log case below we don't need the + binlog events, so these prepared and wsrep-ordered transactions can be + safely committed. + + The guard is WSREP_PROVIDER_EXISTS ("a Galera provider is loaded"): + a node configured with a provider will rejoin and receive + these transactions; a standalone node (no provider) cannot, so + there we keep the conservative default and still report them. + */ + if (WSREP_PROVIDER_EXISTS && wsrep_is_wsrep_xid(info->list + i)) + { + int rc= hton->rollback_by_xid(hton, info->list + i); + if (rc == 0) + { + sql_print_warning("Rolled back orphan prepared wsrep " + "transaction %lld", (longlong) x); + continue; + } + /* + A failed rollback is critical: the storage engine is left with + a transaction in the prepared state, which blocks purge and will + re-surface at the next recovery. We cannot safely continue, so + flag the error and abort startup (ha_recover() returns non-zero, + which makes the caller unireg_abort()). + */ + sql_print_error("Failed to roll back orphan prepared wsrep " + "transaction %lld during recovery (error %d). " + "The storage engine is left with a transaction in " + "the prepared state; aborting startup.", + (longlong) x, rc); + info->error= true; + break; + } +#endif /* WITH_WSREP */ info->found_my_xids++; continue; } @@ -2933,7 +2980,7 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, } } } - if (got < info->len) + if (got < info->len || info->error) break; } } diff --git a/sql/item.cc b/sql/item.cc index c8a628b03a8..ab7701f05c8 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5923,7 +5923,9 @@ static inline bool inner_refs_check(THD *thd, enum_parsing_place place, Item **reference) { +#ifndef DBUG_OFF Item::Type ref_type= (*reference)->type(); +#endif if (!last_checked_context->select_lex->having_fix_field && select->group_list.elements && (place == SELECT_LIST || place == IN_HAVING)) diff --git a/sql/item.h b/sql/item.h index 2c1090a9732..5889c295f90 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2307,6 +2307,7 @@ public: virtual bool enumerate_field_refs_processor(void *arg) { return 0; } virtual bool mark_as_eliminated_processor(void *arg) { return 0; } + virtual bool unmark_as_eliminated_processor(void *arg) { return 0; } virtual bool eliminate_subselect_processor(void *arg) { return 0; } virtual bool view_used_tables_processor(void *arg) { return 0; } virtual bool eval_not_null_tables(void *arg) { return 0; } diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index caf04e40509..30ebc039dad 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -7502,6 +7502,21 @@ bool Item_equal::walk(Item_processor processor, bool walk_subquery, void *arg) } +/** + @brief + Subqueries that equality propagation converted to constants must not + be marked as eliminated, so unmark them. +*/ + +bool Item_equal::unmark_as_eliminated_processor(void *arg) +{ + Item *c= get_const(); + if (c) + c->walk(&Item::unmark_as_eliminated_processor, FALSE, arg); + return FALSE; +} + + Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index e2c18cb2f51..92a56c4d622 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -3533,6 +3533,7 @@ public: SARGABLE_PARAM **sargables) override; SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) override; bool walk(Item_processor processor, bool walk_subquery, void *arg) override; + bool unmark_as_eliminated_processor(void *arg) override; Item *transform(THD *thd, Item_transformer transformer, uchar *arg) override; void print(String *str, enum_query_type query_type) override; const Type_handler *compare_type_handler() const { return m_compare_handler; } diff --git a/sql/item_func.cc b/sql/item_func.cc index 49dacfb3492..4d43d821c95 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -4746,9 +4746,9 @@ user_var_entry *get_variable(HASH *hash, LEX_CSTRING *name, size_t size=ALIGN_SIZE(sizeof(user_var_entry))+name->length+1+extra_size; if (!my_hash_inited(hash)) return 0; - if (!(entry = (user_var_entry*) my_malloc(key_memory_user_var_entry, size, - MYF(MY_WME | ME_FATAL | - MY_THREAD_SPECIFIC)))) + + if (!(entry= (user_var_entry*) alloc_root(current_thd->user_vars_root(), + size))) return 0; entry->name.str=(char*) entry+ ALIGN_SIZE(sizeof(user_var_entry))+ extra_size; @@ -4771,10 +4771,7 @@ user_var_entry *get_variable(HASH *hash, LEX_CSTRING *name, entry->set_handler(&type_handler_long_blob); memcpy((char*) entry->name.str, name->str, name->length+1); if (my_hash_insert(hash,(uchar*) entry)) - { - my_free(entry); return 0; - } } return entry; } diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index e332ec22c9a..5f0bd3969cd 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -163,7 +163,8 @@ String *Item_func_geometry_from_json::val_str(String *str) (const uchar *) js->end()); je.killed_ptr= (uint32_t *) ¤t_thd->killed; - if ((null_value= !Geometry::create_from_json(&buffer, &je, options==1, str))) + if (!json_read_value(&je) && + (null_value= !Geometry::create_from_json(&buffer, &je, options==1, str))) { int code= 0; @@ -1509,7 +1510,9 @@ bool Item_func_spatial_precise_rel::val_bool() null_value= true; goto exit; } - /* fall through */ + handle_sp_crosses_func_case(func, trn, g1, g2, + shape_a, shape_b, null_value); + break; case SP_OVERLAPS_FUNC: { // Both geometries must have the same number of dimensions. diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 4368597fc7a..615d0cb405f 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -577,6 +577,7 @@ void report_path_error_ex(const char *ps, json_path_t *p, Checks if the path has '.*' '[*]' or '**' constructions and sets the NO_WILDCARD_ALLOWED error if the case. */ +__attribute__((nonnull, warn_unused_result)) static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end) { @@ -591,6 +592,13 @@ static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, return 1; } +static inline +CHARSET_INFO *def_path_charset(CHARSET_INFO *cs, CHARSET_INFO *alt) +{ + if (cs) return cs; + if (alt) return alt; + return &my_charset_utf8mb4_bin; +} bool Item_func_json_valid::val_bool() { @@ -630,14 +638,11 @@ bool Item_func_json_equals::val_bool() THD *thd; json_engine_t je; - if ((null_value= args[0]->null_value || args[1]->null_value)) - return 1; - String *a= args[0]->val_json(&a_tmp); - if ((null_value= a == nullptr)) + if ((null_value= a == nullptr || args[0]->null_value)) return 1; String *b= args[1]->val_json(&b_tmp); - if ((null_value= b == nullptr)) + if ((null_value= b == nullptr || args[1]->null_value)) return 1; DYNAMIC_STRING a_res; @@ -777,18 +782,20 @@ bool Json_path_extractor::extract(String *str, Item *item_js, Item *item_jp, { String *s_p= item_jp->val_str(&tmp_path); + if (!s_p) + return true; if (allow_wildcard) { - if (s_p && + if (!s_p->charset() || json_path_setup(&p, s_p->charset(), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) error= true; } else { - if (s_p && - path_setup_nwc(&p, s_p->charset(), (const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length())) + if (path_setup_nwc(&p, def_path_charset(s_p->charset(), cs), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) error= true; } @@ -1618,8 +1625,11 @@ bool Item_func_json_contains::val_bool() if (!path.parsed) { String *s_p= args[2]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->end())) { report_path_error(s_p, &path.p, 2); @@ -2179,8 +2189,11 @@ String *Item_func_json_array_append::val_str(String *str) if (!c_path->parsed) { String *s_p= args[n_arg]->val_str(tmp_paths+n_path); - if (s_p && - path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &c_path->p, n_arg); @@ -2316,15 +2329,19 @@ String *Item_func_json_array_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (!s_p) goto return_null; + if (!s_p->charset()) + goto path_err; - if (path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length()) || + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length()) || c_path->p.last_step - 1 < c_path->p.steps || c_path->p.last_step->type != JSON_PATH_ARRAY) { if (c_path->p.s.error == 0) c_path->p.s.error= SHOULD_END_WITH_ARRAY; - +path_err: report_path_error(s_p, &c_path->p, n_arg); goto return_null; @@ -3180,8 +3197,11 @@ longlong Item_func_json_length::val_int() if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &path.p, 1); @@ -3425,7 +3445,8 @@ String *Item_func_json_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3436,6 +3457,8 @@ String *Item_func_json_insert::val_str(String *str) /* We search to the last step. */ c_path->p.last_step--; } + else + goto return_null; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3690,7 +3713,8 @@ String *Item_func_json_remove::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3707,6 +3731,8 @@ String *Item_func_json_remove::val_str(String *str) goto null_return; } } + else + goto null_return; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3920,13 +3946,16 @@ String *Item_func_json_keys::val_str(String *str) if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) - { - report_path_error(s_p, &path.p, 1); - goto null_return; - } + { + report_path_error(s_p, &path.p, 1); + goto null_return; + } path.parsed= path.constant; } @@ -4863,19 +4892,17 @@ int compare_nested_object(json_engine_t *js, json_engine_t *value) if (json_normalize_engine(&je, &a_res, a.ptr(), a.length(), value->s.cs)) { value->s.error= je.s.error; - value->s.c_str= je.s.c_str; goto error; } if (json_normalize_engine(&je, &b_res, b.ptr(), b.length(), value->s.cs)) { js->s.error= je.s.error; - js->s.c_str= je.s.c_str; goto error; } result= strcmp(a_res.str, b_res.str) ? 0 : 1; - error: +error: dynstr_free(&a_res); dynstr_free(&b_res); @@ -4883,8 +4910,9 @@ int compare_nested_object(json_engine_t *js, json_engine_t *value) } -static int json_find_overlap_with_object(json_engine_t *js, json_engine_t *value, - bool compare_whole) +static int json_find_overlap_with_object(json_engine_t *js, + json_engine_t *value, + bool compare_whole) { if (value->value_type == JSON_VALUE_OBJECT) { diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index f9bd32c111d..e1f49b34461 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -64,6 +64,38 @@ C_MODE_END #define FMT_HEADER_ONLY 1 #include "fmt/args.h" +/* + an allocator that implements std::allocator interface. + can be used with std or fmt. + but it uses our memory accounting + and allows to limit strings to max_allowed_packet + to be more generic needs to take MY_THREAD_SPECIFIC and PSI key as arguments, + and limit the amount of memory allocated, not just size of one allocation +*/ +template struct my_allocator { + using value_type= T; + std::size_t max_alloc; + + my_allocator(std::size_t max_alloc) : max_alloc(max_alloc) {}; + template my_allocator(const my_allocator&o) + : max_alloc(o.max_alloc) {} + + T* allocate(std::size_t n) + { + void* p= n > max_alloc / sizeof(T) + ? nullptr : my_malloc(PSI_INSTRUMENT_MEM, n * sizeof(T), + MYF(MY_THREAD_SPECIFIC)); + if (!p) + throw std::bad_alloc(); + return static_cast(p); + } + + std::size_t max_size() { return max_alloc / sizeof(T); } + void deallocate(T* p, std::size_t) noexcept { my_free(p); } + bool operator==(const my_allocator&) const noexcept { return true; } + bool operator!=(const my_allocator&) const noexcept { return false; } +}; + size_t username_char_length= USERNAME_CHAR_LENGTH; /* @@ -1518,8 +1550,6 @@ bool Item_func_sformat::fix_length_and_dec(THD *thd) if (!val_arg) return TRUE; - ulonglong char_length= 0; - uint flags= MY_COLL_ALLOW_SUPERSET_CONV | MY_COLL_ALLOW_COERCIBLE_CONV | MY_COLL_ALLOW_NUMERIC_CONV; @@ -1540,8 +1570,8 @@ bool Item_func_sformat::fix_length_and_dec(THD *thd) return TRUE; } - char_length= MAX_BLOB_WIDTH; - fix_char_length_ulonglong(char_length); + max_length= (uint32)MY_MIN((ulonglong)thd->variables.max_allowed_packet, + thd->variables.max_mem_used); return FALSE; } @@ -1573,6 +1603,8 @@ struct fmt_locale_comma : std::numpunct }; static std::locale fmt_locale(std::locale(), new fmt_locale_comma); +using fmt_buffer= fmt::basic_memory_buffer >; /* SFORMAT(format_string, ...) This function receives a formatting specification string and N parameters @@ -1621,25 +1653,19 @@ String *Item_func_sformat::val_str(String *res) } } - null_value= false; /* Create the string output */ try { -#ifdef _MSC_VER -/* - C4834 : "discarding return value of function with [[nodiscard]] attribute" - in fmt 12.1 template code, for isalpha() -*/ -#pragma warning(push) -#pragma warning(disable : 4834) -#endif - auto text = fmt::vformat(fmt_locale, fmt_arg->c_ptr_safe(), arg_store); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - res->length(0); - res->set_charset(collation.collation); - res->append(text.c_str(), text.size(), fmt_arg->charset()); + /* have to use *1.5 to match fmt realloc strategy */ + fmt_buffer text{my_allocator(max_length + max_length/2)}; + fmt::vformat_to(std::back_inserter(text), fmt_locale, + fmt_arg->c_ptr_safe(), arg_store); + if (!((null_value= text.size() > max_length))) + { + res->length(0); + res->set_charset(collation.collation); + res->append(text.data(), text.size(), fmt_arg->charset()); + } } catch (const fmt::format_error &ex) { @@ -1649,6 +1675,10 @@ String *Item_func_sformat::val_str(String *res) ER_THD(thd, WARN_SFORMAT_ERROR), ex.what()); null_value= true; } + catch (const std::bad_alloc&) + { + null_value= true; // OOM + } return null_value ? NULL : res; } diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 6b7bc65e39a..6a629ded020 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -375,12 +375,41 @@ bool Item_subselect::enumerate_field_refs_processor(void *arg) return FALSE; } + +/** + @brief + Set the "eliminated" flag set by mark_as_eliminated_processor(). + + @details + Table elimination marks every subquery reachable from an eliminated outer + join's ON expression as eliminated. +*/ + bool Item_subselect::mark_as_eliminated_processor(void *arg) { eliminated= TRUE; return FALSE; } +/** + @brief + Clear the "eliminated" flag set by mark_as_eliminated_processor(). + + @details + Equality propagation (build_equal_items()) can inject a reference to a + subquery that lives in another part of the query (e.g. the WHERE clause) + into that ON expression. Such a subquery still has to be executed, + so after elimination we walk the surviving expressions and clear the flag + on any subquery still referenced from them. +*/ + + +bool Item_subselect::unmark_as_eliminated_processor(void *arg) +{ + eliminated= FALSE; + return FALSE; +} + /** Remove a subselect item from its unit so that the unit no longer diff --git a/sql/item_subselect.h b/sql/item_subselect.h index b3b5e0ff3cc..ef2fa8b1fe1 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -242,6 +242,7 @@ public: bool walk(Item_processor processor, bool walk_subquery, void *arg) override; bool unknown_splocal_processor(void *arg) override; bool mark_as_eliminated_processor(void *arg) override; + bool unmark_as_eliminated_processor(void *arg) override; bool eliminate_subselect_processor(void *arg) override; bool enumerate_field_refs_processor(void *arg) override; bool check_vcol_func_processor(void *arg) override diff --git a/sql/log.cc b/sql/log.cc index 2d3e4affce0..67f73ba3ceb 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -67,6 +67,7 @@ #ifdef WITH_WSREP #include "wsrep_trans_observer.h" #include "wsrep_status.h" +#include "wsrep_xid.h" #endif /* WITH_WSREP */ #ifdef HAVE_REPLICATION @@ -84,8 +85,8 @@ handlerton *binlog_hton; LOGGER logger; -const char *log_bin_index= 0; -const char *log_bin_basename= 0; +READ_ONLY_SYSVAR const char *log_bin_index= 0; +READ_ONLY_SYSVAR const char *log_bin_basename= 0; MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period); @@ -10340,7 +10341,7 @@ ulong tc_log_page_waits= 0; static const uchar tc_log_magic[]={(uchar) 254, 0x23, 0x05, 0x74}; -ulong opt_tc_log_size; +READ_ONLY_SYSVAR ulong opt_tc_log_size; ulong tc_log_max_pages_used=0, tc_log_page_size=0, tc_log_cur_pages_used=0; int TC_LOG_MMAP::open(const char *opt_name) @@ -12637,6 +12638,76 @@ MYSQL_BIN_LOG::recover_gtid_index_abort(Gtid_index_writer *gi) } +#if defined(WITH_WSREP) && defined(HAVE_REPLICATION) +/* + MDEV-38147: A Galera mariabackup SST no longer ships the donor's binary log + (the only thing it carried was a Gtid_list whose position was ahead of the + snapshot, causing error 1950). Instead the joiner starts a fresh binary log, + so its Gtid_list / @@gtid_binlog_pos must be seeded from the recovered wsrep + position - otherwise the joiner would report an empty binlog position until + it re-binlogs new transactions, which breaks its use as an async master. + + The wsrep cluster position lives in the storage-engine checkpoint (restored + by the SST). Async-replica source positions live in mysql.gtid_slave_pos + (also restored from the engine) and are handled separately, so they are not + seeded here. + + This seeding applies only with wsrep_gtid_mode=ON. In that mode cluster + writes are re-tagged to wsrep_gtid_domain_id and binlogged with the cluster + seqno, so @@gtid_binlog_pos in that domain tracks the cluster seqno - which is + exactly the SE checkpoint position, and exactly the position from which IST + resumes re-binlogging. Seeding it keeps the joiner in lockstep and avoids + error 1950 from re-binlogging over an ahead position. + + With wsrep_gtid_mode=OFF cluster writes keep the node's configured + gtid_domain_id and are binlogged with a locally allocated seq_no; the cluster + seqno is not written to the binlog GTID (it lives only in + thd->wsrep_current_gtid_seqno - see the GTID assignment guarded by + wsrep_gtid_mode in wsrep_mysqld.cc). That binlog position is node-local and + unrelated to the checkpoint seqno, so there is nothing cluster-consistent to + seed: a fresh joiner just resumes its own local counter, which cannot produce + an ahead position. We therefore seed nothing in that mode. +*/ +static void wsrep_seed_binlog_gtid_state() +{ + /* + Only wsrep_gtid_mode=ON has a cluster-consistent binlog position that maps + onto the SE checkpoint (see the block comment above). + */ + if (!wsrep_gtid_mode) + return; + + wsrep_server_gtid_t const eng= wsrep_get_SE_checkpoint(); + if (eng.seqno <= 0) + return; /* not a wsrep node / no position */ + + rpl_gtid eng_gtid; + eng_gtid.domain_id= eng.domain_id; /* == wsrep_gtid_domain_id */ + eng_gtid.server_id= eng.server_id; + eng_gtid.seq_no= eng.seqno; + + rpl_gtid *cur= rpl_global_gtid_binlog_state.find_most_recent(eng_gtid.domain_id); + if (cur && cur->seq_no >= eng_gtid.seq_no) + return; /* binlog state already at or ahead of the checkpoint */ + + sql_print_information("WSREP: seeding binlog GTID state to %u-%u-%llu " + "from the storage-engine checkpoint", + eng_gtid.domain_id, eng_gtid.server_id, + (unsigned long long) eng_gtid.seq_no); + /* + Use the locking update() for consistency with the find_most_recent() read + above. With strict=false the only failure is OOM; update() will have called + my_error(ER_OUT_OF_RESOURCES), but there is no current_thd this early in + startup, so report the failure explicitly here as well. + */ + if (rpl_global_gtid_binlog_state.update(&eng_gtid, false)) + sql_print_error("WSREP: failed to seed binlog GTID state to %u-%u-%llu " + "from the storage-engine checkpoint (out of memory)", + eng_gtid.domain_id, eng_gtid.server_id, + (unsigned long long) eng_gtid.seq_no); +} +#endif /* WITH_WSREP && HAVE_REPLICATION */ + int MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery) { @@ -12671,6 +12742,10 @@ MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery) error= 0; } } +#if defined(WITH_WSREP) && defined(HAVE_REPLICATION) + if (!error && WSREP_PROVIDER_EXISTS) + wsrep_seed_binlog_gtid_state(); +#endif return error; } diff --git a/sql/log_event.cc b/sql/log_event.cc index 9cab664101f..9e01ee66bc4 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -332,6 +332,25 @@ int binlog_buf_compress(const uchar *src, uchar *dst, uint32 len, uint32 *comlen dst[1]= uchar(len); lenlen= 1; } + + /* + Write the length of an event no replica can uncompress, so that a + replica's handling of one can be tested. Reaching a value this large + takes four length bytes, and the encoding above spends four only on + content of 16MB or more, so the injection sets the encoding as well + as the value. The compressed content still goes where those four + bytes place it, leaving the event well formed apart from the length + it claims. + */ + DBUG_EXECUTE_IF("binlog_compress_corrupt_len", + { + dst[1]= 0xFF; + dst[2]= 0xFF; + dst[3]= 0xFF; + dst[4]= 0xFC; + lenlen= 4; + }); + dst[0]= 0x80 | (lenlen & 0x07); uLongf tmplen= (uLongf)*comlen - BINLOG_COMPRESSED_HEADER_LEN - lenlen - 1; @@ -354,7 +373,7 @@ int binlog_buf_compress(const uchar *src, uchar *dst, uint32 len, uint32 *comlen 2) If *is_malloc is retuened as false, then 'dst' reuses the passed-in 'buf'. - return zero if successful, non-zero otherwise. + return zero if successful, otherwise the error code the caller reports. */ int @@ -369,8 +388,8 @@ query_event_uncompress(const Format_description_log_event *description_event, uchar *new_dst; // bad event - if (src_len < len) - return 1; + if (unlikely(src_len < len)) + return ER_BINLOG_UNCOMPRESS_ERROR; DBUG_ASSERT((uchar)src[EVENT_TYPE_OFFSET] == QUERY_COMPRESSED_EVENT); @@ -382,30 +401,33 @@ query_event_uncompress(const Format_description_log_event *description_event, tmp+= common_header_len; // bad event - if (end <= tmp) - return 1; + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint db_len= (uint)tmp[Q_DB_LEN_OFFSET]; uint16 status_vars_len= uint2korr(tmp + Q_STATUS_VARS_LEN_OFFSET); tmp+= post_header_len + status_vars_len + db_len + 1; // bad event - if (end <= tmp) - return 1; + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; int32 comp_len= (int32)(len - (tmp - src) - (contain_checksum ? BINLOG_CHECKSUM_LEN : 0)); uint32 un_len= binlog_get_uncompress_len(tmp); // bad event - if (comp_len < 0 || un_len == 0) - return 1; + if (unlikely(comp_len < 0 || un_len == 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; + + if (unlikely(un_len > MAX_MAX_ALLOWED_PACKET)) + return ER_TOO_BIG_FOR_UNCOMPRESS; *newlen= (ulong)(tmp - src) + un_len; if (contain_checksum) *newlen+= BINLOG_CHECKSUM_LEN; - - uint32 alloc_size= (uint32)ALIGN_SIZE(*newlen); + + size_t alloc_size= ALIGN_SIZE(*newlen); if (alloc_size <= buf_size) new_dst= buf; @@ -413,7 +435,7 @@ query_event_uncompress(const Format_description_log_event *description_event, { new_dst= (uchar *) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME)); if (!new_dst) - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; *is_malloc= true; } @@ -426,7 +448,7 @@ query_event_uncompress(const Format_description_log_event *description_event, *is_malloc= false; my_free(new_dst); } - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; } new_dst[EVENT_TYPE_OFFSET]= QUERY_EVENT; @@ -453,8 +475,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, uchar *new_dst= NULL; const uchar *end= tmp + len; - if (src_len < len) - return 1; // bad event + if (unlikely(src_len < len)) + return ER_BINLOG_UNCOMPRESS_ERROR; DBUG_ASSERT(LOG_EVENT_IS_ROW_COMPRESSED(type)); @@ -469,8 +491,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, which includes length bytes */ - if (end - tmp <= 2) - return 1; // bad event + if (unlikely(end - tmp <= 2)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint16 var_header_len= uint2korr(tmp); DBUG_ASSERT(var_header_len >= 2); @@ -489,8 +511,8 @@ row_log_event_uncompress(const Format_description_log_event *description_event, (type - WRITE_ROWS_COMPRESSED_EVENT_V1 + WRITE_ROWS_EVENT_V1); } - if (end <= tmp) - return 1; //bad event + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; ulong m_width= net_field_length((uchar **)&tmp); tmp+= (m_width + 7) / 8; @@ -500,17 +522,20 @@ row_log_event_uncompress(const Format_description_log_event *description_event, tmp+= (m_width + 7) / 8; } - if (end <= tmp) - return 1; //bad event + if (unlikely(end <= tmp)) + return ER_BINLOG_UNCOMPRESS_ERROR; uint32 un_len= binlog_get_uncompress_len(tmp); - if (un_len == 0) - return 1; //bad event + if (unlikely(un_len == 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; + + if (unlikely(un_len > MAX_MAX_ALLOWED_PACKET)) + return ER_TOO_BIG_FOR_UNCOMPRESS; int32 comp_len= (int32)(len - (tmp - src) - (contain_checksum ? BINLOG_CHECKSUM_LEN : 0)); - if (comp_len <=0) - return 1; //bad event + if (unlikely(comp_len <= 0)) + return ER_BINLOG_UNCOMPRESS_ERROR; *newlen= ulong(tmp - src) + un_len; if (contain_checksum) @@ -527,7 +552,7 @@ row_log_event_uncompress(const Format_description_log_event *description_event, { new_dst= (uchar*) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME)); if (!new_dst) - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; *is_malloc= true; } @@ -539,7 +564,7 @@ row_log_event_uncompress(const Format_description_log_event *description_event, { if (*is_malloc) my_free(new_dst); - return 1; + return ER_BINLOG_UNCOMPRESS_ERROR; } new_dst[EVENT_TYPE_OFFSET]= type; @@ -982,7 +1007,7 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, my_bool crc_check, my_bool print_errors) { - Log_event* ev; + Log_event* ev= nullptr; enum_binlog_checksum_alg alg; DBUG_ENTER("Log_event::read_log_event(char*,...)"); DBUG_ASSERT(fdle != 0); @@ -1001,15 +1026,22 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, } uint event_type= buf[EVENT_TYPE_OFFSET]; + switch (event_type) { + case FORMAT_DESCRIPTION_EVENT: + // If event is FD the descriptor is in it. + if (unlikely(get_checksum_alg(buf, event_len, &alg))) + goto exit; + break; + case START_EVENT_V3: // all following START events in the current file are without checksum - if (event_type == START_EVENT_V3) (const_cast< Format_description_log_event *>(fdle))->used_checksum_alg= BINLOG_CHECKSUM_ALG_OFF; + // fall-through + default: /* CRC verification by SQL and Show-Binlog-Events master side. The caller has to provide @fdle->checksum_alg to be the last seen FD's (A) descriptor. - If event is FD the descriptor is in it. Notice, FD of the binlog can be only in one instance and therefore Show-Binlog-Events executing master side thread needs just to know the only FD's (A) value - whereas RL can contain more. @@ -1024,11 +1056,9 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF. */ - alg= (event_type != FORMAT_DESCRIPTION_EVENT) ? - fdle->used_checksum_alg : get_checksum_alg(buf, event_len); + alg= fdle->used_checksum_alg; // Emulate the corruption during reading an event DBUG_EXECUTE_IF("corrupt_read_log_event_char", - if (event_type != FORMAT_DESCRIPTION_EVENT) { uchar *debug_event_buf_c= const_cast(buf); int debug_cor_pos= rand() % (event_len - BINLOG_CHECKSUM_LEN); @@ -1037,23 +1067,23 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, DBUG_SET("-d,corrupt_read_log_event_char"); } ); + } if (crc_check && event_checksum_test(const_cast(buf), event_len, alg)) { #ifdef MYSQL_CLIENT - *error= "Event crc check failed! Most likely there is event corruption."; if (force_opt) { + event_len-= BINLOG_CHECKSUM_LEN; ev= new Unknown_log_event(buf, fdle); - DBUG_RETURN(ev); + goto exit; } - else - DBUG_RETURN(NULL); + *error= "Event crc check failed! Most likely there is event corruption."; #else *error= ER_THD_OR_DEFAULT(current_thd, ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE); if (print_errors) sql_print_error("%s", *error); - DBUG_RETURN(NULL); #endif + DBUG_RETURN(NULL); } if (event_type > fdle->number_of_event_types && @@ -1244,15 +1274,6 @@ Log_event* Log_event::read_log_event(const uchar *buf, uint event_len, } exit: - if (ev) - { -#ifdef MYSQL_CLIENT - ev->read_checksum_alg= alg; - if (alg != BINLOG_CHECKSUM_ALG_OFF && alg != BINLOG_CHECKSUM_ALG_UNDEF) - ev->read_checksum_value= uint4korr(buf + (event_len)); -#endif - } - DBUG_PRINT("read_event", ("%s(type_code: %u; event_len: %u)", ev ? ev->get_type_str() : "", (uchar)buf[EVENT_TYPE_OFFSET], @@ -1287,6 +1308,15 @@ exit: DBUG_RETURN(0); #endif } + + if (ev) + { +#ifdef MYSQL_CLIENT + ev->read_checksum_alg= alg; + if (alg != BINLOG_CHECKSUM_ALG_OFF && alg != BINLOG_CHECKSUM_ALG_UNDEF) + ev->read_checksum_value= uint4korr(buf + (event_len)); +#endif + } DBUG_RETURN(ev); } @@ -1772,7 +1802,7 @@ Query_compressed_log_event::Query_compressed_log_event(const uchar *buf, if (query) { uint32 un_len= binlog_get_uncompress_len((uchar*) query); - if (!un_len) + if (unlikely(!un_len || un_len > MAX_MAX_ALLOWED_PACKET)) { query= 0; return; @@ -2156,7 +2186,8 @@ Format_description_log_event(const uchar *buf, uint event_len, { DBUG_ENTER("Format_description_log_event::Format_description_log_event(char*,...)"); used_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; - if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET) + if (unlikely( + event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET)) { server_version[0]= 0; DBUG_VOID_RETURN; @@ -2169,32 +2200,36 @@ Format_description_log_event(const uchar *buf, uint event_len, created= uint4korr(buf+ST_CREATED_OFFSET); dont_set_created= 1; - if (server_version[0] == 0) + if (unlikely(server_version[0] == 0)) DBUG_VOID_RETURN; /* sanity check */ - if ((common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < LOG_EVENT_MINIMAL_HEADER_LEN) + if (unlikely( + (common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < LOG_EVENT_MINIMAL_HEADER_LEN)) DBUG_VOID_RETURN; /* sanity check */ number_of_event_types= - event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET + 1); + event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET); DBUG_PRINT("info", ("common_header_len=%d number_of_event_types=%d", common_header_len, number_of_event_types)); /* If alloc fails, we'll detect it in is_valid() */ - post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, - buf+ST_COMMON_HEADER_LEN_OFFSET+1, - number_of_event_types* - sizeof(*post_header_len), - MYF(0)); calc_server_version_split(); + buf+= ST_POST_HEADER_LEN_OFFSET; if (!is_version_before_checksum(&server_version_split)) { - /* the last bytes are the checksum alg desc and value (or value's room) */ + /* the last bytes are the checksum alg desc */ + if (unlikely(number_of_event_types < BINLOG_CHECKSUM_ALG_DESC_LEN)) + DBUG_VOID_RETURN; /* sanity check: But there are no last bytes. */ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN; - used_checksum_alg= (enum_binlog_checksum_alg)post_header_len[number_of_event_types]; + used_checksum_alg= (enum_binlog_checksum_alg)buf[number_of_event_types]; } else { used_checksum_alg= BINLOG_CHECKSUM_ALG_OFF; } + post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, + buf, + number_of_event_types* + sizeof(*post_header_len), + MYF(0)); deduct_options_written_to_bin_log(); reset_crypto(); @@ -2308,35 +2343,50 @@ Format_description_log_event::is_version_before_checksum(const master_version_sp } /** - @param buf buffer holding serialized FD event - @param len netto (possible checksum is stripped off) length of the event buf - - @return the version-safe checksum alg descriptor where zero + @param buf buffer holding serialized FD event including the 4-byte checksum + @param len length of the event buf + @param alg output the version-safe checksum alg descriptor where zero designates no checksum, 255 - the orginator is checksum-unaware (effectively no checksum) and the actuall [1-254] range alg descriptor. + @return whether this is an invalid FD event */ -enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, ulong len) +bool get_checksum_alg(const uchar *buf, ulong len, + enum_binlog_checksum_alg *alg) { - enum_binlog_checksum_alg ret; + constexpr ptrdiff_t POST_HEADER_LEN_OFFSET= + LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET; char version[ST_SERVER_VER_LEN]; DBUG_ENTER("get_checksum_alg"); DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); + if (unlikely(len < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); memcpy(version, buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET, ST_SERVER_VER_LEN); version[ST_SERVER_VER_LEN - 1]= 0; Format_description_log_event::master_version_split version_split(version); - ret= Format_description_log_event::is_version_before_checksum(&version_split) - ? BINLOG_CHECKSUM_ALG_UNDEF - : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN]; - DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF || - ret == BINLOG_CHECKSUM_ALG_UNDEF || - ret == BINLOG_CHECKSUM_ALG_CRC32); - DBUG_RETURN(ret); + if (Format_description_log_event::is_version_before_checksum(&version_split)) + *alg= BINLOG_CHECKSUM_ALG_UNDEF; + else + { + /* + len >= POST_HEADER_LEN_OFFSET > + BINLOG_CHECKSUM_LEN + BINLOG_CHECKSUM_ALG_DESC_LEN + */ + ulong checksum_alg_offset= + len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN; + if (unlikely(checksum_alg_offset < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); + *alg= static_cast(buf[checksum_alg_offset]); + } + DBUG_ASSERT(*alg == BINLOG_CHECKSUM_ALG_OFF || + *alg == BINLOG_CHECKSUM_ALG_UNDEF || + *alg == BINLOG_CHECKSUM_ALG_CRC32); + DBUG_RETURN(false); } Start_encryption_log_event:: @@ -3120,9 +3170,17 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len, case RW_V_EXTRAINFO_TAG: { /* Have an 'extra info' section, read it in */ - assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES); + if (unlikely((end - pos) <= EXTRA_ROW_INFO_LEN_OFFSET)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET]; - assert((end - pos) >= infoLen); + if (unlikely(infoLen < EXTRA_ROW_INFO_HDR_BYTES || (end-pos) < infoLen)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } /* Just store/use the first tag of this type, skip others */ if (likely(!m_extra_row_data)) { @@ -3235,8 +3293,12 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len, void Rows_log_event::uncompress_buf() { uint32 un_len= binlog_get_uncompress_len(m_rows_buf); - if (!un_len) + if (unlikely(!un_len || un_len > MAX_MAX_ALLOWED_PACKET)) + { + /* my_bitmap_free() nulls m_cols.bitmap, which is_valid() rejects. */ + my_bitmap_free(&m_cols); return; + } uchar *new_buf= (uchar*) my_malloc(PSI_INSTRUMENT_ME, ALIGN_SIZE(un_len), MYF(MY_WME)); @@ -3259,7 +3321,7 @@ void Rows_log_event::uncompress_buf() my_free(new_buf); } } - m_cols.bitmap= 0; // catch it in is_valid + my_bitmap_free(&m_cols); // catch it in is_valid } Rows_log_event::~Rows_log_event() @@ -3462,6 +3524,14 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, VALIDATE_BYTES_READ(ptr_tbllen, buf, event_len); m_tbllen= *(uchar*) ptr_tbllen; + /* + If either the database or table name is too long, return before allocating + memory for the event, so future validity checks on this event (i.e. + is_valid()) return false. + */ + if (unlikely(m_dblen > NAME_LEN || m_tbllen > NAME_LEN)) + DBUG_VOID_RETURN; + /* Length of table name + counter + terminating null */ uchar const *const ptr_colcnt= ptr_tbllen + m_tbllen + 2; uchar *ptr_after_colcnt= (uchar*) ptr_colcnt; @@ -3487,6 +3557,13 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, /* Copy the different parts into their memory */ strncpy(const_cast(m_dbnam), (const char*)ptr_dblen + 1, m_dblen + 1); strncpy(const_cast(m_tblnam), (const char*)ptr_tbllen + 1, m_tbllen + 1); + /* + Future uses of the database and table name require a null-terminating + string, but the source string from the above copy may not have one, and + strncpy will not add one. So always add it to ensure it exists. + */ + const_cast(m_dbnam)[m_dblen]= '\0'; + const_cast(m_tblnam)[m_tbllen]= '\0'; if (unlikely(ptr_after_colcnt + m_colcnt > buf + event_len)) { my_free(m_memory); @@ -3500,6 +3577,22 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len, m_field_metadata_size= net_field_length(&ptr_after_colcnt); DBUG_EXECUTE_IF("corrupt_table_map_field_metadata_size_read", m_field_metadata_size= (1 << 20);); + + /* + Ensure the field metadata block covers the metadata the declared + column types consume. + */ + size_t metadata_needed= 0; + for (ulong i= 0; i < m_colcnt; i++) + metadata_needed+= table_def::field_metadata_length(m_coltype[i]); + if (unlikely(m_field_metadata_size < metadata_needed)) + { + m_coltype= NULL; + my_free(m_memory); + m_memory= NULL; + DBUG_VOID_RETURN; + } + if (m_field_metadata_size <= (m_colcnt * 2)) { uint num_null_bytes= (m_colcnt + 7) / 8; diff --git a/sql/log_event.h b/sql/log_event.h index b2bfbf1764d..7c40f5db717 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -204,7 +204,7 @@ class String; #define STOP_HEADER_LEN 0 #define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4) #define SLAVE_HEADER_LEN 0 -#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4) +#define START_V3_HEADER_LEN ST_COMMON_HEADER_LEN_OFFSET #define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-header is frozen) #define INTVAR_HEADER_LEN 0 #define CREATE_FILE_HEADER_LEN 4 @@ -285,6 +285,7 @@ class String; #define ST_SERVER_VER_OFFSET 2 #define ST_CREATED_OFFSET (ST_SERVER_VER_OFFSET + ST_SERVER_VER_LEN) #define ST_COMMON_HEADER_LEN_OFFSET (ST_CREATED_OFFSET + 4) +#define ST_POST_HEADER_LEN_OFFSET (ST_COMMON_HEADER_LEN_OFFSET + 1) /* slave event post-header (this event is never written) */ @@ -5507,7 +5508,8 @@ bool slave_execute_deferred_events(THD *thd); bool event_that_should_be_ignored(const uchar *buf); bool event_checksum_test(uchar *buf, ulong event_len, enum_binlog_checksum_alg alg); -enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, ulong len); +bool get_checksum_alg(const uchar *buf, ulong len, + enum_binlog_checksum_alg *alg); extern TYPELIB binlog_checksum_typelib; #ifdef WITH_WSREP enum Log_event_type wsrep_peak_event(rpl_group_info *rgi, ulonglong* event_size); diff --git a/sql/log_event_client.cc b/sql/log_event_client.cc index 9a42a3814e8..513344db3f3 100644 --- a/sql/log_event_client.cc +++ b/sql/log_event_client.cc @@ -2522,13 +2522,18 @@ bool User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) { char str_buf[200]; int str_len= sizeof(str_buf) - 1; - int precision= (int)val[0]; - int scale= (int)val[1]; + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; decimal_digit_t dec_buf[10]; decimal_t dec; dec.len= 10; dec.buf= dec_buf; + /* Verify decimal metadata as well as the actual length */ + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) + goto err; + bin2decimal((uchar*) val+2, &dec, precision, scale); decimal2string(&dec, str_buf, &str_len, 0, 0, 0); str_buf[str_len]= 0; diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index f74720ac04a..86068230117 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2417,9 +2417,14 @@ bool Format_description_log_event::write(Log_event_writer *writer) We don't call Start_log_event_v::write() because this would make 2 my_b_safe_write(). */ - uchar buff[START_V3_HEADER_LEN+1]; - size_t rec_size= sizeof(buff) + BINLOG_CHECKSUM_ALG_DESC_LEN + - number_of_event_types; + uchar buff[ST_POST_HEADER_LEN_OFFSET]; + const size_t buff_size= DBUG_IF("truncate_fde_common_header_len") ? + ST_COMMON_HEADER_LEN_OFFSET : sizeof(buff); + size_t rec_size= buff_size; + if (!DBUG_IF("truncate_fde_post_header_len")) + rec_size += number_of_event_types; + if (!DBUG_IF("truncate_fde_used_checksum_alg")) + rec_size += BINLOG_CHECKSUM_ALG_DESC_LEN; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN); if (!dont_set_created) @@ -2454,9 +2459,11 @@ bool Format_description_log_event::write(Log_event_writer *writer) uint orig_checksum_len= writer->checksum_len; writer->checksum_len= BINLOG_CHECKSUM_LEN; ret= write_header(writer, rec_size) || - write_data(writer, buff, sizeof(buff)) || - write_data(writer, post_header_len, number_of_event_types) || - write_data(writer, &checksum_byte, sizeof(checksum_byte)) || + write_data(writer, buff, buff_size) || + (!DBUG_IF("truncate_fde_post_header_len") && + write_data(writer, post_header_len, number_of_event_types)) || + (!DBUG_IF("truncate_fde_used_checksum_alg") && + write_data(writer, &checksum_byte, sizeof(checksum_byte))) || write_footer(writer); writer->checksum_len= orig_checksum_len; return ret; @@ -3938,6 +3945,15 @@ void User_var_log_event::pack_info(Protocol* protocol) char buf2[DECIMAL_MAX_STR_LENGTH+1]; String str(buf2, sizeof(buf2), &my_charset_bin); buf.length(0); + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; + /* Values were intentionally corrupted in User_var_log_event::write */ + DBUG_EXECUTE_IF("corrupt_user_var_decimal_precision", + precision = 2; scale= 1;); + + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) + return; my_decimal((const uchar *) (val + 2), val[0], val[1]).to_string(&str); if (user_var_append_name_part(protocol->thd, &buf, name, name_len, m_data_type_name) || @@ -4033,6 +4049,9 @@ bool User_var_log_event::write(Log_event_writer *writer) buf2[1]= (char)dec->frac; decimal2bin((decimal_t*)val, buf2+2, buf2[0], buf2[1]); val_len= decimal_bin_size(buf2[0], buf2[1]) + 2; + /* Leave val_len honest and lie about the metadata. */ + DBUG_EXECUTE_IF("corrupt_user_var_decimal_precision", + buf2[0]= 65; buf2[1]= 0;); break; } case STRING_RESULT: @@ -4120,7 +4139,7 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } float8get(real_val, val); it= new (thd->mem_root) Item_float(thd, real_val, 0); @@ -4133,7 +4152,7 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } int_val= (longlong) uint8korr(val); it= new (thd->mem_root) Item_int(thd, int_val); @@ -4142,12 +4161,15 @@ int User_var_log_event::do_apply_event(rpl_group_info *rgi) break; case DECIMAL_RESULT: { - if (val_len < 3) + decimal_digits_t precision= (uchar)val[0]; + decimal_digits_t scale= (uchar)val[1]; + if (precision == 0 || scale > precision || + val_len < decimal_bin_size(precision, scale) + 2) { rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); - return 1; + DBUG_RETURN(1); } Item_decimal *dec= new (thd->mem_root) Item_decimal(thd, (uchar*) val+2, val[0], val[1]); it= dec; @@ -8120,20 +8142,34 @@ void Ignorable_log_event::pack_info(Protocol *protocol) #if defined(HAVE_REPLICATION) Heartbeat_log_event::Heartbeat_log_event(const uchar *buf, uint event_len, const Format_description_log_event* description_event) - :Log_event(buf, description_event) + :Log_event(buf, description_event), ident_len(0), log_ident(NULL) { - uint8 header_size= description_event->common_header_len; + uint sub_header_len= (log_pos == 0) ? HB_SUB_HEADER_LEN : 0; + uint all_headers_len= description_event->common_header_len + sub_header_len; + + /* + The comparison is <= rather than <, so an event whose length stops exactly + at the headers is rejected along with a shorter one. + + * A shorter event must be rejected out of necessity. ident_len is + unsigned, so the subtraction below would wrap and the caller would + read the log file name from far past the event. + * An event of exactly the header length must be rejected as policy. It + carries no log file name, and a heartbeat that names no binary log + file tells the replica nothing it can compare its own coordinates + against. + + Leaving log_ident at NULL is what reports both to the caller, through + is_valid(). + */ + if (event_len <= all_headers_len) + return; + if (log_pos == 0) - { - log_pos= uint8korr(buf + header_size); - log_ident= buf + header_size + HB_SUB_HEADER_LEN; - ident_len= event_len - (header_size + HB_SUB_HEADER_LEN); - } - else - { - log_ident= buf + header_size; - ident_len = event_len - header_size; - } + log_pos= uint8korr(buf + description_event->common_header_len); + + log_ident= buf + all_headers_len; + ident_len= event_len - all_headers_len; } #endif diff --git a/sql/mysqld.cc b/sql/mysqld.cc index fe62382b14e..f662270e8dc 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2023, MariaDB + Copyright (c) 2008, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ #include "optimizer_defaults.h" #include +#include #include #include #include "my_cpu.h" @@ -308,7 +309,7 @@ const char *my_localhost= "localhost", *delayed_user= "delayed", *slave_user= "", *wsrep_user= ""; -bool opt_large_files= sizeof(my_off_t) > 4; +READ_ONLY_SYSVAR my_bool opt_large_files; static my_bool opt_autocommit; ///< for --autocommit command-line option /* Used with --help for detailed option @@ -346,7 +347,7 @@ static char *default_character_set_name; static char *character_set_filesystem_name; static char *lc_messages; static char *lc_time_names_name; -char *my_bind_addr_str; +READ_ONLY_SYSVAR char *my_bind_addr_str; static char *default_collation_name; char *default_storage_engine, *default_tmp_storage_engine; char *enforced_storage_engine=NULL; @@ -365,7 +366,9 @@ char server_uid[SERVER_UID_SIZE+1]; // server uid will be written here /* Global variables */ -bool opt_bin_log, opt_bin_log_used=0, opt_ignore_builtin_innodb= 0; +READ_ONLY_SYSVAR my_bool opt_bin_log; +bool opt_bin_log_used=0; +READ_ONLY_SYSVAR my_bool opt_ignore_builtin_innodb; bool opt_bin_log_compress; uint opt_bin_log_compress_min_len; my_bool opt_log, debug_assert_if_crashed_table= 0, opt_help= 0; @@ -375,11 +378,12 @@ static my_bool opt_abort; ulonglong log_output_options; my_bool opt_userstat_running; bool opt_error_log= IF_WIN(1,0); -bool opt_disable_networking=0, opt_skip_show_db=0; -bool opt_skip_name_resolve=0; +READ_ONLY_SYSVAR my_bool opt_disable_networking; +READ_ONLY_SYSVAR my_bool opt_skip_show_db; +READ_ONLY_SYSVAR my_bool opt_skip_name_resolve; my_bool opt_character_set_client_handshake= 1; bool opt_endinfo, using_udf_functions; -my_bool locked_in_memory; +READ_ONLY_SYSVAR my_bool locked_in_memory; bool opt_using_transactions; bool volatile abort_loop; uint volatile global_disable_checkpoint; @@ -389,6 +393,64 @@ ulong slow_start_timeout; static MEM_ROOT startup_root; MEM_ROOT read_only_root; +#if defined(HAVE_RO_AFTER_INIT) && !defined(EMBEDDED_LIBRARY) +// start and end of the ro_after_init section, to test if a variable is in it +extern char ro_after_init_start[] __attribute__((weak)); +extern char ro_after_init_end[] __attribute__((weak)); + +#elif defined(_MSC_VER) && !defined(EMBEDDED_LIBRARY) + +/* + $a/$z bracket the ro_after_init$m section (see READ_ONLY_SYSVAR in + my_global.h): MSVC's linker merges and alphabetically sorts sections by + their full "name$suffix" across every object file being linked +*/ +#pragma section("ro_after_init$a", read, write) +#pragma section("ro_after_init$z", read, write) +__declspec(allocate("ro_after_init$a")) __declspec(align(4096)) +static char ro_after_init_start_marker; +__declspec(allocate("ro_after_init$z")) __declspec(align(4096)) +static char ro_after_init_end_marker; +static char * const ro_after_init_start= &ro_after_init_start_marker; +static char * const ro_after_init_end= &ro_after_init_end_marker; + +#else + +static constexpr char *ro_after_init_start= 0; +static constexpr char *ro_after_init_end= 0; + +#endif + +/* set protection of the __ro_after_init section */ +static void set_ro_after_init_prot(enum my_vmem_prot prot) +{ + if (size_t size= ro_after_init_end - ro_after_init_start) + my_virtual_mem_protect(ro_after_init_start, size, prot); +} + +/* used for asserts, so returns TRUE if mprotect is impossible */ +bool var_is_ro_after_init(const char *addr) +{ + return ! ro_after_init_start || + (addr >= ro_after_init_start && addr < ro_after_init_end); +} + +/* + FLUSH PRIVILEGES leaves --skip-grant-tables mode by clearing opt_noacl. + But opt_noacl is READ_ONLY_SYSVAR, so briefly remove the protection for this + single write. It's done at most once and only when the server was started + with --skip-grant-tables. +*/ +void clear_opt_noacl() +{ + if (opt_noacl) + { + set_ro_after_init_prot(MY_VMEM_READWRITE); + opt_noacl= 0; + set_ro_after_init_prot(MY_VMEM_READONLY); + } +} + /** @brief 'grant_option' is used to indicate if privileges needs to be checked, in which case the lock, LOCK_grant, is used @@ -400,15 +462,15 @@ bool volatile grant_option; my_bool opt_skip_slave_start = 0; ///< If set, slave is not autostarted my_bool opt_reckless_slave = 0; -my_bool opt_enable_named_pipe= 0; +READ_ONLY_SYSVAR my_bool opt_enable_named_pipe= 0; my_bool opt_local_infile, opt_slave_compressed_protocol; my_bool opt_safe_user_create = 0; my_bool opt_show_slave_auth_info; -my_bool opt_log_slave_updates= 0; -my_bool opt_replicate_annotate_row_events= 0; +READ_ONLY_SYSVAR my_bool opt_log_slave_updates; +READ_ONLY_SYSVAR my_bool opt_replicate_annotate_row_events; my_bool opt_mysql56_temporal_format=0, strict_password_validation= 1; -char *opt_slave_skip_errors; -char *opt_slave_transaction_retry_errors; +READ_ONLY_SYSVAR char *opt_slave_skip_errors; +READ_ONLY_SYSVAR char *opt_slave_transaction_retry_errors; /* Legacy global handlerton. These will be removed (please do not add more). @@ -420,17 +482,18 @@ handlerton *partition_hton; my_bool read_only= 0, opt_readonly= 0; my_bool use_temp_pool, relay_log_purge; my_bool relay_log_recovery; -my_bool opt_sync_frm, opt_allow_suspicious_udfs; +my_bool opt_sync_frm; +READ_ONLY_SYSVAR my_bool opt_allow_suspicious_udfs; my_bool opt_secure_auth= 0; my_bool opt_require_secure_transport= 0; -char* opt_secure_file_priv; -my_bool lower_case_file_system= 0; -my_bool opt_large_pages= 0; +READ_ONLY_SYSVAR char* opt_secure_file_priv; +READ_ONLY_SYSVAR my_bool lower_case_file_system; +READ_ONLY_SYSVAR my_bool opt_large_pages; #ifdef HAVE_SOLARIS_LARGE_PAGES my_bool opt_super_large_pages= 0; #endif my_bool opt_myisam_use_mmap= 0; -uint opt_large_page_size= 0; +READ_ONLY_SYSVAR uint opt_large_page_size; #if defined(ENABLED_DEBUG_SYNC) MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout= 0; #endif /* defined(ENABLED_DEBUG_SYNC) */ @@ -443,10 +506,10 @@ ulong opt_replicate_events_marked_for_skip; changed). False otherwise. */ volatile bool mqh_used = 0; -my_bool opt_noacl; +READ_ONLY_SYSVAR my_bool opt_noacl; my_bool sp_automatic_privileges= 1; -ulong opt_binlog_rows_event_max_size; +READ_ONLY_SYSVAR ulong opt_binlog_rows_event_max_size; ulong binlog_row_metadata; my_bool opt_binlog_gtid_index= TRUE; uint opt_binlog_gtid_index_page_size= 4096; @@ -455,12 +518,13 @@ my_bool opt_master_verify_checksum= 0; my_bool opt_slave_sql_verify_checksum= 1; const char *binlog_format_names[]= {"MIXED", "STATEMENT", "ROW", NullS}; volatile sig_atomic_t calling_initgroups= 0; /**< Used in SIGSEGV handler. */ -uint mysqld_port, select_errors, dropping_tables, ha_open_options; -uint mysqld_extra_port; +READ_ONLY_SYSVAR uint mysqld_port; +uint select_errors, dropping_tables, ha_open_options; +READ_ONLY_SYSVAR uint mysqld_extra_port; uint mysqld_port_timeout; ulong delay_key_write_options; -uint protocol_version; -uint lower_case_table_names; +READ_ONLY_SYSVAR uint protocol_version; +READ_ONLY_SYSVAR uint lower_case_table_names; ulong tc_heuristic_recover= 0; Atomic_counter THD_count::count, CONNECT::count; bool shutdown_wait_for_slaves; @@ -471,10 +535,12 @@ Atomic_counter slave_open_temp_tables; */ Atomic_counter sending_new_binlog_file; ulong thread_created; -ulong back_log, connect_timeout, server_id; +READ_ONLY_SYSVAR ulong back_log; +ulong connect_timeout, server_id; ulong what_to_log; ulong slow_launch_time; -ulong open_files_limit, max_binlog_size; +READ_ONLY_SYSVAR ulong open_files_limit; +ulong max_binlog_size; ulong slave_trans_retries; ulong slave_trans_retry_interval; uint slave_net_timeout; @@ -494,7 +560,7 @@ double slave_max_statement_time_double; ulonglong slave_max_statement_time; ulonglong binlog_stmt_cache_size=0; ulonglong max_binlog_stmt_cache_size=0; -ulonglong test_flags; +READ_ONLY_SYSVAR ulonglong test_flags; ulonglong query_cache_size=0; ulong query_cache_limit=0; ulong executed_events=0; @@ -511,7 +577,7 @@ ulong binlog_gtid_index_hit= 0, binlog_gtid_index_miss= 0; ulong max_connections, max_connect_errors; uint max_password_errors; ulong extra_max_connections; -uint max_digest_length= 0; +READ_ONLY_SYSVAR uint max_digest_length; ulong slave_retried_transactions; ulong transactions_multi_engine; ulong rpl_transactions_multi_engine; @@ -520,7 +586,7 @@ ulonglong slave_skipped_errors; ulong feature_files_opened_with_delayed_keys= 0, feature_check_constraint= 0; ulonglong denied_connections; my_decimal decimal_zero; -long opt_secure_timestamp; +READ_ONLY_SYSVAR long opt_secure_timestamp; uint default_password_lifetime; my_bool disconnect_on_expired_password; @@ -602,14 +668,20 @@ const double log_10[] = { time_t server_start_time, flush_status_time; -char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30]; +READ_ONLY_SYSVAR char mysql_home[FN_REFLEN]; +READ_ONLY_SYSVAR char pidfile_name[FN_REFLEN]; +READ_ONLY_SYSVAR char system_time_zone[30]; char *default_tz_name; -char log_error_file[FN_REFLEN], glob_hostname[FN_REFLEN], *opt_log_basename; -char mysql_real_data_home[FN_REFLEN], - lc_messages_dir[FN_REFLEN], reg_ext[FN_EXTLEN], - mysql_charsets_dir[FN_REFLEN], - *opt_init_file, *opt_tc_log_file, *opt_ddl_recovery_file; -char *lc_messages_dir_ptr= lc_messages_dir, *log_error_file_ptr; +READ_ONLY_SYSVAR char log_error_file[FN_REFLEN]; +READ_ONLY_SYSVAR char glob_hostname[FN_REFLEN]; +char *opt_log_basename; +READ_ONLY_SYSVAR char mysql_real_data_home[FN_REFLEN]; +READ_ONLY_SYSVAR char lc_messages_dir[FN_REFLEN]; +char reg_ext[FN_EXTLEN], mysql_charsets_dir[FN_REFLEN]; +char *opt_tc_log_file, *opt_ddl_recovery_file; +READ_ONLY_SYSVAR char *opt_init_file; +READ_ONLY_SYSVAR char *lc_messages_dir_ptr= lc_messages_dir; +READ_ONLY_SYSVAR char *log_error_file_ptr; char mysql_unpacked_real_data_home[FN_REFLEN]; size_t mysql_unpacked_real_data_home_len; uint mysql_real_data_home_len, mysql_data_home_len= 1; @@ -619,16 +691,18 @@ key_map key_map_full(0); // Will be initialized later Time_zone *default_tz; -const char *mysql_real_data_home_ptr= mysql_real_data_home; +READ_ONLY_SYSVAR const char *mysql_real_data_home_ptr= mysql_real_data_home; extern "C" { -char server_version[SERVER_VERSION_LENGTH]; +READ_ONLY_SYSVAR char server_version[SERVER_VERSION_LENGTH]; } -char *server_version_ptr; -char *mysqld_unix_port, *opt_mysql_tmpdir; -ulong thread_handling; +READ_ONLY_SYSVAR char *server_version_ptr; +READ_ONLY_SYSVAR char *mysqld_unix_port; +READ_ONLY_SYSVAR char *opt_mysql_tmpdir; +READ_ONLY_SYSVAR ulong thread_handling; -my_bool encrypt_binlog; -my_bool encrypt_tmp_disk_tables, encrypt_tmp_files; +READ_ONLY_SYSVAR my_bool encrypt_binlog; +my_bool encrypt_tmp_disk_tables; +READ_ONLY_SYSVAR my_bool encrypt_tmp_files; /** name of reference on left expression in rewritten IN subquery */ const LEX_CSTRING in_left_expr_name= {STRING_WITH_LEN("") }; @@ -693,19 +767,24 @@ uint temp_pool_set_next() return res; } -CHARSET_INFO *system_charset_info, *files_charset_info ; +READ_ONLY_SYSVAR CHARSET_INFO *system_charset_info; CHARSET_INFO *national_charset_info, *table_alias_charset; -CHARSET_INFO *character_set_filesystem; +CHARSET_INFO *character_set_filesystem, *files_charset_info; CHARSET_INFO *error_message_charset_info; MY_LOCALE *my_default_lc_messages; MY_LOCALE *my_default_lc_time_names; -SHOW_COMP_OPTION have_ssl, have_symlink, have_dlopen, have_query_cache; -SHOW_COMP_OPTION have_geometry, have_rtree_keys; -SHOW_COMP_OPTION have_crypt, have_compress; -SHOW_COMP_OPTION have_profiling; -SHOW_COMP_OPTION have_openssl; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_ssl; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_symlink; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_dlopen; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_query_cache; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_geometry; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_rtree_keys; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_crypt; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_compress; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_profiling; +READ_ONLY_SYSVAR SHOW_COMP_OPTION have_openssl; #ifndef EMBEDDED_LIBRARY static std::atomic shutdown_user; @@ -776,11 +855,15 @@ int mysqld_server_started=0, mysqld_server_initialized= 0; File_parser_dummy_hook file_parser_dummy_hook; /* replication parameters, if master_host is not NULL, we are a slave */ -uint report_port= 0; +READ_ONLY_SYSVAR uint report_port= 0; ulong master_retry_count=0; char *master_info_file; -char *relay_log_info_file, *report_user, *report_password, *report_host; -char *opt_relay_logname = 0, *opt_relaylog_index_name=0; +READ_ONLY_SYSVAR char *relay_log_info_file; +READ_ONLY_SYSVAR char *report_user; +READ_ONLY_SYSVAR char *report_password; +READ_ONLY_SYSVAR char *report_host; +READ_ONLY_SYSVAR char *opt_relay_logname= 0; +char *opt_relaylog_index_name=0; char *opt_logname, *opt_slow_logname, *opt_bin_logname; char *opt_binlog_index_name=0; my_bool opt_binlog_legacy_event_pos= FALSE; @@ -798,9 +881,12 @@ my_bool opt_expect_abort= 0, opt_bootstrap= 0; static my_bool opt_myisam_log; static int cleanup_done; static ulong opt_specialflag; -char *mysql_home_ptr, *pidfile_name_ptr; +READ_ONLY_SYSVAR char *mysql_home_ptr; +READ_ONLY_SYSVAR char *pidfile_name_ptr; +#ifdef EMBEDDED_LIBRARY /** Initial command line arguments (count), after load_defaults().*/ static int defaults_argc; +#endif /** Initial command line arguments (arguments), after load_defaults(). This memory is allocated by @c load_defaults() and should be freed @@ -1472,7 +1558,6 @@ static void charset_error_reporter(enum loglevel level, C_MODE_END struct passwd *user_info; -static pthread_t select_thread; #endif /* OS specific variables */ @@ -1509,11 +1594,16 @@ Query_cache query_cache; #endif -my_bool opt_use_ssl = 1; -char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL, - *opt_ssl_cipher= NULL, *opt_ssl_key= NULL, *opt_ssl_crl= NULL, - *opt_ssl_crlpath= NULL, *opt_tls_version= NULL; -ulonglong tls_version= 0; +my_bool opt_use_ssl= 1; +READ_ONLY_SYSVAR char *opt_ssl_ca= NULL; +READ_ONLY_SYSVAR char *opt_ssl_capath= NULL; +READ_ONLY_SYSVAR char *opt_ssl_cert= NULL; +READ_ONLY_SYSVAR char *opt_ssl_cipher= NULL; +READ_ONLY_SYSVAR char *opt_ssl_key= NULL; +READ_ONLY_SYSVAR char *opt_ssl_crl= NULL; +READ_ONLY_SYSVAR char *opt_ssl_crlpath= NULL; +READ_ONLY_SYSVAR ulonglong tls_version= 0; +char *opt_tls_version= NULL; static scheduler_functions thread_scheduler_struct, extra_thread_scheduler_struct; scheduler_functions *thread_scheduler= &thread_scheduler_struct, @@ -1975,6 +2065,8 @@ static void clean_up(bool print_message, bool use_dummy_thd) if (cleanup_done++) return; /* purecov: inspected */ + set_ro_after_init_prot(MY_VMEM_READWRITE); // to allow cleanup + #ifdef HAVE_REPLICATION // We must call end_slave() as clean_up may have been called during startup end_slave(); @@ -2063,7 +2155,7 @@ static void clean_up(bool print_message, bool use_dummy_thd) mysql_library_end(); finish_client_errs(); free_root(&startup_root, MYF(0)); - protect_root(&read_only_root, PROT_READ | PROT_WRITE); + protect_root(&read_only_root, MY_VMEM_READWRITE); free_root(&read_only_root, MYF(0)); cleanup_errmsgs(); free_error_messages(); @@ -2071,13 +2163,6 @@ static void clean_up(bool print_message, bool use_dummy_thd) logger.cleanup_end(); sys_var_end(); free_charsets(); - - my_free(const_cast(log_bin_basename)); - my_free(const_cast(log_bin_index)); -#ifndef EMBEDDED_LIBRARY - my_free(const_cast(relay_log_basename)); - my_free(const_cast(relay_log_index)); -#endif free_list(opt_plugin_load_list_ptr); destroy_proxy_protocol_networks(); @@ -3898,7 +3983,7 @@ static int init_early_variables() global_status_var.global_memory_used= 0; init_alloc_root(PSI_NOT_INSTRUMENTED, &startup_root, 1024, 0, MYF(0)); init_alloc_root(PSI_NOT_INSTRUMENTED, &read_only_root, 1024, 0, - MYF(MY_ROOT_USE_MPROTECT)); + MYF(MY_ROOT_USE_VMEM)); return 0; } @@ -5705,7 +5790,6 @@ static void test_lc_time_sz() static void run_main_loop() { - select_thread=pthread_self(); mysql_mutex_lock(&LOCK_start_thread); select_thread_in_use=1; mysql_mutex_unlock(&LOCK_start_thread); @@ -5755,7 +5839,9 @@ int mysqld_main(int argc, char **argv) orig_argv= argv; my_defaults_mark_files= TRUE; load_defaults_or_exit(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv); +#ifdef EMBEDDED_LIBRARY defaults_argc= argc; +#endif defaults_argv= argv; remaining_argc= argc; remaining_argv= argv; @@ -6058,7 +6144,11 @@ int mysqld_main(int argc, char **argv) #endif /* WITH_WSREP */ /* Protect read_only_root against writes */ - protect_root(&read_only_root, PROT_READ); + move_allocated_sysvars_to_root(&read_only_root); + protect_root(&read_only_root, MY_VMEM_READONLY); + + /* Protect read-only sysvars */ + set_ro_after_init_prot(MY_VMEM_READONLY); if (opt_bootstrap) { @@ -8512,7 +8602,6 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, } break; case OPT_IGNORE_DB_DIRECTORY: - opt_ignore_db_dirs= NULL; // will be set in ignore_db_dirs_process_additions if (*argument == 0) ignore_db_dirs_reset(); else @@ -9111,45 +9200,39 @@ fn_format_relative_to_data_home(char * to, const char *name, bool is_secure_file_path(char *path) { - char buff1[FN_REFLEN], buff2[FN_REFLEN]; - size_t opt_secure_file_priv_len; - /* - All paths are secure if opt_secure_file_path is 0 - */ - if (!opt_secure_file_priv) - return TRUE; + char buf1[FN_REFLEN], buf2[FN_REFLEN]; + const char *cmp; - opt_secure_file_priv_len= strlen(opt_secure_file_priv); + if (opt_secure_file_priv) + cmp= opt_secure_file_priv; + else +#ifdef _WIN32 + return TRUE; // All paths are secure if opt_secure_file_priv is unset +#else + cmp= "/proc/"; // Check that it doesn't start with this prefix +#endif if (strlen(path) >= FN_REFLEN) return FALSE; - if (my_realpath(buff1, path, 0)) + if (my_realpath(buf1, path, 0)) { - /* - The supplied file path might have been a file and not a directory. - */ - size_t length= dirname_length(path); // Guaranteed to be < FN_REFLEN - memcpy(buff2, path, length); - buff2[length]= '\0'; - if (length == 0 || my_realpath(buff1, buff2, 0)) - return FALSE; - } - convert_dirname(buff2, buff1, NullS); - if (!lower_case_file_system) - { - if (strncmp(opt_secure_file_priv, buff2, opt_secure_file_priv_len)) + /* The supplied file path might have been a file and not a directory. */ + size_t length= dirname_length(path); // Guaranteed to be < FN_REFLEN + memcpy(buf2, path, length); + buf2[length]= '\0'; + if (length == 0 || my_realpath(buf1, buf2, 0)) return FALSE; } + convert_dirname(buf2, buf1, NullS); + + size_t cmp_len= strlen(cmp); + bool matched; + if (lower_case_file_system) + matched= !files_charset_info->strnncoll(buf2, strlen(buf2), cmp, cmp_len, 1); else - { - if (files_charset_info->strnncoll(buff2, strlen(buff2), - opt_secure_file_priv, - opt_secure_file_priv_len, - TRUE)) - return FALSE; - } - return TRUE; + matched= !strncmp(cmp, buf2, cmp_len); + return opt_secure_file_priv ? matched : !matched; } diff --git a/sql/mysqld.h b/sql/mysqld.h index 0b00c82e868..b37c8aa6ca8 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -1,5 +1,5 @@ /* Copyright (c) 2006, 2016, Oracle and/or its affiliates. - Copyright (c) 2010, 2021, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -80,6 +80,8 @@ void handle_connection_in_main_thread(CONNECT *thd); void create_thread_to_handle_connection(CONNECT *connect); void unlink_thd(THD *thd); void refresh_status(THD *thd); +void clear_opt_noacl(); +bool var_is_ro_after_init(const char *addr); bool is_secure_file_path(char *path); extern void init_net_server_extension(THD *thd); extern void handle_accepted_socket(MYSQL_SOCKET new_sock, MYSQL_SOCKET sock); @@ -103,8 +105,8 @@ extern CHARSET_INFO *character_set_filesystem; void temp_pool_clear_bit(uint bit); uint temp_pool_set_next(); -extern bool opt_large_files; -extern bool opt_update_log, opt_bin_log, opt_error_log, opt_bin_log_compress; +extern my_bool opt_bin_log, opt_large_files; +extern bool opt_update_log, opt_error_log, opt_bin_log_compress; extern uint opt_bin_log_compress_min_len; extern my_bool opt_log, opt_bootstrap; extern my_bool opt_backup_history_log; @@ -112,9 +114,9 @@ extern my_bool opt_backup_progress_log; extern my_bool opt_support_flashback; extern ulonglong log_output_options; extern ulong log_backup_output_options; -extern bool opt_disable_networking, opt_skip_show_db; -extern bool opt_skip_name_resolve; -extern bool opt_ignore_builtin_innodb; +extern my_bool opt_disable_networking, opt_skip_show_db; +extern my_bool opt_skip_name_resolve; +extern my_bool opt_ignore_builtin_innodb; extern my_bool opt_character_set_client_handshake; extern my_bool debug_assert_on_not_freed_memory; extern MYSQL_PLUGIN_IMPORT bool volatile abort_loop; diff --git a/sql/mysqld_ro.lds b/sql/mysqld_ro.lds new file mode 100644 index 00000000000..4698c1daa55 --- /dev/null +++ b/sql/mysqld_ro.lds @@ -0,0 +1,31 @@ +/* + Copyright (c) 2026, MariaDB plc, MariaDB Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* + Give the "ro_after_init" section a page-aligned span of its own, so that the + variables placed there (with READ_ONLY_SYSVAR tag) can be mprotect()-ed + once server initialization is complete, without affecting neighbouring data. +*/ +SECTIONS +{ + ro_after_init ALIGN(CONSTANT(MAXPAGESIZE)) : + { + ro_after_init_start = .; + *(ro_after_init) + . = ALIGN(CONSTANT(MAXPAGESIZE)); + ro_after_init_end = .; + } +} INSERT AFTER .data; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 1d7770b9361..f45317d422a 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -16178,15 +16178,51 @@ int QUICK_GROUP_MIN_MAX_SELECT::get_next() min_res= next_min(); if (min_res == 0) update_min_result(); + else + { + /* + If next_min returns an unexpected handler error (HA_ERR_...), + then that error will be propagated to the caller of this + method by returning immediately here. + */ + if (min_res != HA_ERR_END_OF_FILE && min_res != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(min_res); // Unrecoverable error. + } } /* If there is no MIN in the group, there is no MAX either. */ if ((have_max && !have_min) || (have_max && have_min && (min_res == 0))) { + /* + next_min() and next_max() are not symmetrical with respect to + how they access the underlying table. next_min() may read the + index when (key_infix_len > 0) but next_max() makes no such check. + Hence the lock wait timeout may be seen by next_max() but not by + next_min() because it reads blindly. + */ max_res= next_max(); if (max_res == 0) update_max_result(); - /* If a MIN was found, a MAX must have been found as well. */ + else + { + /* + If next_max returns an unexpected handler error (HA_ERR_...), + then that error will be propagated to the caller of this + method by returning immediately here. + */ + if (max_res != HA_ERR_END_OF_FILE && max_res != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(max_res); // Unrecoverable error. + } + + /* + If we're computing both MIN and MAX (have_min && have_max) then: + - Since execution has reached here, we have a record with value for + MIN (min_res==0). + - This means we must also have a record for MAX (max_res==0). It can + be the same record that we've found for MIN, or a different one. + We could also have encountered an unrecoverable error while reading + the MAX record but that has been handled above. + */ DBUG_ASSERT((have_max && !have_min) || (have_max && have_min && (max_res == 0))); } @@ -16200,6 +16236,15 @@ int QUICK_GROUP_MIN_MAX_SELECT::get_next() make_prev_keypart_map(real_key_parts), HA_READ_KEY_EXACT); + /* + Set the result of checking this GROUP BY group. + 1. If we're computing a MIN, take min_res. MAX was either not computed + at all or it has been computed successully. Fatal errors have + already been checked. + 2. Otherwise, if we're computing a MAX only, take max_res. + 3. Otherwise, we're just enumerating GROUP BY groups and the 'result' + variable already has the outcome of the check. + */ result= have_min ? min_res : have_max ? max_res : result; } while (result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE); @@ -16257,11 +16302,15 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_min() } /* + The reason that next_min has more complexity than next_max() is + for NULLs handling. In MariaDB, NULLs appear first in a result + and by default sort as less-than every other value. + If the min/max argument field is NULL, skip subsequent rows in the same group with NULL in it. Notice that: - - if the first row in a group doesn't have a NULL in the field, no row - in the same group has (because NULL < any other value), - - min_max_arg_part->field->ptr points to some place in 'record'. + - if the first row in a group doesn't have a NULL in the field, no row + in the same group has (because NULL < any other value), + - min_max_arg_part->field->ptr points to some place in 'record'. */ if (min_max_arg_part && min_max_arg_part->field->is_null()) { diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index c93e5421c6f..53e8bdf328a 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -629,6 +629,75 @@ void add_module_expr(Dep_analysis_context *dac, Dep_module_expr **eq_mod, /*****************************************************************************/ +/** + @brief + Clear the "eliminated" flag on every subquery reachable from the given + expression (see Item_subselect::unmark_as_eliminated_processor). +*/ + +static inline void unmark_eliminated_subqueries(Item *expr) +{ + if (expr) + expr->walk(&Item::unmark_as_eliminated_processor, FALSE, NULL); +} + + +/** + @brief + Clear the "eliminated" flag on subqueries reachable from ON expressions of + outer joins that were NOT eliminated. Such an ON expression is still + evaluated at runtime. +*/ + +static void unmark_live_on_expr_subqueries(JOIN *join, + List *join_list) +{ + TABLE_LIST *tbl; + List_iterator it(*join_list); + while ((tbl= it++)) + { + if (tbl->nested_join) + { + unmark_live_on_expr_subqueries(join, &tbl->nested_join->join_list); + /* Only sweep this nest's ON expr if some of its tables survived. */ + if (tbl->nested_join->used_tables & ~join->eliminated_tables) + unmark_eliminated_subqueries(tbl->on_expr); + } + else if (tbl->table && !(tbl->table->map & join->eliminated_tables)) + unmark_eliminated_subqueries(tbl->on_expr); + } +} + + +/** + @brief + Equality propagation (build_equal_items()) can inject into the ON + expression of an eliminated outer join, a reference to a subquery that + actually lives in another (surviving) part of the query. + mark_as_eliminated() then flags that subquery as eliminated even though + it still has to be executed. + Clear the flag on every subquery that is still reachable. +*/ + +static void unmark_still_reachable(JOIN *join) +{ + unmark_eliminated_subqueries(join->conds); + unmark_eliminated_subqueries(join->having); + + List_iterator live_it(join->fields_list); + Item *item; + while ((item= live_it++)) + unmark_eliminated_subqueries(item); + + for (ORDER *ord= join->order; ord; ord= ord->next) + unmark_eliminated_subqueries(*(ord->item)); + + for (ORDER *ord= join->group_list; ord; ord= ord->next) + unmark_eliminated_subqueries(*(ord->item)); + + unmark_live_on_expr_subqueries(join, join->join_list); +} + /* Perform table elimination @@ -768,6 +837,7 @@ void eliminate_tables(JOIN *join) /* There are some tables that we probably could eliminate. Try it. */ eliminate_tables_for_list(join, join->join_list, all_tables, NULL, used_tables, &trace_eliminated_tables); + unmark_still_reachable(join); } DBUG_VOID_RETURN; } diff --git a/sql/opt_trace.cc b/sql/opt_trace.cc index a7c3ab5886c..aae37be51ef 100644 --- a/sql/opt_trace.cc +++ b/sql/opt_trace.cc @@ -201,11 +201,8 @@ void opt_trace_disable_if_no_security_context_access(THD *thd) existing connection, per the manual. */ if (!(thd->main_security_ctx.check_access(GLOBAL_ACLS & ~GRANT_ACL)) && - (0 != strcmp(thd->main_security_ctx.priv_user, - thd->security_context()->priv_user) || - 0 != my_strcasecmp(system_charset_info, - thd->main_security_ctx.priv_host, - thd->security_context()->priv_host))) + !thd->main_security_ctx.is_priv_user(thd->security_context()->priv_user, + thd->security_context()->priv_host)) trace->missing_privilege(); } diff --git a/sql/privilege.h b/sql/privilege.h index 84efc010d1e..45ae6238765 100644 --- a/sql/privilege.h +++ b/sql/privilege.h @@ -661,11 +661,10 @@ constexpr privilege_t PRIV_STMT_DROP_SERVER= FEDERATED_ADMIN_ACL; /* Privileges related to processes */ -constexpr privilege_t PRIV_COM_PROCESS_INFO= PROCESS_ACL; -// This privilege applies both for SHOW EXPLAIN and SHOW ANALYZE -constexpr privilege_t PRIV_STMT_SHOW_EXPLAIN= PROCESS_ACL; constexpr privilege_t PRIV_STMT_SHOW_ENGINE_STATUS= PROCESS_ACL; constexpr privilege_t PRIV_STMT_SHOW_ENGINE_MUTEX= PROCESS_ACL; +// This privilege is used in thd_visible_in_processlist() and thus applies +// to SHOW PROCESSLIST, I_S.PROCESSLIST, SHOW EXPLAIN and SHOW ANALYZE constexpr privilege_t PRIV_STMT_SHOW_PROCESSLIST= PROCESS_ACL; diff --git a/sql/rpl_utility.cc b/sql/rpl_utility.cc index d3189394820..b805cdce80a 100644 --- a/sql/rpl_utility.cc +++ b/sql/rpl_utility.cc @@ -173,6 +173,34 @@ uint32 table_def::calc_field_size(uint col, uchar *master_data) const PSI_memory_key key_memory_table_def_memory; +uint table_def::field_metadata_length(uint binlog_type) +{ + switch (static_cast(binlog_type)) { + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_BLOB_COMPRESSED: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + return 1; + case MYSQL_TYPE_SET: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VARCHAR_COMPRESSED: + case MYSQL_TYPE_NEWDECIMAL: + return 2; + default: + return 0; + } +} + table_def::table_def(unsigned char *types, ulong size, uchar *field_metadata, int metadata_size, uchar *null_bitmap, uint16 flags) diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index 92a2c00e2ec..055d4e58c4c 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -87,6 +87,12 @@ public: { return static_cast(m_type[index]); } + + /* + Number of field-metadata bytes a column of the given binlog type occupies + in a Table_map event. + */ + static uint field_metadata_length(uint binlog_type); /* Return a representation of the type data for one field. diff --git a/sql/set_var.cc b/sql/set_var.cc index d0fbefbd533..e03cdec53e5 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2002, 2013, Oracle and/or its affiliates. Copyright (c) 2008, 2014, SkySQL Ab. + Copyright (c) 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -183,6 +184,10 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, option.app_type= this; option.var_type= flags & AUTO_SET ? GET_AUTO : 0; + /* only check readonly, global, not-plugin sysvars */ + SYSVAR_ASSERT(!is_readonly() || scope() != GLOBAL || offset == 0 || + var_is_ro_after_init((char*)global_var_ptr())); + if (chain->last) chain->last->next= this; else @@ -192,6 +197,35 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, test_load= &static_test_load; } +/* + move a read-only global char* value to a memroot and reset ALLOCATED flag. + used before mprotect()-ing the memroot from changes. +*/ +void sys_var::move_charptr_to_root(MEM_ROOT *root) +{ + char **ptr= (char**)global_var_ptr(); + if (!is_readonly() || scope() != GLOBAL || cast_pluginvar() || is_struct() + || show_type() != SHOW_CHAR_PTR || !*ptr || var_is_ro_after_init(*ptr)) + return; + + char *old_val= *ptr; + *ptr= strdup_root(root, old_val); + if (flags & ALLOCATED) + { + my_free(old_val); + flags&= ~ALLOCATED; + } +} + + +void move_allocated_sysvars_to_root(MEM_ROOT *root) +{ + for (uint i= 0; i < system_variable_hash.records; i++) + ((sys_var*) my_hash_element(&system_variable_hash, i)) + ->move_charptr_to_root(root); +} + + bool sys_var::update(THD *thd, set_var *var) { enum_var_type type= var->type; @@ -737,6 +771,9 @@ int sql_set_variables(THD *thd, List *var_list, bool free) error|= var->update(thd); // Returns 0, -1 or 1 } + DBUG_EXECUTE_IF("set_skip_name_resolve", opt_skip_name_resolve= 0;); + DBUG_EXECUTE_IF("set_version_buf", server_version_ptr[0]= '2';); + err: if (free) free_underlaid_joins(thd, thd->lex->first_select_lex()); diff --git a/sql/set_var.h b/sql/set_var.h index ef5dd2da15d..73c4c5b930e 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -1,7 +1,7 @@ #ifndef SET_VAR_INCLUDED #define SET_VAR_INCLUDED /* Copyright (c) 2002, 2013, Oracle and/or its affiliates. - Copyright (c) 2009, 2020, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -113,6 +113,7 @@ public: virtual sys_var_pluginvar *cast_pluginvar() { return 0; } bool check(THD *thd, set_var *var); + void move_charptr_to_root(MEM_ROOT *root); const uchar *value_ptr(THD *thd, enum_var_type type, const LEX_CSTRING *base) const; /** @@ -135,8 +136,6 @@ public: return system_charset_info; } bool is_readonly() const { return flags & READONLY; } - void update_flags(int new_flags) { flags = new_flags; } - int get_flags() const { return flags; } /** the following is only true for keycache variables, that support the syntax @@keycache_name.variable_name @@ -268,6 +267,21 @@ protected: friend class Session_tracker; }; +/* + special assert for sysvars. Tells the name of the variable, + and fails even in non-debug builds. + + It is supposed to be used *only* in Sys_var* constructors, + and has name_arg hard-coded to prevent incorrect usage. +*/ +#define SYSVAR_ASSERT(X) \ + while(!(X)) \ + { \ + fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ + DBUG_ASSERT(0); \ + exit(255); \ + } + #include "sql_plugin.h" /* SHOW_HA_ROWS, SHOW_MY_BOOL */ @@ -433,6 +447,7 @@ ulong get_system_variable_hash_records(void); ulonglong get_system_variable_hash_version(void); SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type); +void move_allocated_sysvars_to_root(MEM_ROOT *root); int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond); sys_var *find_sys_var(THD *thd, const char *str, size_t length= 0, diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc index 8f03b7306c6..873675d6ea4 100644 --- a/sql/signal_handler.cc +++ b/sql/signal_handler.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2011, 2012, Oracle and/or its affiliates. - Copyright (c) 2011, 2021, MariaDB Corporation. + Copyright (c) 2011, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -203,9 +203,11 @@ extern "C" sig_handler handle_fatal_signal(int sig) "Please include the information from the server start above, to the end of the\n" "information below.\n\n"); - set_server_version(server_version, sizeof(server_version)); + /* print the real version on crash */ + char real_version[SERVER_VERSION_LENGTH]; + set_server_version(real_version, sizeof(real_version)); my_safe_printf_stderr("Server version: %s source revision: %s\n\n", - server_version, SOURCE_REVISION); + real_version, SOURCE_REVISION); #ifdef WITH_WSREP Wsrep_server_state::handle_fatal_signal(); diff --git a/sql/slave.cc b/sql/slave.cc index fd43ae6316e..e41db54f473 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2009, 2022, MariaDB Corporation + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -81,14 +81,14 @@ uint *slave_transaction_retry_errors; uint slave_transaction_retry_error_length= 0; char slave_transaction_retry_error_names[SHOW_VAR_FUNC_BUFF_SIZE]; -char* slave_load_tmpdir = 0; +READ_ONLY_SYSVAR char* slave_load_tmpdir; Master_info *active_mi= 0; my_bool replicate_same_server_id; -ulonglong relay_log_space_limit = 0; +READ_ONLY_SYSVAR ulonglong relay_log_space_limit; ulonglong opt_read_binlog_speed_limit = 0; -const char *relay_log_index= 0; -const char *relay_log_basename= 0; +READ_ONLY_SYSVAR const char *relay_log_index; +READ_ONLY_SYSVAR const char *relay_log_basename; LEX_CSTRING default_master_connection_name= { (char*) "", 0 }; @@ -3393,10 +3393,13 @@ static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, protocol->store(mi->connection_name.str, mi->connection_name.length, &my_charset_bin); - mysql_mutex_lock(&mi->run_lock); + mysql_mutex_lock(&mi->rli.run_lock); THD *sql_thd= mi->rli.sql_driver_thd; + DEBUG_SYNC(thd, "hold_sss_with_run_lock"); const char *slave_sql_running_state= sql_thd ? sql_thd->get_proc_info() : ""; + mysql_mutex_unlock(&mi->rli.run_lock); + mysql_mutex_lock(&mi->run_lock); THD *io_thd= mi->io_thd; const char *slave_io_running_state= io_thd ? io_thd->get_proc_info() : ""; mysql_mutex_unlock(&mi->run_lock); @@ -6347,25 +6350,77 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) mi->checksum_alg_before_fd : mi->rli.relay_log.relay_log_checksum_alg; const uchar *save_buf= NULL; // needed for checksumming the fake Rotate event - uchar rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN]; + uchar rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN + + BINLOG_CHECKSUM_LEN]; #ifndef DBUG_OFF bool dbg_crash_after_enqueue_gtid_flag= false; #endif + /* + The shortest event a master can legally send. Binlog versions older + than 4, whose header was shorter, are no longer supported. + */ + ulong min_event_len= LOG_EVENT_MINIMAL_HEADER_LEN; DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_OFF || checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); DBUG_ENTER("queue_event"); + + /* + Reject an event that is shorter than the common header before anything + reads that header. The code below indexes into the header unconditionally, + and several of the per-type parsers derive a body length by subtracting + the header length, which wraps on a shorter event. + event_checksum_test() subtracts BINLOG_CHECKSUM_LEN in the same way. + */ + if (unlikely(event_len < min_event_len)) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Event from master is shorter than its " + "common header; the event's length: ")); + error_msg.append_ulonglong(event_len); + unlock_data_lock= FALSE; + goto err; + } + + /* + An event states its length twice: once as the length of the packet the + IO thread read the event from, and once in the EVENT_LEN_OFFSET field + of the event's own header. The relay log write below takes the packet's + length, and every reader of the relay log frames each event by the + length that header declares. An event whose two lengths disagree + therefore leaves the SQL thread beginning its next read at the wrong + offset. + */ + if (unlikely(event_len != uint4korr(buf + EVENT_LEN_OFFSET))) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Event from master declares a length " + "that does not match the packet the " + "event arrived in; the declared " + "length: ")); + error_msg.append_ulonglong(uint4korr(buf + EVENT_LEN_OFFSET)); + error_msg.append(STRING_WITH_LEN(", the packet's length: ")); + error_msg.append_ulonglong(event_len); + unlock_data_lock= FALSE; + goto err; + } + /* FD_queue checksum alg description does not apply in a case of FD itself. The one carries both parts of the checksum data. */ if (buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) { - checksum_alg= get_checksum_alg(buf, event_len); + if (unlikely(get_checksum_alg(buf, event_len, &checksum_alg))) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + unlock_data_lock= FALSE; + goto err; + } } else if (buf[EVENT_TYPE_OFFSET] == START_EVENT_V3) { @@ -6446,7 +6501,58 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) goto err; case ROTATE_EVENT: { - Rotate_log_event rev(buf, checksum_alg != BINLOG_CHECKSUM_ALG_OFF ? + /* + This is normally done in Log_event::read_log_event(), + but we bypass it here because it's expensive and costs dynamic memory. + */ + if (unlikely(ROTATE_EVENT > + mi->rli.relay_log.description_event_for_queue->number_of_event_types)) + { + // The current FDE does not support `ROTATE_EVENT`. + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + /* + RSC_1 and RSC_2 below rewrite the fake Rotate (the one that opens a + connection, naming the binary log file the dump starts in; the file + switches later in the connection send more, but by then the first + format description event has left the relay log carrying the master's + algorithm) through rot_buf, and the first two conditions here are the + pair that selects them: a fake Rotate that carries a checksum the + relay log does not, or the reverse. Both cases memcpy() into rot_buf + a length taken from event_len, so event_len has to be bounded before + they run. The Rotate_log_event constructed below does not bound it. + That constructor caps only new_log_ident, the copy of the file name + it keeps for itself, so event_len remains the length the master sent. + This check is placed at the start of the case block so a rejected event + will not change replication state. + */ + bool is_fake_rotate= uint4korr(&buf[0]) == 0; + bool event_has_checksum= checksum_alg != BINLOG_CHECKSUM_ALG_OFF; + bool relay_log_has_checksum= + mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF; + + /* The longest Rotate a master can send under its own checksum policy. */ + ulong max_rotate_len= + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN + + (event_has_checksum ? BINLOG_CHECKSUM_LEN : 0); + + if (unlikely(is_fake_rotate && + event_has_checksum != relay_log_has_checksum && + event_len > max_rotate_len)) + { + error= ER_SLAVE_FATAL_ERROR; + error_msg.append(STRING_WITH_LEN("Rotate event from master names a " + "binary log file longer than the " + "maximum file name length; the event's " + "length: ")); + error_msg.append_ulonglong(event_len); + error_msg.append(STRING_WITH_LEN(", the maximum: ")); + error_msg.append_ulonglong(max_rotate_len); + goto err; + } + + Rotate_log_event rev(buf, event_has_checksum ? event_len - BINLOG_CHECKSUM_LEN : event_len, mi->rli.relay_log.description_event_for_queue); bool master_changed= false; @@ -6565,8 +6671,7 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) to compute checksum for its first FD event for RL the fake Rotate gets checksummed here. */ - if (uint4korr(&buf[0]) == 0 && checksum_alg == BINLOG_CHECKSUM_ALG_OFF && - mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF) + if (is_fake_rotate && !event_has_checksum && relay_log_has_checksum) { ha_checksum rot_crc= 0; event_len += BINLOG_CHECKSUM_LEN; @@ -6589,8 +6694,7 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) RSC_2: If NM \and fake Rotate \and slave does not compute checksum the fake Rotate's checksum is stripped off before relay-logging. */ - if (uint4korr(&buf[0]) == 0 && checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - mi->rli.relay_log.relay_log_checksum_alg == BINLOG_CHECKSUM_ALG_OFF) + if (is_fake_rotate && event_has_checksum && !relay_log_has_checksum) { event_len -= BINLOG_CHECKSUM_LEN; memcpy(rot_buf, buf, event_len); @@ -6982,13 +7086,14 @@ dbug_gtid_accept: */ case QUERY_COMPRESSED_EVENT: inc_pos= event_len; - if (query_event_uncompress(rli->relay_log.description_event_for_queue, - checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, - buf, event_len, new_buf_arr, sizeof(new_buf_arr), - &is_malloc, &new_buf, &event_len)) + if ((error= + query_event_uncompress(rli->relay_log.description_event_for_queue, + checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, + buf, event_len, new_buf_arr, + sizeof(new_buf_arr), + &is_malloc, &new_buf, &event_len))) { char llbuf[22]; - error = ER_BINLOG_UNCOMPRESS_ERROR; error_msg.append(STRING_WITH_LEN("binlog uncompress error, master log_pos: ")); llstr(mi->master_log_pos, llbuf); error_msg.append(llbuf, strlen(llbuf)); @@ -7006,14 +7111,14 @@ dbug_gtid_accept: case DELETE_ROWS_COMPRESSED_EVENT_V1: inc_pos = event_len; { - if (row_log_event_uncompress(rli->relay_log.description_event_for_queue, - checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, - buf, event_len, new_buf_arr, - sizeof(new_buf_arr), - &is_malloc, &new_buf, &event_len)) + if ((error= + row_log_event_uncompress(rli->relay_log.description_event_for_queue, + checksum_alg == BINLOG_CHECKSUM_ALG_CRC32, + buf, event_len, new_buf_arr, + sizeof(new_buf_arr), + &is_malloc, &new_buf, &event_len))) { char llbuf[22]; - error = ER_BINLOG_UNCOMPRESS_ERROR; error_msg.append(STRING_WITH_LEN("binlog uncompress error, master log_pos: ")); llstr(mi->master_log_pos, llbuf); error_msg.append(llbuf, strlen(llbuf)); @@ -7397,8 +7502,14 @@ err: handle_slave_io() prints it on return. */ if (unlikely(error) && error != ER_SLAVE_RELAY_LOG_WRITE_FAILURE) - mi->report(ERROR_LEVEL, error, NULL, ER_DEFAULT(error), - error_msg.ptr()); + { + if (error == ER_TOO_BIG_FOR_UNCOMPRESS) + mi->report(ERROR_LEVEL, error, error_msg.c_ptr(), ER_DEFAULT(error), + MAX_MAX_ALLOWED_PACKET); + else + mi->report(ERROR_LEVEL, error, NULL, ER_DEFAULT(error), + error_msg.ptr()); + } if (unlikely(is_malloc)) my_free((void *)new_buf); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ba4cdcd9837..98f302e2aa3 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2002, 2016, Oracle and/or its affiliates. - Copyright (c) 2011, 2020, MariaDB + Copyright (c) 2011, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -2975,10 +2975,8 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) 1, TRUE) && (tables.grant.privilege & SELECT_ACL) != NO_ACL) || /* Check if user owns the routine. */ - (!strcmp(sp->m_definer.user.str, - thd->security_ctx->priv_user) && - !strcmp(sp->m_definer.host.str, - thd->security_ctx->priv_host)) || + thd->security_ctx->is_priv_user(sp->m_definer.user.str, + sp->m_definer.host.str) || /* Check if current role or any of the sub-granted roles own the routine. */ (sp->m_definer.host.length == 0 && diff --git a/sql/spatial.cc b/sql/spatial.cc index 2de3c57ef1a..1484ae3d322 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -493,19 +493,14 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, json_engine_t *je, bool er_on_3D, String *res) { Class_info *ci= NULL; - const uchar *coord_start= NULL, *geom_start= NULL, - *features_start= NULL, *geometry_start= NULL; + enum t_enum { T_NONE, T_GEOMETRY, T_GEOMETRIES, T_COORD, T_FEATURE }; + enum t_enum arg= T_NONE; + json_engine_t argje, *je_arg; Geometry *result; uchar key_buf[max_keyname_len]; uint key_len; - int fcoll_type_found= 0, feature_type_found= 0; + bool feature_type_found= false; - const uint32_t *killed_ptr= (uint32_t *) je->killed_ptr; - int stack_p; - - if (json_read_value(je)) - goto err_return; - if (je->value_type != JSON_VALUE_OBJECT) { je->s.error= GEOJ_INCORRECT_GEOJSON; @@ -514,7 +509,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, while (json_scan_next(je) == 0 && je->state != JST_OBJ_END) { - DBUG_ASSERT(je->state == JST_KEY); + if (je->state != JST_KEY) + break; key_len=0; while (json_read_keyname_chr(je) == 0) @@ -543,31 +539,36 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_STRING) { - if ((ci= find_class((const char *) je->value, je->value_len))) + if ((ci= find_class(reinterpret_cast(je->value), je->value_len))) { - if ((coord_start= - (ci == &geometrycollection_class) ? geom_start : coord_start)) + if ((ci == &geometrycollection_class && arg == T_GEOMETRIES) || arg == T_COORD) goto create_geom; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else if (je->value_len == feature_coll_type_len && my_charset_latin1.strnncoll(je->value, je->value_len, - feature_coll_type, feature_coll_type_len) == 0) + feature_coll_type, feature_coll_type_len) == 0) { /* - 'FeatureCollection' type found. Handle the 'Featurecollection'/'features' - GeoJSON construction. + 'FeatureCollection' type found. Handle the 'Featurecollection' + /'features' GeoJSON construction. */ - if (features_start) - goto handle_feature_collection; - fcoll_type_found= 1; + ci= &geometrycollection_class; + if (arg == T_FEATURE) + goto create_geom; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else if (je->value_len == feature_type_len && my_charset_latin1.strnncoll(je->value, je->value_len, - feature_type, feature_type_len) == 0) + feature_type, feature_type_len) == 0) { - if (geometry_start) + if (arg == T_GEOMETRY) goto handle_geometry_key; - feature_type_found= 1; + feature_type_found= true; + if (arg != T_NONE) + break; /* invalid arg present for current geometry */ } else /* can't understand the type. */ break; @@ -578,6 +579,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, else if (key_len == coord_keyname_len && memcmp(key_buf, coord_keyname, coord_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* Found the "coordinates" key. Let's check it's an array and remember where it starts. @@ -587,16 +590,25 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_ARRAY) { - coord_start= je->value_begin; + arg= T_COORD; if (ci && ci != &geometrycollection_class) - goto create_geom; + { + je_arg= je; + goto create_geom; + } + argje= *je; + je_arg= &argje; if (json_skip_level(je)) goto err_return; } + else + break; /* coordinates needs to be an array */ } else if (key_len == geometries_keyname_len && memcmp(key_buf, geometries_keyname, geometries_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* Found the "geometries" key. Let's check it's an array and remember where it starts. @@ -606,17 +618,28 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, if (je->value_type == JSON_VALUE_ARRAY) { - geom_start= je->value_begin; if (ci == &geometrycollection_class) { - coord_start= geom_start; + je_arg= je; goto create_geom; } + if (ci != nullptr) + break; /* geometries only valid inside geometrycollation */ + arg= T_GEOMETRIES; + argje= *je; + je_arg= &argje; + /* skip geometries for now and search for type */ + if (json_skip_level(je)) + goto err_return; } + else + break; /* geometries needs to be an array */ } else if (key_len == features_keyname_len && memcmp(key_buf, features_keyname, features_keyname_len) == 0) { + if (arg != T_NONE) + break; /* previous arg unprocessed */ /* 'features' key found. Handle the 'Featurecollection'/'features' GeoJSON construction. @@ -625,10 +648,23 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, goto err_return; if (je->value_type == JSON_VALUE_ARRAY) { - features_start= je->value_begin; - if (fcoll_type_found) - goto handle_feature_collection; + if (ci == &geometrycollection_class) + { + je_arg= je; + goto create_geom; + } + if (ci != nullptr) + break; /* features only valid inside featurecollation */ + + arg= T_FEATURE; + argje= *je; + je_arg= &argje; + /* skip features for now and search for type */ + if (json_skip_level(je)) + goto err_return; } + else + break; /* feature collections needs to be an array */ } else if (key_len == geometry_keyname_len && memcmp(key_buf, geometry_keyname, geometry_keyname_len) == 0) @@ -637,12 +673,21 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, goto err_return; if (je->value_type == JSON_VALUE_OBJECT) { - geometry_start= je->value_begin; if (feature_type_found) + { + je_arg= je; goto handle_geometry_key; + } + if (ci != nullptr) + break; /* geometry only valid inside feature */ + arg= T_GEOMETRY; + argje= *je; + je_arg= &argje; + if (json_skip_level(je)) + goto err_return; } else - goto err_return; + break; /* geometry needs to be an object */ } else { @@ -661,35 +706,35 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, } goto err_return; -handle_feature_collection: - ci= &geometrycollection_class; - coord_start= features_start; - create_geom: - stack_p= je->stack_p; - json_scan_start(je, je->s.cs, coord_start, je->s.str_end); - je->killed_ptr= killed_ptr; - je->stack_p= stack_p; - if (res->reserve(1 + 4, 512)) goto err_return; result= (*ci->m_create_func)(buffer->data); res->q_append((char) wkb_ndr); res->q_append((uint32) result->get_class_info()->m_type_id); - if (result->init_from_json(je, er_on_3D, res)) + if (result->init_from_json(je_arg, er_on_3D, res)) + { + if (je_arg != je) /* copy error out of copied engine */ + *je= *je_arg; + goto err_return; + } + /* finish of the object scan for validation/geomcollection */ + if (json_skip_level(je)) goto err_return; return result; -handle_geometry_key: - stack_p= je->stack_p; - json_scan_start(je, je->s.cs, geometry_start, je->s.str_end); - je->killed_ptr= killed_ptr; - je->stack_p= stack_p; - return create_from_json(buffer, je, er_on_3D, res); +handle_geometry_key: /* feature */ + result= create_from_json(buffer, je_arg, er_on_3D, res); + + /* skip rest of feature - can be arbitrary fields */ + if (json_skip_level(je)) + goto err_return; + + return result; err_return: return NULL; } @@ -964,7 +1009,6 @@ static int read_point_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); if (json_read_value(je)) return 1; @@ -978,6 +1022,8 @@ static int read_point_from_json(json_engine_t *je, bool er_on_3D, n_coord++; } + if (je->s.error != 0) + return 1; if (n_coord <= 2 || !er_on_3D) return 0; je->s.error= Geometry::GEOJ_DIMENSION_NOT_SUPPORTED; @@ -991,8 +1037,6 @@ bad_coordinates: bool Gis_point::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) { double x, y; - if (json_read_value(je)) - return TRUE; if (je->value_type != JSON_VALUE_ARRAY) { @@ -1006,6 +1050,7 @@ bool Gis_point::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) wkb->q_append(x); wkb->q_append(y); + /* Note GEOJSON RFC7946 3.3.1 - 3D possible, but not WKB? */ return FALSE; } @@ -1282,11 +1327,9 @@ bool Gis_line_string::init_from_json(json_engine_t *je, bool er_on_3D, uint32 np_pos= wkb->length(); Gis_point p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -1297,7 +1340,13 @@ bool Gis_line_string::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (p.init_from_json(je, er_on_3D, wkb)) return TRUE; @@ -1676,11 +1725,9 @@ bool Gis_polygon::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) uint32 lr_pos= wkb->length(); int closed; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -1692,11 +1739,18 @@ bool Gis_polygon::init_from_json(json_engine_t *je, bool er_on_3D, String *wkb) while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { Gis_line_string ls; - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; uint32 ls_pos=wkb->length(); if (ls.init_from_json(je, er_on_3D, wkb)) return TRUE; + ls.set_data_ptr(wkb->ptr() + ls_pos, wkb->length() - ls_pos); if (ls.is_closed(&closed) || !closed) { @@ -2180,6 +2234,8 @@ uint Gis_multi_point::init_from_wkb(const char *wkb, uint len, wkbByteOrder bo, { res->q_append((char)wkb_ndr); res->q_append((uint32)wkb_point); + if ((uchar) wkb[0] > wkb_ndr) /* invalid */ + return 0; if (!p.init_from_wkb(wkb + WKB_HEADER_SIZE, POINT_DATA_SIZE, (wkbByteOrder) wkb[0], res)) return 0; @@ -2195,11 +2251,9 @@ bool Gis_multi_point::init_from_json(json_engine_t *je, bool er_on_3D, uint32 np_pos= wkb->length(); Gis_point p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2210,7 +2264,12 @@ bool Gis_multi_point::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (wkb->reserve(1 + 4, 512)) return TRUE; @@ -2560,11 +2619,9 @@ bool Gis_multi_line_string::init_from_json(json_engine_t *je, bool er_on_3D, uint32 n_line_strings= 0; uint32 ls_pos= wkb->length(); - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2575,9 +2632,14 @@ bool Gis_multi_line_string::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - Gis_line_string ls; - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; + Gis_line_string ls; if (wkb->reserve(1 + 4, 512)) return TRUE; wkb->q_append((char) wkb_ndr); @@ -2966,11 +3028,9 @@ bool Gis_multi_polygon::init_from_json(json_engine_t *je, bool er_on_3D, int np_pos= wkb->length(); Gis_polygon p; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -2981,7 +3041,12 @@ bool Gis_multi_polygon::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state != JST_VALUE) + goto err_geoj_incorrect; + if (json_read_value(je)) + return TRUE; + if (je->value_type != JSON_VALUE_ARRAY) + goto err_geoj_incorrect; if (wkb->reserve(1 + 4, 512)) return TRUE; @@ -3425,6 +3490,9 @@ uint Gis_geometry_collection::init_from_wkb(const char *wkb, uint len, return 0; n_geom= wkb_get_uint(wkb, bo); + if (check_stack_overrun(current_thd, STACK_MIN_SIZE, (uchar*)&wkb_orig)) + return 1; + if (res->reserve(4, 512)) return 0; res->q_append(n_geom); @@ -3465,11 +3533,9 @@ bool Gis_geometry_collection::init_from_json(json_engine_t *je, bool er_on_3D, Geometry_buffer buffer; Geometry *g; - if (json_read_value(je)) - return TRUE; - if (je->value_type != JSON_VALUE_ARRAY) { +err_geoj_incorrect: je->s.error= GEOJ_INCORRECT_GEOJSON; return TRUE; } @@ -3480,17 +3546,17 @@ bool Gis_geometry_collection::init_from_json(json_engine_t *je, bool er_on_3D, while (json_scan_next(je) == 0 && je->state != JST_ARRAY_END) { - json_engine_t sav_je= *je; - - DBUG_ASSERT(je->state == JST_VALUE); + if (je->state == JST_VALUE) + { + if (json_read_value(je)) + return TRUE; + } + if (je->state != JST_OBJ_START) + goto err_geoj_incorrect; if (!(g= create_from_json(&buffer, je, er_on_3D, wkb))) return TRUE; - *je= sav_je; - if (json_skip_array_item(je)) - return TRUE; - n_objects++; } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 491fee43ce6..8ab3eacbad7 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. - Copyright (c) 2009, 2023, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -3795,7 +3795,7 @@ privilege_t acl_get(const char *host, const char *ip, acl_entry *entry; DBUG_ENTER("acl_get"); - tmp_db= strmov(strmov(key, safe_str(ip)) + 1, user) + 1; + tmp_db= strmov(strmov(key, safe_str(ip ? ip : host)) + 1, user) + 1; end= strnmov(tmp_db, db, key + sizeof(key) - tmp_db); if (end >= key + sizeof(key)) // db name was truncated @@ -9618,8 +9618,7 @@ bool get_show_user(THD *thd, LEX_USER *lex_user, const char **username, { *username= lex_user->user.str; *hostname= lex_user->host.str; - do_check_access= strcmp(*username, sctx->priv_user) || - strcmp(*hostname, sctx->priv_host); + do_check_access= !sctx->is_priv_user(*username, *hostname); } if (do_check_access && check_access(thd, SELECT_ACL, "mysql", 0, 0, 1, 0)) @@ -11928,6 +11927,61 @@ Silence_routine_definer_errors::handle_condition( } +/* + The low level function to revoke routine privileges for the given sp handler + @param thd the thd + @param proc_privs the table mysql.proc_privs + @param sp_db the routine database + @param sp_name the routine name + @param sph the sp handler +*/ +static void sp_revoke_privileges_for_handler(THD *thd, TABLE *proc_privs, +#if MYSQL_VERSION_ID < 110501 + const char *sp_db, + const char *sp_name, +#else +#error Remove the above conditional code + const Lex_ident_db &sp_db, + const Lex_ident_routine &sp_name, +#endif + const Sp_handler *sph) +{ + uint counter, revoked; + HASH *hash= sph->get_priv_hash(); + do + { + for (counter= 0, revoked= 0 ; counter < hash->records ; ) + { + GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); +#if MYSQL_VERSION_ID < 110501 + if (!my_strcasecmp(&my_charset_utf8mb3_bin, grant_proc->db, sp_db) && + !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) +#else +#error Remove the above conditional code + if (sp_db.streq(Lex_cstring_strlen(grant_proc->db)) && + sp_name.streq(Lex_cstring_strlen(grant_proc->tname))) +#endif + { + LEX_USER lex_user; + lex_user.user.str= grant_proc->user; + lex_user.user.length= strlen(grant_proc->user); + lex_user.host.str= safe_str(grant_proc->host.hostname); + lex_user.host.length= strlen(lex_user.host.str); + if (replace_routine_table(thd, grant_proc, + proc_privs, lex_user, + grant_proc->db, grant_proc->tname, + sph, ALL_KNOWN_ACL, 1) == 0) + { + revoked= 1; + continue; + } + } + counter++; + } + } while (revoked); +} + + /** Revoke privileges for all users on a stored procedure. Use an error handler that converts errors about missing grants into warnings. @@ -11948,9 +12002,7 @@ Silence_routine_definer_errors::handle_condition( bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, const Sp_handler *sph) { - uint counter, revoked; int result; - HASH *hash= sph->get_priv_hash(); Silence_routine_definer_errors error_handler; DBUG_ENTER("sp_revoke_privileges"); @@ -11970,31 +12022,12 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, mysql_mutex_lock(&acl_cache->lock); /* Remove procedure access */ - do - { - for (counter= 0, revoked= 0 ; counter < hash->records ; ) - { - GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); - if (!my_strcasecmp(&my_charset_utf8mb3_bin, grant_proc->db, sp_db) && - !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) - { - LEX_USER lex_user; - lex_user.user.str= grant_proc->user; - lex_user.user.length= strlen(grant_proc->user); - lex_user.host.str= safe_str(grant_proc->host.hostname); - lex_user.host.length= strlen(lex_user.host.str); - if (replace_routine_table(thd, grant_proc, - tables.procs_priv_table().table(), lex_user, - grant_proc->db, grant_proc->tname, - sph, ALL_KNOWN_ACL, 1) == 0) - { - revoked= 1; - continue; - } - } - counter++; - } - } while (revoked); + if (sph == &sp_handler_package_spec) + sp_revoke_privileges_for_handler(thd, tables.procs_priv_table().table(), + sp_db, sp_name, &sp_handler_package_body); + + sp_revoke_privileges_for_handler(thd, tables.procs_priv_table().table(), + sp_db, sp_name, sph); mysql_mutex_unlock(&acl_cache->lock); mysql_rwlock_unlock(&LOCK_grant); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8fd1b87459a..6df8853379c 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2280,7 +2280,7 @@ retry_share: goto retry_share; } - if (thd->open_tables && thd->open_tables->s->tdc->flushed) + if (!table_list->sequence && thd->open_tables && thd->open_tables->s->tdc->flushed) { /* If the version changes while we're opening the tables, @@ -5381,6 +5381,10 @@ bool open_and_lock_internal_tables(TABLE *table, bool lock_table) MYSQL_LOCK_USE_MALLOC)) goto err; + /* no existing lock to merge with */ + if (save_lock == nullptr) + DBUG_RETURN(0); + if (!(new_lock= mysql_lock_merge(save_lock, thd->lock))) { thd->lock= save_lock; @@ -7154,7 +7158,30 @@ find_field_in_tables(THD *thd, Item_ident *item, { if (report_error == REPORT_ALL_ERRORS || report_error == REPORT_EXCEPT_NON_UNIQUE) + { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + If the user has no rights on this column in any candidate table, + check_grant_column() will issue a generic "access denied" error + */ + if (check_privileges) + { + for (TABLE_LIST *tl= first_table; tl != last_table; + tl= tl->next_name_resolution_table) + { + if (db.str && my_strcasecmp(table_alias_charset, tl->db.str, db.str)) + continue; + if (table_name && + my_strcasecmp(table_alias_charset, tl->alias.str, table_name)) + continue; + if (check_grant_column(thd, &tl->grant, tl->db.str, + tl->table_name.str, name, length, thd->security_ctx)) + return found; + } + } +#endif my_error(ER_BAD_FIELD_ERROR, MYF(0), item->full_name(), thd_where(thd)); + } else found= not_found_field; } @@ -9404,17 +9431,9 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Re-calculate virtual fields to cater for cases when base columns are updated by the triggers. */ - if (table->vfield && fields.elements) - { - Item *fld= (Item_field*) fields.head(); - Item_field *item_field= fld->field_for_view_update(); - if (item_field) - { - DBUG_ASSERT(table == item_field->field->table); - result|= table->update_virtual_fields(table->file, - VCOL_UPDATE_FOR_WRITE); - } - } + if (table->vfield) + result|= table->update_virtual_fields(table->file, + VCOL_UPDATE_FOR_WRITE); } return result; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 1c6ed3e06fb..e744bbd5f8f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2022, MariaDB Corporation. + Copyright (c) 2008, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -102,7 +102,6 @@ extern "C" void free_user_var(void *entry_) char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry)); if (entry->value && entry->value != pos) my_free(entry->value); - my_free(entry); } /* Functions for last-value-from-sequence hash */ @@ -822,6 +821,13 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) &main_mem_root, DEFAULT_ROOT_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); + /* + Use MY_ROOT_USE_VMEM to keep user vars away from the heap, so that a heap + buffer overflow couldn't corrupt a user_var_entry and its value pointer + */ + init_sql_alloc(key_memory_user_var_entry, &user_vars_memroot, + 256, 0, MYF(MY_THREAD_SPECIFIC | MY_ROOT_USE_VMEM)); + /* Allocation of user variables for binary logging is always done with main mem root @@ -1701,8 +1707,22 @@ void THD::cleanup(void) } wt_thd_destroy(&transaction->wt); + bool user_vars_used= user_vars.records != 0; my_hash_free(&user_vars); my_hash_free(&sequences); + /* + MY_ROOT_USE_VMEM is expensive. If this connection used user vars, + let's keep one page prealloc so that the next connection on this THD + wouldn't need to allocate. + */ + if (user_vars_used) + { + reset_root_defaults(&user_vars_memroot, user_vars_memroot.block_size, + user_vars_memroot.block_size); + free_root(&user_vars_memroot, MYF(MY_KEEP_PREALLOC)); + } + else + free_root(&user_vars_memroot, MYF(0)); sp_caches_clear(); auto_inc_intervals_forced.empty(); auto_inc_intervals_in_cur_stmt_for_binlog.empty(); @@ -1856,6 +1876,7 @@ THD::~THD() #endif main_lex.free_set_stmt_mem_root(); free_root(&main_mem_root, MYF(0)); + free_root(&user_vars_memroot, MYF(0)); my_free(m_token_array); my_free(killed_err); main_da.free_memory(); @@ -4792,9 +4813,8 @@ change_security_context(THD *thd, DBUG_ASSERT(definer_user->str && definer_host->str); *backup= NULL; - needs_change= (strcmp(definer_user->str, thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, definer_host->str, - thd->security_ctx->priv_host)); + needs_change= !thd->security_ctx->is_priv_user(definer_user->str, + definer_host->str); if (needs_change) { if (acl_getroot(this, definer_user->str, definer_host->str, @@ -4822,13 +4842,19 @@ Security_context::restore_security_context(THD *thd, #endif -bool Security_context::user_matches(Security_context *them) +/** + check that `this` is not system or pre-auth thread and + is owned by the same account as in `them` + + Note that it's not symmetric, it's used in KILL/SHOW commands + to see whether "them" can kill/see "us", never the other way around! +*/ +bool Security_context::priv_user_matches(const Security_context *them) const { - return ((user != NULL) && (them->user != NULL) && - !strcmp(user, them->user)); + return user && is_priv_user(them->priv_user, them->priv_host); } -bool Security_context::is_priv_user(const char *user, const char *host) +bool Security_context::is_priv_user(const char *user, const char *host) const { return ((user != NULL) && (host != NULL) && !strcmp(user, priv_user) && diff --git a/sql/sql_class.h b/sql/sql_class.h index 0bd5b73ad28..dd2bb8c41d9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, 2022, MariaDB Corporation. + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1762,7 +1762,7 @@ public: void restore_security_context(THD *thd, Security_context *backup); #endif - bool user_matches(Security_context *); + bool priv_user_matches(const Security_context *) const; /** Check global access @param want_access The required privileges @@ -1771,7 +1771,7 @@ public: @return True if the security context fulfills the access requirements. */ bool check_access(const privilege_t want_access, bool match_any = false); - bool is_priv_user(const char *user, const char *host); + bool is_priv_user(const char *user, const char *host) const; bool is_user_defined() const { return user && user != delayed_user && user != slave_user && user != wsrep_user; }; }; @@ -5545,6 +5545,7 @@ public: void mark_transaction_to_rollback(bool all); bool internal_transaction() { return transaction != &default_transaction; } + MEM_ROOT *user_vars_root() { return &user_vars_memroot; } private: /** The current internal error handler for this thread, or NULL. */ @@ -5566,6 +5567,10 @@ private: tree itself is reused between executions and thus is stored elsewhere. */ MEM_ROOT main_mem_root; + /** + Memory root the user_var_entry objects and their names are allocated on. + */ + MEM_ROOT user_vars_memroot; Diagnostics_area main_da; Diagnostics_area *m_stmt_da; diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 6bbade19f20..eaa343f3ee3 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -904,7 +904,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) */ thd->create_tmp_table_for_derived= TRUE; distinct= (unit->first_select()->next_select() ? - unit->union_distinct && !unit->union_distinct->next_select() : + unit->check_distinct_in_union() : unit->distinct); if (!(derived->table) && diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 74e8d7d77a4..8047f8799d2 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2023, MariaDB + Copyright (c) 2008, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -2334,13 +2334,8 @@ dispatch_command_return dispatch_command(enum enum_server_command command, break; case COM_PROCESS_INFO: status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_PROCESSLIST]); - if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_COM_PROCESS_INFO)) - break; general_log_print(thd, command, NullS); - mysqld_list_processes(thd, - thd->security_ctx->master_access & PRIV_COM_PROCESS_INFO ? - NullS : thd->security_ctx->priv_user, 0); + mysqld_list_processes(thd, 0); break; case COM_PROCESS_KILL: { @@ -2884,12 +2879,9 @@ bool sp_process_definer(THD *thd) to create a stored routine under another user one must have SUPER privilege). */ - bool curuser= !strcmp(d->user.str, thd->security_ctx->priv_user); + bool curuser= thd->security_ctx->is_priv_user(d->user.str, d->host.str); bool currole= !curuser && !strcmp(d->user.str, thd->security_ctx->priv_role); - bool curuserhost= curuser && d->host.str && - !my_strcasecmp(system_charset_info, d->host.str, - thd->security_ctx->priv_host); - if (!curuserhost && !currole && + if (!curuser && !currole && check_global_access(thd, PRIV_DEFINER_CLAUSE, false)) DBUG_RETURN(TRUE); } @@ -3934,7 +3926,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) case SQLCOM_SHOW_ANALYZE: { if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_STMT_SHOW_EXPLAIN)) + check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) break; /* @@ -4604,6 +4596,8 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) select_lex->options|= SELECT_NO_UNLOCK; unit->set_limit(select_lex); + if (thd->is_error()) + goto error; if (!(res=open_and_lock_tables(thd, all_tables, TRUE, 0))) { @@ -4859,14 +4853,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) break; } case SQLCOM_SHOW_PROCESSLIST: - if (!thd->security_ctx->priv_user[0] && - check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) - break; - mysqld_list_processes(thd, - (thd->security_ctx->master_access & PRIV_STMT_SHOW_PROCESSLIST ? - NullS : - thd->security_ctx->priv_user), - lex->verbose); + mysqld_list_processes(thd, lex->verbose); break; case SQLCOM_SHOW_AUTHORS: res= mysqld_show_authors(thd); @@ -9138,31 +9125,10 @@ kill_one_thread(THD *thd, my_thread_id id, killed_state kill_signal, killed_type DEBUG_SYNC(thd, "found_killee"); if (tmp->get_command() != COM_DAEMON) { - /* - If we're SUPER, we can KILL anything, including system-threads. - No further checks. - - KILLer: thd->security_ctx->user could in theory be NULL while - we're still in "unauthenticated" state. This is a theoretical - case (the code suggests this could happen, so we play it safe). - - KILLee: tmp->security_ctx->user will be NULL for system threads. - We need to check so Jane Random User doesn't crash the server - when trying to kill a) system threads or b) unauthenticated users' - threads (Bug#43748). - - If user of both killer and killee are non-NULL, proceed with - slayage if both are string-equal. - - It's ok to also kill DELAYED threads with KILL_CONNECTION instead of - KILL_SYSTEM_THREAD; The difference is that KILL_CONNECTION may be - faster and do a harder kill than KILL_SYSTEM_THREAD; - */ - mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from concurrent usage if ((thd->security_ctx->master_access & PRIV_KILL_OTHER_USER_PROCESS) || - thd->security_ctx->user_matches(tmp->security_ctx)) + tmp->security_ctx->priv_user_matches(thd->security_ctx)) { #ifdef WITH_WSREP if (wsrep_thd_is_BF(tmp, false) || tmp->wsrep_applier) @@ -9175,21 +9141,14 @@ kill_one_thread(THD *thd, my_thread_id id, killed_state kill_signal, killed_type tmp->thread_id, (tmp->wsrep_applier ? "wsrep applier" : "high priority")); } + else if (WSREP(tmp)) + error = wsrep_kill_thd(thd, tmp, kill_signal); else - { - if (WSREP(tmp)) - { - error = wsrep_kill_thd(thd, tmp, kill_signal); - } - else - { #endif /* WITH_WSREP */ + { tmp->awake_no_mutex(kill_signal); error= 0; -#ifdef WITH_WSREP - } } -#endif /* WITH_WSREP */ } else error= (type == KILL_TYPE_QUERY ? ER_KILL_QUERY_DENIED_ERROR : @@ -9242,7 +9201,7 @@ static my_bool kill_threads_callback(THD *thd, kill_threads_callback_arg *arg) { if (!(arg->thd->security_ctx->master_access & PRIV_KILL_OTHER_USER_PROCESS) && - !arg->thd->security_ctx->user_matches(thd->security_ctx)) + !thd->security_ctx->priv_user_matches(arg->thd->security_ctx)) { return MY_TEST(arg->thd->security_ctx->master_access & PROCESS_ACL); } diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index edf3d8a9398..ea462160d13 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2005, 2018, Oracle and/or its affiliates. - Copyright (c) 2010, 2020, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -66,9 +66,9 @@ static TYPELIB global_plugin_typelib= static I_List opt_plugin_load_list; I_List *opt_plugin_load_list_ptr= &opt_plugin_load_list; -char *opt_plugin_dir_ptr; -char opt_plugin_dir[FN_REFLEN]; -ulong plugin_maturity; +READ_ONLY_SYSVAR char *opt_plugin_dir_ptr; +READ_ONLY_SYSVAR char opt_plugin_dir[FN_REFLEN]; +READ_ONLY_SYSVAR ulong plugin_maturity; static LEX_CSTRING MYSQL_PLUGIN_NAME= {STRING_WITH_LEN("plugin") }; diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 32e8c9a8b33..5e2531ef78f 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2010, 2016, Oracle and/or its affiliates. - Copyright (c) 2011, 2016, MariaDB + Copyright (c) 2011, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -120,7 +120,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long long options, }); #endif } - opt_noacl= 0; + clear_opt_noacl(); if (unlikely(tmp_thd)) { diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 2a7041d5854..4ec2bd33190 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -225,6 +225,24 @@ static int fake_rotate_event(binlog_send_info *info, ulonglong position, String *packet= info->packet; ha_checksum crc= 0; +#ifndef DBUG_OFF + /* + Name a binary log file longer than any file name a replica can hold, to + test that a replica bounds the event before copying it into a buffer of + its own. The length is a large multiple of FN_REFLEN rather than one byte + over it, so that the copy of a replica missing the bound runs past its + whole stack frame. An overrun of a few hundred bytes only reaches the + buffers next to the target, which that replica never reads on this path. + The name is replaced here, ahead of the header, so that both the event + length the header carries and the checksum below cover it. + */ + static char oversized_ident[32 * FN_REFLEN]; + DBUG_EXECUTE_IF("binlog_sender_oversized_fake_rotate", + memset(oversized_ident, 'a', sizeof(oversized_ident)); + p= oversized_ident; + ident_len= (uint) sizeof(oversized_ident);); +#endif + /* reset transmit packet for the fake rotate event below */ if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) DBUG_RETURN(1); @@ -911,10 +929,28 @@ static int send_heartbeat_event(binlog_send_info *info, event_len+= HB_SUB_HEADER_LEN; } + size_t hb_header_len= sizeof(header); +#ifndef DBUG_OFF + /* + Corrupt the heartbeat event to test that a replica can correctly identify + malformed events. 17 bytes keeps LOG_POS_OFFSET on the wire and clears + queue_event()'s minimum length, so the heartbeat parser rejects it. + 13 bytes falls under that minimum, so queue_event() rejects it first. + */ + DBUG_EXECUTE_IF("binlog_sender_truncated_heartbeat", hb_header_len= 17;); + DBUG_EXECUTE_IF("binlog_sender_undersized_heartbeat", hb_header_len= 13;); + if (hb_header_len != sizeof(header)) + { + ident_len= 0; + event_len= hb_header_len + (sub_header_in_use ? HB_SUB_HEADER_LEN : 0) + + (do_checksum ? BINLOG_CHECKSUM_LEN : 0); + } +#endif + int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, 0); - packet->append(header, sizeof(header)); + packet->append(header, hb_header_len); if (sub_header_in_use) packet->append(sub_header_buf, sizeof(sub_header_buf)); packet->append(p, ident_len); // log_file_name @@ -922,7 +958,7 @@ static int send_heartbeat_event(binlog_send_info *info, if (do_checksum) { char b[BINLOG_CHECKSUM_LEN]; - ha_checksum crc= my_checksum(0, (uchar*) header, sizeof(header)); + ha_checksum crc= my_checksum(0, (uchar*) header, hb_header_len); if (sub_header_in_use) crc= my_checksum(crc, (uchar*) sub_header_buf, sizeof(sub_header_buf)); crc= my_checksum(crc, (uchar*) p, ident_len); @@ -1706,10 +1742,10 @@ gtid_state_from_pos(const char *name, uint32 offset, goto end; } - current_checksum_alg= get_checksum_alg((uchar*) packet.ptr(), - packet.length()); found_format_description_event= true; - if (unlikely(!(tmp= new Format_description_log_event((uchar*) packet.ptr(), + if (unlikely(get_checksum_alg((uchar*) packet.ptr(), packet.length(), + ¤t_checksum_alg) || + !(tmp= new Format_description_log_event((uchar*)packet.ptr(), packet.length(), fdev)))) { @@ -2255,6 +2291,161 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type, return "run 'before_send_event' hook failed"; } +#ifndef DBUG_OFF + /* + Declare a length in the event's header that differs from the number of + bytes the packet carries, to test that a replica reconciles the two + lengths before relay logging the event. Real Rotate events carry this + injection, which keeps the injection on one event of a known length + instead of on every event of the connection. Eight bytes moves the + declared end of the event off the packet's own end in either + direction: short of that end, into the file name the event closes + with, or past that end entirely. + + fix_checksum() cannot serve here. That helper checksums the range + EVENT_LEN_OFFSET declares, the field this injection falsifies, and a + replica checksums the event over the length of the packet the event + arrived in. Recomputing over the packet is what carries the + event past the replica's checksum test and on to the length + comparison. + */ + if (event_type == ROTATE_EVENT) + { + long len_delta= 0; + DBUG_EXECUTE_IF("binlog_sender_short_event_len", len_delta= -8;); + DBUG_EXECUTE_IF("binlog_sender_long_event_len", len_delta= 8;); + if (len_delta) + { + uchar *ev= (uchar*) packet->ptr() + ev_offset; + ulong ev_len= (ulong) (len - ev_offset); + + int4store(ev + EVENT_LEN_OFFSET, (ulong) ((long) ev_len + len_delta)); + if (current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + int4store(ev + ev_len - BINLOG_CHECKSUM_LEN, + my_checksum(0, ev, ev_len - BINLOG_CHECKSUM_LEN)); + } + } + + /* + For a Table_map event, rewrite the copy sent to the replica so its table + name is 255 bytes, the largest a one-byte length can express, with no + terminating null. Growing the name shifts the column count and metadata + after it to the right and lengthens the event, so the event's declared + length is rewritten to match. Note that, for simplicity, the test that + invokes this is configured without checksumming. + */ + DBUG_EXECUTE_IF("binlog_sender_oversized_table_name", + { + if (event_type == TABLE_MAP_EVENT) + { + uchar *base= (uchar*) packet->ptr(); + uchar *ev= base + ev_offset; + uchar *dbnam_len_ptr= ev + LOG_EVENT_HEADER_LEN + TABLE_MAP_HEADER_LEN; + uint dblen= *dbnam_len_ptr; + uchar *tblnam_len_ptr= dbnam_len_ptr + dblen + 2; + uint tbllen= *tblnam_len_ptr; + size_t tblnam_len_off= (size_t) (tblnam_len_ptr - base); + size_t tblnam_off= tblnam_len_off + 1; + size_t tail_off= tblnam_off + tbllen + 1; + size_t tail_len= len - tail_off; + long delta= 256 - (long) (tbllen + 1); + + if (!packet->realloc((size_t) ((long) len + delta))) + { + base= (uchar*) packet->ptr(); + memmove(base + tail_off + delta, base + tail_off, tail_len); + base[tblnam_len_off]= 255; + memset(base + tblnam_off, 'a', 256); + len= (size_t) ((long) len + delta); + packet->length(len); + int4store(base + ev_offset + EVENT_LEN_OFFSET, + (uint32) (len - ev_offset)); + } + } + }); + + /* + Put a second copy of the event in the packet behind the first, the + other shape the same disagreement takes on the wire. This injection + leaves the header alone and grows the packet, so the bytes behind the + length the header declares form a complete event of their own, and a + replica that frames its relay log by those headers goes on to apply + that second event. + + Query events carry this injection, because a statement that runs a + second time inserts a row the master never sent, which the replica's + own data then shows. A row event cannot carry the injection. The + trailing copy reaches the applier after the leading copy's STMT_END_F + has closed the statement's tables and cleared the table map, so + Rows_log_event::do_apply_event() finds no table for the copy's table + id and returns without applying or reporting anything. + + The master must be logging no checksum for the trailing copy to + survive: the packet ends in the checksum bytes the trailing copy + carries, and those bytes cover that copy alone, not the packet. + */ + DBUG_EXECUTE_IF("binlog_sender_append_extra_event", + { + if (event_type == QUERY_EVENT) + { + String extra; + if (!extra.copy(packet->ptr() + ev_offset, len - ev_offset, + &my_charset_bin) && + !packet->append(extra)) + len= packet->length(); + } + }); + /* + Rewrite the body of a Table_map event so it declares columns whose types + need more field metadata than the event carries, to test that a replica + bounds the metadata before decoding it. Every column becomes + MYSQL_TYPE_STRING, which reads two metadata bytes, and the event is left + with a single metadata byte, so a replica missing the bound reads past the + metadata while decoding the columns. The rewrite keeps the column count the + master already wrote, which the test fixes at 24 columns, enough to carry + the metadata reads well past the end of the replica's allocation. + + The event is rewritten here, on the wire, rather than in the master's own + binary log, so the injection lands on the copy the dump thread sends and + the master's binary log stays well formed. + */ + DBUG_EXECUTE_IF("binlog_sender_undersized_table_map_metadata", + { + if (event_type == TABLE_MAP_EVENT) + { + /* The rewritten event can be one byte longer than the original. */ + packet->realloc(len + 1); + uchar *ev= (uchar*) packet->ptr() + ev_offset; + uchar *p= ev + LOG_EVENT_HEADER_LEN + TABLE_MAP_HEADER_LEN; + uint db_len= *p; + p+= 1 + db_len + 1; // db name length byte, name, null + uint tbl_len= *p; + p+= 1 + tbl_len + 1; // table name length byte, name, null + uint colcnt= *p++; // column count (one packed byte) + uchar *coltype= p; + + memset(coltype, MYSQL_TYPE_STRING, colcnt); + uchar *w= coltype + colcnt; + *w++= 1; // field metadata size (one byte) + *w++= 0; // the single metadata byte + uint null_bytes= (colcnt + 7) / 8; + memset(w, 0, null_bytes); + w+= null_bytes; + + ulong new_ev_len= (ulong) (w - ev); + if (current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + new_ev_len+= BINLOG_CHECKSUM_LEN; + int4store(ev + EVENT_LEN_OFFSET, new_ev_len); + if (current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + int4store(ev + new_ev_len - BINLOG_CHECKSUM_LEN, + my_checksum(0, ev, new_ev_len - BINLOG_CHECKSUM_LEN)); + len= ev_offset + new_ev_len; + packet->length(len); + } + }); +#endif + if (my_net_write(info->net, (uchar*) packet->ptr(), len)) { info->error= ER_UNKNOWN_ERROR; @@ -2483,6 +2674,8 @@ static int init_binlog_sender(binlog_send_info *info, static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, LOG_INFO *linfo, my_off_t start_pos) { + static constexpr const char* CORRUPT_FDE= + "Corrupt Format_description event found or out-of-memory"; int error; ulong ev_offset; THD *thd= info->thd; @@ -2549,9 +2742,14 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, DBUG_RETURN(1); } - info->current_checksum_alg= get_checksum_alg((uchar*) packet->ptr() + - ev_offset, - packet->length() - ev_offset); + if (unlikely(get_checksum_alg((uchar*) packet->ptr() + ev_offset, + packet->length() - ev_offset, + &(info->current_checksum_alg)))) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= CORRUPT_FDE; + DBUG_RETURN(1); + } DBUG_ASSERT(info->current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || info->current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || @@ -2579,8 +2777,7 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, ev_len, info->fdev))) { info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; - info->errmsg= "Corrupt Format_description event found " - "or out-of-memory"; + info->errmsg= CORRUPT_FDE; DBUG_RETURN(1); } delete info->fdev; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e4431325f68..b6b2c45fdbe 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -34587,6 +34587,13 @@ bool Sql_cmd_dml::execute(THD *thd) } unit->set_limit(select_lex); + /* + set_limit() evaluates the LIMIT expression and can raise an error, e.g. + ER_INVALID_DEFAULT_PARAM when DEFAULT is bound to a LIMIT placeholder. + Do not go on executing with an error already in the diagnostics area. + */ + if (thd->is_error()) + goto err; /* Perform statement-specific execution */ res = execute_inner(thd); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 46b5798e88c..66999c555a5 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2023, MariaDB + Copyright (c) 2009, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -601,7 +601,7 @@ static DYNAMIC_ARRAY ignore_db_dirs_array; A value for the read only system variable to show a list of ignored directories. */ -char *opt_ignore_db_dirs= NULL; +READ_ONLY_SYSVAR char *opt_ignore_db_dirs; /** This flag is ON if: @@ -716,11 +716,6 @@ ignore_db_dirs_reset() void ignore_db_dirs_free() { - if (opt_ignore_db_dirs) - { - my_free(opt_ignore_db_dirs); - opt_ignore_db_dirs= NULL; - } ignore_db_dirs_reset(); delete_dynamic(&ignore_db_dirs_array); my_hash_free(&ignore_db_dirs_hash); @@ -831,6 +826,7 @@ ignore_db_dirs_process_additions() len--; /* +1 the terminating zero */ + my_free(opt_ignore_db_dirs); ptr= opt_ignore_db_dirs= (char *) my_malloc(key_memory_ignored_db, len + 1, MYF(0)); if (!ptr) @@ -2824,37 +2820,31 @@ static const char *thread_state_info(THD *tmp) Privileged users can see all THDs. - @param user - user name or NULL for privileged users. - @param thd - THD + @param caller - security context of the acting thread + @param thd - THD to check if visible to the caller @retval true - THD visible in processlist @retval false - THD not visible in processlist */ -static bool thd_visible_in_processlist(const char *user, THD *thd) +static bool thd_visible_in_processlist(const Security_context *caller, THD *thd) { if (!thd->vio_ok() && !thd->system_thread) return false; // "something bad happened" thread, don't show it - if (!user) + if (caller->master_access & PRIV_STMT_SHOW_PROCESSLIST) return true; // privileged user can see all threads - - const char *thd_user= thd->security_ctx->user; - if (!thd_user) - return false; // dunno if this ever happens, safety first - bool user_or_event_worker_thread= - !thd->system_thread || thd->system_thread & SYSTEM_THREAD_EVENT_WORKER; + !thd->system_thread || thd->system_thread & SYSTEM_THREAD_EVENT_WORKER; - return user_or_event_worker_thread && !strcmp(thd_user, user); + return user_or_event_worker_thread && + thd->security_ctx->priv_user_matches(caller); } struct list_callback_arg { - list_callback_arg(const char *u, THD *t, ulong m): - user(u), thd(t), max_query_length(m) {} + list_callback_arg(THD *t, ulong m): thd(t), max_query_length(m) {} I_List thread_infos; - const char *user; THD *thd; ulong max_query_length; }; @@ -2865,7 +2855,7 @@ static my_bool list_callback(THD *tmp, list_callback_arg *arg) Security_context *tmp_sctx= tmp->security_ctx; bool got_thd_data; - if (thd_visible_in_processlist(arg->user, tmp)) + if (thd_visible_in_processlist(arg->thd->security_ctx, tmp)) { thread_info *thd_info= new (arg->thd->mem_root) thread_info; @@ -2948,17 +2938,21 @@ static my_bool list_callback(THD *tmp, list_callback_arg *arg) } -void mysqld_list_processes(THD *thd,const char *user, bool verbose) +void mysqld_list_processes(THD *thd, bool verbose) { Item *field; List field_list; - list_callback_arg arg(user, thd, - verbose ? thd->variables.max_allowed_packet : - PROCESS_LIST_WIDTH); + list_callback_arg arg(thd, verbose ? thd->variables.max_allowed_packet + : PROCESS_LIST_WIDTH); Protocol *protocol= thd->protocol; MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_list_processes"); + /* anonymous users cannot see anything */ + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd, PRIV_STMT_SHOW_PROCESSLIST)) + DBUG_VOID_RETURN; + field_list.push_back(new (mem_root) Item_int(thd, "Id", 0, MY_INT32_NUM_DECIMAL_DIGITS), mem_root); @@ -3199,30 +3193,24 @@ void select_result_text_buffer::save_to(String *res) int fill_show_explain_or_analyze(THD *thd, TABLE_LIST *table, COND *cond, bool json_format, bool is_analyze) { - const char *calling_user; THD *tmp; my_thread_id thread_id; DBUG_ENTER("fill_show_explain_or_analyze"); DBUG_ASSERT(cond==NULL); thread_id= thd->lex->value_list.head()->val_int(); - calling_user= (thd->security_ctx->master_access & PRIV_STMT_SHOW_EXPLAIN) ? - NullS : thd->security_ctx->priv_user; if ((tmp= find_thread_by_id(thread_id))) { - Security_context *tmp_sctx= tmp->security_ctx; MEM_ROOT explain_mem_root, *save_mem_root; /* - If calling_user==NULL, calling thread has SUPER or PROCESS - privilege, and so can do SHOW EXPLAIN/SHOW ANALYZE on any user. - - if calling_user!=NULL, he's only allowed to view + Same rule as for SHOW PROCESSLIST: + A thread with PROCESS privilege can do SHOW EXPLAIN/SHOW + ANALYZE on any user, everybody else is only allowed to view SHOW EXPLAIN/SHOW ANALYZE on his own threads. */ - if (calling_user && (!tmp_sctx->user || strcmp(calling_user, - tmp_sctx->user))) + if (!thd_visible_in_processlist(thd->security_ctx, tmp)) { my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS"); mysql_mutex_unlock(&tmp->LOCK_thd_kill); @@ -3368,11 +3356,8 @@ static my_bool processlist_callback(THD *tmp, processlist_callback_arg *arg) const char *val; ulonglong max_counter; bool got_thd_data; - char *user= - arg->thd->security_ctx->master_access & PRIV_STMT_SHOW_PROCESSLIST ? - NullS : arg->thd->security_ctx->priv_user; - if (!thd_visible_in_processlist(user, tmp)) + if (!thd_visible_in_processlist(arg->thd->security_ctx, tmp)) return 0; restore_record(arg->table, s->default_values); @@ -7359,9 +7344,8 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, Security_context *sctx= thd->security_ctx; if (!tables->allowed_show) { - if (!strcmp(tables->definer.user.str, sctx->priv_user) && - !my_strcasecmp(system_charset_info, tables->definer.host.str, - sctx->priv_host)) + if (sctx->is_priv_user(tables->definer.user.str, + tables->definer.host.str)) tables->allowed_show= TRUE; #ifndef NO_EMBEDDED_ACCESS_CHECKS else diff --git a/sql/sql_show.h b/sql/sql_show.h index 8795ddedf33..f86498d9459 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -1,5 +1,5 @@ /* Copyright (c) 2005, 2010, Oracle and/or its affiliates. - Copyright (c) 2012, 2016, MariaDB + Copyright (c) 2012, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -110,7 +110,7 @@ bool mysqld_show_create_db(THD *thd, LEX_CSTRING *db_name, LEX_CSTRING *orig_db_name, const DDL_options_st &options); -void mysqld_list_processes(THD *thd,const char *user,bool verbose); +void mysqld_list_processes(THD *thd, bool verbose); int mysqld_show_status(THD *thd); int mysqld_show_variables(THD *thd,const char *wild); bool mysqld_show_storage_engines(THD *thd); diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 55dce308729..91c9c375a96 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -621,10 +621,6 @@ Open streams: %10lu\n", #elif defined(HAVE_MALLINFO) struct mallinfo info= mallinfo(); #endif -#if __has_feature(memory_sanitizer) - /* Work around missing MSAN instrumentation */ - MEM_MAKE_DEFINED(&info, sizeof info); -#endif #if defined(HAVE_MALLINFO) || defined(HAVE_MALLINFO2) char llbuff[10][22]; printf("\nMemory status:\n\ diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 78ac2362c58..a95b697f150 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -8908,7 +8908,9 @@ void Type_handler_enum:: Column_definition_attributes_frm_pack(const Column_definition_attributes *def, uchar *buff) const { - DBUG_ASSERT(f_decimals(def->pack_flag & ~FIELDFLAG_INTERVAL) == 0); + DBUG_ASSERT(f_decimals(def->pack_flag & + ~(FIELDFLAG_INTERVAL| + FIELDFLAG_FRM_HEX_ENCODED_TYPELIB)) == 0); DBUG_ASSERT(def->decimals == 0); Type_handler::Column_definition_attributes_frm_pack(def, buff); } @@ -8918,7 +8920,9 @@ void Type_handler_set:: Column_definition_attributes_frm_pack(const Column_definition_attributes *def, uchar *buff) const { - DBUG_ASSERT(f_decimals(def->pack_flag & ~FIELDFLAG_BITFIELD) == 0); + DBUG_ASSERT(f_decimals(def->pack_flag & + ~(FIELDFLAG_BITFIELD| + FIELDFLAG_FRM_HEX_ENCODED_TYPELIB)) == 0); DBUG_ASSERT(def->decimals == 0); Type_handler::Column_definition_attributes_frm_pack(def, buff); } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index d86fda49535..2c446ef288e 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -779,10 +779,22 @@ bool select_unit_ext::send_eof() next_sl && next_sl->get_linkage() == INTERSECT_TYPE && !next_sl->distinct; + /* + True at the last INTERSECT of an inline INTERSECT DISTINCT subsequence: the + records that did not match every operand (their intersect counter differs + from curr_step) must be dropped. Instead of a separate table scan this is + folded into the scan/unfold loops below, so the table is scanned only once + (MDEV-38722). When the subsequence is materialized in a derived table this + filtering is done by select_unit::send_eof() instead. + */ + bool filter_intersect= curr_op_type == INTERSECT_DISTINCT && + !(next_sl && + next_sl->get_linkage() == INTERSECT_TYPE); bool need_unfold= disable_index_if_needed(curr_sl); if (((curr_sl->distinct && !is_next_distinct) || curr_op_type == INTERSECT_ALL || + filter_intersect || is_next_intersect_all) && !need_unfold) { @@ -804,6 +816,13 @@ bool select_unit_ext::send_eof() } break; } + if (filter_intersect && additional_cnt->val_int() != curr_step) + { + /* drop a record that did not match every INTERSECT operand */ + if (unlikely(error= delete_record())) + break; + continue; + } store_record(table, record[1]); if (curr_sl->distinct && !is_next_distinct) @@ -857,6 +876,13 @@ bool select_unit_ext::send_eof() } break; } + if (filter_intersect && additional_cnt->val_int() != curr_step) + { + /* drop a record that did not match every INTERSECT operand */ + if (unlikely(error= delete_record())) + break; + continue; + } dup_cnt= (ha_rows)duplicate_cnt->val_int(); /* delete record if not exist in the second operand */ if (dup_cnt == 0) @@ -2115,6 +2141,15 @@ void st_select_lex_unit::optimize_bag_operation(bool is_outer_distinct) sl->distinct= true; disable_index= sl; } + else if (disable_index) + { + /* + EXCEPT ALL needs the unique index in select_unit_ext::send_data(). + If the index would otherwise be released at an earlier node, + postpone the release until (at least) this operation. + */ + disable_index= sl; + } } } else @@ -3029,7 +3064,8 @@ void st_select_lex_unit::set_unique_exclude() bool st_select_lex_unit::check_distinct_in_union() { - if (union_distinct && !union_distinct->next_select()) + if (union_distinct && !union_distinct->next_select() && + union_distinct->distinct) return true; return false; } diff --git a/sql/strfunc.h b/sql/strfunc.h index b2b293e153f..c48c5161298 100644 --- a/sql/strfunc.h +++ b/sql/strfunc.h @@ -30,6 +30,20 @@ uint find_type(const TYPELIB *lib, const char *find, size_t length, uint find_type2(const TYPELIB *lib, const char *find, size_t length, CHARSET_INFO *cs); void unhex_type2(TYPELIB *lib); + +static inline bool typelib_with_0x00(const TYPELIB &typelib) +{ + for (uint pos= 0; pos < typelib.count; pos++) + { + for (uint i= 0; i < typelib.type_lengths[pos]; i++) + { + if (typelib.type_names[pos][i] == 0x00) + return true; + } + } + return false; +} + uint check_word(TYPELIB *lib, const char *val, const char *end, const char **end_of_word); int find_string_in_array(LEX_CSTRING * const haystack, diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 05a7df0fbdb..2a06379df5d 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2012, 2022, MariaDB Corporation. + Copyright (c) 2012, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1930,14 +1930,14 @@ static Sys_var_ulonglong Sys_max_heap_table_size( VALID_RANGE(16384, SIZE_T_MAX), DEFAULT(16*1024*1024), BLOCK_SIZE(1024)); -static ulong mdl_locks_cache_size; +READ_ONLY_SYSVAR static ulong mdl_locks_cache_size; static Sys_var_ulong Sys_metadata_locks_cache_size( "metadata_locks_cache_size", "Unused", READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, 1024*1024), DEFAULT(1024), BLOCK_SIZE(1)); -static ulong mdl_locks_hash_partitions; +READ_ONLY_SYSVAR static ulong mdl_locks_hash_partitions; static Sys_var_ulong Sys_metadata_locks_hash_instances( "metadata_locks_hash_instances", "Unused", READ_ONLY GLOBAL_VAR(mdl_locks_hash_partitions), CMD_LINE(REQUIRED_ARG), @@ -2050,7 +2050,7 @@ Sys_gtid_seq_no( #ifdef HAVE_REPLICATION -static unsigned char opt_gtid_binlog_pos_dummy; +READ_ONLY_SYSVAR static unsigned char opt_gtid_binlog_pos_dummy; static Sys_var_gtid_binlog_pos Sys_gtid_binlog_pos( "gtid_binlog_pos", "Last GTID logged to the binary log, per replication" "domain", @@ -2077,7 +2077,7 @@ Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, } -static unsigned char opt_gtid_current_pos_dummy; +READ_ONLY_SYSVAR static unsigned char opt_gtid_current_pos_dummy; static Sys_var_gtid_current_pos Sys_gtid_current_pos( "gtid_current_pos", "Current GTID position of the server. Per " "replication domain, this is either the last GTID replicated by a " @@ -3510,11 +3510,15 @@ static Sys_var_mybool Sys_require_secure_transport( ON_CHECK(check_require_secure_transport), ON_UPDATE(0)); static Sys_var_charptr_fscs Sys_secure_file_priv( - "secure_file_priv", - "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files " - "within specified directory", - PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv), - CMD_LINE(REQUIRED_ARG, OPT_SEQURE_FILE_PRIV), DEFAULT(0)); + "secure_file_priv", + "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files " + "within specified directory." +#ifndef _WIN32 + " Empty value means no limits except /proc" +#endif + , + PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv), + CMD_LINE(REQUIRED_ARG, OPT_SEQURE_FILE_PRIV), DEFAULT(0)); static bool check_server_id(sys_var *self, THD *thd, set_var *var) { @@ -3557,8 +3561,7 @@ Sys_server_id( VALID_RANGE(1, UINT_MAX32), DEFAULT(1), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_server_id), ON_UPDATE(fix_server_id)); -char *server_uid_ptr= &server_uid[0]; - +READ_ONLY_SYSVAR char *server_uid_ptr= &server_uid[0]; static Sys_var_charptr Sys_server_uid( "server_uid", "Automatically calculated server unique id hash", READ_ONLY GLOBAL_VAR(server_uid_ptr), @@ -4207,7 +4210,7 @@ static Sys_var_mybool Sys_sync_frm( GLOBAL_VAR(opt_sync_frm), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); -static char *system_time_zone_ptr; +READ_ONLY_SYSVAR static char *system_time_zone_ptr; static Sys_var_charptr Sys_system_time_zone( "system_time_zone", "The server system time zone", READ_ONLY GLOBAL_VAR(system_time_zone_ptr), @@ -4543,7 +4546,7 @@ static Sys_var_charptr Sys_version( CMD_LINE_HELP_ONLY, DEFAULT(server_version)); -static char *server_version_comment_ptr; +READ_ONLY_SYSVAR static char *server_version_comment_ptr; static Sys_var_charptr Sys_version_comment( "version_comment", "Value of the COMPILATION_COMMENT option " "specified by CMake when building MariaDB, for example " @@ -4552,14 +4555,14 @@ static Sys_var_charptr Sys_version_comment( CMD_LINE_HELP_ONLY, DEFAULT(MYSQL_COMPILATION_COMMENT)); -static char *server_version_compile_machine_ptr; +READ_ONLY_SYSVAR static char *server_version_compile_machine_ptr; static Sys_var_charptr Sys_version_compile_machine( "version_compile_machine", "The machine type or architecture " "MariaDB was built on, for example i686.", READ_ONLY GLOBAL_VAR(server_version_compile_machine_ptr), CMD_LINE_HELP_ONLY, DEFAULT(DEFAULT_MACHINE)); -static char *server_version_compile_os_ptr; +READ_ONLY_SYSVAR static char *server_version_compile_os_ptr; static Sys_var_charptr Sys_version_compile_os( "version_compile_os", "Operating system that MariaDB was built " "on, for example debian-linux-gnu.", @@ -4568,20 +4571,20 @@ static Sys_var_charptr Sys_version_compile_os( DEFAULT(SYSTEM_TYPE)); #include -static char *server_version_source_revision; +READ_ONLY_SYSVAR static char *server_version_source_revision; static Sys_var_charptr Sys_version_source_revision( "version_source_revision", "Source control revision id for MariaDB source code", READ_ONLY GLOBAL_VAR(server_version_source_revision), CMD_LINE_HELP_ONLY, DEFAULT(SOURCE_REVISION)); -static char *malloc_library; +READ_ONLY_SYSVAR static char *malloc_library; static Sys_var_charptr Sys_malloc_library( "version_malloc_library", "Version of the used malloc library", READ_ONLY GLOBAL_VAR(malloc_library), CMD_LINE_HELP_ONLY, DEFAULT(guess_malloc_library())); -static char *ssl_library; +READ_ONLY_SYSVAR static char *ssl_library; static Sys_var_charptr Sys_ssl_library( "version_ssl_library", "Version of the used SSL library", READ_ONLY GLOBAL_VAR(ssl_library), CMD_LINE_HELP_ONLY, @@ -5201,7 +5204,7 @@ static Sys_var_uint Sys_group_concat_max_len( VALID_RANGE(4, MAX_MAX_ALLOWED_PACKET), DEFAULT(1024*1024), BLOCK_SIZE(1)); -static char *glob_hostname_ptr; +READ_ONLY_SYSVAR static char *glob_hostname_ptr; static Sys_var_charptr Sys_hostname( "hostname", "Server host name", READ_ONLY GLOBAL_VAR(glob_hostname_ptr), NO_CMD_LINE, @@ -5252,7 +5255,7 @@ static Sys_var_mybool Sys_keep_files_on_create( NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), DEPRECATED(1008, "")); -static char *license; +READ_ONLY_SYSVAR static char *license; static Sys_var_charptr Sys_license( "license", "The type of license the server has", READ_ONLY GLOBAL_VAR(license), NO_CMD_LINE, @@ -5488,7 +5491,7 @@ static Sys_var_have Sys_have_symlink( # define SANITIZER_MODE "MSAN" # endif -static char *have_sanitizer; +READ_ONLY_SYSVAR static char *have_sanitizer; static Sys_var_charptr Sys_have_santitizer( "have_sanitizer", "If the server is compiled with sanitize (compiler option), this " @@ -5612,7 +5615,7 @@ static Sys_var_charptr_fscs Sys_relay_log( static Sys_var_charptr_fscs Sys_relay_log_index( "relay_log_index", "The location and name to use for the file " "that keeps a list of the last relay logs.", - READ_ONLY GLOBAL_VAR(relay_log_index), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(relay_log_index), NO_CMD_LINE, DEFAULT(0)); /* @@ -5622,19 +5625,19 @@ static Sys_var_charptr_fscs Sys_relay_log_index( */ static Sys_var_charptr_fscs Sys_binlog_index( "log_bin_index", "File that holds the names for last binary log files.", - READ_ONLY GLOBAL_VAR(log_bin_index), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(log_bin_index), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_relay_log_basename( "relay_log_basename", "The full path of the relay log file names, excluding the extension.", - READ_ONLY GLOBAL_VAR(relay_log_basename), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(relay_log_basename), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_log_bin_basename( "log_bin_basename", "The full path of the binary log file names, excluding the extension.", - READ_ONLY GLOBAL_VAR(log_bin_basename), NO_CMD_LINE, + PREALLOCATED READ_ONLY GLOBAL_VAR(log_bin_basename), NO_CMD_LINE, DEFAULT(0)); static Sys_var_charptr_fscs Sys_relay_log_info_file( @@ -6584,7 +6587,7 @@ static Sys_var_mybool Sys_wsrep_gtid_mode( "ignored (backward compatibility).", GLOBAL_VAR(wsrep_gtid_mode), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -static char *wsrep_patch_version_ptr; +READ_ONLY_SYSVAR static char *wsrep_patch_version_ptr; static Sys_var_charptr Sys_wsrep_patch_version( "wsrep_patch_version", "Wsrep patch version, for example wsrep_25.10.", READ_ONLY GLOBAL_VAR(wsrep_patch_version_ptr), CMD_LINE_HELP_ONLY, @@ -6665,7 +6668,7 @@ static Sys_var_charptr_fscs Sys_ignore_db_dirs( "Specifies a directory to add to the ignore list when collecting " "database names from the datadir. Put a blank argument to reset " "the list accumulated so far.", - READ_ONLY GLOBAL_VAR(opt_ignore_db_dirs), + PREALLOCATED READ_ONLY GLOBAL_VAR(opt_ignore_db_dirs), CMD_LINE(REQUIRED_ARG, OPT_IGNORE_DB_DIRECTORY), DEFAULT(0)); diff --git a/sql/sys_vars.inl b/sql/sys_vars.inl index b1299d82c4a..403a344ca68 100644 --- a/sql/sys_vars.inl +++ b/sql/sys_vars.inl @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2011, Oracle and/or its affiliates. - Copyright (c) 2010, 2020, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -85,22 +85,6 @@ #pragma clang diagnostic ignored "-Winvalid-offsetof" #endif -/* - special assert for sysvars. Tells the name of the variable, - and fails even in non-debug builds. - - It is supposed to be used *only* in Sys_var* constructors, - and has name_arg hard-coded to prevent incorrect usage. -*/ -#define SYSVAR_ASSERT(X) \ - while(!(X)) \ - { \ - fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ - DBUG_ASSERT(0); \ - exit(255); \ - } - - static const char *bool_values[3]= {"OFF", "ON", 0}; TYPELIB bool_typelib= CREATE_TYPELIB_FOR(bool_values); diff --git a/sql/table.cc b/sql/table.cc index e4a0399cb99..901e3a42dae 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -118,10 +118,8 @@ static std::atomic last_table_id; /* Functions defined in this file */ -static bool fix_type_pointers(const char ***typelib_value_names, - uint **typelib_value_lengths, - TYPELIB *point_to_type, uint types, - char *names, size_t names_length); +static bool fix_type_pointers(const char ***, const char **, uint **, + TYPELIB *, uint, char *, size_t); static field_index_t find_field(Field **fields, uchar *record, uint start, uint length); @@ -781,7 +779,7 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, { uint i, j, n_length; uint primary_key_parts= 0; - KEY_PART_INFO *key_part= NULL; + KEY_PART_INFO *key_part= NULL, *key_part_end= NULL; ulong *rec_per_key= NULL; DBUG_ASSERT(keyinfo == first_keyinfo); @@ -790,7 +788,7 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, if (!(keyinfo = (KEY*) alloc_root(&share->mem_root, len))) return 1; bzero((char*) keyinfo, len); - key_part= reinterpret_cast (keyinfo); + key_part= key_part_end= reinterpret_cast (keyinfo); } bzero((char*)first_keyinfo, sizeof(*first_keyinfo)); @@ -858,12 +856,15 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, sizeof(ulong) * *ext_key_parts))) return 1; bzero((char*) rec_per_key, sizeof(*rec_per_key) * *ext_key_parts); + key_part_end= key_part + *ext_key_parts; } keyinfo->key_part= key_part; keyinfo->rec_per_key= rec_per_key; for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++) { + if (key_part >= key_part_end) + return 1; if (strpos + (new_frm_ver >= 1 ? 9 : 7) >= frm_image_end) return 1; if (!(keyinfo->algorithm == HA_KEY_ALG_LONG_HASH)) @@ -919,13 +920,17 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, share->ext_key_parts+= keyinfo->ext_key_parts; DBUG_ASSERT(share->ext_key_parts <= *ext_key_parts); } - keynames->str= (char*) key_part; - keynames->length= strnmov(keynames->str, (char *) strpos, - frm_image_end - strpos) - keynames->str; - strpos+= keynames->length; - if (*strpos++) // key names are \0-terminated + if (key_part > key_part_end) return 1; - keynames->length++; // Include '\0', to make fix_type_pointers() happy. + size_t max_keyname_len= MY_MIN(len, (uint)(frm_image_end - strpos)); + keynames->str= (char*) key_part; + keynames->length= strnmov(keynames->str, (char *)strpos, + max_keyname_len) - keynames->str; + if (keynames->length >= max_keyname_len) + return 1; // meaning key name was not \0-terminated + keynames->length++; // include '\0', to make fix_type_pointers() happy. + strpos+= keynames->length; + len-= (uint)keynames->length; //reading index comments for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++) @@ -937,11 +942,13 @@ static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, keyinfo->comment.length= uint2korr(strpos); strpos+= 2; - if (strpos + keyinfo->comment.length >= frm_image_end) + if (strpos + keyinfo->comment.length >= frm_image_end || + keyinfo->comment.length > len) return 1; keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos, keyinfo->comment.length); strpos+= keyinfo->comment.length; + len-= (uint)keyinfo->comment.length; } DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) == (keyinfo->comment.length > 0)); @@ -1060,7 +1067,8 @@ static void mysql57_calculate_null_position(TABLE_SHARE *share, uchar **null_pos, uint *null_bit_pos, const uchar *strpos, - const uchar *vcol_screen_pos) + const uchar *vcol_screen_pos, + const uchar *vcol_screen_end) { uint field_pack_length= 17; @@ -1071,6 +1079,8 @@ static void mysql57_calculate_null_position(TABLE_SHARE *share, if ((strpos[10] & MYSQL57_GENERATED_FIELD)) { + if (vcol_screen_pos + MYSQL57_GCOL_HEADER_SIZE >= vcol_screen_end) + return; /* Skip virtual (not stored) generated field */ bool stored_in_db= vcol_screen_pos[3]; vcol_screen_pos+= (uint2korr(vcol_screen_pos + 1) + @@ -1796,6 +1806,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, const uchar *forminfo; const uchar *frm_image_end = frm_image + frm_length; uchar *record, *null_flags, *null_pos, *UNINIT_VAR(mysql57_vcol_null_pos); + uchar *data_start, *data_end; + uchar *first_stored= 0, *first_virtual= 0, *next_stored= 0, *next_virtual= 0; const uchar *disk_buff, *strpos; ulong pos, record_offset; ulong rec_buff_length; @@ -1803,13 +1815,13 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, KEY *keyinfo; KEY_PART_INFO *key_part= NULL; Field **field_ptr, *reg_field; - const char **interval_array; + const char **interval_array, **interval_array_end; uint *typelib_value_lengths= NULL; enum legacy_db_type legacy_db_type; my_bitmap_map *bitmaps; bool null_bits_are_used; uint vcol_screen_length; - uchar *vcol_screen_pos; + uchar *vcol_screen_pos, *vcol_screen_end; LEX_CUSTRING options; LEX_CSTRING se_name= empty_clex_str; KEY first_keyinfo; @@ -1819,7 +1831,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, bool vers_can_native= false, frm_created= 0; Field_data_type_info_array field_data_type_info_array; MEM_ROOT *old_root= thd->mem_root; - Virtual_column_info **table_check_constraints; + Virtual_column_info **table_check_constr; bool *interval_unescaped= NULL; extra2_fields extra2; bool extra_index_flags_present= FALSE; @@ -1860,6 +1872,14 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* Length of the MariaDB extra2 segment in the form file. */ len = uint2korr(frm_image+4); + if (frm_length < FRM_HEADER_SIZE + len || + !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len))) + goto err; + + forminfo= frm_image + pos; + if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end) + goto err; + if (read_extra2(frm_image, len, &extra2)) goto err; @@ -1879,17 +1899,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (!share->default_part_plugin) goto err; } -#endif - if (frm_length < FRM_HEADER_SIZE + len || - !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len))) - goto err; - - forminfo= frm_image + pos; - if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end) - goto err; - -#ifdef WITH_PARTITION_STORAGE_ENGINE if (frm_image[61] && !share->default_part_plugin) { enum legacy_db_type db_type= (enum legacy_db_type) (uint) frm_image[61]; @@ -1981,6 +1991,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->keys_in_use.init(keys); ext_key_parts= key_parts; + if ((key_parts && !keys) || key_parts < keys) + goto err; + if (extra2.index_flags.str && extra2.index_flags.length != keys) goto err; @@ -2142,12 +2155,10 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, { //reading long table comment if (next_chunk + 2 > buff_end) - { - DBUG_PRINT("error", - ("long table comment is not defined in .frm")); goto err; - } share->comment.length = uint2korr(next_chunk); + if (next_chunk + 2 + share->comment.length > buff_end) + goto err; if (! (share->comment.str= strmake_root(&share->mem_root, (char*)next_chunk + 2, share->comment.length))) { @@ -2156,17 +2167,18 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, next_chunk+= 2 + share->comment.length; } - DBUG_ASSERT(next_chunk <= buff_end); - if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS_legacy) { if (options.str) goto err; + if (next_chunk + 4 > buff_end) + goto err; options.length= uint4korr(next_chunk); options.str= next_chunk + 4; next_chunk+= options.length + 4; + if (next_chunk > buff_end) + goto err; } - DBUG_ASSERT(next_chunk <= buff_end); } else { @@ -2178,7 +2190,6 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->key_block_size= uint2korr(frm_image+62); keyinfo= share->key_info; - if (extra2.index_flags.str) extra_index_flags_present= TRUE; @@ -2223,6 +2234,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, disk_buff= frm_image + pos + FRM_FORMINFO_SIZE; share->fields= uint2korr(forminfo+258); + if (share->fields <= 0 || share->fields > MAX_FIELDS) + goto err; if (extra2.field_flags.str && extra2.field_flags.length != share->fields) goto err; pos= uint2korr(forminfo+260); /* Length of all screens */ @@ -2244,6 +2257,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->comment.length); } + if (hash_fields > share->fields || share->null_fields > share->fields) + goto err; + DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d vcol_screen_length: %d", interval_count,interval_parts, keys,n_length,int_length, com_length, vcol_screen_length)); /* @@ -2268,12 +2284,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, &share->field, (uint)(share->fields+1)*sizeof(Field*), &share->intervals, (uint)interval_count*sizeof(TYPELIB), &share->check_constraints, (uint) share->table_check_constraints * sizeof(Virtual_column_info*), - /* - This looks wrong: shouldn't it be (+2+interval_count) - instread of (+3) ? - */ - &interval_array, (uint) (share->fields+interval_parts+ keys+3)*sizeof(char *), - &typelib_value_lengths, total_typelib_value_count * sizeof(uint *), + &interval_array, (uint)total_typelib_value_count * sizeof(char *), + &typelib_value_lengths, (uint)total_typelib_value_count * sizeof(uint), &names, (uint) (n_length+int_length), &comment_pos, (uint) com_length, &vcol_screen_pos, vcol_screen_length, @@ -2289,10 +2301,13 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } field_ptr= share->field; - table_check_constraints= share->check_constraints; + interval_array_end= interval_array + total_typelib_value_count; + table_check_constr= share->check_constraints; read_length=(uint) (share->fields * field_pack_length + pos+ (uint) (n_length+int_length+com_length+ vcol_screen_length)); + if (disk_buff + read_length > frm_image_end) + goto err; strpos= disk_buff+pos; if (!interval_count) @@ -2306,21 +2321,24 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, com_length); memcpy(vcol_screen_pos, disk_buff+read_length-vcol_screen_length, vcol_screen_length); + vcol_screen_end= vcol_screen_pos + vcol_screen_length; - if (fix_type_pointers(&interval_array, &typelib_value_lengths, - &share->fieldnames, 1, names, n_length) || + if (fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, &share->fieldnames, 1, names, + n_length) || share->fieldnames.count != share->fields) goto err; - if (fix_type_pointers(&interval_array, &typelib_value_lengths, - share->intervals, interval_count, - names + n_length, int_length)) + if (fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, share->intervals, + interval_count, names + n_length, int_length)) goto err; - if (keynames.length && - (fix_type_pointers(&interval_array, &typelib_value_lengths, - &share->keynames, 1, keynames.str, keynames.length) || - share->keynames.count != keys)) + if ((keynames.length && + fix_type_pointers(&interval_array, interval_array_end, + &typelib_value_lengths, &share->keynames, 1, + keynames.str, keynames.length)) || + share->keynames.count != keys) goto err; /* Allocate handler */ @@ -2344,14 +2362,12 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, */ share->null_bytes= (share->null_fields + null_bit_pos + 7) / 8; } -#ifndef WE_WANT_TO_SUPPORT_VERY_OLD_FRM_FILES else { share->null_bytes= (share->null_fields+7)/8; null_flags= null_pos= record + 1 + share->reclength - share->null_bytes; null_bit_pos= 0; } -#endif use_hash= share->fields >= MAX_FIELDS_BEFORE_HASH; if (use_hash) @@ -2372,7 +2388,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, mysql57_vcol_null_bit_pos= null_bit_pos; mysql57_calculate_null_position(share, &mysql57_vcol_null_pos, &mysql57_vcol_null_bit_pos, - strpos, vcol_screen_pos); + strpos, vcol_screen_pos, vcol_screen_end); } /* Set system versioning information. */ @@ -2421,20 +2437,22 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (extra2.without_overlaps.str) { - if (extra2.application_period.str == NULL) + if (extra2.application_period.str == NULL || + extra2.without_overlaps.length < frm_keyno_size) goto err; const uchar *key_pos= extra2.without_overlaps.str; period.unique_keys= read_frm_keyno(key_pos); + if (period.unique_keys > keys || + extra2.without_overlaps.length != (period.unique_keys+1)*frm_keyno_size) + goto err; for (uint k= 0; k < period.unique_keys; k++) { key_pos+= frm_keyno_size; uint key_nr= read_frm_keyno(key_pos); + if (key_nr >= keys) + goto err; key_info[key_nr].without_overlaps= true; } - - if ((period.unique_keys + 1) * frm_keyno_size - != extra2.without_overlaps.length) - goto err; } if (extra2.field_data_type_info.length && @@ -2468,6 +2486,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, comment.str= (char*) comment_pos; comment.length= comment_length; comment_pos+= comment_length; + if (com_length < comment_length) + goto err; + com_length-= comment_length; } if (strpos[13] == MYSQL_TYPE_VIRTUAL && @@ -2480,7 +2501,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, */ uint vcol_info_length= (uint) strpos[12]; - if (!vcol_info_length) // Expect non-null expression + if (vcol_info_length < 4 || + vcol_screen_pos + vcol_info_length > vcol_screen_end) goto err; attr.frm_unpack_basic(strpos); @@ -2581,15 +2603,17 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, byte 4 = stored_in_db byte 5.. = expr */ - if ((uint)(vcol_screen_pos)[0] != 1) + if (vcol_screen_pos + MYSQL57_GCOL_HEADER_SIZE > vcol_screen_end || + vcol_screen_pos[0] != 1) goto err; vcol_info= new (&share->mem_root) Virtual_column_info(); uint vcol_info_length= uint2korr(vcol_screen_pos + 1); - if (!vcol_info_length) // Expect non-empty expression - goto err; vcol_info->set_vcol_type(vcol_screen_pos[3] ? VCOL_GENERATED_STORED : VCOL_GENERATED_VIRTUAL); vcol_info->utf8= 0; vcol_screen_pos+= vcol_info_length + MYSQL57_GCOL_HEADER_SIZE;; + if (!vcol_info_length || // Expect non-empty expression + vcol_screen_pos > vcol_screen_end) + goto err; share->virtual_fields++; } } @@ -2646,17 +2670,28 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, attr.pack_flag&= ~FIELDFLAG_LONG_DECIMAL; } - if (interval_nr && attr.charset->mbminlen > 1 && - !interval_unescaped[interval_nr - 1]) + if (interval_nr > interval_count) + goto err; + + if (interval_nr && (attr.charset->mbminlen > 1 || + (attr.pack_flag & FIELDFLAG_FRM_HEX_ENCODED_TYPELIB))) { /* - Unescape UCS2/UTF16/UTF32 intervals from HEX notation. - Note, ENUM/SET columns with equal value list share a single - copy of TYPELIB. Unescape every TYPELIB only once. + This flag is used only in FRM and is mangled with other flags. + Unset it to avoid unexpected bit combinations in the rest of the code. */ - TYPELIB *interval= share->intervals + interval_nr - 1; - unhex_type2(interval); - interval_unescaped[interval_nr - 1]= true; + attr.pack_flag&= ~FIELDFLAG_FRM_HEX_ENCODED_TYPELIB; + if (!interval_unescaped[interval_nr - 1]) + { + /* + Decode the interval from the HEX notation. + Note, ENUM/SET columns with equal value lists share a single + copy of TYPELIB. Unescape every TYPELIB only once. + */ + TYPELIB *interval= share->intervals + interval_nr - 1; + unhex_type2(interval); + interval_unescaped[interval_nr - 1]= true; + } } #ifndef TO_BE_DELETED_ON_PRODUCTION @@ -2739,6 +2774,52 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (!reg_field) // Not supported field type goto err; + /* + Verify that fields follow one another in the record without overlaps. + That is the next field starts where the previous field ended. + Virtual fields complicate the check. Stored fields follow each other in + the record order, virtual fields also follow each in the record order + but they're always at the end of the record, and can be interleaved + with normal fields in the table. + + We detect this dynamically - as long as the next field starts where the + previous ended, assume they're normal fields. When the next field starts + with a gap - it's the first virtual (and the future virtual fields must + follow that field). If the next field starts before the previous - it + means the table started from virtual fields and this is the first stored + field. Either ("gap" or "before") can happen only once. + */ + if (!next_stored) // first field, i == 0 + { + first_stored= reg_field->ptr; + next_stored= first_stored + reg_field->pack_length_in_rec(); + } + else if (reg_field->ptr == next_stored) // next stored field + next_stored+= reg_field->pack_length_in_rec(); + else if (reg_field->ptr > next_stored) // virtual + { + if (next_virtual) + { + if (reg_field->ptr != next_virtual) + goto err; + next_virtual+= reg_field->pack_length_in_rec(); + } + else + { + first_virtual= reg_field->ptr; + next_virtual= first_virtual + reg_field->pack_length_in_rec(); + } + } + else // reg_field < next_stored. can happen if the i=0 field was virtual + { + if (next_virtual) + goto err; + first_virtual= first_stored; + next_virtual= next_stored; + first_stored= reg_field->ptr; + next_stored= first_stored + reg_field->pack_length_in_rec(); + } + if (attr.unireg_check == Field::TIMESTAMP_DNUN_FIELD || attr.unireg_check == Field::TIMESTAMP_DN_FIELD) { @@ -2821,7 +2902,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* We want to store the value for the last bits */ swap_variables(uchar*, null_pos, mysql57_vcol_null_pos); swap_variables(uint, null_bit_pos, mysql57_vcol_null_bit_pos); - DBUG_ASSERT((null_pos + (null_bit_pos + 7) / 8) <= share->field[0]->ptr); + if ((null_pos + (null_bit_pos + 7) / 8) > share->field[0]->ptr) + goto err; } share->primary_key= MAX_KEY; @@ -2851,7 +2933,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, hash_keypart->fieldnr= hash_field_used_no + 1; hash_field= share->field[hash_field_used_no]; hash_field->flags|= LONG_UNIQUE_HASH_FIELD;//Used in parse_vcol_defs - DBUG_ASSERT(hash_field->invisible == INVISIBLE_FULL); + if (hash_field->invisible != INVISIBLE_FULL || + hash_field->pack_length() != HA_HASH_FIELD_LENGTH) + goto err; keyinfo->flags|= HA_NOSAME; share->virtual_fields++; share->stored_fields--; @@ -2879,7 +2963,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, key_part= keyinfo->key_part; for (i=0 ; i < keyinfo->user_defined_key_parts ;i++) { - DBUG_ASSERT(key_part[i].fieldnr > 0); + if (key_part[i].fieldnr <= 0 || key_part[i].fieldnr > share->fields) + goto err; // Table field corresponding to the i'th key part. Field *table_field= share->field[key_part[i].fieldnr - 1]; @@ -2994,6 +3079,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, for (i= 0; i < first_key_parts; i++) { uint fieldnr= keyinfo[0].key_part[i].fieldnr; + if (fieldnr <= 0 || fieldnr > share->fields) + goto err; if (share->field[fieldnr-1]->key_length() != keyinfo[0].key_part[i].length) { @@ -3046,6 +3133,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, { uint length_bytes= 0; uint fieldnr= keyinfo->key_part[i].fieldnr; + if (fieldnr <= 0 || fieldnr > share->fields) + goto err; field= share->field[fieldnr-1]; if (field->null_ptr) @@ -3123,7 +3212,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->default_values, (uint) key_part->offset, (uint) key_part->length); - if (!key_part->fieldnr) + if (key_part->fieldnr <= 0 || key_part->fieldnr > share->fields) goto err; field= key_part->field= share->field[key_part->fieldnr-1]; @@ -3280,7 +3369,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, DBUG_ASSERT(share->primary_key == MAX_KEY); } } - if (new_field_pack_flag <= 1) + if (new_field_pack_flag <= 1 && share->null_fields) { /* Old file format with default as not null */ uint null_length= (share->null_fields+7)/8; @@ -3293,8 +3382,6 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, /* Handle virtual expressions */ if (vcol_screen_length && share->frm_version >= FRM_VER_EXPRESSSIONS) { - uchar *vcol_screen_end= vcol_screen_pos + vcol_screen_length; - /* Skip header */ vcol_screen_pos+= FRM_VCOL_NEW_BASE_SIZE; share->vcol_defs.str+= FRM_VCOL_NEW_BASE_SIZE; @@ -3307,6 +3394,9 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, while (vcol_screen_pos < vcol_screen_end) { Virtual_column_info *vcol_info; + if (vcol_screen_end - vcol_screen_pos < FRM_VCOL_NEW_HEADER_SIZE) + goto err; + uint type= (uint) vcol_screen_pos[0]; uint field_nr= uint2korr(vcol_screen_pos+1); uint expr_length= uint2korr(vcol_screen_pos+3); @@ -3319,17 +3409,21 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (field_nr != UINT_MAX16) { - DBUG_ASSERT(field_nr < share->fields); + if (field_nr >= share->fields) + goto err; reg_field= share->field[field_nr]; } else { reg_field= 0; - DBUG_ASSERT(name_length); + if (!name_length || type != VCOL_CHECK_TABLE) + goto err; } vcol_screen_pos+= FRM_VCOL_NEW_HEADER_SIZE; vcol_info->set_vcol_type((enum_vcol_info_type) type); + if ((uint)(vcol_screen_end-vcol_screen_pos) < name_length+expr_length) + goto err; if (name_length) { vcol_info->name.str= strmake_root(&share->mem_root, @@ -3343,7 +3437,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, switch (type) { case VCOL_GENERATED_VIRTUAL: { - uint recpos; + if (reg_field->vcol_info) + goto err; reg_field->vcol_info= vcol_info; share->virtual_fields++; share->stored_fields--; @@ -3352,35 +3447,41 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (reg_field->flags & PART_KEY_FLAG) vcol_info->set_vcol_type(VCOL_GENERATED_VIRTUAL_INDEXED); /* Correct stored_rec_length as non stored fields are last */ - recpos= (uint) (reg_field->ptr - record); + uint recpos= (uint) (reg_field->ptr - record); if (share->stored_rec_length >= recpos) share->stored_rec_length= recpos-1; break; } case VCOL_GENERATED_STORED: - DBUG_ASSERT(!reg_field->vcol_info); + if (reg_field->vcol_info) + goto err; reg_field->vcol_info= vcol_info; share->virtual_fields++; break; case VCOL_DEFAULT: - DBUG_ASSERT(!reg_field->default_value); + if (reg_field->default_value) + goto err; reg_field->default_value= vcol_info; share->default_expressions++; break; case VCOL_CHECK_FIELD: - DBUG_ASSERT(!reg_field->check_constraint); + if (reg_field->check_constraint) + goto err; reg_field->check_constraint= vcol_info; share->field_check_constraints++; break; case VCOL_CHECK_TABLE: - *(table_check_constraints++)= vcol_info; + if ((uint)(table_check_constr - share->check_constraints) >= + share->table_check_constraints) + goto err; + *(table_check_constr++)= vcol_info; break; } } } - DBUG_ASSERT((uint) (table_check_constraints - share->check_constraints) == - (uint) (share->table_check_constraints - - share->field_check_constraints)); + if (table_check_constr - share->check_constraints != + (int)share->table_check_constraints - (int)share->field_check_constraints) + goto err; if (options.str) { @@ -3433,6 +3534,19 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->can_cmp_whole_record= (share->blob_fields == 0 && share->varchar_fields == 0); + data_start= share->default_values; + data_end= data_start + share->reclength; + if (share->null_field_first) + data_start+= share->null_bytes; + else + data_end-= share->null_bytes; + + if (data_end < data_start || + (first_virtual && first_virtual != next_stored) || + first_stored < data_start || + (next_virtual ? next_virtual : next_stored) > data_end) + goto err; + share->column_bitmap_size= bitmap_buffer_size(share->fields); bitmap_count= 1; @@ -4930,19 +5044,20 @@ void open_table_error(TABLE_SHARE *share, enum open_frm_error error, } /* open_table_error */ - /* - ** fix a str_type to a array type - ** typeparts separated with some char. differents types are separated - ** with a '\0' - */ +/* + fix a str_type to a array type + typeparts separated with some char. differents types are separated + with a '\0' +*/ static bool -fix_type_pointers(const char ***typelib_value_names, +fix_type_pointers(const char ***typelib_value_names, const char **names_end, uint **typelib_value_lengths, TYPELIB *point_to_type, uint types, char *ptr, size_t length) { const char *end= ptr + length; + names_end--; // simplify the check below, reserve place for 0 at the end while (types--) { @@ -4974,7 +5089,7 @@ fix_type_pointers(const char ***typelib_value_names, { // Now scan the next value+sep pair char *vend= (char*) memchr(ptr, sep, end - ptr); - if (!vend) + if (!vend || *typelib_value_names >= names_end) return true; // Bad format *((*typelib_value_names)++)= ptr; *((*typelib_value_lengths)++)= (uint) (vend - ptr); diff --git a/sql/table_cache.cc b/sql/table_cache.cc index 7dd8c0c9ba6..5cae8c727c3 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2012, Oracle and/or its affiliates. - Copyright (c) 2010, 2022, MariaDB Corporation. + Copyright (c) 2010, 2026, MariaDB plc. Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation This program is free software; you can redistribute it and/or modify @@ -56,7 +56,7 @@ /** Configuration. */ ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ ulong tc_size; /**< Table cache threshold for LRU eviction. */ -uint32 tc_instances; +READ_ONLY_SYSVAR uint32 tc_instances; static size_t tc_allocated_size; static std::atomic tc_active_instances(1); static std::atomic tc_contention_warning_reported; diff --git a/sql/threadpool_common.cc b/sql/threadpool_common.cc index 1dd30958883..0dd7db08d1f 100644 --- a/sql/threadpool_common.cc +++ b/sql/threadpool_common.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2012, 2020, MariaDB +/* Copyright (C) 2012, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -43,7 +43,7 @@ uint threadpool_max_size; uint threadpool_stall_limit; uint threadpool_max_threads; uint threadpool_oversubscribe; -uint threadpool_mode; +READ_ONLY_SYSVAR uint threadpool_mode; uint threadpool_prio_kickup_timer; my_bool threadpool_exact_stats; my_bool threadpool_dedicated_listener; diff --git a/sql/unireg.cc b/sql/unireg.cc index d2b3eda34b2..82db24a0ee3 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -33,6 +33,7 @@ #include "create_options.h" #include "discover.h" #include +#include "strfunc.h" // typelib_with_0x00 #define FCOMP 17 /* Bytes for a packed field */ @@ -894,13 +895,17 @@ static bool pack_header(THD *thd, uchar *forminfo, if (field->interval) { uint old_int_count=int_count; + bool typelib_with_z= typelib_with_0x00(*field->interval); - if (field->charset->mbminlen > 1) + if (field->charset->mbminlen > 1 || typelib_with_z) { TYPELIB *tmpint; + if (typelib_with_z && field->charset->mbminlen == 1) + field->pack_flag|= FIELDFLAG_FRM_HEX_ENCODED_TYPELIB; /* Escape UCS2 intervals using HEX notation to avoid problems with delimiters between enum elements. + Do the same for single byte intervals with 0x00 characters. As the original representation is still needed in the function make_empty_rec to create a record of filled with default values it is saved in save_interval diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 586f4b3c0ed..dc9809ad526 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -73,7 +73,7 @@ const char *wsrep_SR_store_types[]= { "none", "table", NullS }; extern my_bool plugins_are_initialized; /* System variables. */ -const char *wsrep_provider; +READ_ONLY_SYSVAR const char *wsrep_provider; const char *wsrep_provider_options; const char *wsrep_cluster_address; const char *wsrep_cluster_name; @@ -81,11 +81,11 @@ const char *wsrep_node_name; const char *wsrep_node_address; const char *wsrep_node_incoming_address; const char *wsrep_start_position; -const char *wsrep_data_home_dir; +READ_ONLY_SYSVAR const char *wsrep_data_home_dir; const char *wsrep_dbug_option; -const char *wsrep_notify_cmd; -const char *wsrep_status_file; -const char *wsrep_allowlist; +READ_ONLY_SYSVAR const char *wsrep_notify_cmd; +READ_ONLY_SYSVAR const char *wsrep_status_file; +READ_ONLY_SYSVAR const char *wsrep_allowlist; ulong wsrep_debug; // Debug level logging my_bool wsrep_convert_LOCK_to_trx; // Convert locking sessions to trx @@ -93,7 +93,7 @@ my_bool wsrep_auto_increment_control; // Control auto increment variab my_bool wsrep_drupal_282555_workaround; // Retry autoinc insert after dupkey my_bool wsrep_certify_nonPK; // Certify, even when no primary key ulong wsrep_certification_rules = WSREP_CERTIFICATION_RULES_STRICT; -my_bool wsrep_recovery; // Recovery +READ_ONLY_SYSVAR my_bool wsrep_recovery; // Recovery my_bool wsrep_log_conflicts; my_bool wsrep_load_data_splitting= 0; // Commit load data every 10K intervals my_bool wsrep_slave_UK_checks; // Slave thread does UK checks @@ -128,7 +128,7 @@ long wsrep_max_protocol_version= 4; // Maximum protocol version to u long int wsrep_protocol_version= wsrep_max_protocol_version; ulong wsrep_trx_fragment_unit= WSREP_FRAG_BYTES; // unit for fragment size -ulong wsrep_SR_store_type= WSREP_SR_STORE_TABLE; +READ_ONLY_SYSVAR ulong wsrep_SR_store_type= WSREP_SR_STORE_TABLE; uint wsrep_ignore_apply_errors= 0; std::atomic wsrep_thread_create_failed; @@ -3399,10 +3399,11 @@ static void wsrep_mdl_log(wsrep_mdl_log_t level, */ static void wsrep_log_state(const char *msg, const THD *thd, bool granted) { - char buff[2048]; + char buff[2048]={'\0'}; String buffer(buff, sizeof(buff), system_charset_info); + buffer.length(0); wsrep_get_state(thd, granted, &buffer); - WSREP_DEBUG(msg, buffer.c_ptr()); + WSREP_DEBUG("%s %s", msg, buffer.c_ptr()); } /** This function handles MDL-conflict when thread holding MDL-lock @@ -3431,8 +3432,8 @@ static void wsrep_handle_granted_bf( if (wsrep_debug) { - wsrep_log_state("wsrep_handle_granted_bf() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_granted_bf() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_granted_bf(): ", request_thd, false); + wsrep_log_state("wsrep_handle_granted_bf(): ", granted_thd, true); } if (wsrep_thd_is_aborting(granted_thd)) @@ -3483,8 +3484,8 @@ static void wsrep_handle_locked(THD* request_thd, if (wsrep_debug) { - wsrep_log_state("wsrep_handle_locked() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_locked() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_locked(): ", request_thd, false); + wsrep_log_state("wsrep_handle_locked(): ", granted_thd, true); } if (granted_thd->current_backup_stage != BACKUP_FINISHED && @@ -3523,8 +3524,8 @@ static void wsrep_abort_granted(THD* request_thd, if (wsrep_debug) { - wsrep_log_state("wsrep_abort_granted() : (%s)", request_thd, false); - wsrep_log_state("wsrep_abort_granted() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_abort_granted(): ", request_thd, false); + wsrep_log_state("wsrep_abort_granted(): ", granted_thd, true); } wsrep_mdl_log(WSREP_MDL_DEBUG, "MDL conflict-> BF abort", @@ -3597,8 +3598,8 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx, if (wsrep_debug) { - wsrep_log_state("wsrep_handle_mdl_conflict() : (%s)", request_thd, false); - wsrep_log_state("wsrep_handle_mdl_conflict() : (%s)", granted_thd, true); + wsrep_log_state("wsrep_handle_mdl_conflict(): ", request_thd, false); + wsrep_log_state("wsrep_handle_mdl_conflict(): ", granted_thd, true); } if (granted_thd->wsrep_aborter != 0) diff --git a/sql/wsrep_plugin.cc b/sql/wsrep_plugin.cc index b6446afe4aa..1e0e94df8b5 100644 --- a/sql/wsrep_plugin.cc +++ b/sql/wsrep_plugin.cc @@ -20,15 +20,11 @@ in favor of single options which are initialized from provider. */ -#include "sql_plugin.h" -#include "sql_priv.h" -#include "sql_class.h" -#include "set_var.h" - #include "my_global.h" #include "mysqld_error.h" #include +#include "wsrep_on.h" // WSREP_ON #include "wsrep_mysqld.h" #include "wsrep/provider_options.hpp" #include "wsrep_server_state.h" @@ -286,21 +282,12 @@ static int wsrep_provider_plugin_init(void *p) } provider_plugin_enabled= true; - - // When plugin-wsrep-provider is enabled we set - // wsrep_provider_options parameter as READ_ONLY - sys_var *my_var= find_sys_var(current_thd, "wsrep_provider_options"); - int flags= my_var->get_flags(); - my_var->update_flags(flags |= (int)sys_var::READONLY); return 0; } static int wsrep_provider_plugin_deinit(void *p) { WSREP_DEBUG("wsrep_provider_plugin_deinit()"); - sys_var *my_var= find_sys_var(current_thd, "wsrep_provider_options"); - int flags= my_var->get_flags(); - my_var->update_flags(flags &= (int)~sys_var::READONLY); return 0; } diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc index 96756768026..ca64ad25b3c 100644 --- a/sql/wsrep_sst.cc +++ b/sql/wsrep_sst.cc @@ -403,9 +403,6 @@ static bool wsrep_sst_complete (THD* thd, Wsrep_server_state& server_state= Wsrep_server_state::instance(); enum wsrep::server_state::state state= server_state.state(); bool failed= false; - char start_pos_buf[FN_REFLEN]; - ssize_t len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN-1); - start_pos_buf[len]='\0'; // Do not call sst_received if we are not in joiner or // initialized state on server. This is because it @@ -420,14 +417,31 @@ static bool wsrep_sst_complete (THD* thd, } else { - WSREP_INFO("SST succeeded for position %s", start_pos_buf); + /* + Note: sst_received() does NOT use sst_gtid (the position reported by + the SST script). It determines the position internally from storage via + Wsrep_server_service::get_position(). + For physical SST methods these two may differ (e.g. the joiner's storage + recovers to an earlier position than the script reported). Log the + position actually adopted, not the script-reported one, to avoid + confusion. + */ + wsrep::gtid const received_gtid= wsrep_get_SE_checkpoint(); + char recv_pos_buf[FN_REFLEN]; + ssize_t const recv_len= + wsrep::print_to_c_str(received_gtid, recv_pos_buf, FN_REFLEN-1); + recv_pos_buf[recv_len > 0 ? recv_len : 0]= '\0'; + WSREP_INFO("SST succeeded for position %s", recv_pos_buf); } } else { + char start_pos_buf[FN_REFLEN]; + ssize_t const len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN - 1); + start_pos_buf[len > 0 ? len : 0]= '\0'; + WSREP_ERROR("SST failed for position %s initialized %d server_state %s", - start_pos_buf, - server_state.is_initialized(), + start_pos_buf, server_state.is_initialized(), wsrep::to_c_string(state)); failed= true; } diff --git a/sql/wsrep_var.cc b/sql/wsrep_var.cc index 3714902d6e9..6ab8ff3f349 100644 --- a/sql/wsrep_var.cc +++ b/sql/wsrep_var.cc @@ -84,7 +84,7 @@ static bool refresh_provider_options() { std::string opts= Wsrep_server_state::instance().provider().options(); wsrep_provider_options_init(opts.c_str()); - get_provider_option_value(wsrep_provider_options, + get_provider_option_value(opts.c_str(), (char*)"repl.max_ws_size", &wsrep_max_ws_size); return false; @@ -534,7 +534,9 @@ bool wsrep_provider_options_check(sys_var *self, THD* thd, set_var* var) } if (wsrep_provider_plugin_enabled()) { - my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0), var->var->name.str, "read only"); + my_message(ER_WRONG_ARGUMENTS, + "wsrep_provider_options cannot be changed while the " + "wsrep-provider plugin is loaded", MYF(0)); return true; } return false; diff --git a/storage/archive/ha_archive.cc b/storage/archive/ha_archive.cc index 1484244a428..d3e5477c709 100644 --- a/storage/archive/ha_archive.cc +++ b/storage/archive/ha_archive.cc @@ -949,14 +949,21 @@ uint32 ha_archive::max_row_length(const uchar *record) unsigned int ha_archive::pack_row(const uchar *record, azio_stream *writer) { uchar *ptr; + uint32 max_len; my_ptrdiff_t const rec_offset= record - table->record[0]; DBUG_ENTER("ha_archive::pack_row"); - if (fix_rec_buff(max_row_length(record))) - DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ + max_len= max_row_length(record); if (writer->version == 1) + { + if (fix_rec_buff(max_len)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ DBUG_RETURN(pack_row_v1(record)); + } + + if (fix_rec_buff(max_len + ARCHIVE_ROW_HEADER_SIZE)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* Copy null bits */ memcpy(record_buffer->buffer+ARCHIVE_ROW_HEADER_SIZE, @@ -1620,6 +1627,46 @@ int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt) while (!(rc= get_row(&archive, table->record[0]))) { + /* + If the source is version 3, unpack_row() stores blob data in + record_buffer. Since pack_row() also uses record_buffer for + packing, we must copy blob data out first to avoid corruption. + Version 2 already stores blobs in the 'buffer' member. + */ + if (archive.version >= 3) + { + uint *blob_ptr, *blob_end; + size_t total_blob_length= 0; + for (blob_ptr= table->s->blob_field, + blob_end= blob_ptr + table->s->blob_fields; + blob_ptr != blob_end; blob_ptr++) + { + Field_blob *blob= (Field_blob*) table->field[*blob_ptr]; + if (!blob->is_null()) + total_blob_length+= blob->get_length(); + } + if (buffer.alloc(total_blob_length)) + { + rc= HA_ERR_OUT_OF_MEM; + break; + } + char *pos= (char*) buffer.ptr(); + for (blob_ptr= table->s->blob_field, + blob_end= blob_ptr + table->s->blob_fields; + blob_ptr != blob_end; blob_ptr++) + { + Field_blob *blob= (Field_blob*) table->field[*blob_ptr]; + if (blob->is_null()) + continue; + uint32 length= blob->get_length(); + if (length) + { + memcpy(pos, blob->get_ptr(), length); + blob->set_ptr(length, (uchar*) pos); + pos+= length; + } + } + } real_write_row(table->record[0], &writer); /* Long term it should be possible to optimize this so that @@ -1639,6 +1686,7 @@ int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt) tmp_restore_column_map(&table->read_set, org_bitmap); share->rows_recorded= (ha_rows)writer.rows; + buffer.free(); } DBUG_PRINT("info", ("recovered %llu archive rows", diff --git a/storage/columnstore/columnstore b/storage/columnstore/columnstore index 962e861c6a1..79f0711858e 160000 --- a/storage/columnstore/columnstore +++ b/storage/columnstore/columnstore @@ -1 +1 @@ -Subproject commit 962e861c6a11e375cd95c78a517ab2b5d50e0076 +Subproject commit 79f0711858e34ecb188089baf109efab1f5fceed diff --git a/storage/connect/CMakeLists.txt b/storage/connect/CMakeLists.txt index fe4269859fb..f11b3dc772e 100644 --- a/storage/connect/CMakeLists.txt +++ b/storage/connect/CMakeLists.txt @@ -29,14 +29,14 @@ SET(CONNECT_SOURCES ha_connect.cc connect.cc user_connect.cc mycat.cc fmdlex.c osutil.c rcmsg.c rcmsg.h array.cpp blkfil.cpp colblk.cpp csort.cpp -filamap.cpp filamdbf.cpp filamfix.cpp filamgz.cpp filamtxt.cpp filter.cpp +filamap.cpp filamdbf.cpp filamfix.cpp filamgz.cpp filamtxt.cpp filechk.cpp filter.cpp json.cpp jsonudf.cpp maputil.cpp myconn.cpp myutil.cpp plgdbutl.cpp plugutil.cpp reldef.cpp tabcol.cpp tabdos.cpp tabext.cpp tabfix.cpp tabfmt.cpp tabjson.cpp table.cpp tabmul.cpp tabmysql.cpp taboccur.cpp tabpivot.cpp tabsys.cpp tabtbl.cpp tabutil.cpp tabvir.cpp tabxcl.cpp valblk.cpp value.cpp xindex.cpp xobject.cpp array.h blkfil.h block.h catalog.h checklvl.h colblk.h connect.h csort.h -engmsg.h filamap.h filamdbf.h filamfix.h filamgz.h filamtxt.h +engmsg.h filamap.h filamdbf.h filamfix.h filamgz.h filamtxt.h filechk.h filter.h global.h ha_connect.h inihandl.h json.h jsonudf.h maputil.h msgid.h mycat.h myconn.h myutil.h os.h osutil.h plgcnx.h plgdbsem.h preparse.h reldef.h resource.h tabcol.h tabdos.h tabext.h tabfix.h tabfmt.h tabjson.h tabmul.h diff --git a/storage/connect/bsonudf.cpp b/storage/connect/bsonudf.cpp index c12eefb5b67..41c721f6227 100644 --- a/storage/connect/bsonudf.cpp +++ b/storage/connect/bsonudf.cpp @@ -16,6 +16,7 @@ #include #include "bsonudf.h" +#include "filechk.h" #if defined(UNIX) || defined(UNIV_LINUX) #define _O_RDONLY O_RDONLY @@ -4478,6 +4479,16 @@ char *bson_file(UDF_INIT *initid, UDF_ARGS *args, char *result, PlugSubSet(g->Sarea, g->Sarea_Size); fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *is_null = 1; + return NULL; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *is_null = 1; + return NULL; + } + if (args->arg_count > 1) { int pretty = 3, pty = 3; size_t len; @@ -4629,7 +4640,10 @@ char *bfile_make(UDF_INIT *initid, UDF_ARGS *args, char *result, } // endswitch arg_type if (fn) { - if (!bnx.Serialize(g, jvp, fn, pretty)) + if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + fn = NULL; + } else if (!bnx.Serialize(g, jvp, fn, pretty)) PUSH_WARNING(g->Message); } else PUSH_WARNING("Missing file name"); @@ -6065,6 +6079,16 @@ char *bbin_file(UDF_INIT *initid, UDF_ARGS *args, char *result, fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *error = 1; + goto fin; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *error = 1; + goto fin; + } + for (unsigned int i = 1; i < args->arg_count; i++) if (args->arg_type[i] == INT_RESULT && *(longlong*)args->args[i] < 4) { pretty = (int) * (longlong*)args->args[i]; diff --git a/storage/connect/filamdbf.cpp b/storage/connect/filamdbf.cpp index ee65e0cf5db..d0972ac131f 100644 --- a/storage/connect/filamdbf.cpp +++ b/storage/connect/filamdbf.cpp @@ -246,7 +246,7 @@ PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, PTOS topt, bool info) bool bad, mul; PCSZ target, pwd; DBFHEADER mainhead, *hp = NULL; - DESCRIPTOR thisfield, *tfp; + DESCRIPTOR thisfield, *tfp= &thisfield; FILE *infile = NULL; UNZIPUTL *zutp = NULL; PQRYRES qrp; @@ -308,7 +308,6 @@ PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, PTOS topt, bool info) return NULL; } // endif dbfhead - tfp = &thisfield; } // endif zipped /************************************************************************/ diff --git a/storage/connect/filechk.cpp b/storage/connect/filechk.cpp new file mode 100644 index 00000000000..03e88633e99 --- /dev/null +++ b/storage/connect/filechk.cpp @@ -0,0 +1,41 @@ +/* Copyright (C) MariaDB Corporation Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +#include +#include +#include +#include "filechk.h" + +/* + @return true if the current user may access path. +*/ +bool connect_can_access_file(const char *path) +{ + char real_path[FN_REFLEN]; + + if (!path) + return false; /* callers must check for NULL before calling */ + + if (check_global_access(current_thd, FILE_ACL, true)) + return false; + + /* MY_SAFE_PATH returns NULL on overflow; fail closed rather than validate a truncated path */ + if (!fn_format(real_path, path, mysql_real_data_home, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME | MY_RETURN_REAL_PATH | MY_SAFE_PATH)) + return false; + + return is_secure_file_path(real_path); +} diff --git a/storage/connect/filechk.h b/storage/connect/filechk.h new file mode 100644 index 00000000000..75ab7eda09e --- /dev/null +++ b/storage/connect/filechk.h @@ -0,0 +1,20 @@ +/* Copyright (C) MariaDB Corporation Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +/* File access checks for CONNECT file UDFs */ +#pragma once +bool connect_can_access_file(const char *path); + diff --git a/storage/connect/jsonudf.cpp b/storage/connect/jsonudf.cpp index cc916feaeaa..e11cb2a466b 100644 --- a/storage/connect/jsonudf.cpp +++ b/storage/connect/jsonudf.cpp @@ -15,6 +15,7 @@ #include #include "jsonudf.h" +#include "filechk.h" #if defined(UNIX) || defined(UNIV_LINUX) #define _O_RDONLY O_RDONLY @@ -4562,6 +4563,16 @@ char *json_file(UDF_INIT *initid, UDF_ARGS *args, char *result, PlugSubSet(g->Sarea, g->Sarea_Size); fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *is_null = 1; + return NULL; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *is_null = 1; + return NULL; + } + if (args->arg_count > 1) { int pretty = 3, pty = 3; size_t len; @@ -4715,7 +4726,10 @@ char *jfile_make(UDF_INIT *initid, UDF_ARGS *args, char *result, } // endswitch arg_type if (fn) { - if (!Serialize(g, jvp->GetJson(), fn, pretty)) + if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + fn = NULL; + } else if (!Serialize(g, jvp->GetJson(), fn, pretty)) PUSH_WARNING(g->Message); } else PUSH_WARNING("Missing file name"); @@ -5767,8 +5781,8 @@ my_bool jbin_file_init(UDF_INIT *initid, UDF_ARGS *args, char *message) if (args->arg_count < 1 || args->arg_count > 4) { strcpy(message, "This function only accepts 1 to 4 arguments"); return true; - } else if (args->arg_type[0] != STRING_RESULT || !args->args[0]) { - strcpy(message, "First argument must be a constant string (file name)"); + } else if (args->arg_type[0] != STRING_RESULT) { + strcpy(message, "First argument must be a string (file name)"); return true; } // endifs @@ -5786,7 +5800,10 @@ my_bool jbin_file_init(UDF_INIT *initid, UDF_ARGS *args, char *message) initid->maybe_null = 1; CalcLen(args, false, reslen, memlen); - fl = GetFileLength(args->args[0]); + if (args->args[0]) + fl = GetFileLength(args->args[0]); + else + fl = 100; // What can be done here? reslen += fl; more += fl * M; //memlen += more; @@ -5811,6 +5828,16 @@ char *jbin_file(UDF_INIT *initid, UDF_ARGS *args, char *result, g->Xchk = NULL; fn = MakePSZ(g, args, 0); + if (!fn) { + PUSH_WARNING("Missing file name"); + *error = 1; + goto fin; + } else if (!connect_can_access_file(fn)) { + PUSH_WARNING("Access denied: FILE privilege or secure_file_priv violation"); + *error = 1; + goto fin; + } + for (unsigned int i = 1; i < args->arg_count; i++) if (args->arg_type[i] == INT_RESULT && *(longlong*)args->args[i] < 4) { pretty = (int) * (longlong*)args->args[i]; diff --git a/storage/connect/mysql-test/connect/r/drop-open-error.result b/storage/connect/mysql-test/connect/r/drop-open-error.result index f9b9b7e87d2..8f5341bbcce 100644 --- a/storage/connect/mysql-test/connect/r/drop-open-error.result +++ b/storage/connect/mysql-test/connect/r/drop-open-error.result @@ -5,11 +5,3 @@ drop table mdev9949; Warnings: Warning 1017 Can't find file: 'DATADIR/test/mdev9949.dos' (errno: 2 "No such file or directory") drop table t1; -select @@secure_file_priv 'must be NULL'; -must be NULL -NULL -create table t1 (a char(16)) engine=myisam; -insert into t1 values('Hello World!'); -create table t2 engine=connect file_name='foo/bar.txt' as select * from t1; -ERROR HY000: Got error 174 'Open(a+b) error 2 on foo/bar.txt: No such file or directory' from CONNECT -drop table t1; diff --git a/storage/connect/mysql-test/connect/r/fix.result b/storage/connect/mysql-test/connect/r/fix.result index d6cdad0373b..2b5f2595da2 100644 --- a/storage/connect/mysql-test/connect/r/fix.result +++ b/storage/connect/mysql-test/connect/r/fix.result @@ -129,3 +129,13 @@ Sam Chicago 1979-11-22 2007-10-10 James Dallas 1992-05-13 2009-12-14 Bill Boston 1986-09-11 2008-02-10 DROP TABLE t1; +# +# MDEV-40637 CONNECT crashes on double(255,50) in DOS table +# +create table t (x double(255,30)) engine=connect table_type='dos' file_name='t.dos'; +insert into t values (1e50); +select * from t; +x +1000000000000000000000000000000.000000000000000000000000000000 +drop table t; +# End of 10.6 tests diff --git a/storage/connect/mysql-test/connect/r/json_bson_file_priv.result b/storage/connect/mysql-test/connect/r/json_bson_file_priv.result new file mode 100644 index 00000000000..34cbf8c972b --- /dev/null +++ b/storage/connect/mysql-test/connect/r/json_bson_file_priv.result @@ -0,0 +1,156 @@ +# +# Test FILE privilege and secure_file_priv enforcement for JSON/BSON file UDFs +# +# Privileged user: path inside secure_file_priv MYSQL_TMP_DIR +select json_file('MYSQL_TMP_DIR/bib0.json', 0) IS NOT NULL AS ok; +ok +1 +select bson_file('MYSQL_TMP_DIR/biblio.json', 0) IS NOT NULL AS ok; +ok +1 +Warnings: +Warning 1105 File pretty format doesn't match the specified pretty value +select jfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok; +ok +1 +select bfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok; +ok +1 +select jbin_file('MYSQL_TMP_DIR/bib0.json') IS NOT NULL AS ok; +ok +1 +select bbin_file('MYSQL_TMP_DIR/biblio.json') IS NOT NULL AS ok; +ok +1 +# Privileged user: path outside secure_file_priv +select json_file('DATADIR/bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('DATADIR/biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jfile_make('{"test":1}', 'DATADIR/out.json', 0) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bfile_make('{"test":1}', 'DATADIR/out.json', 0) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jbin_file('DATADIR/bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bbin_file('DATADIR/biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# NULL argument: no "access denied", UDF reports its own error +select json_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bson_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select jfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select jbin_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +select bbin_file(NULL) IS NULL AS ok; +ok +1 +Warnings: +Warning 1105 Missing file name +# Privileged user: relative path resolving outside secure_file_priv +select json_file('../../bib0.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('../../biblio.json') IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# Overlong filename: MY_SAFE_PATH rejects it, fail closed +select json_file(concat('MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file(concat('MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +# Non-constant filename expression (args->args[0] is NULL at init time) +set @fname = 'MYSQL_TMP_DIR/bib0.json'; +select jbin_file(@fname) IS NOT NULL AS ok; +ok +1 +set @fname = 'MYSQL_TMP_DIR/biblio.json'; +select bbin_file(@fname) IS NOT NULL AS ok; +ok +1 +# User without FILE privilege +create user unprivileged@localhost; +grant select ON *.* TO unprivileged@localhost; +connect unprivileged,localhost,unprivileged,,; +connection unprivileged; +# inside secure_file_priv: denied without FILE privilege +select json_file('MYSQL_TMP_DIR/bib0.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bson_file('MYSQL_TMP_DIR/biblio.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bfile_make('{"test":1}', 'MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select jbin_file('MYSQL_TMP_DIR/bib0.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +select bbin_file('MYSQL_TMP_DIR/biblio.json') IS NULL AS denied;; +denied +1 +Warnings: +Warning 1105 Access denied: FILE privilege or secure_file_priv violation +disconnect unprivileged; +connection default; +drop user unprivileged@localhost; diff --git a/storage/connect/mysql-test/connect/t/drop-open-error.opt b/storage/connect/mysql-test/connect/t/drop-open-error.opt deleted file mode 100644 index 22520f0aa99..00000000000 --- a/storage/connect/mysql-test/connect/t/drop-open-error.opt +++ /dev/null @@ -1 +0,0 @@ ---secure-file-priv="" diff --git a/storage/connect/mysql-test/connect/t/drop-open-error.test b/storage/connect/mysql-test/connect/t/drop-open-error.test index dd286c96466..97d938e4310 100644 --- a/storage/connect/mysql-test/connect/t/drop-open-error.test +++ b/storage/connect/mysql-test/connect/t/drop-open-error.test @@ -18,14 +18,3 @@ copy_file $MTR_SUITE_DIR/std_data/mdev9949.frm $datadir/test/mdev9949.frm; --replace_result $MARIADB_DATADIR DATADIR/ './' 'DATADIR/' drop table mdev9949; drop table t1; - -# -# MDEV-7935 CREATE TABLE ... AS SELECT ... can cause a Server crash (Assertion `0' in Protocol::end_statement) -# -select @@secure_file_priv 'must be NULL'; # otherwise foo/bar.txt won't be allowed -create table t1 (a char(16)) engine=myisam; -insert into t1 values('Hello World!'); -replace_regex @on .*/foo/@on foo/@; -error ER_GET_ERRMSG; -create table t2 engine=connect file_name='foo/bar.txt' as select * from t1; -drop table t1; diff --git a/storage/connect/mysql-test/connect/t/fix.test b/storage/connect/mysql-test/connect/t/fix.test index f05abbb9b92..a38fe5623a6 100644 --- a/storage/connect/mysql-test/connect/t/fix.test +++ b/storage/connect/mysql-test/connect/t/fix.test @@ -106,3 +106,14 @@ DROP TABLE t1; --remove_file $MYSQLD_DATADIR/test/dept.dat --remove_file $MYSQLD_DATADIR/test/boys.txt --remove_file $MYSQLD_DATADIR/test/boyswin.txt + +--echo # +--echo # MDEV-40637 CONNECT crashes on double(255,50) in DOS table +--echo # +create table t (x double(255,30)) engine=connect table_type='dos' file_name='t.dos'; +insert into t values (1e50); +select * from t; +drop table t; +--remove_file $MYSQLD_DATADIR/test/t.dos + +--echo # End of 10.6 tests diff --git a/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt b/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt new file mode 100644 index 00000000000..e9a43a5584d --- /dev/null +++ b/storage/connect/mysql-test/connect/t/json_bson_file_priv.opt @@ -0,0 +1 @@ +--secure_file_priv=$MYSQL_TMP_DIR diff --git a/storage/connect/mysql-test/connect/t/json_bson_file_priv.test b/storage/connect/mysql-test/connect/t/json_bson_file_priv.test new file mode 100644 index 00000000000..04c91184a94 --- /dev/null +++ b/storage/connect/mysql-test/connect/t/json_bson_file_priv.test @@ -0,0 +1,100 @@ +--source include/not_embedded.inc +--source json_udf.inc +--source bson_udf.inc + +let $MYSQLD_DATADIR = `select @@datadir`; + +--copy_file $MTR_SUITE_DIR/std_data/bib0.json $MYSQL_TMP_DIR/bib0.json +--copy_file $MTR_SUITE_DIR/std_data/biblio.json $MYSQL_TMP_DIR/biblio.json + +--echo # +--echo # Test FILE privilege and secure_file_priv enforcement for JSON/BSON file UDFs +--echo # + +--echo # Privileged user: path inside secure_file_priv MYSQL_TMP_DIR +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file('$MYSQL_TMP_DIR/bib0.json', 0) IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file('$MYSQL_TMP_DIR/biblio.json', 0) IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok +--remove_file $MYSQL_TMP_DIR/out.json +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NOT NULL AS ok +--remove_file $MYSQL_TMP_DIR/out.json +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jbin_file('$MYSQL_TMP_DIR/bib0.json') IS NOT NULL AS ok +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bbin_file('$MYSQL_TMP_DIR/biblio.json') IS NOT NULL AS ok + +--echo # Privileged user: path outside secure_file_priv +--replace_result $MYSQLD_DATADIR DATADIR +--eval select json_file('$MYSQLD_DATADIR/bib0.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bson_file('$MYSQLD_DATADIR/biblio.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select jfile_make('{"test":1}', '$MYSQLD_DATADIR/out.json', 0) IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bfile_make('{"test":1}', '$MYSQLD_DATADIR/out.json', 0) IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select jbin_file('$MYSQLD_DATADIR/bib0.json') IS NULL AS denied +--replace_result $MYSQLD_DATADIR DATADIR +--eval select bbin_file('$MYSQLD_DATADIR/biblio.json') IS NULL AS denied + +--echo # NULL argument: no "access denied", UDF reports its own error +select json_file(NULL) IS NULL AS ok; +select bson_file(NULL) IS NULL AS ok; +select jfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +select bfile_make('{"test":1}', NULL, 0) IS NULL AS ok; +select jbin_file(NULL) IS NULL AS ok; +select bbin_file(NULL) IS NULL AS ok; + +--echo # Privileged user: relative path resolving outside secure_file_priv +select json_file('../../bib0.json') IS NULL AS denied; +select bson_file('../../biblio.json') IS NULL AS denied; + +--echo # Overlong filename: MY_SAFE_PATH rejects it, fail closed +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file(concat('$MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file(concat('$MYSQL_TMP_DIR/', repeat('x', 512))) IS NULL AS denied + +--echo # Non-constant filename expression (args->args[0] is NULL at init time) +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval set @fname = '$MYSQL_TMP_DIR/bib0.json' +select jbin_file(@fname) IS NOT NULL AS ok; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval set @fname = '$MYSQL_TMP_DIR/biblio.json' +select bbin_file(@fname) IS NOT NULL AS ok; + +--echo # User without FILE privilege +create user unprivileged@localhost; +grant select ON *.* TO unprivileged@localhost; + +--connect(unprivileged,localhost,unprivileged,,) +--connection unprivileged + +--echo # inside secure_file_priv: denied without FILE privilege +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select json_file('$MYSQL_TMP_DIR/bib0.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bson_file('$MYSQL_TMP_DIR/biblio.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bfile_make('{"test":1}', '$MYSQL_TMP_DIR/out.json', 0) IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select jbin_file('$MYSQL_TMP_DIR/bib0.json') IS NULL AS denied; +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--eval select bbin_file('$MYSQL_TMP_DIR/biblio.json') IS NULL AS denied; + +--disconnect unprivileged +--connection default + +--remove_file $MYSQL_TMP_DIR/bib0.json +--remove_file $MYSQL_TMP_DIR/biblio.json + +drop user unprivileged@localhost; + +--source json_udf2.inc +--source bson_udf2.inc diff --git a/storage/connect/value.cpp b/storage/connect/value.cpp index b648aa1c956..7dcc8d55394 100644 --- a/storage/connect/value.cpp +++ b/storage/connect/value.cpp @@ -746,7 +746,7 @@ bool TYPVAL::SetValue_char(const char *p, int n) for (; n > 0 && *p == ' '; p++) n--; - memcpy(buf, p, MY_MIN(n, 31)); + memcpy(buf, p, n= MY_MIN(n, 31)); buf[n] = '\0'; Tval = atof(buf); diff --git a/storage/csv/ha_tina.cc b/storage/csv/ha_tina.cc index 43fc7d2bda0..de195cae54f 100644 --- a/storage/csv/ha_tina.cc +++ b/storage/csv/ha_tina.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2004, 2011, Oracle and/or its affiliates. + Copyright (c) 2026, MariaDB plc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -646,7 +647,8 @@ int ha_tina::chain_append() if (chain_alloced) { if ((chain= (tina_set *) my_realloc(csv_key_memory_tina_set, - (uchar*)chain, chain_size, + (uchar*)chain, + chain_size * sizeof(tina_set), MYF(MY_WME))) == NULL) return -1; } diff --git a/storage/heap/_check.c b/storage/heap/_check.c index 1a640fa13da..3663eada0b2 100644 --- a/storage/heap/_check.c +++ b/storage/heap/_check.c @@ -17,6 +17,7 @@ /* Check that heap-structure is ok */ #include "heapdef.h" +#include "my_compare.h" static int check_one_key(HP_KEYDEF *, uint, ulong, ulong, my_bool); static int check_one_rb_key(const HP_INFO *, uint, ulong, my_bool); diff --git a/storage/heap/heapdef.h b/storage/heap/heapdef.h index e51fe88d8e2..1880a9839cf 100644 --- a/storage/heap/heapdef.h +++ b/storage/heap/heapdef.h @@ -53,7 +53,7 @@ typedef struct st_hp_hash_info } HASH_INFO; typedef struct { - HA_KEYSEG *keyseg; + struct st_HA_KEYSEG *keyseg; uint key_length; uint search_flag; } heap_rb_param; diff --git a/storage/heap/hp_create.c b/storage/heap/hp_create.c index f35e8e3fac9..84df122829a 100644 --- a/storage/heap/hp_create.c +++ b/storage/heap/hp_create.c @@ -15,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #include "heapdef.h" +#include "my_compare.h" #include static int keys_compare(void *heap_rb, const void *key1, const void *key2); diff --git a/storage/heap/hp_hash.c b/storage/heap/hp_hash.c index a0139151730..45e82a777b8 100644 --- a/storage/heap/hp_hash.c +++ b/storage/heap/hp_hash.c @@ -18,6 +18,7 @@ /* The hash functions used for saveing keys */ #include "heapdef.h" +#include "my_compare.h" #include diff --git a/storage/heap/hp_rfirst.c b/storage/heap/hp_rfirst.c index 60596a2c650..1b1610ac52d 100644 --- a/storage/heap/hp_rfirst.c +++ b/storage/heap/hp_rfirst.c @@ -34,8 +34,7 @@ int heap_rfirst(HP_INFO *info, uchar *record, int inx) if ((pos = tree_search_edge(&keyinfo->rb_tree, info->parents, &info->last_pos, offsetof(TREE_ELEMENT, left)))) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; memcpy(record, pos, (size_t)share->reclength); /* diff --git a/storage/heap/hp_rkey.c b/storage/heap/hp_rkey.c index 2d9fae4c520..3e4b4dee6ed 100644 --- a/storage/heap/hp_rkey.c +++ b/storage/heap/hp_rkey.c @@ -55,7 +55,10 @@ int heap_rkey(HP_INFO *info, uchar *record, int inx, const uchar *key, info->update= HA_STATE_NO_KEY; DBUG_RETURN(my_errno= HA_ERR_KEY_NOT_FOUND); } - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), sizeof(uchar*)); + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); info->current_ptr= pos; } else diff --git a/storage/heap/hp_rlast.c b/storage/heap/hp_rlast.c index ed9c3499d5e..c95d8af5d47 100644 --- a/storage/heap/hp_rlast.c +++ b/storage/heap/hp_rlast.c @@ -34,8 +34,7 @@ int heap_rlast(HP_INFO *info, uchar *record, int inx) if ((pos = tree_search_edge(&keyinfo->rb_tree, info->parents, &info->last_pos, offsetof(TREE_ELEMENT, right)))) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + memcpy(&pos, pos + keyinfo->get_key_length(keyinfo, pos), sizeof(uchar*)); info->current_ptr = pos; memcpy(record, pos, (size_t)share->reclength); info->update = HA_STATE_AKTIV; diff --git a/storage/heap/hp_rnext.c b/storage/heap/hp_rnext.c index ac21ed83da2..1f04f94d478 100644 --- a/storage/heap/hp_rnext.c +++ b/storage/heap/hp_rnext.c @@ -53,9 +53,9 @@ int heap_rnext(HP_INFO *info, uchar *record) or heap_rfirst(). As last key position (info->last_pos) is available, we only need to climb the tree using tree_search_next(). */ - pos = tree_search_next(&keyinfo->rb_tree, &info->last_pos, - offsetof(TREE_ELEMENT, left), - offsetof(TREE_ELEMENT, right)); + pos= tree_search_next(&keyinfo->rb_tree, &info->last_pos, + offsetof(TREE_ELEMENT, left), + offsetof(TREE_ELEMENT, right)); } else if (!info->lastkey_len) { @@ -84,17 +84,19 @@ int heap_rnext(HP_INFO *info, uchar *record) */ custom_arg.keyseg = keyinfo->seg; custom_arg.key_length = info->lastkey_len; - custom_arg.search_flag = SEARCH_SAME | SEARCH_FIND; + custom_arg.search_flag = SEARCH_BIGGER; info->last_find_flag= HA_READ_KEY_OR_NEXT; - pos = tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, + pos= tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, &info->last_pos, info->last_find_flag, &custom_arg); info->key_version= info->s->key_version; } if (pos) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); - info->current_ptr = pos; + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); + info->current_ptr= pos; } else { diff --git a/storage/heap/hp_rprev.c b/storage/heap/hp_rprev.c index cc81d179570..eb816ed676a 100644 --- a/storage/heap/hp_rprev.c +++ b/storage/heap/hp_rprev.c @@ -54,7 +54,7 @@ int heap_rprev(HP_INFO *info, uchar *record) { custom_arg.keyseg = keyinfo->seg; custom_arg.key_length = keyinfo->length; - custom_arg.search_flag = SEARCH_SAME; + custom_arg.search_flag = SEARCH_SMALLER; info->last_find_flag= HA_READ_KEY_OR_PREV; pos = tree_search_key(&keyinfo->rb_tree, info->lastkey, info->parents, &info->last_pos, info->last_find_flag, &custom_arg); @@ -62,8 +62,10 @@ int heap_rprev(HP_INFO *info, uchar *record) } if (pos) { - memcpy(&pos, pos + (*keyinfo->get_key_length)(keyinfo, pos), - sizeof(uchar*)); + info->lastkey_len= keyinfo->get_key_length(keyinfo, pos); + if ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART_KEY)) != HA_NOSAME) + memcpy(info->lastkey, pos, info->lastkey_len + sizeof(uchar*)); + memcpy(&pos, pos + info->lastkey_len, sizeof(uchar*)); info->current_ptr = pos; } else diff --git a/storage/heap/hp_test1.c b/storage/heap/hp_test1.c index 9f87ea6b33a..8cc62362b62 100644 --- a/storage/heap/hp_test1.c +++ b/storage/heap/hp_test1.c @@ -23,6 +23,7 @@ #include #include #include "heap.h" +#include "my_compare.h" static int get_options(int argc, char *argv[]); diff --git a/storage/heap/hp_test2.c b/storage/heap/hp_test2.c index 2ca1dd77fbb..a5b03354b9f 100644 --- a/storage/heap/hp_test2.c +++ b/storage/heap/hp_test2.c @@ -17,6 +17,7 @@ /* Test av isam-databas: stor test */ #include "heapdef.h" /* Because of hp_find_block */ +#include "my_compare.h" #include #define MAX_RECORDS 100000 diff --git a/storage/heap/hp_update.c b/storage/heap/hp_update.c index ad56ca979de..c99ac370ac8 100644 --- a/storage/heap/hp_update.c +++ b/storage/heap/hp_update.c @@ -47,6 +47,7 @@ int heap_update(HP_INFO *info, const uchar *old, const uchar *heap_new) if ((*keydef->delete_key)(info, keydef, old, pos, keydef == p_lastinx) || (*keydef->write_key)(info, keydef, heap_new, pos)) goto err; + key_changed= 1; if (share->auto_key == (uint) (keydef - share->keydef + 1)) auto_key_changed= 1; } diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 61c29762fff..be75599ebcd 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1995, 2018, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2013, 2023, MariaDB Corporation. +Copyright (c) 2013, 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -1351,14 +1351,9 @@ bool buf_pool_t::create() noexcept retry: { NUMA_MEMPOLICY_INTERLEAVE_IN_SCOPE; -#ifdef _WIN32 memory_unaligned= my_virtual_mem_reserve(&size); - if (!memory_unaligned) - goto oom; -#else - memory_unaligned= my_large_virtual_alloc(&size); if (memory_unaligned); -# if defined __aarch64__ || defined __riscv || defined __mips__ || defined __loongarch64 +#if defined __aarch64__ || defined __riscv || defined __mips__ || defined __loongarch64 else if (size_in_bytes_max_default != 0 && size_in_bytes_max == size_in_bytes_max_default) { @@ -1379,10 +1374,9 @@ bool buf_pool_t::create() noexcept size_in_bytes_max= std::max(size_t(1ULL << 37), size_in_bytes_requested); goto init; } -# endif +#endif else goto oom; -#endif } const size_t alignment_waste= diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc index 9985a2f23c5..1b64a5be866 100644 --- a/storage/innobase/dict/dict0stats.cc +++ b/storage/innobase/dict/dict0stats.cc @@ -332,7 +332,6 @@ static int dtype_sql_name(unsigned mtype, unsigned prtype, unsigned len, return snprintf(name, name_sz, "%s%s%s", Main, Unsigned, Not_null); } -static bool innodb_table_stats_not_found; static bool innodb_index_stats_not_found; static bool innodb_table_stats_not_found_reported; static bool innodb_index_stats_not_found_reported; @@ -363,7 +362,6 @@ dict_table_schema_check( if (innodb_table_stats_not_found_reported) { return DB_STATS_DO_NOT_EXIST; } - innodb_table_stats_not_found = true; innodb_table_stats_not_found_reported = true; } else { ut_ad(req_schema == &index_stats_schema); diff --git a/storage/maria/ma_checkpoint.c b/storage/maria/ma_checkpoint.c index d910a5fbd0f..1c301ac76a8 100644 --- a/storage/maria/ma_checkpoint.c +++ b/storage/maria/ma_checkpoint.c @@ -53,9 +53,6 @@ static PAGECACHE_FILE *dfiles, /**< data files to flush in background */ *dfiles_end; /**< list of data files ends here */ static PAGECACHE_FILE *kfiles, /**< index files to flush in background */ *kfiles_end; /**< list of index files ends here */ -/* those two statistics below could serve in SHOW GLOBAL STATUS */ -static uint checkpoints_total= 0, /**< all checkpoint requests made */ - checkpoints_ok_total= 0; /**< all checkpoints which succeeded */ struct st_filter_param { @@ -314,8 +311,6 @@ end: my_free(record_pieces[i].str); mysql_mutex_lock(&LOCK_checkpoint); checkpoint_in_progress= CHECKPOINT_NONE; - checkpoints_total++; - checkpoints_ok_total+= !error; mysql_mutex_unlock(&LOCK_checkpoint); DBUG_RETURN(error); } diff --git a/storage/maria/ma_delete.c b/storage/maria/ma_delete.c index 489e81121a4..cdeff50b085 100644 --- a/storage/maria/ma_delete.c +++ b/storage/maria/ma_delete.c @@ -1333,6 +1333,9 @@ err: @retval # How many chars was removed */ +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 +#pragma optimize("g", off) +#endif static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, uchar *keypos, uchar *lastkey, uchar *page_end, my_off_t *next_block, @@ -1473,6 +1476,9 @@ static uint remove_key(MARIA_KEYDEF *keyinfo, uint page_flag, uint nod_flag, DBUG_RETURN((uint) s_length); } /* remove_key */ +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 +#pragma optimize("", on) +#endif /**************************************************************************** Logging of redos diff --git a/storage/maria/ma_trnman.h b/storage/maria/ma_trnman.h index 4a73dcda680..a0cbd5ccb6c 100644 --- a/storage/maria/ma_trnman.h +++ b/storage/maria/ma_trnman.h @@ -32,7 +32,7 @@ static inline void _ma_set_trn_for_table(MARIA_HA *tbl, TRN *newtrn) /* check that we are not calling this twice in a row */ DBUG_ASSERT(newtrn->used_instances != (void*) tbl); DBUG_ASSERT(newtrn != &dummy_transaction_object); - DBUG_ASSERT(tbl->trn == 0); + DBUG_ASSERT(tbl->trn == 0 || tbl->trn == &dummy_transaction_object); DBUG_ASSERT(tbl->trn_next == 0); DBUG_ASSERT(tbl->trn_prev == 0); diff --git a/storage/mroonga/ha_mroonga.cpp b/storage/mroonga/ha_mroonga.cpp index ef3eb1f8f7e..ff49e269270 100644 --- a/storage/mroonga/ha_mroonga.cpp +++ b/storage/mroonga/ha_mroonga.cpp @@ -4296,18 +4296,15 @@ int ha_mroonga::wrapper_open(const char *name, int mode, uint open_options) if (error) DBUG_RETURN(error); - if (!(open_options & HA_OPEN_FOR_REPAIR)) { - error = open_table(name); - if (error) - DBUG_RETURN(error); + error = open_table(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) + goto error_exit; - error = wrapper_open_indexes(name); - if (error) { - grn_obj_unlink(ctx, grn_table); - grn_table = NULL; - DBUG_RETURN(error); - } - } + error = wrapper_open_indexes(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) + goto error_exit; + + error = 0; mrn_init_alloc_root(&mem_root, 1024, 0, MYF(0)); wrap_key_info = mrn_create_key_info_for_table(share, table, &error); @@ -4396,6 +4393,7 @@ int ha_mroonga::wrapper_open(const char *name, int mode, uint open_options) } } +error_exit: if (error) { grn_obj_unlink(ctx, grn_table); @@ -4668,37 +4666,35 @@ int ha_mroonga::storage_open(const char *name, int mode, uint open_options) DBUG_RETURN(error); } + error = storage_open_indexes(name); + if (error && !(open_options & HA_OPEN_FOR_REPAIR)) { + storage_close_columns(); + grn_obj_unlink(ctx, grn_table); + grn_table = NULL; + DBUG_RETURN(error); + } + + storage_set_keys_in_use(); + if (!(open_options & HA_OPEN_FOR_REPAIR)) { - error = storage_open_indexes(name); - if (error) { - storage_close_columns(); - grn_obj_unlink(ctx, grn_table); - grn_table = NULL; - DBUG_RETURN(error); - } - - storage_set_keys_in_use(); - - { - mrn::Lock lock(&mrn_operations_mutex); - mrn::PathMapper mapper(name); - const char *table_name = mapper.table_name(); - size_t table_name_size = strlen(table_name); - if (db->is_broken_table(table_name, table_name_size)) { - GRN_LOG(ctx, GRN_LOG_NOTICE, - "Auto repair is started: <%s>", - name); - error = operations_->repair(table_name, table_name_size); + mrn::Lock lock(&mrn_operations_mutex); + mrn::PathMapper mapper(name); + const char *table_name = mapper.table_name(); + size_t table_name_size = strlen(table_name); + if (db->is_broken_table(table_name, table_name_size)) { + GRN_LOG(ctx, GRN_LOG_NOTICE, + "Auto repair is started: <%s>", + name); + error = operations_->repair(table_name, table_name_size); + if (!error) + db->mark_table_repaired(table_name, table_name_size); + if (!share->disable_keys) { if (!error) - db->mark_table_repaired(table_name, table_name_size); - if (!share->disable_keys) { - if (!error) - error = storage_reindex(); - } - GRN_LOG(ctx, GRN_LOG_NOTICE, - "Auto repair is done: <%s>: %s", - name, error == 0 ? "success" : "failure"); + error = storage_reindex(); } + GRN_LOG(ctx, GRN_LOG_NOTICE, + "Auto repair is done: <%s>: %s", + name, error == 0 ? "success" : "failure"); } } @@ -5242,7 +5238,7 @@ void ha_mroonga::storage_set_keys_in_use() if (i == table_share->primary_key) { continue; } - if (!grn_index_tables[i]) { + if (grn_index_tables && !grn_index_tables[i]) { /* disabled */ table_share->keys_in_use.clear_bit(i); DBUG_PRINT("info", ("mroonga: key %u disabled", i)); diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result index 4926a72a77a..af85e783cd5 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result +++ b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_broken.result @@ -9,10 +9,17 @@ INSERT INTO diaries VALUES ('Hello'); FLUSH TABLES; CHECK TABLE diaries; Table Op Msg_type Msg_text +check_test.diaries check Error system call error: No such file or directory: failed to open path: check_test.diaries check error Corrupt REPAIR TABLE diaries; Table Op Msg_type Msg_text +check_test.diaries repair Error system call error: No such file or directory: failed to open path: check_test.diaries repair status OK +SELECT * +FROM diaries +WHERE MATCH(title) AGAINST('+Hello' IN BOOLEAN MODE); +title +Hello DROP TABLE diaries; DROP DATABASE check_test; USE test; diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result new file mode 100644 index 00000000000..628a33af7f9 --- /dev/null +++ b/storage/mroonga/mysql-test/mroonga/storage/r/check_table_innodb.result @@ -0,0 +1,13 @@ +# +# MDEV-39556 SIGSEGV in ha_mroonga::storage_set_keys_in_use on SELECT +# +CREATE TABLE t (c INT KEY,c2 GEOMETRY NOT NULL,SPATIAL INDEX idx_sp (c2)) ENGINE=InnoDB; +ALTER TABLE t ENGINE=Mroonga; +CHECK TABLE t; +Table Op Msg_type Msg_text +test.t check status OK +SELECT COUNT(*) > 0 AS r FROM information_schema.STATISTICS; +r +1 +DROP TABLE t; +# End of 10.6 tests diff --git a/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result b/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result index 24d427ed2ab..f58b3916eaf 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result +++ b/storage/mroonga/mysql-test/mroonga/storage/r/repair_table_no_index_file.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("+starting" IN BOOLEAN MODE); ERROR HY000: system call error: No such file or directory: failed to open path: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error system call error: No such file or directory: failed to open path: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test index d438a912ae2..d4666e60055 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test +++ b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_broken.test @@ -31,12 +31,16 @@ CREATE TABLE diaries ( INSERT INTO diaries VALUES ('Hello'); --remove_file $MYSQLD_DATADIR/check_test.mrn.000010C - FLUSH TABLES; CHECK TABLE diaries; REPAIR TABLE diaries; + +SELECT * + FROM diaries + WHERE MATCH(title) AGAINST('+Hello' IN BOOLEAN MODE); + DROP TABLE diaries; DROP DATABASE check_test; diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test new file mode 100644 index 00000000000..e34d30ec29d --- /dev/null +++ b/storage/mroonga/mysql-test/mroonga/storage/t/check_table_innodb.test @@ -0,0 +1,16 @@ +--source include/have_innodb.inc +--source ../../include/mroonga/have_mroonga.inc + +--echo # +--echo # MDEV-39556 SIGSEGV in ha_mroonga::storage_set_keys_in_use on SELECT +--echo # + +CREATE TABLE t (c INT KEY,c2 GEOMETRY NOT NULL,SPATIAL INDEX idx_sp (c2)) ENGINE=InnoDB; + +ALTER TABLE t ENGINE=Mroonga; +CHECK TABLE t; +SELECT COUNT(*) > 0 AS r FROM information_schema.STATISTICS; + +DROP TABLE t; + +--echo # End of 10.6 tests diff --git a/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test b/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test index 0f04bd3e423..63624a9d56e 100644 --- a/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test +++ b/storage/mroonga/mysql-test/mroonga/storage/t/repair_table_no_index_file.test @@ -43,6 +43,7 @@ FLUSH TABLES; --error ER_CANT_OPEN_FILE SELECT * FROM diaries WHERE MATCH(body) AGAINST("+starting" IN BOOLEAN MODE); +--replace_result "[io][open] " "" REPAIR TABLE diaries; SELECT * FROM diaries; diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result index 8258a03bf42..98de2e89518 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result +++ b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_files.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); ERROR HY000: mroonga: failed to open table: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error mroonga: failed to open table: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result index cca7aee95de..271c086c4b3 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result +++ b/storage/mroonga/mysql-test/mroonga/wrapper/r/repair_table_no_index_file.result @@ -17,6 +17,7 @@ SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); ERROR HY000: system call error: No such file or directory: failed to open path: REPAIR TABLE diaries; Table Op Msg_type Msg_text +repair_test.diaries repair Error system call error: No such file or directory: failed to open path: repair_test.diaries repair status OK SELECT * FROM diaries; id title body diff --git a/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test b/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test index 2fefadc254d..5061a268d8a 100644 --- a/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test +++ b/storage/mroonga/mysql-test/mroonga/wrapper/t/repair_table_no_index_file.test @@ -44,6 +44,7 @@ FLUSH TABLES; --error ER_CANT_OPEN_FILE SELECT * FROM diaries WHERE MATCH(body) AGAINST("starting"); +--replace_result "[io][open] " "" REPAIR TABLE diaries; SELECT * FROM diaries; diff --git a/storage/myisam/ft_static.c b/storage/myisam/ft_static.c index 78123cdf261..2b84efb7673 100644 --- a/storage/myisam/ft_static.c +++ b/storage/myisam/ft_static.c @@ -1,4 +1,5 @@ /* Copyright (c) 2000-2008 MySQL AB, 2009 Sun Microsystems, Inc. + Copyright (c) 2026, MariaDB plc. Use is subject to license terms. This program is free software; you can redistribute it and/or modify @@ -18,9 +19,9 @@ #include "ftdefs.h" -ulong ft_min_word_len= 4; -ulong ft_max_word_len= HA_FT_MAXCHARLEN; -ulong ft_query_expansion_limit= 5; +READ_ONLY_SYSVAR ulong ft_min_word_len= 4; +READ_ONLY_SYSVAR ulong ft_max_word_len= HA_FT_MAXCHARLEN; +READ_ONLY_SYSVAR ulong ft_query_expansion_limit= 5; const char *ft_boolean_syntax= DEFAULT_FTB_SYNTAX; const HA_KEYSEG ft_keysegs[FT_SEGS]= { @@ -55,7 +56,7 @@ const struct _ft_vft _ft_vft_boolean= { ft_boolean_get_relevance, ft_boolean_reinit_search }; -const char *ft_stopword_file= 0; +READ_ONLY_SYSVAR const char *ft_stopword_file= 0; const char *ft_precompiled_stopwords[]= { #ifdef COMPILE_STOPWORDS_IN diff --git a/storage/myisam/mi_delete.c b/storage/myisam/mi_delete.c index 62409a15a46..aa9196dc0a4 100644 --- a/storage/myisam/mi_delete.c +++ b/storage/myisam/mi_delete.c @@ -767,7 +767,7 @@ err: returns how many chars was removed or 0 on error */ -#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 #pragma optimize("g", off) #endif @@ -896,6 +896,6 @@ static uint remove_key(MI_KEYDEF *keyinfo, uint nod_flag, DBUG_RETURN((uint) s_length); } /* remove_key */ -#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 +#if defined(_MSC_VER) && defined(_M_X64) && _MSC_VER >= 1930 && _MSC_VER < 1951 #pragma optimize("",on) #endif diff --git a/storage/perfschema/pfs.cc b/storage/perfschema/pfs.cc index 195a646da35..1d867e44975 100644 --- a/storage/perfschema/pfs.cc +++ b/storage/perfschema/pfs.cc @@ -3298,7 +3298,7 @@ pfs_get_thread_file_name_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= NULL; + wait->m_object_instance_addr= 0; wait->m_weak_file= NULL; wait->m_weak_version= 0; wait->m_event_id= pfs_thread->m_event_id++; @@ -3378,7 +3378,7 @@ pfs_get_thread_file_stream_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= pfs_file; + wait->m_object_instance_addr= pfs_file->m_identity; wait->m_weak_file= pfs_file; wait->m_weak_version= pfs_file->get_version(); wait->m_event_id= pfs_thread->m_event_id++; @@ -3487,7 +3487,7 @@ pfs_get_thread_file_descriptor_locker_v1(PSI_file_locker_state *state, wait->m_class= klass; wait->m_timer_start= 0; wait->m_timer_end= 0; - wait->m_object_instance_addr= pfs_file; + wait->m_object_instance_addr= pfs_file->m_identity; wait->m_weak_file= pfs_file; wait->m_weak_version= pfs_file->get_version(); wait->m_event_id= pfs_thread->m_event_id++; @@ -4667,7 +4667,7 @@ void pfs_end_file_wait_v1(PSI_file_locker *locker, wait->m_timer_end= timer_end; wait->m_number_of_bytes= bytes; wait->m_end_event_id= thread->m_event_id; - wait->m_object_instance_addr= file; + wait->m_object_instance_addr= (file ? file->m_identity : 0); wait->m_weak_file= file; wait->m_weak_version= (file ? file->get_version() : 0); diff --git a/storage/perfschema/pfs.h b/storage/perfschema/pfs.h index 6e0cf258c91..b73e79498a9 100644 --- a/storage/perfschema/pfs.h +++ b/storage/perfschema/pfs.h @@ -23,11 +23,15 @@ #ifndef PFS_H #define PFS_H +#include + /** @file storage/perfschema/pfs.h Performance schema instrumentation (declarations). */ +typedef std::uint64_t pfs_identity; + #define HAVE_PSI_1 #include @@ -60,4 +64,3 @@ extern bool THR_PFS_initialized; #define PSI_COUNT_VOLATILITY 2 #endif - diff --git a/storage/perfschema/pfs_account.cc b/storage/perfschema/pfs_account.cc index 2a877836649..88038e947e1 100644 --- a/storage/perfschema/pfs_account.cc +++ b/storage/perfschema/pfs_account.cc @@ -180,7 +180,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_account_container.allocate(& dirty_state); + pfs= global_account_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/pfs_buffer_container.cc b/storage/perfschema/pfs_buffer_container.cc index 04f6f5219a3..4b96366c4a9 100644 --- a/storage/perfschema/pfs_buffer_container.cc +++ b/storage/perfschema/pfs_buffer_container.cc @@ -29,6 +29,26 @@ #include "pfs_buffer_container.h" #include "pfs_builtin_memory.h" +pfs_identity make_identity(pfs_container_id container_id, pfs_page_id page_id, + pfs_object_id object_id, + pfs_dirty_state *dirty_state) { + /* See pfs_lock, the version number is stored in the high 30 bits. */ + uint32_t version_32 = (dirty_state->m_version_state & VERSION_MASK) >> 2; + /* Isolate 16 least significant bits from the version number. */ + uint16_t version_16 = version_32 & 0xFFFF; + pfs_identity id; + id = container_id; + id <<= 16; + id += page_id; + id <<= 16; + id += object_id; + id <<= 16; + id += version_16; + return id; +} + +std::atomic global_container_id{1}; + PFS_buffer_default_allocator default_mutex_allocator(& builtin_memory_mutex); PFS_mutex_container global_mutex_container(& default_mutex_allocator); @@ -880,4 +900,3 @@ void PFS_user_allocator::free_array(PFS_user_array *array) PFS_user_allocator user_allocator; PFS_user_container global_user_container(& user_allocator); - diff --git a/storage/perfschema/pfs_buffer_container.h b/storage/perfschema/pfs_buffer_container.h index c1cd9e644ef..c4305795892 100644 --- a/storage/perfschema/pfs_buffer_container.h +++ b/storage/perfschema/pfs_buffer_container.h @@ -33,7 +33,26 @@ #include "pfs_prepared_stmt.h" #include "pfs_builtin_memory.h" -#define USE_SCALABLE +typedef std::uint16_t pfs_container_id; +typedef std::uint16_t pfs_page_id; +typedef std::uint16_t pfs_object_id; + +/** + * Build an artificial object identity, for OBJECT_INSTANCE_BEGIN columns. + * To be globally unique, identity consists of: + * - the container id, so objects A and B from different containers + * (instrument classes) will not collide. Note that partitioned containers + * get a container id per partition. + * - the page id within the container + * - the object index within the page + * - the version number from the pfs_lock dirty_state, to resolve ABA + * problems + */ +extern pfs_identity make_identity(pfs_container_id container_id, + pfs_page_id page_id, pfs_object_id object_id, + pfs_dirty_state *dirty_state); + +extern std::atomic global_container_id; class PFS_opaque_container_page; class PFS_opaque_container; @@ -77,7 +96,9 @@ class PFS_buffer_default_array public: typedef T value_type; - value_type *allocate(pfs_dirty_state *dirty_state) + value_type *allocate(pfs_dirty_state *dirty_state, + pfs_container_id container_id, pfs_page_id page_id, + pfs_identity *id) { uint index; uint monotonic; @@ -97,10 +118,11 @@ public: if (pfs->m_lock.free_to_dirty(dirty_state)) { + if (id != nullptr) + *id = make_identity(container_id, page_id, index, dirty_state); return pfs; } monotonic= m_monotonic.m_u32.fetch_add(1); - } m_full= true; @@ -400,6 +422,11 @@ template ::max(), + "pfs_object_id field is only 16 bits"); + static_assert(PFS_PAGE_COUNT <= std::numeric_limits::max(), + "pfs_page_id field is only 16 bits"); + friend class PFS_buffer_scalable_iterator; /** @@ -427,6 +454,7 @@ public: PFS_buffer_scalable_container(allocator_type *allocator) { + m_container_id = global_container_id++; m_allocator= allocator; m_initialized= false; m_lost= 0; @@ -533,7 +561,7 @@ public: return get_row_count() * get_row_size(); } - value_type *allocate(pfs_dirty_state *dirty_state) + value_type *allocate(pfs_dirty_state *dirty_state, pfs_identity *id) { if (m_full) { @@ -578,7 +606,7 @@ public: if (array != NULL) { - pfs= array->allocate(dirty_state); + pfs = array->allocate(dirty_state, m_container_id, index, id); if (pfs != NULL) { /* Keep a pointer to the parent page, for deallocate(). */ @@ -695,7 +723,7 @@ public: } assert(array != NULL); - pfs= array->allocate(dirty_state); + pfs= array->allocate(dirty_state, m_container_id, current_page_count, id); if (pfs != NULL) { /* Keep a pointer to the parent page, for deallocate(). */ @@ -1019,6 +1047,7 @@ private: array_type * m_pages[PFS_PAGE_COUNT]; allocator_type *m_allocator; pthread_mutex_t m_critical_section; + pfs_container_id m_container_id; }; template @@ -1178,11 +1207,12 @@ public: return sum; } - value_type *allocate(pfs_dirty_state *dirty_state, uint partition) + value_type *allocate(pfs_dirty_state *dirty_state, uint partition, + pfs_identity *id) { assert(partition < PFS_PARTITION_COUNT); - return m_partitions[partition]->allocate(dirty_state); + return m_partitions[partition]->allocate(dirty_state, id); } void deallocate(value_type *safe_pfs) @@ -1373,116 +1403,60 @@ private: uint m_sub_index; }; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_mutex_basic_container; typedef PFS_partitioned_buffer_scalable_container PFS_mutex_container; -#else -typedef PFS_buffer_container PFS_mutex_container; -#endif typedef PFS_mutex_container::iterator_type PFS_mutex_iterator; extern PFS_mutex_container global_mutex_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_rwlock_container; -#else -typedef PFS_buffer_container PFS_rwlock_container; -#endif typedef PFS_rwlock_container::iterator_type PFS_rwlock_iterator; extern PFS_rwlock_container global_rwlock_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_cond_container; -#else -typedef PFS_buffer_container PFS_cond_container; -#endif typedef PFS_cond_container::iterator_type PFS_cond_iterator; extern PFS_cond_container global_cond_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_file_container; -#else -typedef PFS_buffer_container PFS_file_container; -#endif typedef PFS_file_container::iterator_type PFS_file_iterator; extern PFS_file_container global_file_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_socket_container; -#else -typedef PFS_buffer_container PFS_socket_container; -#endif typedef PFS_socket_container::iterator_type PFS_socket_iterator; extern PFS_socket_container global_socket_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_mdl_container; -#else -typedef PFS_buffer_container PFS_mdl_container; -#endif typedef PFS_mdl_container::iterator_type PFS_mdl_iterator; extern PFS_mdl_container global_mdl_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_setup_actor_container; -#else -typedef PFS_buffer_container PFS_setup_actor_container; -#endif typedef PFS_setup_actor_container::iterator_type PFS_setup_actor_iterator; extern PFS_setup_actor_container global_setup_actor_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_setup_object_container; -#else -typedef PFS_buffer_container PFS_setup_object_container; -#endif typedef PFS_setup_object_container::iterator_type PFS_setup_object_iterator; extern PFS_setup_object_container global_setup_object_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_container; -#else -typedef PFS_buffer_container PFS_table_container; -#endif typedef PFS_table_container::iterator_type PFS_table_iterator; extern PFS_table_container global_table_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_container; -#else -typedef PFS_buffer_container PFS_table_share_container; -#endif typedef PFS_table_share_container::iterator_type PFS_table_share_iterator; extern PFS_table_share_container global_table_share_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_index_container; -#else -typedef PFS_buffer_container PFS_table_share_index_container; -#endif typedef PFS_table_share_index_container::iterator_type PFS_table_share_index_iterator; extern PFS_table_share_index_container global_table_share_index_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_table_share_lock_container; -#else -typedef PFS_buffer_container PFS_table_share_lock_container; -#endif typedef PFS_table_share_lock_container::iterator_type PFS_table_share_lock_iterator; extern PFS_table_share_lock_container global_table_share_lock_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_program_container; -#else -typedef PFS_buffer_container PFS_program_container; -#endif typedef PFS_program_container::iterator_type PFS_program_iterator; extern PFS_program_container global_program_container; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_prepared_stmt_container; -#else -typedef PFS_buffer_container PFS_prepared_stmt_container; -#endif typedef PFS_prepared_stmt_container::iterator_type PFS_prepared_stmt_iterator; extern PFS_prepared_stmt_container global_prepared_stmt_container; @@ -1503,17 +1477,9 @@ public: void free_array(PFS_account_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_account_container; -#else -typedef PFS_buffer_container PFS_account_container; -#endif typedef PFS_account_container::iterator_type PFS_account_iterator; extern PFS_account_container global_account_container; @@ -1534,17 +1500,11 @@ public: void free_array(PFS_host_array *array); }; -#ifdef USE_SCALABLE typedef PFS_buffer_scalable_container PFS_host_container; -#else -typedef PFS_buffer_container PFS_host_container; -#endif typedef PFS_host_container::iterator_type PFS_host_iterator; extern PFS_host_container global_host_container; @@ -1577,17 +1537,8 @@ public: void free_array(PFS_thread_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_thread_container; -#else -typedef PFS_buffer_container PFS_thread_container; -#endif typedef PFS_thread_container::iterator_type PFS_thread_iterator; extern PFS_thread_container global_thread_container; @@ -1608,17 +1559,8 @@ public: void free_array(PFS_user_array *array); }; -#ifdef USE_SCALABLE -typedef PFS_buffer_scalable_container PFS_user_container; -#else -typedef PFS_buffer_container PFS_user_container; -#endif typedef PFS_user_container::iterator_type PFS_user_iterator; extern PFS_user_container global_user_container; diff --git a/storage/perfschema/pfs_events_waits.h b/storage/perfschema/pfs_events_waits.h index 93dcb136834..0c950c84a33 100644 --- a/storage/perfschema/pfs_events_waits.h +++ b/storage/perfschema/pfs_events_waits.h @@ -29,6 +29,7 @@ Events waits data structures (declarations). */ +#include "pfs.h" #include "pfs_column_types.h" #include "pfs_lock.h" #include "pfs_events.h" @@ -88,8 +89,8 @@ struct PFS_events_waits : public PFS_events PFS_metadata_lock *m_weak_metadata_lock; /** For weak pointers, target object version. */ uint32 m_weak_version; - /** Address in memory of the object instance waited on. */ - const void *m_object_instance_addr; + /** Identity of the object instance waited on. */ + pfs_identity m_object_instance_addr; /** Operation performed. */ enum_operation_type m_operation; /** @@ -154,4 +155,3 @@ void reset_table_io_waits_by_table_handle(); void reset_table_lock_waits_by_table_handle(); #endif - diff --git a/storage/perfschema/pfs_host.cc b/storage/perfschema/pfs_host.cc index bf8421909ba..408a45c4d84 100644 --- a/storage/perfschema/pfs_host.cc +++ b/storage/perfschema/pfs_host.cc @@ -169,7 +169,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_host_container.allocate(& dirty_state); + pfs= global_host_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/pfs_instr.cc b/storage/perfschema/pfs_instr.cc index dad6125fa83..550b37b3efa 100644 --- a/storage/perfschema/pfs_instr.cc +++ b/storage/perfschema/pfs_instr.cc @@ -297,18 +297,18 @@ void cleanup_file_hash(void) /** Create instrumentation for a mutex instance. @param klass the mutex class - @param identity the mutex address @return a mutex instance, or NULL */ -PFS_mutex* create_mutex(PFS_mutex_class *klass, const void *identity) +PFS_mutex* create_mutex(PFS_mutex_class *klass, const void * /* identity */) { PFS_mutex *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_mutex_container.allocate(& dirty_state, klass->m_volatility); + pfs= global_mutex_container.allocate(& dirty_state, klass->m_volatility, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -343,18 +343,18 @@ void destroy_mutex(PFS_mutex *pfs) /** Create instrumentation for a rwlock instance. @param klass the rwlock class - @param identity the rwlock address @return a rwlock instance, or NULL */ -PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void *identity) +PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void * /* identity */) { PFS_rwlock *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_rwlock_container.allocate(& dirty_state); + pfs= global_rwlock_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -391,18 +391,18 @@ void destroy_rwlock(PFS_rwlock *pfs) /** Create instrumentation for a condition instance. @param klass the condition class - @param identity the condition address @return a condition instance, or NULL */ -PFS_cond* create_cond(PFS_cond_class *klass, const void *identity) +PFS_cond* create_cond(PFS_cond_class *klass, const void * /* identity */) { PFS_cond *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_cond_container.allocate(& dirty_state); + pfs= global_cond_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -523,7 +523,7 @@ PFS_thread* create_thread(PFS_thread_class *klass, const void *identity, PFS_thread *pfs; pfs_dirty_state dirty_state; - pfs= global_thread_container.allocate(& dirty_state); + pfs= global_thread_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_thread_internal_id= @@ -848,6 +848,7 @@ find_or_create_file(PFS_thread *thread, PFS_file_class *klass, uint retry_count= 0; const uint retry_max= 3; pfs_dirty_state dirty_state; + pfs_identity id; search: @@ -870,9 +871,10 @@ search: return NULL; } - pfs= global_file_container.allocate(& dirty_state); + pfs= global_file_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity = id; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -881,7 +883,6 @@ search: pfs->m_filename_length= normalized_length; pfs->m_file_stat.m_open_count= 1; pfs->m_file_stat.m_io_stat.reset(); - pfs->m_identity= (const void *)pfs; pfs->m_temporary= false; int res; @@ -1110,19 +1111,19 @@ void destroy_file(PFS_thread *thread, PFS_file *pfs) Create instrumentation for a table instance. @param share the table share @param opening_thread the opening thread - @param identity the table address @return a table instance, or NULL */ PFS_table* create_table(PFS_table_share *share, PFS_thread *opening_thread, - const void *identity) + const void * /* identity */) { PFS_table *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_table_container.allocate(& dirty_state); + pfs= global_table_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_share= share; pfs->m_io_enabled= share->m_enabled && flag_global_instrumentation && global_table_io_class.m_enabled; @@ -1294,6 +1295,7 @@ PFS_socket* create_socket(PFS_socket_class *klass, const my_socket *fd, { PFS_socket *pfs; pfs_dirty_state dirty_state; + pfs_identity id; uint fd_used= 0; uint addr_len_used= addr_len; @@ -1304,13 +1306,13 @@ PFS_socket* create_socket(PFS_socket_class *klass, const my_socket *fd, if (addr_len_used > sizeof(sockaddr_storage)) addr_len_used= sizeof(sockaddr_storage); - pfs= global_socket_container.allocate(& dirty_state); + pfs= global_socket_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity= id; pfs->m_fd= fd_used; /* There is no socket object, so we use the instrumentation. */ - pfs->m_identity= pfs; pfs->m_class= klass; pfs->m_enabled= klass->m_enabled && flag_global_instrumentation; pfs->m_timed= klass->m_timed; @@ -1378,7 +1380,7 @@ void destroy_socket(PFS_socket *pfs) global_socket_container.deallocate(pfs); } -PFS_metadata_lock* create_metadata_lock(void *identity, +PFS_metadata_lock* create_metadata_lock(void * /* identity */, const MDL_key *mdl_key, opaque_mdl_type mdl_type, opaque_mdl_duration mdl_duration, @@ -1388,11 +1390,12 @@ PFS_metadata_lock* create_metadata_lock(void *identity, { PFS_metadata_lock *pfs; pfs_dirty_state dirty_state; + pfs_identity id; - pfs= global_mdl_container.allocate(& dirty_state); + pfs= global_mdl_container.allocate(& dirty_state, &id); if (pfs != NULL) { - pfs->m_identity= identity; + pfs->m_identity= id; pfs->m_enabled= global_metadata_class.m_enabled && flag_global_instrumentation; pfs->m_timed= global_metadata_class.m_timed; pfs->m_mdl_key.mdl_key_init(mdl_key); diff --git a/storage/perfschema/pfs_instr.h b/storage/perfschema/pfs_instr.h index 868f772267c..2fe5b05f7b8 100644 --- a/storage/perfschema/pfs_instr.h +++ b/storage/perfschema/pfs_instr.h @@ -91,8 +91,8 @@ struct PFS_instr /** Instrumented mutex implementation. @see PSI_mutex. */ struct PFS_ALIGNED PFS_mutex : public PFS_instr { - /** Mutex identity, typically a pthread_mutex_t. */ - const void *m_identity; + /** Mutex identity. */ + pfs_identity m_identity; /** Mutex class. */ PFS_mutex_class *m_class; /** Instrument statistics. */ @@ -109,8 +109,8 @@ struct PFS_ALIGNED PFS_mutex : public PFS_instr /** Instrumented rwlock implementation. @see PSI_rwlock. */ struct PFS_ALIGNED PFS_rwlock : public PFS_instr { - /** RWLock identity, typically a pthread_rwlock_t. */ - const void *m_identity; + /** RWLock identity. */ + pfs_identity m_identity; /** RWLock class. */ PFS_rwlock_class *m_class; /** Instrument statistics. */ @@ -134,8 +134,8 @@ struct PFS_ALIGNED PFS_rwlock : public PFS_instr /** Instrumented cond implementation. @see PSI_cond. */ struct PFS_ALIGNED PFS_cond : public PFS_instr { - /** Condition identity, typically a pthread_cond_t. */ - const void *m_identity; + /** Condition identity. */ + pfs_identity m_identity; /** Condition class. */ PFS_cond_class *m_class; /** Condition instance usage statistics. */ @@ -148,8 +148,8 @@ struct PFS_ALIGNED PFS_file : public PFS_instr uint32 get_version() { return m_lock.get_version(); } - /** File identity */ - const void *m_identity; + /** File identity. */ + pfs_identity m_identity; /** File name. */ char m_filename[FN_REFLEN]; /** File name length in bytes. */ @@ -241,8 +241,8 @@ public: ulonglong m_owner_event_id; /** Table share. */ PFS_table_share *m_share; - /** Table identity, typically a handler. */ - const void *m_identity; + /** Table identity. */ + pfs_identity m_identity; /** Table statistics. */ PFS_table_stat m_table_stat; /** Current internal lock. */ @@ -266,8 +266,8 @@ struct PFS_ALIGNED PFS_socket : public PFS_instr uint32 get_version() { return m_lock.get_version(); } - /** Socket identity, typically int */ - const void *m_identity; + /** Socket identity. */ + pfs_identity m_identity; /** Owning thread, if applicable */ PFS_thread *m_thread_owner; /** Socket file descriptor */ @@ -291,7 +291,7 @@ struct PFS_ALIGNED PFS_metadata_lock : public PFS_instr { return m_lock.get_version(); } /** Lock identity. */ - const void *m_identity; + pfs_identity m_identity; MDL_key m_mdl_key; opaque_mdl_type m_mdl_type; opaque_mdl_duration m_mdl_duration; @@ -814,4 +814,3 @@ extern LF_HASH pfs_filename_hash; /** @} */ #endif - diff --git a/storage/perfschema/pfs_instr_class.cc b/storage/perfschema/pfs_instr_class.cc index 86cc6365c31..ea2e7a747bc 100644 --- a/storage/perfschema/pfs_instr_class.cc +++ b/storage/perfschema/pfs_instr_class.cc @@ -716,7 +716,7 @@ create_table_share_lock_stat() pfs_dirty_state dirty_state; /* Create a new record in table stat array. */ - pfs= global_table_share_lock_container.allocate(& dirty_state); + pfs= global_table_share_lock_container.allocate(& dirty_state, NULL); if (pfs != NULL) { /* Reset the stats. */ @@ -769,13 +769,11 @@ create_table_share_index_stat(const TABLE_SHARE *server_share, uint server_index pfs_dirty_state dirty_state; /* Create a new record in index stat array. */ - pfs= global_table_share_index_container.allocate(& dirty_state); + pfs= global_table_share_index_container.allocate(& dirty_state, NULL); if (pfs != NULL) { if (server_index == MAX_INDEXES) - { pfs->m_key.m_name_length= 0; - } else { KEY *key_info= server_share->key_info + server_index; @@ -1797,7 +1795,7 @@ search: */ } - pfs= global_table_share_container.allocate(& dirty_state); + pfs= global_table_share_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; @@ -2061,4 +2059,3 @@ void update_program_share_derived_flags(PFS_thread *thread) } /** @} */ - diff --git a/storage/perfschema/pfs_prepared_stmt.cc b/storage/perfschema/pfs_prepared_stmt.cc index 50e4e27bb1e..6985720eef1 100644 --- a/storage/perfschema/pfs_prepared_stmt.cc +++ b/storage/perfschema/pfs_prepared_stmt.cc @@ -75,23 +75,23 @@ void reset_prepared_stmt_instances() } PFS_prepared_stmt* -create_prepared_stmt(void *identity, +create_prepared_stmt(void * /* identity */, PFS_thread *thread, PFS_program *pfs_program, PFS_events_statements *pfs_stmt, uint stmt_id, const char* stmt_name, uint stmt_name_length) { PFS_prepared_stmt *pfs= NULL; pfs_dirty_state dirty_state; + pfs_identity id; /* Create a new record in prepared stmt stat array. */ - pfs= global_prepared_stmt_container.allocate(& dirty_state); + pfs= global_prepared_stmt_container.allocate(& dirty_state, &id); if (pfs != NULL) { + pfs->m_identity= id; /* Reset the stats. */ pfs->reset_data(); /* Do the assignments. */ - pfs->m_identity= identity; - pfs->m_sqltext_length= 0; if (stmt_name != NULL) diff --git a/storage/perfschema/pfs_prepared_stmt.h b/storage/perfschema/pfs_prepared_stmt.h index 1a06122382c..395b6e2c015 100644 --- a/storage/perfschema/pfs_prepared_stmt.h +++ b/storage/perfschema/pfs_prepared_stmt.h @@ -38,7 +38,7 @@ struct PFS_ALIGNED PFS_prepared_stmt : public PFS_instr { /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** STATEMENT_ID */ ulonglong m_stmt_id; diff --git a/storage/perfschema/pfs_program.cc b/storage/perfschema/pfs_program.cc index 20b0aaa8489..b10f00b7d52 100644 --- a/storage/perfschema/pfs_program.cc +++ b/storage/perfschema/pfs_program.cc @@ -232,7 +232,7 @@ search: &is_enabled, &is_timed); /* Else create a new record in program stat array. */ - pfs= global_program_container.allocate(& dirty_state); + pfs= global_program_container.allocate(& dirty_state, NULL); if (pfs != NULL) { /* Do the assignments. */ diff --git a/storage/perfschema/pfs_server.cc b/storage/perfschema/pfs_server.cc index 258c2153d24..57adf9211cb 100644 --- a/storage/perfschema/pfs_server.cc +++ b/storage/perfschema/pfs_server.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2008, 2023, Oracle and/or its affiliates. + Copyright (c) 2026, MariaDB plc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -50,7 +51,7 @@ //#include "template_utils.h" #include "pfs_prepared_stmt.h" -PFS_global_param pfs_param; +READ_ONLY_SYSVAR PFS_global_param pfs_param; PFS_table_stat PFS_table_stat::g_reset_template; diff --git a/storage/perfschema/pfs_setup_actor.cc b/storage/perfschema/pfs_setup_actor.cc index 638b2e5648d..07b2dbd21cd 100644 --- a/storage/perfschema/pfs_setup_actor.cc +++ b/storage/perfschema/pfs_setup_actor.cc @@ -154,7 +154,7 @@ int insert_setup_actor(const String *user, const String *host, const String *rol PFS_setup_actor *pfs; pfs_dirty_state dirty_state; - pfs= global_setup_actor_container.allocate(& dirty_state); + pfs= global_setup_actor_container.allocate(& dirty_state, NULL); if (pfs != NULL) { set_setup_actor_key(&pfs->m_key, diff --git a/storage/perfschema/pfs_setup_object.cc b/storage/perfschema/pfs_setup_object.cc index a961c51d0e9..989c49979e8 100644 --- a/storage/perfschema/pfs_setup_object.cc +++ b/storage/perfschema/pfs_setup_object.cc @@ -157,7 +157,7 @@ int insert_setup_object(enum_object_type object_type, const String *schema, PFS_setup_object *pfs; pfs_dirty_state dirty_state; - pfs= global_setup_object_container.allocate(& dirty_state); + pfs= global_setup_object_container.allocate(& dirty_state, NULL); if (pfs != NULL) { set_setup_object_key(&pfs->m_key, object_type, diff --git a/storage/perfschema/pfs_user.cc b/storage/perfschema/pfs_user.cc index d83ee52a77d..e3ec2992fa1 100644 --- a/storage/perfschema/pfs_user.cc +++ b/storage/perfschema/pfs_user.cc @@ -166,7 +166,7 @@ search: lf_hash_search_unpin(pins); - pfs= global_user_container.allocate(& dirty_state); + pfs= global_user_container.allocate(& dirty_state, NULL); if (pfs != NULL) { pfs->m_key= key; diff --git a/storage/perfschema/table_events_waits.cc b/storage/perfschema/table_events_waits.cc index dbfe98e105d..b6d3a34379e 100644 --- a/storage/perfschema/table_events_waits.cc +++ b/storage/perfschema/table_events_waits.cc @@ -39,8 +39,6 @@ THR_LOCK table_events_waits_current::m_table_lock; -#define OBJECT_INSTANCE_BEGIN(X) (((intptr)X) - ((intptr) &pfs_truncatable_acl)) - PFS_engine_table_share_state table_events_waits_current::m_share_state = { false /* m_checked */ @@ -253,7 +251,7 @@ int table_events_waits_common::make_table_object_columns(PFS_events_waits *wait) m_row.m_index_name_length= 0; } - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; return 0; } @@ -268,7 +266,7 @@ int table_events_waits_common::make_file_object_columns(PFS_events_waits *wait) m_row.m_object_type= "FILE"; m_row.m_object_type_length= 4; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; if (safe_file->get_version() == wait->m_weak_version) { @@ -300,7 +298,7 @@ int table_events_waits_common::make_socket_object_columns(PFS_events_waits *wait m_row.m_object_type= "SOCKET"; m_row.m_object_type_length= 6; m_row.m_object_schema_length= 0; - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; if (safe_socket->get_version() == wait->m_weak_version) { @@ -434,7 +432,7 @@ int table_events_waits_common::make_metadata_lock_object_columns(PFS_events_wait if (m_row.m_object_name_length > 0) memcpy(m_row.m_object_name, mdl->name(), m_row.m_object_name_length); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; } else { @@ -500,17 +498,17 @@ void table_events_waits_common::make_row(PFS_events_waits *wait) break; case WAIT_CLASS_MUTEX: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_mutex_class((PFS_mutex_class*) wait->m_class); break; case WAIT_CLASS_RWLOCK: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_rwlock_class((PFS_rwlock_class*) wait->m_class); break; case WAIT_CLASS_COND: clear_object_columns(); - m_row.m_object_instance_addr= OBJECT_INSTANCE_BEGIN(wait->m_object_instance_addr); + m_row.m_object_instance_addr= wait->m_object_instance_addr; safe_class= sanitize_cond_class((PFS_cond_class*) wait->m_class); break; case WAIT_CLASS_TABLE: @@ -1163,4 +1161,3 @@ table_events_waits_history_long::get_row_count(void) { return events_waits_history_long_size; } - diff --git a/storage/perfschema/table_events_waits.h b/storage/perfschema/table_events_waits.h index 85a9b146745..5ddf57582d1 100644 --- a/storage/perfschema/table_events_waits.h +++ b/storage/perfschema/table_events_waits.h @@ -79,7 +79,7 @@ struct row_events_waits /** Length in bytes of @c m_index_name. */ uint m_index_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - intptr m_object_instance_addr; + pfs_identity m_object_instance_addr; /** Column SOURCE. */ char m_source[COL_SOURCE_SIZE]; /** Length in bytes of @c m_source. */ diff --git a/storage/perfschema/table_events_waits_summary.cc b/storage/perfschema/table_events_waits_summary.cc index c848ebf4418..d0502932ec7 100644 --- a/storage/perfschema/table_events_waits_summary.cc +++ b/storage/perfschema/table_events_waits_summary.cc @@ -83,7 +83,7 @@ table_events_waits_summary_by_instance void table_events_waits_summary_by_instance ::make_instr_row(PFS_instr *pfs, PFS_instr_class *klass, - const void *object_instance_begin, + pfs_identity object_instance_begin, PFS_single_stat *pfs_stat) { pfs_optimistic_state lock; @@ -97,7 +97,7 @@ void table_events_waits_summary_by_instance m_row.m_name= klass->m_name; m_row.m_name_length= klass->m_name_length; - m_row.m_object_instance_addr= (intptr) object_instance_begin; + m_row.m_object_instance_addr= object_instance_begin; get_normalizer(klass); m_row.m_stat.set(m_normalizer, pfs_stat); @@ -161,11 +161,7 @@ void table_events_waits_summary_by_instance::make_file_row(PFS_file *pfs) PFS_single_stat sum; pfs->m_file_stat.m_io_stat.sum_waits(& sum); - /* - Files don't have a in memory structure associated to it, - so we use the address of the PFS_file buffer as object_instance_begin - */ - make_instr_row(pfs, safe_class, pfs, & sum); + make_instr_row(pfs, safe_class, pfs->m_identity, & sum); } /** @@ -186,11 +182,7 @@ void table_events_waits_summary_by_instance::make_socket_row(PFS_socket *pfs) PFS_byte_stat pfs_stat; pfs->m_socket_stat.m_io_stat.sum(&pfs_stat); - /* - Sockets don't have an associated in-memory structure, so use the address of - the PFS_socket buffer as object_instance_begin. - */ - make_instr_row(pfs, safe_class, pfs, &pfs_stat); + make_instr_row(pfs, safe_class, pfs->m_identity, &pfs_stat); } int table_events_waits_summary_by_instance @@ -240,4 +232,3 @@ int table_events_waits_summary_by_instance return 0; } - diff --git a/storage/perfschema/table_events_waits_summary.h b/storage/perfschema/table_events_waits_summary.h index 74f3a9ac959..0135b5e9aa9 100644 --- a/storage/perfschema/table_events_waits_summary.h +++ b/storage/perfschema/table_events_waits_summary.h @@ -48,7 +48,7 @@ struct row_events_waits_summary_by_instance /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - intptr m_object_instance_addr; + pfs_identity m_object_instance_addr; /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER_WAIT. */ PFS_stat_row m_stat; }; @@ -65,7 +65,7 @@ public: protected: void make_instr_row(PFS_instr *pfs, PFS_instr_class *klass, - const void *object_instance_begin, + pfs_identity object_instance_begin, PFS_single_stat *pfs_stat); void make_mutex_row(PFS_mutex *pfs) override; void make_rwlock_row(PFS_rwlock *pfs) override; diff --git a/storage/perfschema/table_file_summary_by_instance.cc b/storage/perfschema/table_file_summary_by_instance.cc index 89e08d49161..a622430df88 100644 --- a/storage/perfschema/table_file_summary_by_instance.cc +++ b/storage/perfschema/table_file_summary_by_instance.cc @@ -203,7 +203,7 @@ int table_file_summary_by_instance::read_row_values(TABLE *table, m_row.m_event_name.set_field(f); break; case 2: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (ulonglong)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 3:/* COUNT_STAR */ diff --git a/storage/perfschema/table_file_summary_by_instance.h b/storage/perfschema/table_file_summary_by_instance.h index 09f4383394d..34a91663b8e 100644 --- a/storage/perfschema/table_file_summary_by_instance.h +++ b/storage/perfschema/table_file_summary_by_instance.h @@ -50,8 +50,9 @@ struct row_file_summary_by_instance /** Column EVENT_NAME. */ PFS_event_name_row m_event_name; - /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + /** Column OBJECT_INSTANCE_BEGIN. */ + pfs_identity m_identity; + /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER and NUMBER_OF_BYTES for READ, WRITE and MISC operation types. diff --git a/storage/perfschema/table_helper.h b/storage/perfschema/table_helper.h index 35039b47ccc..bfd15d8af0f 100644 --- a/storage/perfschema/table_helper.h +++ b/storage/perfschema/table_helper.h @@ -700,4 +700,3 @@ private: /** @} */ #endif - diff --git a/storage/perfschema/table_md_locks.cc b/storage/perfschema/table_md_locks.cc index e7b76c12764..54d91623012 100644 --- a/storage/perfschema/table_md_locks.cc +++ b/storage/perfschema/table_md_locks.cc @@ -177,7 +177,7 @@ int table_metadata_locks::read_row_values(TABLE *table, m_row.m_object.set_nullable_field(f->field_index, f); break; case 3: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 4: /* LOCK_TYPE */ set_field_mdl_type(f, m_row.m_mdl_type, m_row.m_object.m_object_type == OBJECT_TYPE_BACKUP); @@ -211,4 +211,3 @@ int table_metadata_locks::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_md_locks.h b/storage/perfschema/table_md_locks.h index 62f681435f6..47b8f6f78ae 100644 --- a/storage/perfschema/table_md_locks.h +++ b/storage/perfschema/table_md_locks.h @@ -43,7 +43,7 @@ struct PFS_metadata_lock; struct row_metadata_lock { /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; opaque_mdl_type m_mdl_type; opaque_mdl_duration m_mdl_duration; opaque_mdl_status m_mdl_status; diff --git a/storage/perfschema/table_prepared_stmt_instances.cc b/storage/perfschema/table_prepared_stmt_instances.cc index f064d8f03ae..0ad60048be5 100644 --- a/storage/perfschema/table_prepared_stmt_instances.cc +++ b/storage/perfschema/table_prepared_stmt_instances.cc @@ -235,7 +235,7 @@ int table_prepared_stmt_instances switch(f->field_index) { case 0: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 1: /* STATEMENT_ID */ set_field_ulonglong(f, m_row.m_stmt_id); @@ -298,4 +298,3 @@ int table_prepared_stmt_instances return 0; } - diff --git a/storage/perfschema/table_prepared_stmt_instances.h b/storage/perfschema/table_prepared_stmt_instances.h index 838deec55a0..80ccdd620f6 100644 --- a/storage/perfschema/table_prepared_stmt_instances.h +++ b/storage/perfschema/table_prepared_stmt_instances.h @@ -43,7 +43,7 @@ struct row_prepared_stmt_instances { /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** Column STMT_ID. */ ulonglong m_stmt_id; diff --git a/storage/perfschema/table_socket_instances.cc b/storage/perfschema/table_socket_instances.cc index 2435d19df3c..8d2d8d92335 100644 --- a/storage/perfschema/table_socket_instances.cc +++ b/storage/perfschema/table_socket_instances.cc @@ -184,7 +184,7 @@ int table_socket_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_event_name, m_row.m_event_name_length); break; case 1: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* THREAD_ID */ if (m_row.m_thread_id_set) diff --git a/storage/perfschema/table_socket_instances.h b/storage/perfschema/table_socket_instances.h index 61cb121afd4..e5e0f7ea0a1 100644 --- a/storage/perfschema/table_socket_instances.h +++ b/storage/perfschema/table_socket_instances.h @@ -44,7 +44,7 @@ struct row_socket_instances /** Length in bytes of @c m_event_name. */ uint m_event_name_length; /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** Column THREAD_ID */ ulonglong m_thread_id; /** True if thread_is is set */ diff --git a/storage/perfschema/table_socket_summary_by_instance.cc b/storage/perfschema/table_socket_summary_by_instance.cc index eb0568c05c2..3182e8f3deb 100644 --- a/storage/perfschema/table_socket_summary_by_instance.cc +++ b/storage/perfschema/table_socket_summary_by_instance.cc @@ -195,7 +195,7 @@ int table_socket_summary_by_instance::read_row_values(TABLE *table, m_row.m_event_name.set_field(f); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr)m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2:/* COUNT_STAR */ diff --git a/storage/perfschema/table_socket_summary_by_instance.h b/storage/perfschema/table_socket_summary_by_instance.h index 2f51d8b5e39..1ed679621fd 100644 --- a/storage/perfschema/table_socket_summary_by_instance.h +++ b/storage/perfschema/table_socket_summary_by_instance.h @@ -49,7 +49,7 @@ struct row_socket_summary_by_instance PFS_event_name_row m_event_name; /** Column OBJECT_INSTANCE_BEGIN */ - const void *m_identity; + pfs_identity m_identity; /** Columns COUNT_STAR, SUM/MIN/AVG/MAX TIMER and NUMBER_OF_BYTES for each operation. */ PFS_socket_io_stat_row m_io_stat; diff --git a/storage/perfschema/table_sync_instances.cc b/storage/perfschema/table_sync_instances.cc index 497f7fa1854..2cf8cc65db4 100644 --- a/storage/perfschema/table_sync_instances.cc +++ b/storage/perfschema/table_sync_instances.cc @@ -174,7 +174,7 @@ int table_mutex_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* LOCKED_BY_THREAD_ID */ if (m_row.m_locked) @@ -334,7 +334,7 @@ int table_rwlock_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 2: /* WRITE_LOCKED_BY_THREAD_ID */ if (m_row.m_write_locked) @@ -480,7 +480,7 @@ int table_cond_instances::read_row_values(TABLE *table, set_field_varchar_utf8(f, m_row.m_name, m_row.m_name_length); break; case 1: /* OBJECT_INSTANCE */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; default: assert(false); @@ -490,4 +490,3 @@ int table_cond_instances::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_sync_instances.h b/storage/perfschema/table_sync_instances.h index 1a6cf81a173..2807b45d7c2 100644 --- a/storage/perfschema/table_sync_instances.h +++ b/storage/perfschema/table_sync_instances.h @@ -48,7 +48,7 @@ struct row_mutex_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** True if column LOCKED_BY_THREAD_ID is not null. */ bool m_locked; /** Column LOCKED_BY_THREAD_ID. */ @@ -104,7 +104,7 @@ struct row_rwlock_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** True if column WRITE_LOCKED_BY_THREAD_ID is not null. */ bool m_write_locked; /** Column WRITE_LOCKED_BY_THREAD_ID. */ @@ -162,7 +162,7 @@ struct row_cond_instances /** Length in bytes of @c m_name. */ uint m_name_length; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; }; /** Table PERFORMANCE_SCHEMA.COND_INSTANCES. */ diff --git a/storage/perfschema/table_table_handles.cc b/storage/perfschema/table_table_handles.cc index 7ea2b0a3316..82b5f9dd6a2 100644 --- a/storage/perfschema/table_table_handles.cc +++ b/storage/perfschema/table_table_handles.cc @@ -196,7 +196,7 @@ int table_table_handles::read_row_values(TABLE *table, m_row.m_object.set_field(f->field_index, f); break; case 3: /* OBJECT_INSTANCE_BEGIN */ - set_field_ulonglong(f, (intptr) m_row.m_identity); + set_field_ulonglong(f, m_row.m_identity); break; case 4: /* OWNER_THREAD_ID */ set_field_ulonglong(f, m_row.m_owner_thread_id); @@ -218,4 +218,3 @@ int table_table_handles::read_row_values(TABLE *table, return 0; } - diff --git a/storage/perfschema/table_table_handles.h b/storage/perfschema/table_table_handles.h index d2330104e5d..883849d415d 100644 --- a/storage/perfschema/table_table_handles.h +++ b/storage/perfschema/table_table_handles.h @@ -48,7 +48,7 @@ struct row_table_handles /** Column OBJECT_TYPE, SCHEMA_NAME, OBJECT_NAME. */ PFS_object_row m_object; /** Column OBJECT_INSTANCE_BEGIN. */ - const void *m_identity; + pfs_identity m_identity; /** Column OWNER_THREAD_ID. */ ulonglong m_owner_thread_id; /** Column OWNER_EVENT_ID. */ diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test index 473bebdda89..081096d8077 100644 --- a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test +++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test @@ -33,10 +33,10 @@ insert into r1 values (5,5,5,5,5,5,5,5); update r1 set value1=value1+100 where id1=1 and id2=1 and id3='1'; --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load test --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load --rocksdb_bulk_load_allow_sk test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load --rocksdb_bulk_load_allow_sk test rollback; @@ -49,17 +49,17 @@ source include/search_pattern_in_file.inc; set @save_default_storage_engine=@@global.default_storage_engine; SET GLOBAL default_storage_engine=rocksdb; --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test source include/search_pattern_in_file.inc; # Sanity test mysqldump when the --innodb-stats-on-metadata is specified (no effect) --echo ==== mysqldump with --innodb-stats-on-metadata ==== --replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/ ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --innodb-stats-on-metadata test +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --innodb-stats-on-metadata test # testing mysqldump work with statement based binary logging SET GLOBAL binlog_format=statement; ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test > /dev/null +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test > /dev/null SET GLOBAL binlog_format=row; drop table r1; diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test index 4f4f5ed092b..d0cf34725b9 100644 --- a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test +++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test @@ -29,7 +29,7 @@ let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; select variable_value into @a from information_schema.global_status where variable_name='rocksdb_block_cache_add'; ---exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb test > /dev/null +--exec $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb test > /dev/null # verifying block cache was not filled select case when variable_value - @a > 20 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add'; diff --git a/storage/rocksdb/rdb_mariadb_server_port.cc b/storage/rocksdb/rdb_mariadb_server_port.cc index e74c792c75b..9d0c3476a54 100644 --- a/storage/rocksdb/rdb_mariadb_server_port.cc +++ b/storage/rocksdb/rdb_mariadb_server_port.cc @@ -12,6 +12,7 @@ //#include #include +#include /* MyRocks includes */ #include "./rdb_threads.h" diff --git a/support-files/CMakeLists.txt b/support-files/CMakeLists.txt index 1e2b5ef37c3..8dc3d235dcd 100644 --- a/support-files/CMakeLists.txt +++ b/support-files/CMakeLists.txt @@ -210,11 +210,8 @@ IF(UNIX AND NOT WITHOUT_SERVER) ENDIF() IF((HAVE_SYSTEMD OR INSTALL_SYSTEMD_TMPUSERS) AND INSTALL_SYSTEMD_TMPFILESDIR) - IF(MYSQL_DATADIR STREQUAL INSTALL_RUNDATADIR) - SET(DISABLE_RUNDATADIR "#") - ENDIF() get_filename_component(MYSQL_UNIX_ADDRDIR ${MYSQL_UNIX_ADDR} DIRECTORY) - IF(MYSQL_UNIX_ADDRDIR STREQUAL MYSQL_DATADIR OR MYSQL_UNIX_ADDRDIR STREQUAL INSTALL_RUNDATADIR) + IF(MYSQL_UNIX_ADDRDIR STREQUAL MYSQL_DATADIR OR MYSQL_UNIX_ADDRDIR STREQUAL INSTALL_RUNDIR) SET(DISABLE_MYSQL_UNIX_ADDRDIR "#") ENDIF() diff --git a/support-files/mariadb.service.in b/support-files/mariadb.service.in index 84c7da3bfc6..a18716fc856 100644 --- a/support-files/mariadb.service.in +++ b/support-files/mariadb.service.in @@ -65,21 +65,9 @@ ProtectControlGroups=true # Prevent accessing /home, /root and /run/user ProtectHome=true -# Use an environment file to pass variable _WSREP_NEW_CLUSTER -EnvironmentFile=-@INSTALL_RUNDATADIR@/wsrep-new-cluster - -# Use an environment file to pass variable _WSREP_START_POSITION -EnvironmentFile=-@INSTALL_RUNDATADIR@/wsrep-start-position - -# Perform automatic wsrep recovery. When server is started without wsrep, -# galera_recovery simply returns an empty string. In any case, however, -# the script is not expected to return with a non-zero status. -# It is always safe to remove @INSTALL_RUNDATADIR@/wsrep-start-position -# environment file. -# Do not panic if galera_recovery script is not available. (MDEV-10538) -ExecStartPre=/bin/sh -c "[ ! -e @bindir@/galera_recovery ] && VAR= || \ - VAR=`@bindir@/galera_recovery`; [ $? -eq 0 ] \ - && echo _WSREP_START_POSITION=$VAR > @INSTALL_RUNDATADIR@/wsrep-start-position || exit 1" +# Use an environment file to pass variable _WSREP_NEW_CLUSTER. +# Must not be writable by mysql user. +EnvironmentFile=-@INSTALL_RUNDIR@/mariadb-wsrep-new-cluster # Needed to create system tables etc. # ExecStartPre=@scriptdir@/mariadb-install-db @@ -89,10 +77,17 @@ ExecStartPre=/bin/sh -c "[ ! -e @bindir@/galera_recovery ] && VAR= || \ # Use the [Service] section and Environment="MYSQLD_OPTS=...". # This isn't a replacement for my.cnf. # _WSREP_NEW_CLUSTER is for the exclusive use of the script galera_new_cluster -ExecStart=@sbindir@/mariadbd $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION +# +# Perform automatic wsrep recovery. When server is started without wsrep, +# galera_recovery simply returns an empty string. In any case, however, it is +# not expected to return a non-zero status. +ExecStart=/bin/sh -c "set -f; [ ! -e @bindir@/galera_recovery ] && VAR= || \ + VAR=`@bindir@/galera_recovery`; [ $? -eq 0 ] || exit 1; \ + exec @sbindir@/mariadbd $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $VAR" -# Unset _WSREP_START_POSITION/_WSREP_NEW_CLUSTER environment variable. -ExecStartPost=/bin/rm -f @INSTALL_RUNDATADIR@/wsrep-start-position @INSTALL_RUNDATADIR@/wsrep-new-cluster +# have to set the name explicitly now, otherwise it'll be the +# first name from ExecStart +SyslogIdentifier=mariadbd @SYSTEMD_EXECSTARTPOST@ diff --git a/support-files/rpm/server-prein.sh b/support-files/rpm/server-prein.sh index a57dcd348d2..c472483bdd9 100644 --- a/support-files/rpm/server-prein.sh +++ b/support-files/rpm/server-prein.sh @@ -68,6 +68,8 @@ fi # Create a MariaDB user and group. Do not report any problems if it already exists. groupadd -r %{mysqld_group} 2> /dev/null || true -useradd -M -r --home %{mysqldatadir} --shell /sbin/nologin --comment "MariaDB server" --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true +useradd -M -r --home /nonexistent --shell /sbin/nologin --comment "MariaDB server" --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true # The user may already exist, make sure it has the proper group nevertheless (BUG#12823) usermod --gid %{mysqld_group} %{mysqld_user} 2> /dev/null || true +# Make sure the home dir is correct too (must not be datadir) +usermod -d /nonexistent %{mysqld_user} 2> /dev/null || true diff --git a/support-files/tmpfiles.conf.in b/support-files/tmpfiles.conf.in index 6a2a28b59a3..e41585c08a8 100644 --- a/support-files/tmpfiles.conf.in +++ b/support-files/tmpfiles.conf.in @@ -1,9 +1,6 @@ # This is the directory where the unix socket # of MariaDB may be created. # -# Under Galera this is where an envfile of -# the Galera start position is stored. -# # Other temporary directories can be created here like: # * tmpdir # * innodb_tmpdir @@ -12,5 +9,4 @@ # etc. # It shouldn't be used for datadir which is why it # may be disabled. -@DISABLE_RUNDATADIR@d @INSTALL_RUNDATADIR@ 0755 @MYSQLD_USER@ @MYSQLD_USER@ - @DISABLE_MYSQL_UNIX_ADDRDIR@d @MYSQL_UNIX_ADDRDIR@ 0755 @MYSQLD_USER@ root -