revision-id: e2b8c84147ed6d71e1ed1f682553c0828ab0d047 (mariadb-5.5.62-2-ge2b8c84) parent(s): 65cfc5873e8a9d528451d741b512d12a77eff81f committer: Alexey Botchkov timestamp: 2018-10-30 17:20:25 +0400 message: MDEV-15888 Implement FLUSH TABLES tbl_name [, tbl_name] ... WITH READ LOCK for views. Check if the locked instance is a view and lock the linked tables. --- mysql-test/r/flush.result | 21 +++++++--- mysql-test/t/flush.test | 23 ++++++++--- sql/sql_base.cc | 97 +++++++++++++++++++++++++---------------------- sql/sql_base.h | 2 + sql/sql_reload.cc | 59 +++++++++++++++++++++++++--- sql/sql_yacc.yy | 1 - 6 files changed, 141 insertions(+), 62 deletions(-) diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index d3b3cd1..5a17615 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -262,16 +262,16 @@ create view v1 as select 1; create view v2 as select * from t1; create view v3 as select * from v2; flush table v1, v2, v3 with read lock; -ERROR HY000: 'test.v1' is not BASE TABLE +unlock tables; flush table v1 with read lock; -ERROR HY000: 'test.v1' is not BASE TABLE +unlock tables; flush table v2 with read lock; -ERROR HY000: 'test.v2' is not BASE TABLE +unlock tables; flush table v3 with read lock; -ERROR HY000: 'test.v3' is not BASE TABLE +unlock tables; create temporary table v1 (a int); flush table v1 with read lock; -ERROR HY000: 'test.v1' is not BASE TABLE +unlock tables; drop view v1; create table v1 (a int); flush table v1 with read lock; @@ -489,3 +489,14 @@ UNLOCK TABLES; # Switching to connection 'default'. COMMIT; DROP TABLE t1; +# +# Test for MDEV-15888 Implement FLUSH TABLES tbl_name [, tbl_name] +# ... WITH READ LOCK for views +CREATE TABLE t1 (qty INT, price INT); +CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t1; +FLUSH TABLES v WITH READ LOCK; +FLUSH TABLES t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +UNLOCK TABLES; +DROP VIEW v; +DROP TABLE t1; diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index 037e2fc..d2aadbb 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -389,17 +389,17 @@ create view v1 as select 1; create view v2 as select * from t1; create view v3 as select * from v2; ---error ER_WRONG_OBJECT flush table v1, v2, v3 with read lock; ---error ER_WRONG_OBJECT +unlock tables; flush table v1 with read lock; ---error ER_WRONG_OBJECT +unlock tables; flush table v2 with read lock; ---error ER_WRONG_OBJECT +unlock tables; flush table v3 with read lock; +unlock tables; create temporary table v1 (a int); ---error ER_WRONG_OBJECT flush table v1 with read lock; +unlock tables; drop view v1; create table v1 (a int); flush table v1 with read lock; @@ -701,3 +701,16 @@ disconnect con1; connection default; COMMIT; DROP TABLE t1; + +--echo # +--echo # Test for MDEV-15888 Implement FLUSH TABLES tbl_name [, tbl_name] +--echo # ... WITH READ LOCK for views +CREATE TABLE t1 (qty INT, price INT); +CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t1; +FLUSH TABLES v WITH READ LOCK; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +FLUSH TABLES t1; +UNLOCK TABLES; +DROP VIEW v; +DROP TABLE t1; + diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 272aa11..f575a7a 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -9428,6 +9428,56 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, } +void tdc_remove_share(THD *thd, TABLE_SHARE *share, + enum_tdc_remove_table_type remove_type) +{ + if (share->ref_count) + { + TABLE *table; + I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); +#ifndef DBUG_OFF + if (remove_type == TDC_RT_REMOVE_ALL) + { + DBUG_ASSERT(share->used_tables.is_empty()); + } + else if (remove_type == TDC_RT_REMOVE_NOT_OWN || + remove_type == TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE) + { + I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables); + while ((table= it2++)) + if (table->in_use != thd) + { + DBUG_ASSERT(0); + } + } +#endif + /* + Mark share to ensure that it gets automatically deleted once + it is no longer referenced. + + Note that code in TABLE_SHARE::wait_for_old_version() assumes + that marking share as old and removal of its unused tables + and of the share itself from TDC happens atomically under + protection of LOCK_open, or, putting it another way, that + TDC does not contain old shares which don't have any tables + used. + */ + if (remove_type == TDC_RT_REMOVE_NOT_OWN) + share->remove_from_cache_at_close(); + else + { + /* Ensure that no can open the table while it's used */ + share->protect_against_usage(); + } + + while ((table= it++)) + free_cache_entry(table); + } + else + (void) my_hash_delete(&table_def_cache, (uchar*) share); +} + + /** Remove all or some (depending on parameter) instances of TABLE and TABLE_SHARE from the table definition cache. @@ -9464,7 +9514,6 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, { char key[MAX_DBKEY_LENGTH]; uint key_length; - TABLE *table; TABLE_SHARE *share; DBUG_ENTER("tdc_remove_table"); DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); @@ -9484,51 +9533,7 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, key_length))) - { - if (share->ref_count) - { - I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); -#ifndef DBUG_OFF - if (remove_type == TDC_RT_REMOVE_ALL) - { - DBUG_ASSERT(share->used_tables.is_empty()); - } - else if (remove_type == TDC_RT_REMOVE_NOT_OWN || - remove_type == TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE) - { - I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables); - while ((table= it2++)) - if (table->in_use != thd) - { - DBUG_ASSERT(0); - } - } -#endif - /* - Mark share to ensure that it gets automatically deleted once - it is no longer referenced. - - Note that code in TABLE_SHARE::wait_for_old_version() assumes - that marking share as old and removal of its unused tables - and of the share itself from TDC happens atomically under - protection of LOCK_open, or, putting it another way, that - TDC does not contain old shares which don't have any tables - used. - */ - if (remove_type == TDC_RT_REMOVE_NOT_OWN) - share->remove_from_cache_at_close(); - else - { - /* Ensure that no can open the table while it's used */ - share->protect_against_usage(); - } - - while ((table= it++)) - free_cache_entry(table); - } - else - (void) my_hash_delete(&table_def_cache, (uchar*) share); - } + tdc_remove_share(thd, share, remove_type); if (! has_lock) mysql_mutex_unlock(&LOCK_open); diff --git a/sql/sql_base.h b/sql/sql_base.h index 646e391..0fde95f 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -323,6 +323,8 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connect_string); void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, ha_extra_function extra); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); +void tdc_remove_share(THD *thd, TABLE_SHARE *share, + enum_tdc_remove_table_type remove_type); void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, const char *db, const char *table_name, bool has_lock); diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 8cc9dbd..c9934af 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -28,6 +28,7 @@ #include "rpl_mi.h" // Master_info::data_lock #include "debug_sync.h" #include "des_key_file.h" +#include "sql_table.h" // build_table_filename static void disable_checkpoints(THD *thd); @@ -468,16 +469,64 @@ bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); + mysql_mutex_lock(&LOCK_open); for (table_list= all_tables; table_list; table_list= table_list->next_global) { - /* Request removal of table from cache. */ - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table_list->db, - table_list->table_name, FALSE); - /* Reset ticket to satisfy asserts in open_tables(). */ + TABLE_SHARE *share; + char key[SAFE_NAME_LEN*2+2]; + uint key_length; + my_hash_value_type hash_value; + int error; + char path[FN_REFLEN + 1]; + enum legacy_db_type not_used; + MEM_ROOT frm_mem; + + key_length= create_table_def_key(key, + table_list->db, table_list->table_name); + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, + key_length))) + { + if (!share->is_view) + { + tdc_remove_share(thd, share, TDC_RT_REMOVE_UNUSED); + goto do_continue; + } + } + else + { + (void) build_table_filename(path, sizeof(path) - 1, + table_list->db, table_list->table_name, reg_ext, 0); + + if (dd_frm_type(thd, path, ¬_used) != FRMTYPE_VIEW) + goto do_continue; + + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); + + if (!(share= get_table_share(thd, table_list, key, key_length, + OPEN_VIEW | OPEN_VIEW_ONLY, &error, hash_value))) + goto do_continue; + } + + /* Handle view. */ + DBUG_ASSERT(share->is_view); + + init_sql_alloc(&frm_mem, 8024, 0); + (void) open_new_frm(thd, share, table_list->alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + thd->open_options, 0, table_list, &frm_mem); + free_root(&frm_mem, MYF(0)); + + if (share->ref_count) /* after get_table_share() */ + release_table_share(share); + tdc_remove_share(thd, share, TDC_RT_REMOVE_UNUSED); + +do_continue: table_list->mdl_request.ticket= NULL; } + mysql_mutex_unlock(&LOCK_open); /* Before opening and locking tables the below call also waits diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index bf47153..1f884cc 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11868,7 +11868,6 @@ opt_with_read_lock: for (; tables; tables= tables->next_global) { tables->mdl_request.set_type(MDL_SHARED_NO_WRITE); - tables->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ tables->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ } }