diff options
-rw-r--r-- | mysql-test/r/flush.result | 96 | ||||
-rw-r--r-- | mysql-test/t/flush.test | 100 | ||||
-rw-r--r-- | sql/sql_parse.cc | 116 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 29 |
4 files changed, 332 insertions, 9 deletions
diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index 2136bcd92f1..fd23bfa0562 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -111,3 +111,99 @@ commit; # which was already released by commit. unlock tables; drop tables t1, t2; +# +# Tests for WL#5000 FLUSH TABLES|TABLE table_list WITH READ LOCK +# +# I. Check the incompatible changes in the grammar. +# +flush tables with read lock, hosts; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' hosts' at line 1 +flush privileges, tables; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tables' at line 1 +flush privileges, tables with read lock; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tables with read lock' at line 1 +flush privileges, tables; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tables' at line 1 +flush tables with read lock, tables; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' tables' at line 1 +show tables; +Tables_in_test +# +# II. Check the allowed syntax. +# +drop table if exists t1, t2, t3; +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +lock table t1 read, t2 read; +flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +flush tables with read lock; +flush tables t1, t2 with read lock; +flush tables t1, t2 with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +select * from t1; +a +select * from t2; +a +select * from t3; +ERROR HY000: Table 't3' was not locked with LOCK TABLES +insert into t1 (a) values (1); +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +insert into t2 (a) values (1); +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +insert into t3 (a) values (1); +ERROR HY000: Table 't3' was not locked with LOCK TABLES +lock table no_such_table read; +ERROR 42S02: Table 'test.no_such_table' doesn't exist +# +# We implicitly left the locked tables +# mode but still have the read lock. +# +insert into t2 (a) values (1); +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +insert into t1 (a) values (1); +insert into t2 (a) values (1); +flush table t1, t2 with read lock; +select * from t1; +a +1 +select * from t2; +a +1 +select * from t3; +ERROR HY000: Table 't3' was not locked with LOCK TABLES +insert into t1 (a) values (2); +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +insert into t2 (a) values (2); +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +insert into t3 (a) values (2); +ERROR HY000: Table 't3' was not locked with LOCK TABLES +lock table no_such_table read; +ERROR 42S02: Table 'test.no_such_table' doesn't exist +insert into t3 (a) values (2); +# +# III. Concurrent tests. +# +# --> connection default +# +# Check that flush tables <list> with read lock +# does not affect non-locked tables. +# +flush tables t1 with read lock; +# --> connection con1; +select * from t1; +a +1 +select * from t2; +a +1 +insert into t2 (a) values (3); +# --> connection default; +unlock tables; +# --> connection con1 +drop table t1, t2, t3; diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index d41ac3100b0..582d2562fc6 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -224,3 +224,103 @@ commit; --echo # which was already released by commit. unlock tables; drop tables t1, t2; + + + +--echo # +--echo # Tests for WL#5000 FLUSH TABLES|TABLE table_list WITH READ LOCK +--echo # +--echo # I. Check the incompatible changes in the grammar. +--echo # +--error ER_PARSE_ERROR +flush tables with read lock, hosts; +--error ER_PARSE_ERROR +flush privileges, tables; +--error ER_PARSE_ERROR +flush privileges, tables with read lock; +--error ER_PARSE_ERROR +flush privileges, tables; +--error ER_PARSE_ERROR +flush tables with read lock, tables; +show tables; +--echo # +--echo # II. Check the allowed syntax. +--echo # +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +create table t3 (a int); +lock table t1 read, t2 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +flush tables with read lock; +unlock tables; +flush tables with read lock; +flush tables t1, t2 with read lock; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +flush tables t1, t2 with read lock; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +flush tables with read lock; +select * from t1; +select * from t2; +--error ER_TABLE_NOT_LOCKED +select * from t3; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +insert into t1 (a) values (1); +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +insert into t2 (a) values (1); +--error ER_TABLE_NOT_LOCKED +insert into t3 (a) values (1); +--error ER_NO_SUCH_TABLE +lock table no_such_table read; +--echo # +--echo # We implicitly left the locked tables +--echo # mode but still have the read lock. +--echo # +--error ER_CANT_UPDATE_WITH_READLOCK +insert into t2 (a) values (1); +unlock tables; +insert into t1 (a) values (1); +insert into t2 (a) values (1); +flush table t1, t2 with read lock; +select * from t1; +select * from t2; +--error ER_TABLE_NOT_LOCKED +select * from t3; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +insert into t1 (a) values (2); +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +insert into t2 (a) values (2); +--error ER_TABLE_NOT_LOCKED +insert into t3 (a) values (2); +--error ER_NO_SUCH_TABLE +lock table no_such_table read; +insert into t3 (a) values (2); + + +--echo # +--echo # III. Concurrent tests. +--echo # +connect (con1,localhost,root,,); +--echo # --> connection default +--echo # +--echo # Check that flush tables <list> with read lock +--echo # does not affect non-locked tables. +connection default; +--echo # +flush tables t1 with read lock; +--echo # --> connection con1; +connection con1; +select * from t1; +select * from t2; +insert into t2 (a) values (3); +--echo # --> connection default; +connection default; +unlock tables; +--echo # --> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +connection default; +drop table t1, t2, t3; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index e66f2756c54..7a13c46e4f6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1588,6 +1588,113 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, /** + Implementation of FLUSH TABLES <table_list> WITH READ LOCK. + + In brief: take exclusive locks, expel tables from the table + cache, reopen the tables, enter the 'LOCKED TABLES' mode, + downgrade the locks. + + Required privileges + ------------------- + Since the statement implicitly enters LOCK TABLES mode, + it requires LOCK TABLES privilege on every table. + But since the rest of FLUSH commands require + the global RELOAD_ACL, it also requires RELOAD_ACL. + + Compatibility with the global read lock + --------------------------------------- + We don't wait for the GRL, since neither the + 5.1 combination that this new statement is intended to + replace (LOCK TABLE <list> WRITE; FLUSH TABLES;), + nor FLUSH TABLES WITH READ LOCK do. + @todo: this is not implemented, Dmitry disagrees. + Currently we wait for GRL in another connection, + but are compatible with a GRL in our own connection. + + Behaviour under LOCK TABLES + --------------------------- + Bail out: i.e. don't perform an implicit UNLOCK TABLES. + This is not consistent with LOCK TABLES statement, but is + in line with behaviour of FLUSH TABLES WITH READ LOCK, and we + try to not introduce any new statements with implicit + semantics. + + Compatibility with parallel updates + ----------------------------------- + As a result, we will wait for all open transactions + against the tables to complete. After the lock downgrade, + new transactions will be able to read the tables, but not + write to them. + + Differences from FLUSH TABLES <list> + ------------------------------------- + - you can't flush WITH READ LOCK a non-existent table + - you can't flush WITH READ LOCK under LOCK TABLES + - currently incompatible with the GRL (@todo: fix) +*/ + +static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) +{ + Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; + TABLE_LIST *table_list; + + /* + This is called from SQLCOM_FLUSH, the transaction has + been committed implicitly. + */ + + /* RELOAD_ACL is checked by the caller. Check table-level privileges. */ + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, + FALSE, UINT_MAX, FALSE)) + goto error; + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + goto error; + } + + /* + @todo: Since lock_table_names() acquires a global IX + lock, this actually waits for a GRL in another connection. + We are thus introducing an incompatibility. + Do nothing for now, since not taking a global IX violates + current internal MDL asserts, fix after discussing with + Dmitry. + */ + if (lock_table_names(thd, all_tables)) + goto error; + + if (open_and_lock_tables(thd, all_tables, FALSE, + MYSQL_OPEN_HAS_MDL_LOCK, + &lock_tables_prelocking_strategy) || + thd->locked_tables_list.init_locked_tables(thd)) + { + close_thread_tables(thd); + goto error; + } + + /* + Downgrade the exclusive locks. + Use MDL_SHARED_NO_WRITE as the intended + post effect of this call is identical + to LOCK TABLES <...> READ, and we didn't use + thd->in_lock_talbes and thd->sql_command= SQLCOM_LOCK_TABLES + hacks to enter the LTM. + @todo: release the global IX lock here!!! + */ + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + table_list->mdl_request.ticket->downgrade_exclusive_lock(MDL_SHARED_NO_WRITE); + + return FALSE; + +error: + return TRUE; +} + + +/** Read query from packet and store in thd->query. Used in COM_QUERY and COM_STMT_PREPARE. @@ -3728,9 +3835,18 @@ end_with_restore_list: case SQLCOM_FLUSH: { bool write_to_binlog; + if (check_global_access(thd,RELOAD_ACL)) goto error; + if (first_table && lex->type & REFRESH_READ_LOCK) + { + if (flush_tables_with_read_lock(thd, all_tables)) + goto error; + my_ok(thd); + break; + } + /* reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index f03694cb359..5648e3cbdb5 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -767,10 +767,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %pure_parser /* We have threads */ /* - Currently there are 169 shift/reduce conflicts. + Currently there are 168 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 169 +%expect 168 /* Comments for TOKENS. @@ -1554,6 +1554,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); opt_column_list grant_privileges grant_ident grant_list grant_option object_privilege object_privilege_list user_list rename_list clear_privileges flush_options flush_option + opt_with_read_lock flush_options_list equal optional_braces opt_mi_check_type opt_to mi_check_types normal_join table_to_table_list table_to_table opt_table_list opt_as @@ -11095,17 +11096,27 @@ flush: ; flush_options: - flush_options ',' flush_option + table_or_tables + { Lex->type|= REFRESH_TABLES; } + opt_table_list {} + opt_with_read_lock {} + | flush_options_list + ; + +opt_with_read_lock: + /* empty */ {} + | WITH READ_SYM LOCK_SYM + { Lex->type|= REFRESH_READ_LOCK; } + ; + +flush_options_list: + flush_options_list ',' flush_option | flush_option + {} ; flush_option: - table_or_tables - { Lex->type|= REFRESH_TABLES; } - opt_table_list {} - | TABLES WITH READ_SYM LOCK_SYM - { Lex->type|= REFRESH_TABLES | REFRESH_READ_LOCK; } - | ERROR_SYM LOGS_SYM + ERROR_SYM LOGS_SYM { Lex->type|= REFRESH_ERROR_LOG; } | ENGINE_SYM LOGS_SYM { Lex->type|= REFRESH_ENGINE_LOG; } |