Commit a8deefa3 authored by dlenev@mysql.com's avatar dlenev@mysql.com

Manual merge of patch implementing WL#1339 with main tree.

parents 7adeebd9 ffa73933
...@@ -437,6 +437,10 @@ my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; } ...@@ -437,6 +437,10 @@ my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; }
#endif #endif
static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val,
int len); 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) static void do_eval(DYNAMIC_STRING* query_eval, const char* query)
{ {
...@@ -951,7 +955,7 @@ static void do_exec(struct st_query* q) ...@@ -951,7 +955,7 @@ static void do_exec(struct st_query* q)
ds= &ds_res; ds= &ds_res;
while (fgets(buf, sizeof(buf), res_file)) while (fgets(buf, sizeof(buf), res_file))
replace_dynstr_append_mem(ds, buf, strlen(buf)); replace_dynstr_append(ds, buf);
} }
error= pclose(res_file); error= pclose(res_file);
...@@ -1613,6 +1617,29 @@ void init_manager() ...@@ -1613,6 +1617,29 @@ void init_manager()
} }
#endif #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, int safe_connect(MYSQL* con, const char* host, const char* user,
const char* pass, const char* pass,
const char* db, int port, const char* sock) const char* db, int port, const char* sock)
...@@ -1634,6 +1661,114 @@ int safe_connect(MYSQL* con, const char* host, const char* user, ...@@ -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) int do_connect(struct st_query* q)
{ {
char* con_name, *con_user,*con_pass, *con_host, *con_port_str, char* con_name, *con_user,*con_pass, *con_host, *con_port_str,
...@@ -1642,6 +1777,8 @@ int do_connect(struct st_query* q) ...@@ -1642,6 +1777,8 @@ int do_connect(struct st_query* q)
char buff[FN_REFLEN]; char buff[FN_REFLEN];
int con_port; int con_port;
int free_con_sock = 0; int free_con_sock = 0;
int error= 0;
int create_conn= 1;
DBUG_ENTER("do_connect"); DBUG_ENTER("do_connect");
DBUG_PRINT("enter",("connect: %s",p)); DBUG_PRINT("enter",("connect: %s",p));
...@@ -1706,18 +1843,28 @@ int do_connect(struct st_query* q) ...@@ -1706,18 +1843,28 @@ int do_connect(struct st_query* q)
/* Special database to allow one to connect without a database name */ /* Special database to allow one to connect without a database name */
if (con_db && !strcmp(con_db,"*NO-ONE*")) if (con_db && !strcmp(con_db,"*NO-ONE*"))
con_db=0; con_db=0;
if ((safe_connect(&next_con->mysql, con_host,
con_user, con_pass, 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))) con_db, con_port, con_sock ? con_sock: 0)))
die("Could not open connection '%s': %s", con_name, die("Could not open connection '%s': %s", con_name,
mysql_error(&next_con->mysql)); 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 (!(next_con->name = my_strdup(con_name, MYF(MY_WME)))) if (create_conn)
{
if (!(next_con->name= my_strdup(con_name, MYF(MY_WME))))
die(NullS); die(NullS);
cur_con = next_con++; cur_con= next_con++;
}
if (free_con_sock) if (free_con_sock)
my_free(con_sock, MYF(MY_WME)); 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, ...@@ -2356,6 +2503,13 @@ static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val,
dynstr_append_mem(ds, val, len); 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' 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) ...@@ -2502,93 +2656,14 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags)
(!(last_result= res= mysql_store_result(mysql)) && (!(last_result= res= mysql_store_result(mysql)) &&
mysql_field_count(mysql))) mysql_field_count(mysql)))
{ {
if (q->require_file) if (normal_handle_error(query, q, mysql, ds))
{
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; error= 1;
goto end; 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 && if (normal_handle_no_error(q))
q->expected_errno[0].code.errnum != 0)
{ {
/* Error code we wanted was != 0, i.e. not an expected success */ error= 1;
verbose_msg("query '%s' succeeded - should have failed with errno %d...",
q->query, q->expected_errno[0].code.errnum);
error = 1;
goto end;
}
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);
error = 1;
goto end; goto end;
} }
...@@ -2608,8 +2683,7 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) ...@@ -2608,8 +2683,7 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags)
{ {
if (i) if (i)
dynstr_append_mem(ds, "\t", 1); dynstr_append_mem(ds, "\t", 1);
replace_dynstr_append_mem(ds, field[i].name, replace_dynstr_append(ds, field[i].name);
strlen(field[i].name));
} }
dynstr_append_mem(ds, "\n", 1); dynstr_append_mem(ds, "\n", 1);
} }
...@@ -2686,6 +2760,135 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) ...@@ -2686,6 +2760,135 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags)
} }
/*
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 * 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) ...@@ -2879,8 +3082,7 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags)
{ {
if (col_idx) if (col_idx)
dynstr_append_mem(ds, "\t", 1); dynstr_append_mem(ds, "\t", 1);
replace_dynstr_append_mem(ds, field[col_idx].name, replace_dynstr_append(ds, field[col_idx].name);
strlen(field[col_idx].name));
} }
dynstr_append_mem(ds, "\n", 1); dynstr_append_mem(ds, "\n", 1);
} }
...@@ -3148,11 +3350,9 @@ static int run_query_stmt_handle_error(char *query, struct st_query *q, ...@@ -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 */ /* Only log error if there is one possible error */
dynstr_append_mem(ds,"ERROR ",6); dynstr_append_mem(ds,"ERROR ",6);
replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), replace_dynstr_append(ds, mysql_stmt_sqlstate(stmt));
strlen(mysql_stmt_sqlstate(stmt)));
dynstr_append_mem(ds, ": ", 2); dynstr_append_mem(ds, ": ", 2);
replace_dynstr_append_mem(ds,mysql_stmt_error(stmt), replace_dynstr_append(ds,mysql_stmt_error(stmt));
strlen(mysql_stmt_error(stmt)));
dynstr_append_mem(ds,"\n",1); dynstr_append_mem(ds,"\n",1);
} }
/* Don't log error if we may not get an error */ /* 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, ...@@ -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, DBUG_PRINT("info",("i: %d expected_errors: %d", i,
q->expected_errors)); q->expected_errors));
dynstr_append_mem(ds, "ERROR ",6); dynstr_append_mem(ds, "ERROR ",6);
replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), replace_dynstr_append(ds, mysql_stmt_sqlstate(stmt));
strlen(mysql_stmt_sqlstate(stmt)));
dynstr_append_mem(ds,": ",2); dynstr_append_mem(ds,": ",2);
replace_dynstr_append_mem(ds, mysql_stmt_error(stmt), replace_dynstr_append(ds, mysql_stmt_error(stmt));
strlen(mysql_stmt_error(stmt)));
dynstr_append_mem(ds,"\n",1); dynstr_append_mem(ds,"\n",1);
if (i) if (i)
{ {
...@@ -3452,7 +3650,9 @@ int main(int argc, char **argv) ...@@ -3452,7 +3650,9 @@ int main(int argc, char **argv)
{ {
processed = 1; processed = 1;
switch (q->type) { 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_CONNECTION: select_connection(q->first_argument); break;
case Q_DISCONNECT: case Q_DISCONNECT:
case Q_DIRTY_CLOSE: case Q_DIRTY_CLOSE:
......
...@@ -102,6 +102,7 @@ user CREATE TABLE `user` ( ...@@ -102,6 +102,7 @@ user CREATE TABLE `user` (
`max_questions` int(11) unsigned NOT NULL default '0', `max_questions` int(11) unsigned NOT NULL default '0',
`max_updates` 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_connections` int(11) unsigned NOT NULL default '0',
`max_user_connections` int(11) unsigned NOT NULL default '0',
PRIMARY KEY (`Host`,`User`) PRIMARY KEY (`Host`,`User`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges' ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'
show create table func; show create table func;
......
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;
#
# 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;
...@@ -153,6 +153,7 @@ then ...@@ -153,6 +153,7 @@ then
c_u="$c_u max_questions int(11) unsigned DEFAULT 0 NOT NULL," 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_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_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 PRIMARY KEY Host (Host,User)"
c_u="$c_u ) engine=MyISAM" c_u="$c_u ) engine=MyISAM"
c_u="$c_u CHARACTER SET utf8 COLLATE utf8_bin" c_u="$c_u CHARACTER SET utf8 COLLATE utf8_bin"
...@@ -160,24 +161,24 @@ then ...@@ -160,24 +161,24 @@ then
if test "$1" = "test" if test "$1" = "test"
then 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); 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); 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); 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 ('localhost','');
INSERT INTO user (host,user) values ('$hostname','');" INSERT INTO user (host,user) values ('$hostname','');"
else 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" if test "$windows" = "0"
then then
i_u="$i_u 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 ('$hostname','');
INSERT INTO user (host,user) values ('localhost','');" INSERT INTO user (host,user) values ('localhost','');"
else else
i_u="$i_u 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 ('%','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); 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);" 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 fi
fi fi
......
...@@ -197,6 +197,11 @@ ALTER TABLE db ADD Execute_priv enum('N','Y') DEFAULT 'N' NOT NULL AFTER Alter_r ...@@ -197,6 +197,11 @@ ALTER TABLE db ADD Execute_priv enum('N','Y') DEFAULT 'N' NOT NULL AFTER Alter_r
UPDATE user SET Create_routine_priv=Create_priv, Alter_routine_priv=Alter_priv where user<>"" AND @hadCreateRoutinePriv = 0; UPDATE user SET Create_routine_priv=Create_priv, Alter_routine_priv=Alter_priv where user<>"" AND @hadCreateRoutinePriv = 0;
UPDATE db SET Create_routine_priv=Create_priv, Alter_routine_priv=Alter_priv, Execute_priv=Select_priv where user<>"" AND @hadCreateRoutinePriv = 0; 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 some possible missing tables
# #
......
...@@ -304,6 +304,7 @@ static SYMBOL symbols[] = { ...@@ -304,6 +304,7 @@ static SYMBOL symbols[] = {
{ "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)}, { "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)},
{ "MAX_ROWS", SYM(MAX_ROWS)}, { "MAX_ROWS", SYM(MAX_ROWS)},
{ "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)}, { "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)},
{ "MAX_USER_CONNECTIONS", SYM(MAX_USER_CONNECTIONS_SYM)},
{ "MEDIUM", SYM(MEDIUM_SYM)}, { "MEDIUM", SYM(MEDIUM_SYM)},
{ "MEDIUMBLOB", SYM(MEDIUMBLOB)}, { "MEDIUMBLOB", SYM(MEDIUMBLOB)},
{ "MEDIUMINT", SYM(MEDIUMINT)}, { "MEDIUMINT", SYM(MEDIUMINT)},
......
...@@ -1026,6 +1026,7 @@ extern my_bool opt_readonly, lower_case_file_system; ...@@ -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_enable_named_pipe, opt_sync_frm;
extern my_bool opt_secure_auth; extern my_bool opt_secure_auth;
extern my_bool sp_automatic_privileges; extern my_bool sp_automatic_privileges;
extern my_bool opt_old_style_user_limits;
extern uint opt_crash_binlog_innodb; extern uint opt_crash_binlog_innodb;
extern char *shared_memory_base_name, *mysqld_unix_port; extern char *shared_memory_base_name, *mysqld_unix_port;
extern bool opt_enable_shared_memory; extern bool opt_enable_shared_memory;
......
...@@ -298,6 +298,12 @@ my_bool lower_case_file_system= 0; ...@@ -298,6 +298,12 @@ my_bool lower_case_file_system= 0;
my_bool opt_innodb_safe_binlog= 0; my_bool opt_innodb_safe_binlog= 0;
my_bool opt_large_pages= 0; my_bool opt_large_pages= 0;
uint opt_large_page_size= 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; volatile bool mqh_used = 0;
my_bool sp_automatic_privileges= 1; my_bool sp_automatic_privileges= 1;
...@@ -4204,7 +4210,8 @@ enum options_mysqld ...@@ -4204,7 +4210,8 @@ enum options_mysqld
OPT_UPDATABLE_VIEWS_WITH_LIMIT, OPT_UPDATABLE_VIEWS_WITH_LIMIT,
OPT_SP_AUTOMATIC_PRIVILEGES, OPT_SP_AUTOMATIC_PRIVILEGES,
OPT_AUTO_INCREMENT, OPT_AUTO_INCREMENT_OFFSET, 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).", ...@@ -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, "Only use one thread (for debugging under Linux).", 0, 0, 0, GET_NO_ARG,
NO_ARG, 0, 0, 0, 0, 0, 0}, NO_ARG, 0, 0, 0, 0, 0, 0},
#endif #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.", {"pid-file", OPT_PID_FILE, "Pid file used by safe_mysqld.",
(gptr*) &pidfile_name_ptr, (gptr*) &pidfile_name_ptr, 0, GET_STR, (gptr*) &pidfile_name_ptr, (gptr*) &pidfile_name_ptr, 0, GET_STR,
REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
......
...@@ -253,8 +253,7 @@ sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size", ...@@ -253,8 +253,7 @@ sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size",
fix_max_relay_log_size); fix_max_relay_log_size);
sys_var_thd_ulong sys_max_sort_length("max_sort_length", sys_var_thd_ulong sys_max_sort_length("max_sort_length",
&SV::max_sort_length); &SV::max_sort_length);
sys_var_long_ptr sys_max_user_connections("max_user_connections", sys_var_max_user_conn sys_max_user_connections("max_user_connections");
&max_user_connections);
sys_var_thd_ulong sys_max_tmp_tables("max_tmp_tables", sys_var_thd_ulong sys_max_tmp_tables("max_tmp_tables",
&SV::max_tmp_tables); &SV::max_tmp_tables);
sys_var_long_ptr sys_max_write_lock_count("max_write_lock_count", sys_var_long_ptr sys_max_write_lock_count("max_write_lock_count",
...@@ -2527,6 +2526,51 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type) ...@@ -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; 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 Functions to update thd->options bits
*/ */
......
...@@ -727,6 +727,23 @@ class sys_var_thd_time_zone :public sys_var_thd ...@@ -727,6 +727,23 @@ class sys_var_thd_time_zone :public sys_var_thd
Time_zone **get_tz_ptr(THD *thd, enum_var_type type); 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 Classes for parsing of the SET command
****************************************************************************/ ****************************************************************************/
......
...@@ -344,10 +344,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) ...@@ -344,10 +344,19 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
ptr = get_field(&mem, table->field[next_field++]); ptr = get_field(&mem, table->field[next_field++]);
user.user_resource.updates=ptr ? atoi(ptr) : 0; user.user_resource.updates=ptr ? atoi(ptr) : 0;
ptr = get_field(&mem, table->field[next_field++]); 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 || if (user.user_resource.questions || user.user_resource.updates ||
user.user_resource.connections) user.user_resource.conn_per_hour)
mqh_used=1; 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 else
{ {
...@@ -935,12 +944,14 @@ static void acl_update_user(const char *user, const char *host, ...@@ -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)) !my_strcasecmp(system_charset_info, host, acl_user->host.hostname))
{ {
acl_user->access=privileges; acl_user->access=privileges;
if (mqh->bits & 1) if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
acl_user->user_resource.questions=mqh->questions; 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; acl_user->user_resource.updates=mqh->updates;
if (mqh->bits & 4) if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
acl_user->user_resource.connections=mqh->connections; 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) if (ssl_type != SSL_TYPE_NOT_SPECIFIED)
{ {
acl_user->ssl_type= ssl_type; acl_user->ssl_type= ssl_type;
...@@ -1623,7 +1634,8 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, ...@@ -1623,7 +1634,8 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo,
if (combo.password.str) // If password given if (combo.password.str) // If password given
table->field[2]->store(password, password_len, system_charset_info); table->field[2]->store(password, password_len, system_charset_info);
else if (!rights && !revoke_grant && 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); DBUG_RETURN(0);
} }
...@@ -1685,13 +1697,16 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, ...@@ -1685,13 +1697,16 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo,
} }
USER_RESOURCES mqh= lex->mqh; 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); 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); table->field[29]->store((longlong) mqh.updates);
if (mqh.bits & 4) if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
table->field[30]->store((longlong) mqh.connections); table->field[30]->store((longlong) mqh.conn_per_hour);
mqh_used = mqh_used || mqh.questions || mqh.updates || mqh.connections; 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) if (old_row_exists)
{ {
...@@ -3816,8 +3831,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) ...@@ -3816,8 +3831,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user)
} }
} }
if ((want_access & GRANT_ACL) || if ((want_access & GRANT_ACL) ||
(acl_user->user_resource.questions | acl_user->user_resource.updates | (acl_user->user_resource.questions ||
acl_user->user_resource.connections)) acl_user->user_resource.updates ||
acl_user->user_resource.conn_per_hour ||
acl_user->user_resource.user_conn))
{ {
global.append(" WITH",5); global.append(" WITH",5);
if (want_access & GRANT_ACL) if (want_access & GRANT_ACL)
...@@ -3826,8 +3843,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) ...@@ -3826,8 +3843,10 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user)
"MAX_QUERIES_PER_HOUR"); "MAX_QUERIES_PER_HOUR");
add_user_option(&global, acl_user->user_resource.updates, add_user_option(&global, acl_user->user_resource.updates,
"MAX_UPDATES_PER_HOUR"); "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"); "MAX_CONNECTIONS_PER_HOUR");
add_user_option(&global, acl_user->user_resource.user_conn,
"MAX_USER_CONNECTIONS");
} }
protocol->prepare_for_resend(); protocol->prepare_for_resend();
protocol->store(global.ptr(),global.length(),global.charset()); protocol->store(global.ptr(),global.length(),global.charset());
......
...@@ -176,14 +176,10 @@ static int get_or_create_user_conn(THD *thd, const char *user, ...@@ -176,14 +176,10 @@ static int get_or_create_user_conn(THD *thd, const char *user,
} }
uc->user=(char*) (uc+1); uc->user=(char*) (uc+1);
memcpy(uc->user,temp_user,temp_len+1); memcpy(uc->user,temp_user,temp_len+1);
uc->user_len= user_len; uc->host= uc->user + user_len + 1;
uc->host=uc->user + uc->user_len + 1;
uc->len = temp_len; uc->len = temp_len;
uc->connections = 1; uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0;
uc->questions=uc->updates=uc->conn_per_hour=0;
uc->user_resources=*mqh; 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; uc->intime=thd->thr_create_time;
if (my_hash_insert(&hash_user_connections, (byte*) uc)) if (my_hash_insert(&hash_user_connections, (byte*) uc))
{ {
...@@ -356,12 +352,16 @@ int check_user(THD *thd, enum enum_server_command command, ...@@ -356,12 +352,16 @@ int check_user(THD *thd, enum enum_server_command command,
thd->db_access=0; thd->db_access=0;
/* Don't allow user to connect if he has done too many queries */ /* 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) && 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); DBUG_RETURN(-1);
if (thd->user_connect && 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) && max_user_connections) &&
check_for_max_user_connections(thd, thd->user_connect)) check_for_max_user_connections(thd, thd->user_connect))
DBUG_RETURN(-1); DBUG_RETURN(-1);
...@@ -452,19 +452,28 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc) ...@@ -452,19 +452,28 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc)
DBUG_ENTER("check_for_max_user_connections"); DBUG_ENTER("check_for_max_user_connections");
(void) pthread_mutex_lock(&LOCK_user_conn); (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) max_user_connections < (uint) uc->connections)
{ {
net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user); net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user);
error=1; error=1;
goto end; goto end;
} }
if (uc->user_resources.connections && if (uc->user_resources.user_conn &&
uc->user_resources.connections <= uc->conn_per_hour) 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, net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user,
"max_connections", "max_connections",
(long) uc->user_resources.connections); (long) uc->user_resources.conn_per_hour);
error=1; error=1;
goto end; goto end;
} }
...@@ -3522,7 +3531,7 @@ mysql_execute_command(THD *thd) ...@@ -3522,7 +3531,7 @@ mysql_execute_command(THD *thd)
Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE); Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
mysql_bin_log.write(&qinfo); 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); List_iterator <LEX_USER> str_list(lex->users_list);
LEX_USER *user; LEX_USER *user;
...@@ -5692,8 +5701,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, ...@@ -5692,8 +5701,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
{ {
acl_reload(thd); acl_reload(thd);
grant_reload(thd); grant_reload(thd);
if (mqh_used) reset_mqh((LEX_USER *)NULL, TRUE);
reset_mqh((LEX_USER *) NULL,TRUE);
} }
#endif #endif
if (options & REFRESH_LOG) if (options & REFRESH_LOG)
......
...@@ -335,6 +335,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); ...@@ -335,6 +335,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token MAX_CONNECTIONS_PER_HOUR %token MAX_CONNECTIONS_PER_HOUR
%token MAX_QUERIES_PER_HOUR %token MAX_QUERIES_PER_HOUR
%token MAX_UPDATES_PER_HOUR %token MAX_UPDATES_PER_HOUR
%token MAX_USER_CONNECTIONS_SYM
%token MEDIUM_SYM %token MEDIUM_SYM
%token MIN_ROWS %token MIN_ROWS
%token NAMES_SYM %token NAMES_SYM
...@@ -6978,6 +6979,7 @@ keyword: ...@@ -6978,6 +6979,7 @@ keyword:
| MAX_CONNECTIONS_PER_HOUR {} | MAX_CONNECTIONS_PER_HOUR {}
| MAX_QUERIES_PER_HOUR {} | MAX_QUERIES_PER_HOUR {}
| MAX_UPDATES_PER_HOUR {} | MAX_UPDATES_PER_HOUR {}
| MAX_USER_CONNECTIONS_SYM {}
| MEDIUM_SYM {} | MEDIUM_SYM {}
| MERGE_SYM {} | MERGE_SYM {}
| MICROSECOND_SYM {} | MICROSECOND_SYM {}
...@@ -7802,17 +7804,22 @@ grant_option: ...@@ -7802,17 +7804,22 @@ grant_option:
| MAX_QUERIES_PER_HOUR ULONG_NUM | MAX_QUERIES_PER_HOUR ULONG_NUM
{ {
Lex->mqh.questions=$2; Lex->mqh.questions=$2;
Lex->mqh.bits |= 1; Lex->mqh.specified_limits|= USER_RESOURCES::QUERIES_PER_HOUR;
} }
| MAX_UPDATES_PER_HOUR ULONG_NUM | MAX_UPDATES_PER_HOUR ULONG_NUM
{ {
Lex->mqh.updates=$2; Lex->mqh.updates=$2;
Lex->mqh.bits |= 2; Lex->mqh.specified_limits|= USER_RESOURCES::UPDATES_PER_HOUR;
} }
| MAX_CONNECTIONS_PER_HOUR ULONG_NUM | MAX_CONNECTIONS_PER_HOUR ULONG_NUM
{ {
Lex->mqh.connections=$2; Lex->mqh.conn_per_hour= $2;
Lex->mqh.bits |= 4; 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;
} }
; ;
......
...@@ -206,16 +206,65 @@ typedef struct st_lex_user { ...@@ -206,16 +206,65 @@ typedef struct st_lex_user {
} 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 { 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; } USER_RESOURCES;
/*
This structure is used for counting resources consumed and for checking
them against specified user limits.
*/
typedef struct user_conn { 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; 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; time_t intime;
} USER_CONN; } USER_CONN;
/* Bits in form->update */ /* Bits in form->update */
#define REG_MAKE_DUPP 1 /* Make a copy of record when read */ #define REG_MAKE_DUPP 1 /* Make a copy of record when read */
#define REG_NEW_RECORD 2 /* Write a new record if not found */ #define REG_NEW_RECORD 2 /* Write a new record if not found */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment