From 957cb7b7ba355184aebf0f5dc91b7f2aa620c0e0 Mon Sep 17 00:00:00 2001 From: Anel Husakovic Date: Tue, 12 May 2020 16:16:05 +0200 Subject: MDEV-22312: Bad error message for SET DEFAULT ROLE when user account is not granted the role - `SET DEFAULT ROLE xxx [FOR yyy]` should say: "User yyy has not been granted a role xxx" if: - The current user (not the user `yyy` in the FOR clause) can see the role xxx. It can see the role if: * role exists in `mysql.roles_mappings` (traverse the graph), * If the current user has read access on `mysql.user` table - in that case, it can see all roles, granted or not. - Otherwise it should be "Invalid role specification". In other words, it should not be possible to use `SET DEFAULT ROLE` to discover whether a specific role exist or not. --- mysql-test/suite/roles/set_default_role_for.result | 4 +- .../suite/roles/set_default_role_invalid.result | 87 +++++++++++++++ .../suite/roles/set_default_role_invalid.test | 107 ++++++++++++++++++ mysql-test/suite/roles/set_role-recursive.result | 2 +- sql/set_var.cc | 16 ++- sql/set_var.h | 1 + sql/sql_acl.cc | 120 ++++++++++++++------- sql/sql_acl.h | 2 +- 8 files changed, 294 insertions(+), 45 deletions(-) diff --git a/mysql-test/suite/roles/set_default_role_for.result b/mysql-test/suite/roles/set_default_role_for.result index 7289319a428..fcea28e882a 100644 --- a/mysql-test/suite/roles/set_default_role_for.result +++ b/mysql-test/suite/roles/set_default_role_for.result @@ -14,7 +14,7 @@ set default role role_a for user_a@localhost; set default role invalid_role for user_a@localhost; ERROR OP000: Invalid role specification `invalid_role`. set default role role_b for user_a@localhost; -ERROR OP000: Invalid role specification `role_b`. +ERROR OP000: User `user_a@localhost` has not been granted role `role_b` set default role role_b for user_b@localhost; show grants; Grants for user_a@localhost @@ -36,7 +36,7 @@ user host default_role user_a localhost role_a user_b localhost role_b set default role role_b for current_user; -ERROR OP000: Invalid role specification `role_b`. +ERROR OP000: User `user_a@localhost` has not been granted role `role_b` show grants; Grants for user_b@localhost GRANT role_b TO 'user_b'@'localhost' diff --git a/mysql-test/suite/roles/set_default_role_invalid.result b/mysql-test/suite/roles/set_default_role_invalid.result index 3916bacfd4c..b6036e8de99 100644 --- a/mysql-test/suite/roles/set_default_role_invalid.result +++ b/mysql-test/suite/roles/set_default_role_invalid.result @@ -38,3 +38,90 @@ ERROR 42000: SELECT command denied to user 'test_user'@'localhost' for table 'us drop role test_role; drop role not_granted_role; drop user test_user@localhost; +# +# MDEV-22312: Bad error message for SET DEFAULT ROLE when user account +# is not granted the role +# +CREATE USER a; +CREATE USER b; +CREATE ROLE r1; +CREATE ROLE r2; +SET DEFAULT ROLE r1 FOR a; +ERROR OP000: User `a@%` has not been granted role `r1` +GRANT r1 TO b; +GRANT r2 TO b; +SET DEFAULT ROLE r1 FOR b; +# Change user b +SELECT CURRENT_ROLE; +CURRENT_ROLE +r1 +SET ROLE r2; +SELECT CURRENT_ROLE; +CURRENT_ROLE +r2 +SET DEFAULT ROLE r1 FOR a; +ERROR 42000: Access denied for user 'b'@'%' to database 'mysql' +SET DEFAULT ROLE r2; +# Change user root (session 1: select_priv to b) +GRANT SELECT ON mysql.* TO b; +# Change user b (session 1: select_priv) +SHOW GRANTS FOR b; +Grants for b@% +GRANT r1 TO 'b'@'%' +GRANT r2 TO 'b'@'%' +GRANT USAGE ON *.* TO 'b'@'%' +GRANT SELECT ON `mysql`.* TO 'b'@'%' +SET DEFAULT ROLE r1 FOR a; +ERROR 42000: Access denied for user 'b'@'%' to database 'mysql' +SELECT CURRENT_ROLE; +CURRENT_ROLE +r2 +SET DEFAULT ROLE NONE; +SELECT CURRENT_ROLE; +CURRENT_ROLE +r2 +SET DEFAULT ROLE current_role FOR current_user; +SET DEFAULT ROLE invalid_role; +ERROR OP000: Invalid role specification `invalid_role`. +SET DEFAULT ROLE invalid_role FOR a; +ERROR 42000: Access denied for user 'b'@'%' to database 'mysql' +SET DEFAULT ROLE none FOR a; +ERROR 42000: Access denied for user 'b'@'%' to database 'mysql' +# Change user root (session 2: adding update_priv to user b) +GRANT UPDATE ON mysql.* TO b; +# Change user b +SHOW GRANTS FOR b; +Grants for b@% +GRANT r1 TO 'b'@'%' +GRANT r2 TO 'b'@'%' +GRANT USAGE ON *.* TO 'b'@'%' +GRANT SELECT, UPDATE ON `mysql`.* TO 'b'@'%' +SET DEFAULT ROLE r1 FOR a; +ERROR OP000: User `a@%` has not been granted role `r1` +SET DEFAULT ROLE invalid_role; +ERROR OP000: Invalid role specification `invalid_role`. +SET DEFAULT ROLE invalid_role FOR a; +ERROR OP000: Invalid role specification `invalid_role`. +SET DEFAULT ROLE none FOR a; +# Change user root (session 3: Grant role to user a) +GRANT r1 TO a; +SET DEFAULT ROLE r1 FOR a; +# Change user a (verify session 3) +SELECT CURRENT_ROLE; +CURRENT_ROLE +r1 +SET DEFAULT ROLE None; +# Change user b (session 3: role granted to user a) +SET DEFAULT ROLE r1 FOR a; +SET DEFAULT ROLE r2 FOR a; +ERROR OP000: User `a@%` has not been granted role `r2` +SET DEFAULT ROLE invalid_role; +ERROR OP000: Invalid role specification `invalid_role`. +SET DEFAULT ROLE invalid_role FOR a; +ERROR OP000: Invalid role specification `invalid_role`. +SELECT user, host, default_role FROM mysql.user where user='a' or user='b'; +user host default_role +a % r1 +b % r2 +DROP ROLE r1, r2; +DROP USER a, b; diff --git a/mysql-test/suite/roles/set_default_role_invalid.test b/mysql-test/suite/roles/set_default_role_invalid.test index 8e72e316d4b..02fca1107e2 100644 --- a/mysql-test/suite/roles/set_default_role_invalid.test +++ b/mysql-test/suite/roles/set_default_role_invalid.test @@ -60,3 +60,110 @@ change_user 'root'; drop role test_role; drop role not_granted_role; drop user test_user@localhost; + +--echo # +--echo # MDEV-22312: Bad error message for SET DEFAULT ROLE when user account +--echo # is not granted the role +--echo # + +CREATE USER a; +CREATE USER b; +CREATE ROLE r1; +CREATE ROLE r2; +# Role has not been granted to user a, but the role is visible to current_user +--error ER_INVALID_ROLE +SET DEFAULT ROLE r1 FOR a; +# Granting roles to user b +GRANT r1 TO b; +GRANT r2 TO b; +# After granting the role, role can be set as default +SET DEFAULT ROLE r1 FOR b; + +--echo # Change user b +change_user b; +SELECT CURRENT_ROLE; +SET ROLE r2; +SELECT CURRENT_ROLE; +# User b has no UPDATE_PRIV for mysql.user +--error ER_DBACCESS_DENIED_ERROR +SET DEFAULT ROLE r1 FOR a; +SET DEFAULT ROLE r2; + +--echo # Change user root (session 1: select_priv to b) +change_user root; +# Let's grant select_priv to user b +GRANT SELECT ON mysql.* TO b; + +--echo # Change user b (session 1: select_priv) +change_user b; +SHOW GRANTS FOR b; +# User must have update_priv before setting the role +--error ER_DBACCESS_DENIED_ERROR +SET DEFAULT ROLE r1 FOR a; +# Testing the `CURRENT_ROLE` as a special case +SELECT CURRENT_ROLE; +SET DEFAULT ROLE NONE; +SELECT CURRENT_ROLE; +SET DEFAULT ROLE current_role FOR current_user; +# Testing of non-existing role +--error ER_INVALID_ROLE +SET DEFAULT ROLE invalid_role; +# Testing of non-existing role for different user +--error ER_DBACCESS_DENIED_ERROR +SET DEFAULT ROLE invalid_role FOR a; +# Testing the `None` role for different user +-- error ER_DBACCESS_DENIED_ERROR +SET DEFAULT ROLE none FOR a; + +--echo # Change user root (session 2: adding update_priv to user b) +change_user root; +# update_priv are enough +GRANT UPDATE ON mysql.* TO b; + +--echo # Change user b +change_user b; +SHOW GRANTS FOR b; +# In all tests in session user a has not been granted the role +# Testing setting role for different user, should fail with new error +--error ER_INVALID_ROLE +SET DEFAULT ROLE r1 FOR a; +# Testing of non-existing role +--error ER_INVALID_ROLE +SET DEFAULT ROLE invalid_role; +# Testing of non-existing role for different user with update_priv +--error ER_INVALID_ROLE +SET DEFAULT ROLE invalid_role FOR a; +# Testing the `None` role for different user with update_priv +SET DEFAULT ROLE none FOR a; + +--echo # Change user root (session 3: Grant role to user a) +change_user root; +# After granting the privilege for a, user b can set default role +GRANT r1 TO a; +SET DEFAULT ROLE r1 FOR a; + +--echo # Change user a (verify session 3) +change_user a; +SELECT CURRENT_ROLE; +SET DEFAULT ROLE None; + +--echo # Change user b (session 3: role granted to user a) +change_user b; +# This should set role because b has update_priv +SET DEFAULT ROLE r1 FOR a; +# Testing non-granted role r2 still should fail +-- error ER_INVALID_ROLE +SET DEFAULT ROLE r2 FOR a; +# Testing of non-existing role +--error ER_INVALID_ROLE +SET DEFAULT ROLE invalid_role; +# Testing of non-existing role for different user +--error ER_INVALID_ROLE +SET DEFAULT ROLE invalid_role FOR a; + +# Clear the workspace +change_user root; +--sorted_result +SELECT user, host, default_role FROM mysql.user where user='a' or user='b'; +DROP ROLE r1, r2; +DROP USER a, b; diff --git a/mysql-test/suite/roles/set_role-recursive.result b/mysql-test/suite/roles/set_role-recursive.result index 9e62558fc14..b0d79377183 100644 --- a/mysql-test/suite/roles/set_role-recursive.result +++ b/mysql-test/suite/roles/set_role-recursive.result @@ -66,7 +66,7 @@ Grants for test_user@localhost GRANT USAGE ON *.* TO 'test_user'@'localhost' GRANT test_role1 TO 'test_user'@'localhost' set role test_role2; -ERROR OP000: Invalid role specification `test_role2`. +ERROR OP000: User `test_user@localhost` has not been granted role `test_role2` select current_user(), current_role(); current_user() current_role() test_user@localhost NULL diff --git a/sql/set_var.cc b/sql/set_var.cc index b5f017eb85e..4b1ef63c5f3 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -923,8 +923,17 @@ int set_var_default_role::check(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS real_user= get_current_user(thd, user); - int status= acl_check_set_default_role(thd, real_user->host.str, real_user->user.str); - return status; + real_role= role.str; + if (role.str == current_role.str) + { + if (!thd->security_ctx->priv_role[0]) + real_role= "NONE"; + else + real_role= thd->security_ctx->priv_role; + } + + return acl_check_set_default_role(thd, real_user->host.str, + real_user->user.str, real_role); #else return 0; #endif @@ -935,7 +944,8 @@ int set_var_default_role::update(THD *thd) #ifndef NO_EMBEDDED_ACCESS_CHECKS Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; thd->m_reprepare_observer= 0; - int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str, role.str); + int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str, + real_role); thd->m_reprepare_observer= save_reprepare_observer; return res; #else diff --git a/sql/set_var.h b/sql/set_var.h index fc79e906270..572ff9fe60a 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -339,6 +339,7 @@ class set_var_default_role: public set_var_base { LEX_USER *user, *real_user; LEX_STRING role; + const char *real_role; public: set_var_default_role(LEX_USER *user_arg, LEX_STRING role_arg) : user(user_arg), role(role_arg) {} diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index b6a6f806e50..af8685c458b 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -201,6 +201,8 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; +class ACL_USER; +static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip); #ifndef NO_EMBEDDED_ACCESS_CHECKS static plugin_ref old_password_plugin; @@ -2034,8 +2036,21 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, DBUG_RETURN(res); } -static int check_user_can_set_role(const char *user, const char *host, - const char *ip, const char *rolename, ulonglong *access) +static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) +{ + LEX_CSTRING *rolename= static_cast(data); + if (rolename->length == grantee->user.length && + !strcmp(rolename->str, grantee->user.str)) + return -1; // End search, we've found our role. + + /* Keep looking, we haven't found our role yet. */ + return 0; +} + + +static int check_user_can_set_role(THD *thd, const char *user, const char *host, + const char *ip, const char *rolename, + ulonglong *access) { ACL_ROLE *role; ACL_USER_BASE *acl_user_base; @@ -2053,8 +2068,7 @@ static int check_user_can_set_role(const char *user, const char *host, acl_user= find_user_wild(host, user, ip); if (acl_user == NULL) { - my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); - result= -1; + result= ER_INVALID_CURRENT_USER; } else if (access) *access= acl_user->access; @@ -2065,9 +2079,9 @@ static int check_user_can_set_role(const char *user, const char *host, role= find_acl_role(rolename); /* According to SQL standard, the same error message must be presented */ - if (role == NULL) { - my_error(ER_INVALID_ROLE, MYF(0), rolename); - result= -1; + if (role == NULL) + { + result= ER_INVALID_ROLE; goto end; } @@ -2088,7 +2102,6 @@ static int check_user_can_set_role(const char *user, const char *host, /* According to SQL standard, the same error message must be presented */ if (!is_granted) { - my_error(ER_INVALID_ROLE, MYF(0), rolename); result= 1; goto end; } @@ -2097,17 +2110,66 @@ static int check_user_can_set_role(const char *user, const char *host, { *access = acl_user->access | role->access; } + end: mysql_mutex_unlock(&acl_cache->lock); - return result; + /* We present different error messages depending if the user has sufficient + privileges to know if the INVALID_ROLE exists. */ + switch (result) + { + case ER_INVALID_CURRENT_USER: + my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); + break; + case ER_INVALID_ROLE: + /* Role doesn't exist at all */ + my_error(ER_INVALID_ROLE, MYF(0), rolename); + break; + case 1: + StringBuffer<1024> c_usr; + LEX_CSTRING role_lex; + /* First, check if current user can see mysql database. */ + bool read_access= !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 1); + + role_lex.str= rolename; + role_lex.length= strlen(rolename); + mysql_mutex_lock(&acl_cache->lock); + ACL_USER *cur_user= find_user_or_anon(thd->security_ctx->priv_host, + thd->security_ctx->priv_user, + thd->security_ctx->ip); + + /* If the current user does not have select priv to mysql database, + see if the current user can discover the role if it was granted to him. + */ + if (cur_user && (read_access || + traverse_role_graph_down(cur_user, &role_lex, + check_role_is_granted_callback, + NULL) == -1)) + { + /* Role is not granted but current user can see the role */ + c_usr.append(user, strlen(user)); + c_usr.append('@'); + c_usr.append(host, strlen(host)); + my_printf_error(ER_INVALID_ROLE, "User %`s has not been granted role %`s", + MYF(0), c_usr.c_ptr(), rolename); + } + else + { + /* Role is not granted and current user cannot see the role */ + my_error(ER_INVALID_ROLE, MYF(0), rolename); + } + mysql_mutex_unlock(&acl_cache->lock); + break; + } + + return result; } + int acl_check_setrole(THD *thd, char *rolename, ulonglong *access) { - /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ - return check_user_can_set_role(thd->security_ctx->priv_user, - thd->security_ctx->host, thd->security_ctx->ip, rolename, access); + return check_user_can_set_role(thd, thd->security_ctx->priv_user, + thd->security_ctx->host, thd->security_ctx->ip, rolename, access); } @@ -2886,9 +2948,12 @@ WSREP_ERROR_LABEL: DBUG_RETURN(result); } -int acl_check_set_default_role(THD *thd, const char *host, const char *user) +int acl_check_set_default_role(THD *thd, const char *host, const char *user, + const char *role) { - return check_alter_user(thd, host, user); + DBUG_ENTER("acl_check_set_default_role"); + DBUG_RETURN(check_alter_user(thd, host, user) || + check_user_can_set_role(thd, user, host, NULL, role, NULL)); } int acl_set_default_role(THD *thd, const char *host, const char *user, @@ -2910,16 +2975,6 @@ int acl_set_default_role(THD *thd, const char *host, const char *user, DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'", safe_str(user), safe_str(host), safe_str(rolename))); - if (rolename == current_role.str) { - if (!thd->security_ctx->priv_role[0]) - rolename= "NONE"; - else - rolename= thd->security_ctx->priv_role; - } - - if (check_user_can_set_role(user, host, host, rolename, NULL)) - DBUG_RETURN(result); - if (!strcasecmp(rolename, "NONE")) clear_role= TRUE; @@ -3370,7 +3425,7 @@ static bool test_if_create_new_users(THD *thd) if (!(db_access & INSERT_ACL)) { if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) - create_new_users=0; + create_new_users=0; } } return create_new_users; @@ -8508,17 +8563,6 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) mysql_mutex_unlock(&acl_cache->lock); } -static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) -{ - LEX_CSTRING *rolename= static_cast(data); - if (rolename->length == grantee->user.length && - !strcmp(rolename->str, grantee->user.str)) - return -1; // End search, we've found our role. - - /* Keep looking, we haven't found our role yet. */ - return 0; -} - /* Initialize a TABLE_LIST array and open grant tables @@ -10329,7 +10373,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, Security context in THD contains two pairs of (user,host): 1. (user,host) pair referring to inbound connection. 2. (priv_user,priv_host) pair obtained from mysql.user table after doing - authnetication of incoming connection. + authentication of incoming connection. Privileges should be checked wrt (priv_user, priv_host) tuple, because (user,host) pair obtained from inbound connection may have different values than what is actually stored in mysql.user table and while granting @@ -10746,7 +10790,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ulong j,test_access= want_access & ~GRANT_ACL; for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1) { - if (test_access & j) + if (test_access & j) { if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, command_array[priv_id], diff --git a/sql/sql_acl.h b/sql/sql_acl.h index c191cb83de5..3bd896cab79 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -402,7 +402,7 @@ bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user, bool with_grant); int acl_setrole(THD *thd, char *rolename, ulonglong access); int acl_check_setrole(THD *thd, char *rolename, ulonglong *access); -int acl_check_set_default_role(THD *thd, const char *host, const char *user); +int acl_check_set_default_role(THD *thd, const char *host, const char *user, const char *role); int acl_set_default_role(THD *thd, const char *host, const char *user, const char *rolename); -- cgit v1.2.1 From a1b3bebe1f7f7221daf520e35b81e13c1478d189 Mon Sep 17 00:00:00 2001 From: Anel Husakovic Date: Thu, 28 May 2020 19:34:27 +0200 Subject: fix pre-definition for embedded server for find_user_or_anon() Pre-definitions are allowed for non-embedded. Failur catched with: ``` cmake ../../10.1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++-9 -DCMAKE_C_COMPILER=gcc-9 -DWITH_EMBEDDED_SERVER=ON -DCMAKE_BUILD_TYPE=Debug -DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,PERFSCHEMA,SPIDER,SPHINX}=N -DMYSQL_MAINTAINER_MODE=ON -DNOT_FOR_DISTRIBUTION=ON ``` Alternative fix would be ``` --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -201,8 +201,10 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; +#ifndef EMBEDDED_LIBRARY class ACL_USER; static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip); +#endif ``` --- sql/sql_acl.cc | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index af8685c458b..6e6135b75bb 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -201,9 +201,6 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; -class ACL_USER; -static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip); - #ifndef NO_EMBEDDED_ACCESS_CHECKS static plugin_ref old_password_plugin; #endif @@ -2047,6 +2044,28 @@ static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) return 0; } +/* + unlike find_user_exact and find_user_wild, + this function finds anonymous users too, it's when a + user is not empty, but priv_user (acl_user->user) is empty. +*/ +static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip) +{ + ACL_USER *result= NULL; + mysql_mutex_assert_owner(&acl_cache->lock); + for (uint i=0; i < acl_users.elements; i++) + { + ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); + if ((!acl_user_tmp->user.str || + !strcmp(user, acl_user_tmp->user.str)) && + compare_hostname(&acl_user_tmp->host, host, ip)) + { + result= acl_user_tmp; + break; + } + } + return result; +} static int check_user_can_set_role(THD *thd, const char *user, const char *host, const char *ip, const char *rolename, @@ -3128,31 +3147,6 @@ bool is_acl_user(const char *host, const char *user) return res; } - -/* - unlike find_user_exact and find_user_wild, - this function finds anonymous users too, it's when a - user is not empty, but priv_user (acl_user->user) is empty. -*/ -static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip) -{ - ACL_USER *result= NULL; - mysql_mutex_assert_owner(&acl_cache->lock); - for (uint i=0; i < acl_users.elements; i++) - { - ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); - if ((!acl_user_tmp->user.str || - !strcmp(user, acl_user_tmp->user.str)) && - compare_hostname(&acl_user_tmp->host, host, ip)) - { - result= acl_user_tmp; - break; - } - } - return result; -} - - /* Find first entry that matches the specified user@host pair */ -- cgit v1.2.1 From a2932e86b5dcbc76bf2bef545cf942f202abd3e8 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Fri, 29 May 2020 15:31:24 +0400 Subject: MDEV-22744 *SAN: sql/item_xmlfunc.cc:791:43: runtime error: downcast of address ... which does not point to an object of type 'Item_func' note: object is of type 'Item_bool' (on optimized builds) In Item_nodeset_func_predicate::val_nodeset, args[1] is not necessarily an Item_func descendant. It can be Item_bool. Removing a wrong cast. It was not really needed anyway. --- sql/item_xmlfunc.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc index d33cd30a928..97ef24b0851 100644 --- a/sql/item_xmlfunc.cc +++ b/sql/item_xmlfunc.cc @@ -797,7 +797,6 @@ String *Item_nodeset_func_attributebyname::val_nodeset(String *nodeset) String *Item_nodeset_func_predicate::val_nodeset(String *str) { Item_nodeset_func *nodeset_func= (Item_nodeset_func*) args[0]; - Item_func *comp_func= (Item_func*)args[1]; uint pos= 0, size; prepare(str); size= fltend - fltbeg; @@ -807,7 +806,7 @@ String *Item_nodeset_func_predicate::val_nodeset(String *str) ((XPathFilter*)(&nodeset_func->context_cache))->append_element(flt->num, flt->pos, size); - if (comp_func->val_int()) + if (args[1]->val_int()) ((XPathFilter*)str)->append_element(flt->num, pos++); } return str; -- cgit v1.2.1 From 1055a7f4fc0a095e6ab3a6a138d64688b764a78b Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Fri, 11 Oct 2019 17:20:28 +0400 Subject: Simplified away statistics_for_tables_is_needed() Removed redundant loops, integrated logics into the caller instead. Unified condition in read_statistics_for_tables(), less "table_share != NULL" checks, no more potential "table_share == NULL" dereferencing. Part of MDEV-19061 - table_share used for reading statistical tables is not protected --- sql/sql_statistics.cc | 112 ++++++++++++++------------------------------------ 1 file changed, 31 insertions(+), 81 deletions(-) diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index 11e2db9ae44..0f8daa6e52f 100644 --- a/sql/sql_statistics.cc +++ b/sql/sql_statistics.cc @@ -3035,71 +3035,6 @@ void delete_stat_values_for_table_share(TABLE_SHARE *table_share) } -/** - @brief - Check whether any statistics is to be read for tables from a table list - - @param - thd The thread handle - @param - tables The tables list for whose tables the check is to be done - - @details - The function checks whether for any of the tables opened and locked for - a statement statistics from statistical tables is needed to be read. - - @retval - TRUE statistics for any of the tables is needed to be read - @retval - FALSE Otherwise -*/ - -static -bool statistics_for_tables_is_needed(THD *thd, TABLE_LIST *tables) -{ - if (!tables) - return FALSE; - - /* - Do not read statistics for any query that explicity involves - statistical tables, failure to to do so we may end up - in a deadlock. - */ - - for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) - { - if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) - { - TABLE_SHARE *table_share= tl->table->s; - if (table_share && - table_share->table_category != TABLE_CATEGORY_USER - && is_stat_table(tl->db, tl->alias)) - return FALSE; - } - } - - for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) - { - if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) - { - TABLE_SHARE *table_share= tl->table->s; - if (table_share && - table_share->stats_cb.stats_can_be_read && - (!table_share->stats_cb.stats_is_read || - (!table_share->stats_cb.histograms_are_read && - thd->variables.optimizer_use_condition_selectivity > 3))) - return TRUE; - if (table_share->stats_cb.stats_is_read) - tl->table->stats_is_read= TRUE; - if (table_share->stats_cb.histograms_are_read) - tl->table->histograms_are_read= TRUE; - } - } - - return FALSE; -} - - /** @brief Read histogram for a table from the persistent statistical tables @@ -3221,13 +3156,16 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) if (thd->bootstrap || thd->variables.use_stat_tables == NEVER) DBUG_RETURN(0); + bool found_stat_table= false; + bool statistics_for_tables_is_needed= false; + for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) { - if (tl->table) + TABLE_SHARE *table_share; + if (!tl->is_view_or_derived() && tl->table && (table_share= tl->table->s) && + table_share->tmp_table == NO_TMP_TABLE) { - TABLE_SHARE *table_share= tl->table->s; - if (table_share && table_share->table_category == TABLE_CATEGORY_USER && - table_share->tmp_table == NO_TMP_TABLE) + if (table_share->table_category == TABLE_CATEGORY_USER) { if (table_share->stats_cb.stats_can_be_read || !alloc_statistics_for_table_share(thd, table_share)) @@ -3244,15 +3182,29 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) for ( ; *field_ptr; field_ptr++, table_field_ptr++) (*table_field_ptr)->read_stats= (*field_ptr)->read_stats; tl->table->stats_is_read= table_share->stats_cb.stats_is_read; + tl->table->histograms_are_read= + table_share->stats_cb.histograms_are_read; + + if (!tl->table->stats_is_read || + (!table_share->stats_cb.histograms_are_read && + thd->variables.optimizer_use_condition_selectivity > 3)) + statistics_for_tables_is_needed= true; } } } + else if (is_stat_table(tl->db, tl->alias)) + found_stat_table= true; } } DEBUG_SYNC(thd, "statistics_read_start"); - if (!statistics_for_tables_is_needed(thd, tables)) + /* + Do not read statistics for any query that explicity involves + statistical tables, failure to to do so we may end up + in a deadlock. + */ + if (found_stat_table || !statistics_for_tables_is_needed) DBUG_RETURN(0); if (open_stat_tables(thd, stat_tables, &open_tables_backup, FALSE)) @@ -3260,14 +3212,12 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) { - if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) - { - TABLE_SHARE *table_share= tl->table->s; - if (table_share && !(table_share->table_category == TABLE_CATEGORY_USER)) - continue; - - if (table_share && - table_share->stats_cb.stats_can_be_read && + TABLE_SHARE *table_share; + if (!tl->is_view_or_derived() && tl->table && (table_share= tl->table->s) && + table_share->tmp_table == NO_TMP_TABLE && + table_share->table_category == TABLE_CATEGORY_USER) + { + if (table_share->stats_cb.stats_can_be_read && !table_share->stats_cb.stats_is_read) { (void) read_statistics_for_table(thd, tl->table, stat_tables); @@ -3275,8 +3225,8 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) } if (table_share->stats_cb.stats_is_read) tl->table->stats_is_read= TRUE; - if (thd->variables.optimizer_use_condition_selectivity > 3 && - table_share && table_share->stats_cb.stats_can_be_read && + if (thd->variables.optimizer_use_condition_selectivity > 3 && + table_share->stats_cb.stats_can_be_read && !table_share->stats_cb.histograms_are_read) { (void) read_histograms_for_table(thd, tl->table, stat_tables); @@ -3285,7 +3235,7 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) if (table_share->stats_cb.histograms_are_read) tl->table->histograms_are_read= TRUE; } - } + } close_system_tables(thd, &open_tables_backup); -- cgit v1.2.1 From 609a0d3db3fecc12c2d63b52bec3e3a305e6c702 Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Wed, 16 Oct 2019 18:19:59 +0400 Subject: Thread safe statistics loading Previously multiple threads were allowed to load statistics concurrently. There were no known problems caused by this. But given amount of data races in this code, it'd happen sooner or later. To avoid scalability bottleneck, statistics loading is protected by per-TABLE_SHARE atomic variable. Whenever statistics were loaded by preceding statement (hot-path), a scalable load-acquire check is performed. Whenever statistics have to be loaded anew, mutual exclusion for loaders is established by atomic variable. If statistics are being loaded concurrently, statement waits until load is completed. TABLE_STATISTICS_CB::stats_can_be_read and TABLE_STATISTICS_CB::stats_is_read are replaced with a tri state atomic variable. Part of MDEV-19061 - table_share used for reading statistical tables is not protected --- sql/sql_statistics.cc | 99 +++++++++++++++++++++++---------------------------- sql/table.cc | 2 -- sql/table.h | 75 +++++++++++++++++++++++++++++++++++--- 3 files changed, 116 insertions(+), 60 deletions(-) diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index 0f8daa6e52f..a99dcca89e4 100644 --- a/sql/sql_statistics.cc +++ b/sql/sql_statistics.cc @@ -2204,27 +2204,13 @@ static int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share) DBUG_ENTER("alloc_statistics_for_table_share"); - DEBUG_SYNC(thd, "statistics_mem_alloc_start1"); - DEBUG_SYNC(thd, "statistics_mem_alloc_start2"); - - mysql_mutex_lock(&table_share->LOCK_share); - - if (stats_cb->stats_can_be_read) - { - mysql_mutex_unlock(&table_share->LOCK_share); - DBUG_RETURN(0); - } - Table_statistics *table_stats= stats_cb->table_stats; if (!table_stats) { table_stats= (Table_statistics *) alloc_root(&stats_cb->mem_root, sizeof(Table_statistics)); if (!table_stats) - { - mysql_mutex_unlock(&table_share->LOCK_share); DBUG_RETURN(1); - } memset(table_stats, 0, sizeof(Table_statistics)); stats_cb->table_stats= table_stats; } @@ -2290,13 +2276,7 @@ static int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share) } } } - - if (column_stats && index_stats && idx_avg_frequency) - stats_cb->stats_can_be_read= TRUE; - - mysql_mutex_unlock(&table_share->LOCK_share); - - DBUG_RETURN(0); + DBUG_RETURN(column_stats && index_stats && idx_avg_frequency ? 0 : 1); } @@ -2913,11 +2893,22 @@ int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) Field **field_ptr; KEY *key_info, *key_info_end; TABLE_SHARE *table_share= table->s; - Table_statistics *read_stats= table_share->stats_cb.table_stats; DBUG_ENTER("read_statistics_for_table"); + DEBUG_SYNC(thd, "statistics_mem_alloc_start1"); + DEBUG_SYNC(thd, "statistics_mem_alloc_start2"); + + if (!table_share->stats_cb.start_stats_load()) + DBUG_RETURN(table_share->stats_cb.stats_are_ready() ? 0 : 1); + + if (alloc_statistics_for_table_share(thd, table_share)) + { + table_share->stats_cb.abort_stats_load(); + DBUG_RETURN(1); + } /* Read statistics from the statistical table table_stats */ + Table_statistics *read_stats= table_share->stats_cb.table_stats; stat_table= stat_tables[TABLE_STAT].table; Table_stat table_stat(stat_table, table); table_stat.set_key_fields(); @@ -2995,9 +2986,7 @@ int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) } } } - - table->stats_is_read= TRUE; - + table_share->stats_cb.end_stats_load(); DBUG_RETURN(0); } @@ -3146,6 +3135,23 @@ int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables) } +static void dump_stats_from_share_to_table(TABLE *table) +{ + TABLE_SHARE *table_share= table->s; + KEY *key_info= table_share->key_info; + KEY *key_info_end= key_info + table_share->keys; + KEY *table_key_info= table->key_info; + for ( ; key_info < key_info_end; key_info++, table_key_info++) + table_key_info->read_stats= key_info->read_stats; + + Field **field_ptr= table_share->field; + Field **table_field_ptr= table->field; + for ( ; *field_ptr; field_ptr++, table_field_ptr++) + (*table_field_ptr)->read_stats= (*field_ptr)->read_stats; + table->stats_is_read= true; +} + + int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) { TABLE_LIST stat_tables[STATISTICS_TABLES]; @@ -3167,30 +3173,17 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) { if (table_share->table_category == TABLE_CATEGORY_USER) { - if (table_share->stats_cb.stats_can_be_read || - !alloc_statistics_for_table_share(thd, table_share)) + if (table_share->stats_cb.stats_are_ready()) { - if (table_share->stats_cb.stats_can_be_read) - { - KEY *key_info= table_share->key_info; - KEY *key_info_end= key_info + table_share->keys; - KEY *table_key_info= tl->table->key_info; - for ( ; key_info < key_info_end; key_info++, table_key_info++) - table_key_info->read_stats= key_info->read_stats; - Field **field_ptr= table_share->field; - Field **table_field_ptr= tl->table->field; - for ( ; *field_ptr; field_ptr++, table_field_ptr++) - (*table_field_ptr)->read_stats= (*field_ptr)->read_stats; - tl->table->stats_is_read= table_share->stats_cb.stats_is_read; - tl->table->histograms_are_read= - table_share->stats_cb.histograms_are_read; - - if (!tl->table->stats_is_read || - (!table_share->stats_cb.histograms_are_read && - thd->variables.optimizer_use_condition_selectivity > 3)) - statistics_for_tables_is_needed= true; - } + if (!tl->table->stats_is_read) + dump_stats_from_share_to_table(tl->table); + tl->table->histograms_are_read= + table_share->stats_cb.histograms_are_read; + if (table_share->stats_cb.histograms_are_read || + thd->variables.optimizer_use_condition_selectivity <= 3) + continue; } + statistics_for_tables_is_needed= true; } else if (is_stat_table(tl->db, tl->alias)) found_stat_table= true; @@ -3217,16 +3210,14 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) table_share->tmp_table == NO_TMP_TABLE && table_share->table_category == TABLE_CATEGORY_USER) { - if (table_share->stats_cb.stats_can_be_read && - !table_share->stats_cb.stats_is_read) + if (!tl->table->stats_is_read) { - (void) read_statistics_for_table(thd, tl->table, stat_tables); - table_share->stats_cb.stats_is_read= TRUE; + if (!read_statistics_for_table(thd, tl->table, stat_tables)) + dump_stats_from_share_to_table(tl->table); + else + continue; } - if (table_share->stats_cb.stats_is_read) - tl->table->stats_is_read= TRUE; if (thd->variables.optimizer_use_condition_selectivity > 3 && - table_share->stats_cb.stats_can_be_read && !table_share->stats_cb.histograms_are_read) { (void) read_histograms_for_table(thd, tl->table, stat_tables); diff --git a/sql/table.cc b/sql/table.cc index 192faa0d00e..1d3bebcc5d5 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -416,8 +416,6 @@ void TABLE_SHARE::destroy() delete_stat_values_for_table_share(this); free_root(&stats_cb.mem_root, MYF(0)); - stats_cb.stats_can_be_read= FALSE; - stats_cb.stats_is_read= FALSE; stats_cb.histograms_can_be_read= FALSE; stats_cb.histograms_are_read= FALSE; diff --git a/sql/table.h b/sql/table.h index 29b4cfdbcf3..85ba7b29ce2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -594,15 +594,82 @@ enum open_frm_error { from persistent statistical tables */ -struct TABLE_STATISTICS_CB +class TABLE_STATISTICS_CB { + class Statistics_state + { + enum state_codes + { + EMPTY, /** data is not loaded */ + LOADING, /** data is being loaded in some connection */ + READY /** data is loaded and available for use */ + }; + int32 state; + + public: + /** No state copy */ + Statistics_state &operator=(const Statistics_state &) { return *this; } + + /** Checks if data loading have been completed */ + bool is_ready() const + { + return my_atomic_load32_explicit(const_cast(&state), + MY_MEMORY_ORDER_ACQUIRE) == READY; + } + + /** + Sets mutual exclusion for data loading + + If stats are in LOADING state, waits until state change. + + @return + @retval true atomic EMPTY -> LOADING transfer completed, ok to load + @retval false stats are in READY state, no need to load + */ + bool start_load() + { + for (;;) + { + int32 expected= EMPTY; + if (my_atomic_cas32_weak_explicit(&state, &expected, LOADING, + MY_MEMORY_ORDER_RELAXED, + MY_MEMORY_ORDER_RELAXED)) + return true; + if (expected == READY) + return false; + (void) LF_BACKOFF; + } + } + + /** Marks data available for subsequent use */ + void end_load() + { + DBUG_ASSERT(my_atomic_load32_explicit(&state, MY_MEMORY_ORDER_RELAXED) == + LOADING); + my_atomic_store32_explicit(&state, READY, MY_MEMORY_ORDER_RELEASE); + } + + /** Restores empty state on error (e.g. OOM) */ + void abort_load() + { + DBUG_ASSERT(my_atomic_load32_explicit(&state, MY_MEMORY_ORDER_RELAXED) == + LOADING); + my_atomic_store32_explicit(&state, EMPTY, MY_MEMORY_ORDER_RELAXED); + } + }; + + class Statistics_state stats_state; + +public: MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */ Table_statistics *table_stats; /* Structure to access the statistical data */ - bool stats_can_be_read; /* Memory for statistical data is allocated */ - bool stats_is_read; /* Statistical data for table has been read - from statistical tables */ bool histograms_can_be_read; bool histograms_are_read; + + bool stats_are_ready() const { return stats_state.is_ready(); } + bool start_stats_load() { return stats_state.start_load(); } + void end_stats_load() { stats_state.end_load(); } + void abort_stats_load() { stats_state.abort_load(); } }; -- cgit v1.2.1 From c2798784931a59f996275e14220b74f450828b4b Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Wed, 16 Oct 2019 19:00:43 +0400 Subject: Thread safe histograms loading Previously multiple threads were allowed to load histograms concurrently. There were no known problems caused by this. But given amount of data races in this code, it'd happen sooner or later. To avoid scalability bottleneck, histograms loading is protected by per-TABLE_SHARE atomic variable. Whenever histograms were loaded by preceding statement (hot-path), a scalable load-acquire check is performed. Whenever histograms have to be loaded anew, mutual exclusion for loaders is established by atomic variable. If histograms are being loaded concurrently, statement waits until load is completed. - Table_statistics::total_hist_size moved to TABLE_STATISTICS_CB: only meaningful within TABLE_SHARE (not used for collected stats). - TABLE_STATISTICS_CB::histograms_can_be_read and TABLE_STATISTICS_CB::histograms_are_read are replaced with a tri state atomic variable. - Simplified away alloc_histograms_for_table_share(). Note: there's still likely a data race if a thread attempts accessing histograms data after it failed to load it (because of concurrent load). It was there previously and goes out of the scope of this effort. One way of fixing it could be reviving TABLE::histograms_are_read and adding appropriate checks whenever it is needed. Part of MDEV-19061 - table_share used for reading statistical tables is not protected --- sql/sql_statistics.cc | 118 +++++++++----------------------------------------- sql/sql_statistics.h | 1 - sql/table.cc | 2 - sql/table.h | 16 ++++++- 4 files changed, 34 insertions(+), 103 deletions(-) diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index a99dcca89e4..ae02254c745 100644 --- a/sql/sql_statistics.cc +++ b/sql/sql_statistics.cc @@ -2280,78 +2280,6 @@ static int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share) } -/** - @brief - Allocate memory for the histogram used by a table share - - @param - thd Thread handler - @param - table_share Table share for which the memory for histogram data is allocated - @param - is_safe TRUE <-> at any time only one thread can perform the function - - @note - The function allocates the memory for the histogram built for a table in the - table's share memory with the intention to read the data there from the - system persistent statistical table mysql.column_stats, - The memory is allocated in the table_share's mem_root. - If the parameter is_safe is TRUE then it is guaranteed that at any given time - only one thread is executed the code of the function. - - @retval - 0 If the memory for all statistical data has been successfully allocated - @retval - 1 Otherwise - - @note - Currently the function always is called with the parameter is_safe set - to FALSE. -*/ - -static -int alloc_histograms_for_table_share(THD* thd, TABLE_SHARE *table_share, - bool is_safe) -{ - TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb; - - DBUG_ENTER("alloc_histograms_for_table_share"); - - if (!is_safe) - mysql_mutex_lock(&table_share->LOCK_share); - - if (stats_cb->histograms_can_be_read) - { - if (!is_safe) - mysql_mutex_unlock(&table_share->LOCK_share); - DBUG_RETURN(0); - } - - Table_statistics *table_stats= stats_cb->table_stats; - ulong total_hist_size= table_stats->total_hist_size; - - if (total_hist_size && !table_stats->histograms) - { - uchar *histograms= (uchar *) alloc_root(&stats_cb->mem_root, - total_hist_size); - if (!histograms) - { - if (!is_safe) - mysql_mutex_unlock(&table_share->LOCK_share); - DBUG_RETURN(1); - } - memset(histograms, 0, total_hist_size); - table_stats->histograms= histograms; - stats_cb->histograms_can_be_read= TRUE; - } - - if (!is_safe) - mysql_mutex_unlock(&table_share->LOCK_share); - - DBUG_RETURN(0); - -} - /** @brief Initialize the aggregation fields to collect statistics on a column @@ -2925,7 +2853,7 @@ int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) column_stat.get_stat_values(); total_hist_size+= table_field->read_stats->histogram.get_size(); } - read_stats->total_hist_size= total_hist_size; + table_share->stats_cb.total_hist_size= total_hist_size; /* Read statistics from the statistical table index_stats */ stat_table= stat_tables[INDEX_STAT].table; @@ -3059,26 +2987,25 @@ void delete_stat_values_for_table_share(TABLE_SHARE *table_share) static int read_histograms_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) { - TABLE_SHARE *table_share= table->s; - + TABLE_STATISTICS_CB *stats_cb= &table->s->stats_cb; DBUG_ENTER("read_histograms_for_table"); - if (!table_share->stats_cb.histograms_can_be_read) + if (stats_cb->start_histograms_load()) { - (void) alloc_histograms_for_table_share(thd, table_share, FALSE); - } - if (table_share->stats_cb.histograms_can_be_read && - !table_share->stats_cb.histograms_are_read) - { - Field **field_ptr; - uchar *histogram= table_share->stats_cb.table_stats->histograms; - TABLE *stat_table= stat_tables[COLUMN_STAT].table; - Column_stat column_stat(stat_table, table); - for (field_ptr= table_share->field; *field_ptr; field_ptr++) + uchar *histogram= (uchar *) alloc_root(&stats_cb->mem_root, + stats_cb->total_hist_size); + if (!histogram) + { + stats_cb->abort_histograms_load(); + DBUG_RETURN(1); + } + memset(histogram, 0, stats_cb->total_hist_size); + + Column_stat column_stat(stat_tables[COLUMN_STAT].table, table); + for (Field **field_ptr= table->s->field; *field_ptr; field_ptr++) { Field *table_field= *field_ptr; - uint hist_size= table_field->read_stats->histogram.get_size(); - if (hist_size) + if (uint hist_size= table_field->read_stats->histogram.get_size()) { column_stat.set_key_fields(table_field); table_field->read_stats->histogram.set_values(histogram); @@ -3086,8 +3013,9 @@ int read_histograms_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) histogram+= hist_size; } } + stats_cb->end_histograms_load(); } - + table->histograms_are_read= true; DBUG_RETURN(0); } @@ -3178,8 +3106,8 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) if (!tl->table->stats_is_read) dump_stats_from_share_to_table(tl->table); tl->table->histograms_are_read= - table_share->stats_cb.histograms_are_read; - if (table_share->stats_cb.histograms_are_read || + table_share->stats_cb.histograms_are_ready(); + if (table_share->stats_cb.histograms_are_ready() || thd->variables.optimizer_use_condition_selectivity <= 3) continue; } @@ -3217,14 +3145,8 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables) else continue; } - if (thd->variables.optimizer_use_condition_selectivity > 3 && - !table_share->stats_cb.histograms_are_read) - { + if (thd->variables.optimizer_use_condition_selectivity > 3) (void) read_histograms_for_table(thd, tl->table, stat_tables); - table_share->stats_cb.histograms_are_read= TRUE; - } - if (table_share->stats_cb.histograms_are_read) - tl->table->histograms_are_read= TRUE; } } diff --git a/sql/sql_statistics.h b/sql/sql_statistics.h index 71d727eab07..151618ca365 100644 --- a/sql/sql_statistics.h +++ b/sql/sql_statistics.h @@ -280,7 +280,6 @@ public: Column_statistics *column_stats; /* Array of statistical data for columns */ Index_statistics *index_stats; /* Array of statistical data for indexes */ ulong *idx_avg_frequency; /* Array of records per key for index prefixes */ - ulong total_hist_size; /* Total size of all histograms */ uchar *histograms; /* Sequence of histograms */ }; diff --git a/sql/table.cc b/sql/table.cc index 1d3bebcc5d5..20fc86e35f0 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -416,8 +416,6 @@ void TABLE_SHARE::destroy() delete_stat_values_for_table_share(this); free_root(&stats_cb.mem_root, MYF(0)); - stats_cb.histograms_can_be_read= FALSE; - stats_cb.histograms_are_read= FALSE; /* The mutexes are initialized only for shares that are part of the TDC */ if (tmp_table == NO_TMP_TABLE) diff --git a/sql/table.h b/sql/table.h index 85ba7b29ce2..93795113fab 100644 --- a/sql/table.h +++ b/sql/table.h @@ -659,13 +659,25 @@ class TABLE_STATISTICS_CB }; class Statistics_state stats_state; + class Statistics_state hist_state; public: MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */ Table_statistics *table_stats; /* Structure to access the statistical data */ - bool histograms_can_be_read; - bool histograms_are_read; + ulong total_hist_size; /* Total size of all histograms */ + bool histograms_are_ready() const + { + return !total_hist_size || hist_state.is_ready(); + } + + bool start_histograms_load() + { + return total_hist_size && hist_state.start_load(); + } + + void end_histograms_load() { hist_state.end_load(); } + void abort_histograms_load() { hist_state.abort_load(); } bool stats_are_ready() const { return stats_state.is_ready(); } bool start_stats_load() { return stats_state.start_load(); } void end_stats_load() { stats_state.end_load(); } -- cgit v1.2.1 From 49854811fa499c70ddbf5d7d4daf22c97796b98e Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Fri, 29 May 2020 22:51:45 +0400 Subject: Attempt fixing mroonga gcc 8 build failure Part of MDEV-19061 - table_share used for reading statistical tables is not protected --- storage/mroonga/mrn_table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/mroonga/mrn_table.cpp b/storage/mroonga/mrn_table.cpp index 8653092e45f..8fd48ffca17 100644 --- a/storage/mroonga/mrn_table.cpp +++ b/storage/mroonga/mrn_table.cpp @@ -932,7 +932,7 @@ MRN_SHARE *mrn_get_share(const char *table_name, TABLE *table, int *error) share->wrap_key_info = NULL; share->wrap_primary_key = MAX_KEY; } - memcpy(wrap_table_share, table->s, sizeof(*wrap_table_share)); + *wrap_table_share= *table->s; mrn_init_sql_alloc(current_thd, &(wrap_table_share->mem_root)); wrap_table_share->keys = share->wrap_keys; wrap_table_share->key_info = share->wrap_key_info; -- cgit v1.2.1 From 4832b751ad47626f236e25067508230df72749e6 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 31 May 2020 11:00:47 +0200 Subject: cmake: quieter --- CMakeLists.txt | 3 +++ cmake/FindBISON.cmake | 8 ++++++++ storage/connect/CMakeLists.txt | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 cmake/FindBISON.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 097d79fa5e7..13d9b1be546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ IF(CMAKE_VERSION VERSION_EQUAL "3.0.0" OR CMAKE_POLICY(SET CMP0045 OLD) CMAKE_POLICY(SET CMP0042 OLD) ENDIF() +IF(POLICY CMP0048) + CMAKE_POLICY(SET CMP0048 NEW) +ENDIF() IF(POLICY CMP0054) CMAKE_POLICY(SET CMP0054 NEW) ENDIF() diff --git a/cmake/FindBISON.cmake b/cmake/FindBISON.cmake new file mode 100644 index 00000000000..28ae3f8fecb --- /dev/null +++ b/cmake/FindBISON.cmake @@ -0,0 +1,8 @@ +IF(DEFINED BISON_EXECUTABLE) + SET(bison_quiet QUIET) +ENDIF() + +set(orig_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +find_package(BISON ${BISON_FIND_VERSION} ${bison_quiet} ${BISON_FIND_REQUIRED}) +set(CMAKE_MODULE_PATH ${orig_CMAKE_MODULE_PATH}) diff --git a/storage/connect/CMakeLists.txt b/storage/connect/CMakeLists.txt index 92e2e53a61b..2c1656e4431 100644 --- a/storage/connect/CMakeLists.txt +++ b/storage/connect/CMakeLists.txt @@ -318,7 +318,7 @@ ENDIF(CONNECT_WITH_MONGO) OPTION(CONNECT_WITH_REST "Compile CONNECT storage engine with REST support" ON) IF(CONNECT_WITH_REST) - MESSAGE(STATUS "=====> REST support is ON") + MESSAGE_ONCE(CONNECT_WITH_REST "REST support is ON") SET(CONNECT_SOURCES ${CONNECT_SOURCES} tabrest.cpp tabrest.h) add_definitions(-DREST_SUPPORT) FIND_PACKAGE(cpprestsdk QUIET) -- cgit v1.2.1 From c50b7bee331f8c0a467080335b1372b4ba15ec46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 1 Jun 2020 10:18:47 +0300 Subject: MDEV-21615 InnoDB: innodb_page_size=x requires... should be logged as error innobase_init(): On every path to refused startup, log the reason to refuse startup as an error, instead of a note. --- storage/innobase/handler/ha_innodb.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 8638ed66538..895761a5a63 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -3781,7 +3781,7 @@ innobase_init( pages, even for larger pages */ if (UNIV_PAGE_SIZE > UNIV_PAGE_SIZE_DEF && innobase_buffer_pool_size < (24 * 1024 * 1024)) { - ib::info() << "innodb_page_size=" + ib::error() << "innodb_page_size=" << UNIV_PAGE_SIZE << " requires " << "innodb_buffer_pool_size > 24M current " << innobase_buffer_pool_size; -- cgit v1.2.1 From 83d0e72b34154dd24bb5b66f1732fb7753665d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 1 Jun 2020 10:23:11 +0300 Subject: Cleanup: Remove thr_is_recv(), trx_is_recv() Compare to trx_roll_crash_recv_trx directly where needed. --- storage/innobase/btr/btr0cur.cc | 2 +- storage/innobase/include/que0que.h | 12 +----------- storage/innobase/include/que0que.ic | 15 +-------------- storage/innobase/include/trx0roll.h | 11 +---------- storage/innobase/row/row0umod.cc | 2 +- storage/innobase/row/row0upd.cc | 3 ++- storage/innobase/trx/trx0roll.cc | 13 ------------- 7 files changed, 7 insertions(+), 51 deletions(-) diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 37ba133cb0f..f7cef687dc8 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -3998,7 +3998,7 @@ btr_cur_optimistic_update( ULINT_UNDEFINED, heap); #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG ut_a(!rec_offs_any_null_extern(rec, *offsets) - || trx_is_recv(thr_get_trx(thr))); + || thr_get_trx(thr) == trx_roll_crash_recv_trx); #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ if (!row_upd_changes_field_size_or_external(index, *offsets, update)) { diff --git a/storage/innobase/include/que0que.h b/storage/innobase/include/que0que.h index 8489551e64d..f6609993601 100644 --- a/storage/innobase/include/que0que.h +++ b/storage/innobase/include/que0que.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2019, MariaDB Corporation. +Copyright (c) 2017, 2020, MariaDB Corporation. 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 @@ -169,16 +169,6 @@ trx_t* thr_get_trx( /*========*/ que_thr_t* thr); /*!< in: query thread */ -/*******************************************************************//** -Determines if this thread is rolling back an incomplete transaction -in crash recovery. -@return TRUE if thr is rolling back an incomplete transaction in crash -recovery */ -UNIV_INLINE -ibool -thr_is_recv( -/*========*/ - const que_thr_t* thr); /*!< in: query thread */ /***********************************************************************//** Gets the type of a graph node. */ UNIV_INLINE diff --git a/storage/innobase/include/que0que.ic b/storage/innobase/include/que0que.ic index 5b775820df7..1c3ac242bf2 100644 --- a/storage/innobase/include/que0que.ic +++ b/storage/innobase/include/que0que.ic @@ -1,6 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2013, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2020, MariaDB Corporation. 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 @@ -36,20 +37,6 @@ thr_get_trx( return(thr->graph->trx); } -/*******************************************************************//** -Determines if this thread is rolling back an incomplete transaction -in crash recovery. -@return TRUE if thr is rolling back an incomplete transaction in crash -recovery */ -UNIV_INLINE -ibool -thr_is_recv( -/*========*/ - const que_thr_t* thr) /*!< in: query thread */ -{ - return(trx_is_recv(thr->graph->trx)); -} - /***********************************************************************//** Gets the first thr in a fork. */ UNIV_INLINE diff --git a/storage/innobase/include/trx0roll.h b/storage/innobase/include/trx0roll.h index cae548d442b..0fd6973e551 100644 --- a/storage/innobase/include/trx0roll.h +++ b/storage/innobase/include/trx0roll.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2015, 2019, MariaDB Corporation. +Copyright (c) 2015, 2020, MariaDB Corporation. 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 @@ -34,15 +34,6 @@ Created 3/26/1996 Heikki Tuuri extern bool trx_rollback_or_clean_is_active; extern const trx_t* trx_roll_crash_recv_trx; -/*******************************************************************//** -Determines if this transaction is rolling back an incomplete transaction -in crash recovery. -@return TRUE if trx is an incomplete transaction that is being rolled -back in crash recovery */ -ibool -trx_is_recv( -/*========*/ - const trx_t* trx); /*!< in: transaction */ /*******************************************************************//** Returns a transaction savepoint taken at this point in time. @return savepoint */ diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc index ac4500f5c3e..80d90f40379 100644 --- a/storage/innobase/row/row0umod.cc +++ b/storage/innobase/row/row0umod.cc @@ -884,7 +884,7 @@ row_undo_mod_upd_del_sec( does not exist. However, this situation may only occur during the rollback of incomplete transactions. */ - ut_a(thr_is_recv(thr)); + ut_a(thr_get_trx(thr) == trx_roll_crash_recv_trx); } else { err = row_undo_mod_del_mark_or_remove_sec( node, thr, index, entry); diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index a316041ed0e..a1e840a9cb9 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -1882,7 +1882,8 @@ row_upd_changes_ord_field_binary_func( when the server had crashed before storing the field. */ ut_ad(thr->graph->trx->is_recovered); - ut_ad(trx_is_recv(thr->graph->trx)); + ut_ad(thr->graph->trx + == trx_roll_crash_recv_trx); return(TRUE); } diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index 0e03346bb9d..9c63c68f84f 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -551,19 +551,6 @@ trx_release_savepoint_for_mysql( return(savep != NULL ? DB_SUCCESS : DB_NO_SAVEPOINT); } -/*******************************************************************//** -Determines if this transaction is rolling back an incomplete transaction -in crash recovery. -@return TRUE if trx is an incomplete transaction that is being rolled -back in crash recovery */ -ibool -trx_is_recv( -/*========*/ - const trx_t* trx) /*!< in: transaction */ -{ - return(trx == trx_roll_crash_recv_trx); -} - /*******************************************************************//** Returns a transaction savepoint taken at this point in time. @return savepoint */ -- cgit v1.2.1 From 02f68552a4b77042edf1848ce0ce33070ab8389b Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Mon, 1 Jun 2020 14:34:16 +0530 Subject: MDEV-22650 Dirty compressed page checksum validation fails Problem: ======= While evicting the uncompressed page from buffer pool, InnoDB writes the checksum for the compressed page in buf_LRU_free_page(). So while flushing the compressed page, checksum validation fails when innodb_checksum_algorithm variable changed to strict_none. Solution: ======== - Calculate the checksum only during flushing of page. Removed the checksum write in buf_LRU_free_page(). --- storage/innobase/buf/buf0buf.cc | 2 +- storage/innobase/buf/buf0flu.cc | 6 ++---- storage/innobase/buf/buf0lru.cc | 21 --------------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 3beae8982b6..5cecb9581bd 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -4625,7 +4625,7 @@ evict_from_pool: buf_pool->mutex or block->mutex. */ { - bool success = buf_zip_decompress(block, TRUE); + bool success = buf_zip_decompress(block, false); if (!success) { buf_pool_mutex_enter(buf_pool); diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 3c3cc99de72..3c91c9595c4 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -1069,10 +1069,8 @@ buf_flush_write_block_low( case BUF_BLOCK_ZIP_DIRTY: frame = bpage->zip.data; - mach_write_to_8(frame + FIL_PAGE_LSN, - bpage->newest_modification); - - ut_a(page_zip_verify_checksum(frame, bpage->size.physical())); + buf_flush_update_zip_checksum(frame, bpage->size.physical(), + bpage->newest_modification); break; case BUF_BLOCK_FILE_PAGE: frame = bpage->zip.data; diff --git a/storage/innobase/buf/buf0lru.cc b/storage/innobase/buf/buf0lru.cc index 6e2dba77513..107d0628400 100644 --- a/storage/innobase/buf/buf0lru.cc +++ b/storage/innobase/buf/buf0lru.cc @@ -1617,27 +1617,6 @@ func_exit: UNIV_MEM_INVALID(((buf_block_t*) bpage)->frame, UNIV_PAGE_SIZE); - if (b != NULL) { - - /* Compute and stamp the compressed page - checksum while not holding any mutex. The - block is already half-freed - (BUF_BLOCK_REMOVE_HASH) and removed from - buf_pool->page_hash, thus inaccessible by any - other thread. */ - - ut_ad(b->size.is_compressed()); - - const uint32_t checksum = page_zip_calc_checksum( - b->zip.data, - b->size.physical(), - static_cast( - srv_checksum_algorithm)); - - mach_write_to_4(b->zip.data + FIL_PAGE_SPACE_OR_CHKSUM, - checksum); - } - buf_pool_mutex_enter(buf_pool); if (b != NULL) { -- cgit v1.2.1 From 50641db2d11ad8a2228f7938d851e52decb71a9b Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 1 Jun 2020 15:38:04 +0200 Subject: fix warning --- win/packaging/ca/CustomAction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win/packaging/ca/CustomAction.cpp b/win/packaging/ca/CustomAction.cpp index 48d56ba1183..d3bea12ffd9 100644 --- a/win/packaging/ca/CustomAction.cpp +++ b/win/packaging/ca/CustomAction.cpp @@ -317,7 +317,7 @@ static bool IsPortInUse(unsigned short port) } bool in_use = false; - if (bind(ip_sock, a->ai_addr, a->ai_addrlen) == SOCKET_ERROR) + if (bind(ip_sock, a->ai_addr, (int)a->ai_addrlen) == SOCKET_ERROR) { DWORD last_error = WSAGetLastError(); in_use = (last_error == WSAEADDRINUSE || last_error == WSAEACCES); -- cgit v1.2.1 From 804761a844906c7f7bd11139a366a5dfc1d8bcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 2 Jun 2020 08:18:17 +0300 Subject: MDEV-22770 trx_undo_report_rename() fails to release page latches commit f74023b955b5825fc3e957f644d5ee75c8171c98 (MDEV-15090) inadvertently removed a mtr_t::commit() call from trx_undo_report_rename(), causing an InnoDB hang if we failed to log a RENAME operation. It is unclear whether this condition is possible in practice. The test case involved SET GLOBAL innodb_trx_rseg_n_slots_debug=1 and a failed CREATE TABLE...SELECT, whose error handling would internally invoke RENAME in InnoDB. --- storage/innobase/trx/trx0rec.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index 940c90ee3e3..b282bb177af 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -1934,10 +1934,9 @@ dberr_t trx_undo_report_rename(trx_t* trx, const dict_table_t* table) } } } - - mtr.commit(); } + mtr.commit(); return err; } -- cgit v1.2.1 From 457e3128e0294cd822cf72e90ba0dd85a7477e73 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 2 Jun 2020 21:28:21 +0300 Subject: Added larger timeout to backup_stages.test MDEV-21546 main.backup_stages occasionally fails with lock wait timeout --- mysql-test/main/backup_stages.result | 4 ++-- mysql-test/main/backup_stages.test | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/backup_stages.result b/mysql-test/main/backup_stages.result index caea1fda0b9..4704a4b6bd1 100644 --- a/mysql-test/main/backup_stages.result +++ b/mysql-test/main/backup_stages.result @@ -63,7 +63,7 @@ INSERT INTO t_permanent_myisam SET col1 = 1; connect backup,localhost,root,,; connect con11,localhost,root,,; SET AUTOCOMMIT = 0; -set session lock_wait_timeout=0; +set session lock_wait_timeout=1; connect con12,localhost,root,,; SET AUTOCOMMIT = 1; # Between (connection default) BACKUP STAGE START and FLUSH @@ -208,7 +208,7 @@ ERROR HY000: Lock wait timeout exceeded; try restarting transaction DROP TABLE t_con1_myisam; ERROR HY000: Lock wait timeout exceeded; try restarting transaction connection con12; -SET STATEMENT lock_wait_timeout=0 FOR UPDATE t_permanent_innodb SET col1 = 9; +SET STATEMENT lock_wait_timeout=1 FOR UPDATE t_permanent_innodb SET col1 = 9; ERROR HY000: Lock wait timeout exceeded; try restarting transaction connection backup; BACKUP STAGE END; diff --git a/mysql-test/main/backup_stages.test b/mysql-test/main/backup_stages.test index ba9c15a1d7e..e8cc5f7a7b9 100644 --- a/mysql-test/main/backup_stages.test +++ b/mysql-test/main/backup_stages.test @@ -119,7 +119,7 @@ INSERT INTO t_permanent_myisam SET col1 = 1; --connect(backup,localhost,root,,) --connect(con11,localhost,root,,) SET AUTOCOMMIT = 0; -set session lock_wait_timeout=0; +set session lock_wait_timeout=1; --connect(con12,localhost,root,,) SET AUTOCOMMIT = 1; @@ -244,7 +244,7 @@ DROP TABLE t_con1_innodb; DROP TABLE t_con1_myisam; --connection con12 --error ER_LOCK_WAIT_TIMEOUT -SET STATEMENT lock_wait_timeout=0 FOR UPDATE t_permanent_innodb SET col1 = 9; +SET STATEMENT lock_wait_timeout=1 FOR UPDATE t_permanent_innodb SET col1 = 9; --connection backup BACKUP STAGE END; -- cgit v1.2.1 From 95ac7902969d31550768007b3062013db175d8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 2 Jun 2020 21:25:51 +0300 Subject: MDEV-22773 Assertion page_get_page_no... in btr_pcur_store_position() btr_pcur_store_position(): Replace a too strict debug assertion. It is possible to have a clustered index B-tree for a logically empty table, which will consist of a node pointer from the root page to a leaf page that contains the metadata record. The too strict debug assertion was added in commit 0e5a4ac2532c64a545796c787354dc41d61d0e62 (MDEV-15562). --- storage/innobase/btr/btr0pcur.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/innobase/btr/btr0pcur.cc b/storage/innobase/btr/btr0pcur.cc index 11e822f9e54..230a9b4914f 100644 --- a/storage/innobase/btr/btr0pcur.cc +++ b/storage/innobase/btr/btr0pcur.cc @@ -154,7 +154,7 @@ before_first: ut_ad(index->table->instant); ut_ad(page_get_n_recs(block->frame) == 1); ut_ad(page_is_leaf(block->frame)); - ut_ad(page_get_page_no(block->frame) == index->page); + ut_ad(!page_has_siblings(block->frame)); cursor->rel_pos = BTR_PCUR_AFTER_LAST_IN_TREE; return; } -- cgit v1.2.1