summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <dlenev@mysql.com>2004-12-29 22:35:16 +0300
committerunknown <dlenev@mysql.com>2004-12-29 22:35:16 +0300
commitf174d5a2414e22b6cf452a2e8cbb6b72395a56dc (patch)
tree1a687bd272d791651de5a4a1716efef6eebbe4b6
parent44728324f0abe3e327fc2095a99e647b1c44dd8d (diff)
parent05b709fc77e2f321fa3fa45ee5b2ec2ceae54055 (diff)
downloadmariadb-git-f174d5a2414e22b6cf452a2e8cbb6b72395a56dc.tar.gz
Manual merge of patch implementing WL#1339 with main tree.
client/mysqltest.c: Auto merged mysql-test/r/system_mysql_db.result: Auto merged sql/lex.h: Auto merged sql/set_var.cc: Auto merged sql/sql_acl.cc: Auto merged sql/sql_yacc.yy: Auto merged sql/structs.h: Auto merged mysql-test/r/grant.result: Manual merge scripts/mysql_create_system_tables.sh: Manual merge scripts/mysql_fix_privilege_tables.sql: Manual merge sql/mysql_priv.h: Manual merge sql/mysqld.cc: Manual merge sql/sql_parse.cc: Manual merge
-rw-r--r--client/mysqltest.c414
-rw-r--r--mysql-test/r/system_mysql_db.result1
-rw-r--r--mysql-test/r/user_limits.result89
-rw-r--r--mysql-test/t/user_limits.test153
-rw-r--r--scripts/mysql_create_system_tables.sh17
-rw-r--r--scripts/mysql_fix_privilege_tables.sql5
-rw-r--r--sql/lex.h1
-rw-r--r--sql/mysql_priv.h1
-rw-r--r--sql/mysqld.cc13
-rw-r--r--sql/set_var.cc50
-rw-r--r--sql/set_var.h17
-rw-r--r--sql/sql_acl.cc49
-rw-r--r--sql/sql_parse.cc40
-rw-r--r--sql/sql_yacc.yy15
-rw-r--r--sql/structs.h55
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 */