diff options
-rw-r--r-- | client/mysqltest.c | 414 | ||||
-rw-r--r-- | mysql-test/r/system_mysql_db.result | 1 | ||||
-rw-r--r-- | mysql-test/r/user_limits.result | 89 | ||||
-rw-r--r-- | mysql-test/t/user_limits.test | 153 | ||||
-rw-r--r-- | scripts/mysql_create_system_tables.sh | 17 | ||||
-rw-r--r-- | scripts/mysql_fix_privilege_tables.sql | 5 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/mysql_priv.h | 1 | ||||
-rw-r--r-- | sql/mysqld.cc | 13 | ||||
-rw-r--r-- | sql/set_var.cc | 50 | ||||
-rw-r--r-- | sql/set_var.h | 17 | ||||
-rw-r--r-- | sql/sql_acl.cc | 49 | ||||
-rw-r--r-- | sql/sql_parse.cc | 40 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 15 | ||||
-rw-r--r-- | sql/structs.h | 55 |
15 files changed, 763 insertions, 157 deletions
diff --git a/client/mysqltest.c b/client/mysqltest.c index 8ddcfb90cad..d62cf3c741a 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -437,6 +437,10 @@ my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; } #endif static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, int len); +static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); +static int normal_handle_error(const char *query, struct st_query *q, + MYSQL *mysql, DYNAMIC_STRING *ds); +static int normal_handle_no_error(struct st_query *q); static void do_eval(DYNAMIC_STRING* query_eval, const char* query) { @@ -951,7 +955,7 @@ static void do_exec(struct st_query* q) ds= &ds_res; while (fgets(buf, sizeof(buf), res_file)) - replace_dynstr_append_mem(ds, buf, strlen(buf)); + replace_dynstr_append(ds, buf); } error= pclose(res_file); @@ -1613,6 +1617,29 @@ void init_manager() } #endif + +/* + Connect to a server doing several retries if needed. + + SYNOPSIS + safe_connect() + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + + NOTE + This function will try to connect to the given server MAX_CON_TRIES + times and sleep CON_RETRY_SLEEP seconds between attempts before + finally giving up. This helps in situation when the client starts + before the server (which happens sometimes). + It will ignore any errors during these retries. One should use + connect_n_handle_errors() if he expects a connection error and wants + handle as if it was an error from a usual statement. + + RETURN VALUE + 0 - success, non-0 - failure +*/ + int safe_connect(MYSQL* con, const char* host, const char* user, const char* pass, const char* db, int port, const char* sock) @@ -1634,6 +1661,114 @@ int safe_connect(MYSQL* con, const char* host, const char* user, } +/* + Connect to a server and handle connection errors in case when they occur. + + SYNOPSIS + connect_n_handle_errors() + q - context of connect "query" (command) + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + create_conn - out parameter, set to zero if connection was + not established and is not touched otherwise + + DESCRIPTION + This function will try to establish a connection to server and handle + possible errors in the same manner as if "connect" was usual SQL-statement + (If error is expected it will ignore it once it occurs and log the + "statement" to the query log). + Unlike safe_connect() it won't do several attempts. + + RETURN VALUE + 0 - success, non-0 - failure +*/ + +int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host, + const char* user, const char* pass, + const char* db, int port, const char* sock, + int* create_conn) +{ + DYNAMIC_STRING ds_tmp, *ds; + int error= 0; + + /* + Altough we ignore --require or --result before connect() command we still + need to handle record_file because of "@result_file sql-command" syntax. + */ + if (q->record_file[0]) + { + init_dynamic_string(&ds_tmp, "", 16384, 65536); + ds= &ds_tmp; + } + else + ds= &ds_res; + + if (!disable_query_log) + { + /* + It is nice to have connect() statement logged in result file + in this case. + QQ: Should we do this only if we are expecting an error ? + */ + char port_buff[22]; /* This should be enough for any int */ + char *port_end; + dynstr_append_mem(ds, "connect(", 8); + replace_dynstr_append(ds, host); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, user); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, pass); + dynstr_append_mem(ds, ",", 1); + if (db) + replace_dynstr_append(ds, db); + dynstr_append_mem(ds, ",", 1); + port_end= int10_to_str(port, port_buff, 10); + replace_dynstr_append_mem(ds, port_buff, port_end - port_buff); + dynstr_append_mem(ds, ",", 1); + if (sock) + replace_dynstr_append(ds, sock); + dynstr_append_mem(ds, ")", 1); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + if (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock: 0, + CLIENT_MULTI_STATEMENTS)) + { + error= normal_handle_error("connect", q, con, ds); + *create_conn= 0; + goto err; + } + else if (normal_handle_no_error(q)) + { + /* + Fail if there was no error but we expected it. + We also don't want to have connection in this case. + */ + mysql_close(con); + *create_conn= 0; + error= 1; + goto err; + } + + if (record) + { + if (!q->record_file[0] && !result_file) + die("At line %u: Missing result file", start_lineno); + if (!result_file) + str_to_file(q->record_file, ds->str, ds->length); + } + else if (q->record_file[0]) + error|= check_result(ds, q->record_file, q->require_file); + +err: + free_replace(); + if (ds == &ds_tmp) + dynstr_free(&ds_tmp); + return error; +} + + int do_connect(struct st_query* q) { char* con_name, *con_user,*con_pass, *con_host, *con_port_str, @@ -1642,6 +1777,8 @@ int do_connect(struct st_query* q) char buff[FN_REFLEN]; int con_port; int free_con_sock = 0; + int error= 0; + int create_conn= 1; DBUG_ENTER("do_connect"); DBUG_PRINT("enter",("connect: %s",p)); @@ -1706,18 +1843,28 @@ int do_connect(struct st_query* q) /* Special database to allow one to connect without a database name */ if (con_db && !strcmp(con_db,"*NO-ONE*")) con_db=0; - if ((safe_connect(&next_con->mysql, con_host, - con_user, con_pass, - con_db, con_port, con_sock ? con_sock: 0))) - die("Could not open connection '%s': %s", con_name, - mysql_error(&next_con->mysql)); - if (!(next_con->name = my_strdup(con_name, MYF(MY_WME)))) - die(NullS); - cur_con = next_con++; + if (q->abort_on_error) + { + if ((safe_connect(&next_con->mysql, con_host, con_user, con_pass, + con_db, con_port, con_sock ? con_sock: 0))) + die("Could not open connection '%s': %s", con_name, + mysql_error(&next_con->mysql)); + } + else + error= connect_n_handle_errors(q, &next_con->mysql, con_host, con_user, + con_pass, con_db, con_port, con_sock, + &create_conn); + + if (create_conn) + { + if (!(next_con->name= my_strdup(con_name, MYF(MY_WME)))) + die(NullS); + cur_con= next_con++; + } if (free_con_sock) my_free(con_sock, MYF(MY_WME)); - DBUG_RETURN(0); + DBUG_RETURN(error); } @@ -2356,6 +2503,13 @@ static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, dynstr_append_mem(ds, val, len); } +/* Append zero-terminated string to ds, with optional replace */ + +static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) +{ + replace_dynstr_append_mem(ds, val, strlen(val)); +} + /* Append all results to the dynamic string separated with '\t' @@ -2502,93 +2656,14 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) (!(last_result= res= mysql_store_result(mysql)) && mysql_field_count(mysql))) { - if (q->require_file) - { - abort_not_supported_test(); - } - if (q->abort_on_error) - die("At line %u: query '%s' failed: %d: %s", start_lineno, query, - mysql_errno(mysql), mysql_error(mysql)); - else - { - for (i=0 ; (uint) i < q->expected_errors ; i++) - { - if (((q->expected_errno[i].type == ERR_ERRNO) && - (q->expected_errno[i].code.errnum == mysql_errno(mysql))) || - ((q->expected_errno[i].type == ERR_SQLSTATE) && - (strcmp(q->expected_errno[i].code.sqlstate,mysql_sqlstate(mysql)) == 0))) - { - if (i == 0 && q->expected_errors == 1) - { - /* Only log error if there is one possible error */ - dynstr_append_mem(ds,"ERROR ",6); - replace_dynstr_append_mem(ds, mysql_sqlstate(mysql), - strlen(mysql_sqlstate(mysql))); - dynstr_append_mem(ds, ": ", 2); - replace_dynstr_append_mem(ds,mysql_error(mysql), - strlen(mysql_error(mysql))); - dynstr_append_mem(ds,"\n",1); - } - /* Don't log error if we may not get an error */ - else if (q->expected_errno[0].type == ERR_SQLSTATE || - (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0)) - dynstr_append(ds,"Got one of the listed errors\n"); - goto end; /* Ok */ - } - } - DBUG_PRINT("info",("i: %d expected_errors: %d", i, - q->expected_errors)); - dynstr_append_mem(ds, "ERROR ",6); - replace_dynstr_append_mem(ds, mysql_sqlstate(mysql), - strlen(mysql_sqlstate(mysql))); - dynstr_append_mem(ds,": ",2); - replace_dynstr_append_mem(ds, mysql_error(mysql), - strlen(mysql_error(mysql))); - dynstr_append_mem(ds,"\n",1); - if (i) - { - if (q->expected_errno[0].type == ERR_ERRNO) - verbose_msg("query '%s' failed with wrong errno %d instead of %d...", - q->query, mysql_errno(mysql), q->expected_errno[0].code.errnum); - else - verbose_msg("query '%s' failed with wrong sqlstate %s instead of %s...", - q->query, mysql_sqlstate(mysql), q->expected_errno[0].code.sqlstate); - error= 1; - goto end; - } - verbose_msg("query '%s' failed: %d: %s", q->query, mysql_errno(mysql), - mysql_error(mysql)); - /* - if we do not abort on error, failure to run the query does - not fail the whole test case - */ - goto end; - } - /*{ - verbose_msg("failed in mysql_store_result for query '%s' (%d)", query, - mysql_errno(mysql)); - error = 1; - goto end; - }*/ - } - - if (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0) - { - /* Error code we wanted was != 0, i.e. not an expected success */ - verbose_msg("query '%s' succeeded - should have failed with errno %d...", - q->query, q->expected_errno[0].code.errnum); - error = 1; + if (normal_handle_error(query, q, mysql, ds)) + error= 1; goto end; } - else if (q->expected_errno[0].type == ERR_SQLSTATE && - strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) + + if (normal_handle_no_error(q)) { - /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ - verbose_msg("query '%s' succeeded - should have failed with sqlstate %s...", - q->query, q->expected_errno[0].code.sqlstate); - error = 1; + error= 1; goto end; } @@ -2608,8 +2683,7 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) { if (i) dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[i].name, - strlen(field[i].name)); + replace_dynstr_append(ds, field[i].name); } dynstr_append_mem(ds, "\n", 1); } @@ -2686,6 +2760,135 @@ end: } +/* + Handle errors which occurred after execution of conventional (non-prepared) + statement. + + SYNOPSIS + normal_handle_error() + query - query string + q - query context + mysql - connection through which query was sent to server + ds - dynamic string which is used for output buffer + + NOTE + If there is an unexpected error this function will abort mysqltest + immediately. + + RETURN VALUE + 0 - OK + 1 - Some other error was expected. +*/ + +static int normal_handle_error(const char *query, struct st_query *q, + MYSQL *mysql, DYNAMIC_STRING *ds) +{ + uint i; + + DBUG_ENTER("normal_handle_error"); + + if (q->require_file) + abort_not_supported_test(); + + if (q->abort_on_error) + die("At line %u: query '%s' failed: %d: %s", start_lineno, query, + mysql_errno(mysql), mysql_error(mysql)); + else + { + for (i= 0 ; (uint) i < q->expected_errors ; i++) + { + if (((q->expected_errno[i].type == ERR_ERRNO) && + (q->expected_errno[i].code.errnum == mysql_errno(mysql))) || + ((q->expected_errno[i].type == ERR_SQLSTATE) && + (strcmp(q->expected_errno[i].code.sqlstate, mysql_sqlstate(mysql)) == 0))) + { + if (q->expected_errors == 1) + { + /* Only log error if there is one possible error */ + dynstr_append_mem(ds, "ERROR ", 6); + replace_dynstr_append(ds, mysql_sqlstate(mysql)); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, mysql_error(mysql)); + dynstr_append_mem(ds,"\n",1); + } + /* Don't log error if we may not get an error */ + else if (q->expected_errno[0].type == ERR_SQLSTATE || + (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0)) + dynstr_append(ds,"Got one of the listed errors\n"); + /* OK */ + DBUG_RETURN(0); + } + } + + DBUG_PRINT("info",("i: %d expected_errors: %d", i, q->expected_errors)); + + dynstr_append_mem(ds, "ERROR ",6); + replace_dynstr_append(ds, mysql_sqlstate(mysql)); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, mysql_error(mysql)); + dynstr_append_mem(ds, "\n", 1); + + if (i) + { + if (q->expected_errno[0].type == ERR_ERRNO) + verbose_msg("query '%s' failed with wrong errno %d instead of %d...", + q->query, mysql_errno(mysql), + q->expected_errno[0].code.errnum); + else + verbose_msg("query '%s' failed with wrong sqlstate %s instead of %s...", + q->query, mysql_sqlstate(mysql), + q->expected_errno[0].code.sqlstate); + DBUG_RETURN(1); + } + + /* + If we do not abort on error, failure to run the query does not fail the + whole test case. + */ + verbose_msg("query '%s' failed: %d: %s", q->query, mysql_errno(mysql), + mysql_error(mysql)); + DBUG_RETURN(0); + } +} + + +/* + Handle absence of errors after execution of convetional statement. + + SYNOPSIS + normal_handle_error() + q - context of query + + RETURN VALUE + 0 - OK + 1 - Some error was expected from this query. +*/ + +static int normal_handle_no_error(struct st_query *q) +{ + DBUG_ENTER("normal_handle_no_error"); + + if (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + verbose_msg("query '%s' succeeded - should have failed with errno %d...", + q->query, q->expected_errno[0].code.errnum); + DBUG_RETURN(1); + } + else if (q->expected_errno[0].type == ERR_SQLSTATE && + strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) + { + /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ + verbose_msg("query '%s' succeeded - should have failed with sqlstate %s...", + q->query, q->expected_errno[0].code.sqlstate); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + /****************************************************************************\ * If --ps-protocol run ordinary statements using prepared statemnt C API \****************************************************************************/ @@ -2879,8 +3082,7 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) { if (col_idx) dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[col_idx].name, - strlen(field[col_idx].name)); + replace_dynstr_append(ds, field[col_idx].name); } dynstr_append_mem(ds, "\n", 1); } @@ -3148,11 +3350,9 @@ static int run_query_stmt_handle_error(char *query, struct st_query *q, { /* Only log error if there is one possible error */ dynstr_append_mem(ds,"ERROR ",6); - replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), - strlen(mysql_stmt_sqlstate(stmt))); + replace_dynstr_append(ds, mysql_stmt_sqlstate(stmt)); dynstr_append_mem(ds, ": ", 2); - replace_dynstr_append_mem(ds,mysql_stmt_error(stmt), - strlen(mysql_stmt_error(stmt))); + replace_dynstr_append(ds,mysql_stmt_error(stmt)); dynstr_append_mem(ds,"\n",1); } /* Don't log error if we may not get an error */ @@ -3166,11 +3366,9 @@ static int run_query_stmt_handle_error(char *query, struct st_query *q, DBUG_PRINT("info",("i: %d expected_errors: %d", i, q->expected_errors)); dynstr_append_mem(ds, "ERROR ",6); - replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), - strlen(mysql_stmt_sqlstate(stmt))); + replace_dynstr_append(ds, mysql_stmt_sqlstate(stmt)); dynstr_append_mem(ds,": ",2); - replace_dynstr_append_mem(ds, mysql_stmt_error(stmt), - strlen(mysql_stmt_error(stmt))); + replace_dynstr_append(ds, mysql_stmt_error(stmt)); dynstr_append_mem(ds,"\n",1); if (i) { @@ -3452,7 +3650,9 @@ int main(int argc, char **argv) { processed = 1; switch (q->type) { - case Q_CONNECT: do_connect(q); break; + case Q_CONNECT: + error|= do_connect(q); + break; case Q_CONNECTION: select_connection(q->first_argument); break; case Q_DISCONNECT: case Q_DIRTY_CLOSE: diff --git a/mysql-test/r/system_mysql_db.result b/mysql-test/r/system_mysql_db.result index e9606ec5f88..2bc96136778 100644 --- a/mysql-test/r/system_mysql_db.result +++ b/mysql-test/r/system_mysql_db.result @@ -102,6 +102,7 @@ user CREATE TABLE `user` ( `max_questions` int(11) unsigned NOT NULL default '0', `max_updates` int(11) unsigned NOT NULL default '0', `max_connections` int(11) unsigned NOT NULL default '0', + `max_user_connections` int(11) unsigned NOT NULL default '0', PRIMARY KEY (`Host`,`User`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges' show create table func; diff --git a/mysql-test/r/user_limits.result b/mysql-test/r/user_limits.result new file mode 100644 index 00000000000..d15f99337b7 --- /dev/null +++ b/mysql-test/r/user_limits.result @@ -0,0 +1,89 @@ +drop table if exists t1; +create table t1 (i int); +delete from mysql.user where user like 'mysqltest\_%'; +delete from mysql.db where user like 'mysqltest\_%'; +delete from mysql.tables_priv where user like 'mysqltest\_%'; +delete from mysql.columns_priv where user like 'mysqltest\_%'; +flush privileges; +grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 2; +select * from t1; +i +select * from t1; +i +select * from t1; +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_questions' resource (current value: 2) +select * from t1; +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_questions' resource (current value: 2) +drop user mysqltest_1@localhost; +grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 2; +select * from t1; +i +select * from t1; +i +select * from t1; +i +delete from t1; +delete from t1; +delete from t1; +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_updates' resource (current value: 2) +select * from t1; +i +delete from t1; +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_updates' resource (current value: 2) +select * from t1; +i +drop user mysqltest_1@localhost; +grant usage on *.* to mysqltest_1@localhost with max_connections_per_hour 2; +select * from t1; +i +select * from t1; +i +connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK); +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_connections' resource (current value: 2) +select * from t1; +i +connect(localhost,mysqltest_1,,test,9306,/home/dlenev/src/mysql-5.0-1339/mysql-test/var/tmp/master.sock); +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_connections' resource (current value: 2) +drop user mysqltest_1@localhost; +flush privileges; +grant usage on *.* to mysqltest_1@localhost with max_user_connections 2; +select * from t1; +i +select * from t1; +i +connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK); +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 2) +select * from t1; +i +grant usage on *.* to mysqltest_1@localhost with max_user_connections 3; +select * from t1; +i +connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK); +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 3) +drop user mysqltest_1@localhost; +select @@session.max_user_connections, @@global.max_user_connections; +@@session.max_user_connections @@global.max_user_connections +0 0 +set session max_user_connections= 2; +ERROR HY000: Variable 'max_user_connections' is a GLOBAL variable and should be set with SET GLOBAL +set global max_user_connections= 2; +select @@session.max_user_connections, @@global.max_user_connections; +@@session.max_user_connections @@global.max_user_connections +2 2 +grant usage on *.* to mysqltest_1@localhost; +select @@session.max_user_connections, @@global.max_user_connections; +@@session.max_user_connections @@global.max_user_connections +2 2 +select * from t1; +i +connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK); +ERROR 42000: User mysqltest_1 has already more than 'max_user_connections' active connections +grant usage on *.* to mysqltest_1@localhost with max_user_connections 3; +select @@session.max_user_connections, @@global.max_user_connections; +@@session.max_user_connections @@global.max_user_connections +3 2 +connect(localhost,mysqltest_1,,test,MYSQL_PORT,MYSQL_SOCK); +ERROR 42000: User 'mysqltest_1' has exceeded the 'max_user_connections' resource (current value: 3) +set global max_user_connections= 0; +drop user mysqltest_1@localhost; +drop table t1; diff --git a/mysql-test/t/user_limits.test b/mysql-test/t/user_limits.test new file mode 100644 index 00000000000..e911e1741c9 --- /dev/null +++ b/mysql-test/t/user_limits.test @@ -0,0 +1,153 @@ +# +# Test behavior of various per-account limits (aka quotas) +# + +# Prepare play-ground +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (i int); +# Just be sure that nothing will bother us +delete from mysql.user where user like 'mysqltest\_%'; +delete from mysql.db where user like 'mysqltest\_%'; +delete from mysql.tables_priv where user like 'mysqltest\_%'; +delete from mysql.columns_priv where user like 'mysqltest\_%'; +flush privileges; + +# Test of MAX_QUERIES_PER_HOUR limit +grant usage on *.* to mysqltest_1@localhost with max_queries_per_hour 2; +connect (mqph, localhost, mysqltest_1,,); +connection mqph; +select * from t1; +select * from t1; +--error 1226 +select * from t1; +connect (mqph2, localhost, mysqltest_1,,); +connection mqph2; +--error 1226 +select * from t1; +# cleanup +connection default; +drop user mysqltest_1@localhost; +disconnect mqph; +disconnect mqph2; + +# Test of MAX_UPDATES_PER_HOUR limit +grant usage on *.* to mysqltest_1@localhost with max_updates_per_hour 2; +connect (muph, localhost, mysqltest_1,,); +connection muph; +select * from t1; +select * from t1; +select * from t1; +delete from t1; +delete from t1; +--error 1226 +delete from t1; +select * from t1; +connect (muph2, localhost, mysqltest_1,,); +connection muph2; +--error 1226 +delete from t1; +select * from t1; +# Cleanup +connection default; +drop user mysqltest_1@localhost; +disconnect muph; +disconnect muph2; + +# Test of MAX_CONNECTIONS_PER_HOUR limit +grant usage on *.* to mysqltest_1@localhost with max_connections_per_hour 2; +connect (mcph1, localhost, mysqltest_1,,); +connection mcph1; +select * from t1; +connect (mcph2, localhost, mysqltest_1,,); +connection mcph2; +select * from t1; +--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK +--error 1226 +connect (mcph3, localhost, mysqltest_1,,); +# Old connection is still ok +select * from t1; +# Let us try to close old connections and try again. This will also test that +# counters are not thrown away if there are no connections for this user. +disconnect mcph1; +disconnect mcph2; +--error 1226 +connect (mcph3, localhost, mysqltest_1,,); +# Cleanup +connection default; +drop user mysqltest_1@localhost; + +# Test of MAX_USER_CONNECTIONS limit +# We need this to reset internal mqh_used variable +flush privileges; +grant usage on *.* to mysqltest_1@localhost with max_user_connections 2; +connect (muc1, localhost, mysqltest_1,,); +connection muc1; +select * from t1; +connect (muc2, localhost, mysqltest_1,,); +connection muc2; +select * from t1; +--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK +--error 1226 +connect (muc3, localhost, mysqltest_1,,); +# Closing of one of connections should help +disconnect muc1; +connect (muc3, localhost, mysqltest_1,,); +select * from t1; +# Changing of limit should also help (and immediately) +connection default; +grant usage on *.* to mysqltest_1@localhost with max_user_connections 3; +connect (muc4, localhost, mysqltest_1,,); +connection muc4; +select * from t1; +--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK +--error 1226 +connect (muc5, localhost, mysqltest_1,,); +# Clean up +connection default; +disconnect muc2; +disconnect muc3; +disconnect muc4; +drop user mysqltest_1@localhost; + +# Now let us test interaction between global and per-account +# max_user_connections limits +select @@session.max_user_connections, @@global.max_user_connections; +# Local max_user_connections variable can't be set directly +# since this limit is per-account +--error 1229 +set session max_user_connections= 2; +# But it is ok to set global max_user_connections +set global max_user_connections= 2; +select @@session.max_user_connections, @@global.max_user_connections; +# Let us check that global limit works +grant usage on *.* to mysqltest_1@localhost; +connect (muca1, localhost, mysqltest_1,,); +connection muca1; +select @@session.max_user_connections, @@global.max_user_connections; +connect (muca2, localhost, mysqltest_1,,); +connection muca2; +select * from t1; +--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK +--error 1203 +connect (muca3, localhost, mysqltest_1,,); +# Now we are testing that per-account limit prevails over gloabl limit +connection default; +grant usage on *.* to mysqltest_1@localhost with max_user_connections 3; +connect (muca3, localhost, mysqltest_1,,); +connection muca3; +select @@session.max_user_connections, @@global.max_user_connections; +--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK +--error 1226 +connect (muca4, localhost, mysqltest_1,,); +# Cleanup +connection default; +disconnect muca1; +disconnect muca2; +disconnect muca3; +set global max_user_connections= 0; +drop user mysqltest_1@localhost; + +# Final cleanup +drop table t1; diff --git a/scripts/mysql_create_system_tables.sh b/scripts/mysql_create_system_tables.sh index be99a081bcb..75ab48227f7 100644 --- a/scripts/mysql_create_system_tables.sh +++ b/scripts/mysql_create_system_tables.sh @@ -153,6 +153,7 @@ then c_u="$c_u max_questions int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u max_updates int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u max_connections int(11) unsigned DEFAULT 0 NOT NULL," + c_u="$c_u max_user_connections int(11) unsigned DEFAULT 0 NOT NULL," c_u="$c_u PRIMARY KEY Host (Host,User)" c_u="$c_u ) engine=MyISAM" c_u="$c_u CHARACTER SET utf8 COLLATE utf8_bin" @@ -160,24 +161,24 @@ then if test "$1" = "test" then - i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); - INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); - REPLACE INTO user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); + i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); + INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); + REPLACE INTO user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); INSERT INTO user (host,user) values ('localhost',''); INSERT INTO user (host,user) values ('$hostname','');" else - i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0);" + i_u="INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);" if test "$windows" = "0" then i_u="$i_u - INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); + INSERT INTO user VALUES ('$hostname','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); INSERT INTO user (host,user) values ('$hostname',''); INSERT INTO user (host,user) values ('localhost','');" else i_u="$i_u - INSERT INTO user VALUES ('%','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); - INSERT INTO user VALUES ('localhost','','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0); - INSERT INTO user VALUES ('%','','','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','','','','',0,0,0);" + INSERT INTO user VALUES ('%','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); + INSERT INTO user VALUES ('localhost','','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0); + INSERT INTO user VALUES ('%','','','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','','','','',0,0,0,0);" fi fi fi diff --git a/scripts/mysql_fix_privilege_tables.sql b/scripts/mysql_fix_privilege_tables.sql index d4f095f5201..2b254799683 100644 --- a/scripts/mysql_fix_privilege_tables.sql +++ b/scripts/mysql_fix_privilege_tables.sql @@ -198,6 +198,11 @@ UPDATE user SET Create_routine_priv=Create_priv, Alter_routine_priv=Alter_priv w UPDATE db SET Create_routine_priv=Create_priv, Alter_routine_priv=Alter_priv, Execute_priv=Select_priv where user<>"" AND @hadCreateRoutinePriv = 0; # +# Add max_user_connections resource limit +# +ALTER TABLE user ADD max_user_connections int(11) unsigned DEFAULT '0' NOT NULL AFTER max_connections; + +# # Create some possible missing tables # CREATE TABLE IF NOT EXISTS procs_priv ( diff --git a/sql/lex.h b/sql/lex.h index 5b6c86bf0ed..8e7902c54e3 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -304,6 +304,7 @@ static SYMBOL symbols[] = { { "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)}, { "MAX_ROWS", SYM(MAX_ROWS)}, { "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)}, + { "MAX_USER_CONNECTIONS", SYM(MAX_USER_CONNECTIONS_SYM)}, { "MEDIUM", SYM(MEDIUM_SYM)}, { "MEDIUMBLOB", SYM(MEDIUMBLOB)}, { "MEDIUMINT", SYM(MEDIUMINT)}, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d69669d097a..54d0a15520c 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1026,6 +1026,7 @@ extern my_bool opt_readonly, lower_case_file_system; extern my_bool opt_enable_named_pipe, opt_sync_frm; extern my_bool opt_secure_auth; extern my_bool sp_automatic_privileges; +extern my_bool opt_old_style_user_limits; extern uint opt_crash_binlog_innodb; extern char *shared_memory_base_name, *mysqld_unix_port; extern bool opt_enable_shared_memory; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index b99ca3721a7..9ee4437540d 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -298,6 +298,12 @@ my_bool lower_case_file_system= 0; my_bool opt_innodb_safe_binlog= 0; my_bool opt_large_pages= 0; uint opt_large_page_size= 0; +my_bool opt_old_style_user_limits= 0; +/* + True if there is at least one per-hour limit for some user, so we should check + them before each query (and possibly reset counters when hour is changed). + False otherwise. +*/ volatile bool mqh_used = 0; my_bool sp_automatic_privileges= 1; @@ -4204,7 +4210,8 @@ enum options_mysqld OPT_UPDATABLE_VIEWS_WITH_LIMIT, OPT_SP_AUTOMATIC_PRIVILEGES, OPT_AUTO_INCREMENT, OPT_AUTO_INCREMENT_OFFSET, - OPT_ENABLE_LARGE_PAGES + OPT_ENABLE_LARGE_PAGES, + OPT_OLD_STYLE_USER_LIMITS }; @@ -4622,6 +4629,10 @@ Disable with --skip-ndbcluster (will save memory).", "Only use one thread (for debugging under Linux).", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, #endif + {"old-style-user-limits", OPT_OLD_STYLE_USER_LIMITS, + "Enable old-style user limits (before 5.0.3 user resources were counted per each user+host vs. per account)", + (gptr*) &opt_old_style_user_limits, (gptr*) &opt_old_style_user_limits, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"pid-file", OPT_PID_FILE, "Pid file used by safe_mysqld.", (gptr*) &pidfile_name_ptr, (gptr*) &pidfile_name_ptr, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, diff --git a/sql/set_var.cc b/sql/set_var.cc index 2d453925fa5..8c249cee4a8 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -253,8 +253,7 @@ sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size", fix_max_relay_log_size); sys_var_thd_ulong sys_max_sort_length("max_sort_length", &SV::max_sort_length); -sys_var_long_ptr sys_max_user_connections("max_user_connections", - &max_user_connections); +sys_var_max_user_conn sys_max_user_connections("max_user_connections"); sys_var_thd_ulong sys_max_tmp_tables("max_tmp_tables", &SV::max_tmp_tables); sys_var_long_ptr sys_max_write_lock_count("max_write_lock_count", @@ -2483,7 +2482,7 @@ bool sys_var_thd_time_zone::check(THD *thd, set_var *var) bool sys_var_thd_time_zone::update(THD *thd, set_var *var) { - /* We are using Time_zone object found during check() phase */ + /* We are using Time_zone object found during check() phase */ *get_tz_ptr(thd,var->type)= var->save_result.time_zone; return 0; } @@ -2527,6 +2526,51 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type) thd->variables.time_zone= global_system_variables.time_zone; } + +bool sys_var_max_user_conn::check(THD *thd, set_var *var) +{ + if (var->type == OPT_GLOBAL) + return sys_var_thd::check(thd, var); + else + { + /* + Per-session values of max_user_connections can't be set directly. + QQ: May be we should have a separate error message for this? + */ + my_error(ER_GLOBAL_VARIABLE, MYF(0), name); + return TRUE; + } +} + +bool sys_var_max_user_conn::update(THD *thd, set_var *var) +{ + DBUG_ASSERT(var->type == OPT_GLOBAL); + pthread_mutex_lock(&LOCK_global_system_variables); + max_user_connections= var->save_result.ulonglong_value; + pthread_mutex_unlock(&LOCK_global_system_variables); + return 0; +} + + +void sys_var_max_user_conn::set_default(THD *thd, enum_var_type type) +{ + DBUG_ASSERT(type == OPT_GLOBAL); + pthread_mutex_lock(&LOCK_global_system_variables); + max_user_connections= (ulong) option_limits->def_value; + pthread_mutex_unlock(&LOCK_global_system_variables); +} + + +byte *sys_var_max_user_conn::value_ptr(THD *thd, enum_var_type type, + LEX_STRING *base) +{ + if (type != OPT_GLOBAL && + thd->user_connect && thd->user_connect->user_resources.user_conn) + return (byte*) &(thd->user_connect->user_resources.user_conn); + return (byte*) &(max_user_connections); +} + + /* Functions to update thd->options bits */ diff --git a/sql/set_var.h b/sql/set_var.h index c34ebbca4b2..7c9f11041c8 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -727,6 +727,23 @@ public: Time_zone **get_tz_ptr(THD *thd, enum_var_type type); }; + +class sys_var_max_user_conn : public sys_var_thd +{ +public: + sys_var_max_user_conn(const char *name_arg): + sys_var_thd(name_arg) {} + bool check(THD *thd, set_var *var); + bool update(THD *thd, set_var *var); + bool check_default(enum_var_type type) + { + return type != OPT_GLOBAL || !option_limits; + } + void set_default(THD *thd, enum_var_type type); + SHOW_TYPE type() { return SHOW_LONG; } + byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); +}; + /**************************************************************************** Classes for parsing of the SET command ****************************************************************************/ diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 2de3fc4a711..a7bbb189466 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -344,10 +344,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) ptr = get_field(&mem, table->field[next_field++]); user.user_resource.updates=ptr ? atoi(ptr) : 0; ptr = get_field(&mem, table->field[next_field++]); - user.user_resource.connections=ptr ? atoi(ptr) : 0; + user.user_resource.conn_per_hour= ptr ? atoi(ptr) : 0; if (user.user_resource.questions || user.user_resource.updates || - user.user_resource.connections) + user.user_resource.conn_per_hour) mqh_used=1; + + if (table->fields >= 34) + { + /* Starting from 5.0.3 we have max_user_connections field */ + ptr= get_field(&mem, table->field[next_field++]); + user.user_resource.user_conn= ptr ? atoi(ptr) : 0; + } + else + user.user_resource.user_conn= 0; } else { @@ -935,12 +944,14 @@ static void acl_update_user(const char *user, const char *host, !my_strcasecmp(system_charset_info, host, acl_user->host.hostname)) { acl_user->access=privileges; - if (mqh->bits & 1) + if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) acl_user->user_resource.questions=mqh->questions; - if (mqh->bits & 2) + if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) acl_user->user_resource.updates=mqh->updates; - if (mqh->bits & 4) - acl_user->user_resource.connections=mqh->connections; + if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) + acl_user->user_resource.conn_per_hour= mqh->conn_per_hour; + if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS) + acl_user->user_resource.user_conn= mqh->user_conn; if (ssl_type != SSL_TYPE_NOT_SPECIFIED) { acl_user->ssl_type= ssl_type; @@ -1623,7 +1634,8 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, if (combo.password.str) // If password given table->field[2]->store(password, password_len, system_charset_info); else if (!rights && !revoke_grant && - lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && !lex->mqh.bits) + lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && + !lex->mqh.specified_limits) { DBUG_RETURN(0); } @@ -1685,13 +1697,16 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, } USER_RESOURCES mqh= lex->mqh; - if (mqh.bits & 1) + if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) table->field[28]->store((longlong) mqh.questions); - if (mqh.bits & 2) + if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) table->field[29]->store((longlong) mqh.updates); - if (mqh.bits & 4) - table->field[30]->store((longlong) mqh.connections); - mqh_used = mqh_used || mqh.questions || mqh.updates || mqh.connections; + if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) + table->field[30]->store((longlong) mqh.conn_per_hour); + if (table->fields >= 34 && + (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) + table->field[33]->store((longlong) mqh.user_conn); + mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour; } if (old_row_exists) { @@ -3816,8 +3831,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) } } if ((want_access & GRANT_ACL) || - (acl_user->user_resource.questions | acl_user->user_resource.updates | - acl_user->user_resource.connections)) + (acl_user->user_resource.questions || + acl_user->user_resource.updates || + acl_user->user_resource.conn_per_hour || + acl_user->user_resource.user_conn)) { global.append(" WITH",5); if (want_access & GRANT_ACL) @@ -3826,8 +3843,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) "MAX_QUERIES_PER_HOUR"); add_user_option(&global, acl_user->user_resource.updates, "MAX_UPDATES_PER_HOUR"); - add_user_option(&global, acl_user->user_resource.connections, + add_user_option(&global, acl_user->user_resource.conn_per_hour, "MAX_CONNECTIONS_PER_HOUR"); + add_user_option(&global, acl_user->user_resource.user_conn, + "MAX_USER_CONNECTIONS"); } protocol->prepare_for_resend(); protocol->store(global.ptr(),global.length(),global.charset()); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 4a357c6eefe..68009c11c76 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -176,14 +176,10 @@ static int get_or_create_user_conn(THD *thd, const char *user, } uc->user=(char*) (uc+1); memcpy(uc->user,temp_user,temp_len+1); - uc->user_len= user_len; - uc->host=uc->user + uc->user_len + 1; + uc->host= uc->user + user_len + 1; uc->len = temp_len; - uc->connections = 1; - uc->questions=uc->updates=uc->conn_per_hour=0; + uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0; uc->user_resources=*mqh; - if (max_user_connections && mqh->connections > max_user_connections) - uc->user_resources.connections = max_user_connections; uc->intime=thd->thr_create_time; if (my_hash_insert(&hash_user_connections, (byte*) uc)) { @@ -356,12 +352,16 @@ int check_user(THD *thd, enum enum_server_command command, thd->db_access=0; /* Don't allow user to connect if he has done too many queries */ - if ((ur.questions || ur.updates || ur.connections || + if ((ur.questions || ur.updates || ur.conn_per_hour || ur.user_conn || max_user_connections) && - get_or_create_user_conn(thd,thd->user,thd->host_or_ip,&ur)) + get_or_create_user_conn(thd, + opt_old_style_user_limits ? thd->user : thd->priv_user, + opt_old_style_user_limits ? thd->host_or_ip : thd->priv_host, + &ur)) DBUG_RETURN(-1); if (thd->user_connect && - (thd->user_connect->user_resources.connections || + (thd->user_connect->user_resources.conn_per_hour || + thd->user_connect->user_resources.user_conn || max_user_connections) && check_for_max_user_connections(thd, thd->user_connect)) DBUG_RETURN(-1); @@ -452,19 +452,28 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc) DBUG_ENTER("check_for_max_user_connections"); (void) pthread_mutex_lock(&LOCK_user_conn); - if (max_user_connections && + if (max_user_connections && !uc->user_resources.user_conn && max_user_connections < (uint) uc->connections) { net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user); error=1; goto end; } - if (uc->user_resources.connections && - uc->user_resources.connections <= uc->conn_per_hour) + if (uc->user_resources.user_conn && + uc->user_resources.user_conn < uc->connections) + { + net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, + "max_user_connections", + (long) uc->user_resources.user_conn); + error= 1; + goto end; + } + if (uc->user_resources.conn_per_hour && + uc->user_resources.conn_per_hour <= uc->conn_per_hour) { net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, "max_connections", - (long) uc->user_resources.connections); + (long) uc->user_resources.conn_per_hour); error=1; goto end; } @@ -3522,7 +3531,7 @@ create_error: Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE); mysql_bin_log.write(&qinfo); } - if (mqh_used && lex->sql_command == SQLCOM_GRANT) + if (lex->sql_command == SQLCOM_GRANT) { List_iterator <LEX_USER> str_list(lex->users_list); LEX_USER *user; @@ -5692,8 +5701,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, { acl_reload(thd); grant_reload(thd); - if (mqh_used) - reset_mqh((LEX_USER *) NULL,TRUE); + reset_mqh((LEX_USER *)NULL, TRUE); } #endif if (options & REFRESH_LOG) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5c03a4c98ef..0ad5470d213 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -335,6 +335,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token MAX_CONNECTIONS_PER_HOUR %token MAX_QUERIES_PER_HOUR %token MAX_UPDATES_PER_HOUR +%token MAX_USER_CONNECTIONS_SYM %token MEDIUM_SYM %token MIN_ROWS %token NAMES_SYM @@ -6978,6 +6979,7 @@ keyword: | MAX_CONNECTIONS_PER_HOUR {} | MAX_QUERIES_PER_HOUR {} | MAX_UPDATES_PER_HOUR {} + | MAX_USER_CONNECTIONS_SYM {} | MEDIUM_SYM {} | MERGE_SYM {} | MICROSECOND_SYM {} @@ -7802,18 +7804,23 @@ grant_option: | MAX_QUERIES_PER_HOUR ULONG_NUM { Lex->mqh.questions=$2; - Lex->mqh.bits |= 1; + Lex->mqh.specified_limits|= USER_RESOURCES::QUERIES_PER_HOUR; } | MAX_UPDATES_PER_HOUR ULONG_NUM { Lex->mqh.updates=$2; - Lex->mqh.bits |= 2; + Lex->mqh.specified_limits|= USER_RESOURCES::UPDATES_PER_HOUR; } | MAX_CONNECTIONS_PER_HOUR ULONG_NUM { - Lex->mqh.connections=$2; - Lex->mqh.bits |= 4; + Lex->mqh.conn_per_hour= $2; + Lex->mqh.specified_limits|= USER_RESOURCES::CONNECTIONS_PER_HOUR; } + | MAX_USER_CONNECTIONS_SYM ULONG_NUM + { + Lex->mqh.user_conn= $2; + Lex->mqh.specified_limits|= USER_RESOURCES::USER_CONNECTIONS; + } ; begin: diff --git a/sql/structs.h b/sql/structs.h index 0b59c3abeb3..cbc7161ee20 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -206,16 +206,65 @@ typedef struct st_lex_user { } LEX_USER; +/* + This structure specifies the maximum amount of resources which + can be consumed by each account. Zero value of a member means + there is no limit. +*/ typedef struct user_resources { - uint questions, updates, connections, bits; + /* Maximum number of queries/statements per hour. */ + uint questions; + /* + Maximum number of updating statements per hour (which statements are + updating is defined by uc_update_queries array). + */ + uint updates; + /* Maximum number of connections established per hour. */ + uint conn_per_hour; + /* Maximum number of concurrent connections. */ + uint user_conn; + /* + Values of this enum and specified_limits member are used by the + parser to store which user limits were specified in GRANT statement. + */ + enum {QUERIES_PER_HOUR= 1, UPDATES_PER_HOUR= 2, CONNECTIONS_PER_HOUR= 4, + USER_CONNECTIONS= 8}; + uint specified_limits; } USER_RESOURCES; + +/* + This structure is used for counting resources consumed and for checking + them against specified user limits. +*/ typedef struct user_conn { - char *user, *host; - uint len, connections, conn_per_hour, updates, questions, user_len; + /* + Pointer to user+host key (pair separated by '\0') defining the entity + for which resources are counted (By default it is user account thus + priv_user/priv_host pair is used. If --old-style-user-limits option + is enabled, resources are counted for each user+host separately). + */ + char *user; + /* Pointer to host part of the key. */ + char *host; + /* Total length of the key. */ + uint len; + /* Current amount of concurrent connections for this account. */ + uint connections; + /* + Current number of connections per hour, number of updating statements + per hour and total number of statements per hour for this account. + */ + uint conn_per_hour, updates, questions; + /* Maximum amount of resources which account is allowed to consume. */ USER_RESOURCES user_resources; + /* + The moment of time when per hour counters were reset last time + (i.e. start of "hour" for conn_per_hour, updates, questions counters). + */ time_t intime; } USER_CONN; + /* Bits in form->update */ #define REG_MAKE_DUPP 1 /* Make a copy of record when read */ #define REG_NEW_RECORD 2 /* Write a new record if not found */ |