/* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ // First include (the generated) my_config.h, to get correct platform defines. #include "my_config.h" #include #include "test_utils.h" #include "table_cache.h" #include "mysqld_thd_manager.h" #include "ha_example.h" /* We need example_hton to be able short-cut creation of example handler instances for mock TABLE objects. */ extern handlerton *example_hton; namespace table_cache_unittest { using my_testing::Server_initializer; #ifdef SAFE_MUTEX static const char *assert_string= ".*Assertion.*count > 0.*my_thread_equal.*"; #endif /** Test fixture for basic tests involving Table_cache and Table_cache_manager classes. Unlike more advanced fixture it doesn't initialize table cache manager, but only prepares THD objects necessary for testing. */ class TableCacheBasicTest : public ::testing::Test { protected: static const uint MAX_THREADS= 3; virtual void SetUp() { Global_THD_manager *thd_manager= Global_THD_manager::get_instance(); thd_manager->set_unit_test(); // Reset thread ID counter for each test. thd_manager->set_thread_id_counter(1); for (uint i= 0; i < MAX_THREADS; ++i) { initializer[i].SetUp(); } ::testing::FLAGS_gtest_death_test_style = "threadsafe"; } virtual void TearDown() { for (uint i= 0; i < MAX_THREADS; ++i) initializer[i].TearDown(); } THD *get_thd(uint index) { return initializer[index].thd(); } Server_initializer initializer[MAX_THREADS]; }; /** A more advanced fixture that also initializes table_cache_manager with one Table_cache instance and TDC. */ class TableCacheSingleCacheTest : public TableCacheBasicTest { protected: virtual uint CachesNumber() { return 1; } virtual void SetUp() { TableCacheBasicTest::SetUp(); /* In addition to table_cache_manager we want to have initialized TDC so we can use its HASH object for calculating hash values and be able to free TABLE objects correctly (we need LOCK_open initialized for this). */ table_cache_instances= CachesNumber(); table_cache_size_per_instance= 100; ASSERT_FALSE(table_def_init()); } virtual void TearDown() { table_def_free(); TableCacheBasicTest::TearDown(); } }; /** Another advanced fixture that also initializes table_cache_manager with two Table_cache instances and TDC. */ class TableCacheDoubleCacheTest : public TableCacheSingleCacheTest { protected: virtual uint CachesNumber() { return 2; } }; /** Class for mock TABLE_SHARE object which also allows to create associated TABLE objects which are usable with Table_cache. */ class Mock_share : public TABLE_SHARE { MEM_ROOT m_mem_root; Table_cache_element *cache_element_arr[Table_cache_manager::MAX_TABLE_CACHES]; public: Mock_share(const char *key) { /* Both table_cache_key and cache_element array are used by Table_cache code. */ table_cache_key.str= (char*)key; table_cache_key.length= strlen(key); memset(cache_element_arr, 0, sizeof(cache_element_arr)); cache_element= cache_element_arr; // MEM_ROOT is used for constructing ha_example() instances. init_alloc_root(PSI_NOT_INSTRUMENTED, &m_mem_root, 1024, 0); /* Assertion in some of Table_cache methods check that version of the share is up-to-date. */ version= refresh_version; // Ensure that share is never destroyed. ref_count= UINT_MAX; } ~Mock_share() { free_root(&m_mem_root, MYF(0)); } TABLE *create_table(THD *thd) { TABLE *result= (TABLE *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(TABLE), MYF(0)); ::new(result) TABLE; result->s= this; // We create TABLE which is already marked as used result->in_use= thd; /* Assertions in some of Table_cache methods need non-NULL TABLE::file and TABLE::db_stat. Code that frees unused TABLE objects needs proper "handler" instance. */ result->file= new (&m_mem_root) ha_example(example_hton, this); result->db_stat= HA_READ_ONLY; return result; } void destroy_table(TABLE *table) { my_free(table); } }; // Google Test recommends DeathTest suffix for classes used in death tests. typedef TableCacheBasicTest TableCacheBasicDeathTest; typedef TableCacheDoubleCacheTest TableCacheDoubleCacheDeathTest; /* Test initilization/destruction of Table_cache. */ TEST_F(TableCacheBasicDeathTest, CacheCreateAndDestroy) { Table_cache table_cache; ASSERT_FALSE(table_cache.init()); // Cache should be empty after creation EXPECT_EQ(0U, table_cache.cached_tables()); // Cache should be not locked after creation #ifdef SAFE_MUTEX EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(), assert_string); #endif table_cache.destroy(); } /* Test locking for Table_cache object. */ TEST_F(TableCacheBasicDeathTest, CacheLockAndUnlock) { Table_cache table_cache; ASSERT_FALSE(table_cache.init()); #ifdef SAFE_MUTEX // Cache should not be locked after creation EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(), assert_string); #endif // And get locked after we call its lock() method table_cache.lock(); table_cache.assert_owner(); // And get unlocked after we call its unlock() method table_cache.unlock(); #ifdef SAFE_MUTEX EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(), assert_string); #endif table_cache.destroy(); } /* Tests for the rest of methods of Table_cache need to use an object controlled by the global instance of Table_cache_manager. Let us start testing of Table_cache_manager with test for its initialization/destruction. This test also covers well Table_cache_manager::get_cache() method. */ TEST_F(TableCacheBasicDeathTest, ManagerCreateAndDestroy) { // Request two instances of Table_cache table_cache_instances= 2; ASSERT_FALSE(table_cache_manager.init()); // All caches are empty after creation EXPECT_EQ(0U, table_cache_manager.cached_tables()); // There should be two different caches in the manager Table_cache *cache_1, *cache_2, *cache_3; cache_1= table_cache_manager.get_cache(get_thd(0)); cache_2= table_cache_manager.get_cache(get_thd(1)); cache_3= table_cache_manager.get_cache(get_thd(2)); EXPECT_TRUE(cache_1 != cache_2); // And not three ! EXPECT_TRUE(cache_3 == cache_1); // Both caches should be empty EXPECT_EQ(0U, cache_1->cached_tables()); EXPECT_EQ(0U, cache_2->cached_tables()); // And not locked #ifdef SAFE_MUTEX EXPECT_DEATH_IF_SUPPORTED(cache_1->assert_owner(), assert_string); EXPECT_DEATH_IF_SUPPORTED(cache_2->assert_owner(), assert_string); #endif table_cache_manager.destroy(); } /* Test addition and removal of TABLE objects to/from the table cache. */ TEST_F(TableCacheSingleCacheTest, CacheAddAndRemove) { THD *thd= get_thd(0); Mock_share share_1("share_1"); TABLE *table_1= share_1.create_table(thd); Table_cache *table_cache= table_cache_manager.get_cache(thd); table_cache->lock(); EXPECT_FALSE(table_cache->add_used_table(thd, table_1)); // There should be one TABLE in the cache after we have added table_1. EXPECT_EQ(1U, table_cache->cached_tables()); // There should be no unused TABLE objects for the same table in the // cache. OTOH it should contain info about table share of table_1. my_hash_value_type hash_value= my_calc_hash(&table_def_cache, (uchar*)share_1.table_cache_key.str, share_1.table_cache_key.length); TABLE *table_2; TABLE_SHARE *share_2; table_2= table_cache->get_table(thd, hash_value, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == NULL); EXPECT_TRUE(share_2 == &share_1); // Table_cache_iterator should be able to find only one TABLE instance // in all caches. And this instance should be table_1. Table_cache_iterator it(&share_1); EXPECT_TRUE(it++ == table_1); EXPECT_TRUE(it++ == NULL); // We must be able to release TABLE into table cache and reuse it after // this. table_cache->release_table(thd, table_1); table_2= table_cache->get_table(thd, hash_value, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == table_1); EXPECT_TRUE(share_2 == &share_1); table_cache->remove_table(table_1); // Once TABLE is removed from the cache the latter should become empty. EXPECT_EQ(0U, table_cache->cached_tables()); table_2= table_cache->get_table(thd, hash_value, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == NULL); EXPECT_TRUE(share_2 == NULL); it.rewind(); EXPECT_TRUE(it++ == NULL); // Also it should be possible to remove unused TABLE from the cache // Add TABLE instance and mark it as unused EXPECT_FALSE(table_cache->add_used_table(thd, table_1)); table_cache->release_table(thd, table_1); table_cache->remove_table(table_1); // Once TABLE is removed from cache the latter should become empty. EXPECT_EQ(0U, table_cache->cached_tables()); table_2= table_cache->get_table(thd, hash_value, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == NULL); EXPECT_TRUE(share_2 == NULL); table_cache->unlock(); share_1.destroy_table(table_1); } /* Now let us test how Table_cache handles overflows. */ TEST_F(TableCacheSingleCacheTest, CacheOverflow) { THD *thd= get_thd(0); // Set cache size low so it will overflow quickly. table_cache_size_per_instance= 2; Mock_share share_1("share_1"); Mock_share share_2("share_2"); TABLE *table_1= share_1.create_table(thd); TABLE *table_2= share_1.create_table(thd); TABLE *table_3= share_2.create_table(thd); Table_cache *table_cache= table_cache_manager.get_cache(thd); table_cache->lock(); table_cache->add_used_table(thd, table_1); table_cache->add_used_table(thd, table_2); // There should be two TABLE instances in the cache. EXPECT_EQ(2U, table_cache->cached_tables()); table_cache->release_table(thd, table_1); table_cache->release_table(thd, table_2); // Still there should be two TABLE instances in the cache. EXPECT_EQ(2U, table_cache->cached_tables()); table_cache->add_used_table(thd, table_3); // One TABLE was added and one expelled (table_1), so still two TABLE objects. EXPECT_EQ(2U, table_cache->cached_tables()); // Old value of table_1 points to garbage thanks to expelling table_1= share_1.create_table(thd); table_cache->add_used_table(thd, table_1); // Still two TABLE instances (table_2 was expelled). EXPECT_EQ(2U, table_cache->cached_tables()); // Old value of table_2 points to garbage thanks to expelling table_2= share_1.create_table(thd); table_cache->add_used_table(thd, table_2); /* Now we should have three TABLE instances in cache since all of them are used. */ EXPECT_EQ(3U, table_cache->cached_tables()); table_cache->release_table(thd, table_2); // The first table that gets released is expelled. EXPECT_EQ(2U, table_cache->cached_tables()); table_cache->remove_table(table_1); table_cache->remove_table(table_3); // Cache should be empty after that EXPECT_EQ(0U, table_cache->cached_tables()); table_cache->unlock(); share_1.destroy_table(table_1); share_1.destroy_table(table_3); } TEST_F(TableCacheSingleCacheTest, CacheGetAndRelease) { THD *thd= get_thd(0); Table_cache *table_cache= table_cache_manager.get_cache(thd); table_cache->lock(); TABLE *table_1, *table_2, *table_3; Mock_share share_1("share_1"), share_0("share_0"); TABLE_SHARE *share_2; // There should be no TABLE in cache, nor information about share. my_hash_value_type hash_value_1= my_calc_hash(&table_def_cache, (uchar*)share_1.table_cache_key.str, share_1.table_cache_key.length); table_1= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_1 == NULL); EXPECT_TRUE(share_2 == NULL); table_1= share_1.create_table(thd); table_cache->add_used_table(thd, table_1); // There should be no unused TABLE in cache, but there should be // information about the share. table_2= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == NULL); EXPECT_TRUE(share_2 == &share_1); // There should be even no information about the share for which // TABLE was not added to cache. my_hash_value_type hash_value_0= my_calc_hash(&table_def_cache, (uchar*)share_0.table_cache_key.str, share_0.table_cache_key.length); table_2= table_cache->get_table(thd, hash_value_0, share_0.table_cache_key.str, share_0.table_cache_key.length, &share_2); EXPECT_TRUE(table_2 == NULL); EXPECT_TRUE(share_2 == NULL); table_2= share_1.create_table(thd); table_cache->add_used_table(thd, table_2); // Still there should be no unused TABLE in cache, but there should // be information about the share. table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 == NULL); EXPECT_TRUE(share_2 == &share_1); table_cache->release_table(thd, table_1); // After releasing one of TABLE objects it should be possible to get // unused TABLE from cache. table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 == table_1); EXPECT_TRUE(share_2 == &share_1); // But only once! table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 == NULL); EXPECT_TRUE(share_2 == &share_1); // After releasing of both TABLE objects it should be possible to // get two unused TABLE objects from cache (for 'share_1'). // There should be nothing for 'share_0'. table_cache->release_table(thd, table_1); table_cache->release_table(thd, table_2); table_3= table_cache->get_table(thd, hash_value_0, share_0.table_cache_key.str, share_0.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 == NULL); EXPECT_TRUE(share_2 == NULL); table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 != NULL); EXPECT_TRUE(share_2 == &share_1); table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 != NULL); EXPECT_TRUE(share_2 == &share_1); table_3= table_cache->get_table(thd, hash_value_1, share_1.table_cache_key.str, share_1.table_cache_key.length, &share_2); EXPECT_TRUE(table_3 == NULL); EXPECT_TRUE(share_2 == &share_1); // Clean-up table_cache->remove_table(table_1); table_cache->remove_table(table_2); share_1.destroy_table(table_1); share_1.destroy_table(table_2); table_cache->unlock(); } /* Test for Table_cache_manager/Table_cache::free_all_unused_tables(). */ TEST_F(TableCacheDoubleCacheTest, ManagerFreeAllUnused) { THD *thd_1= get_thd(0); THD *thd_2= get_thd(1); Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1); Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2); // There should be no TABLE instances in all cachea. EXPECT_EQ(0U, table_cache_manager.cached_tables()); Mock_share share_1("share_1"); Mock_share share_2("share_2"); Mock_share share_3("share_2"); TABLE *table_1= share_1.create_table(thd_1); TABLE *table_2= share_1.create_table(thd_1); TABLE *table_3= share_2.create_table(thd_1); TABLE *table_4= share_2.create_table(thd_1); TABLE *table_5= share_1.create_table(thd_2); TABLE *table_6= share_3.create_table(thd_2); table_cache_manager.lock_all_and_tdc(); table_cache_1->add_used_table(thd_1, table_1); table_cache_1->add_used_table(thd_1, table_2); table_cache_1->add_used_table(thd_1, table_3); table_cache_1->add_used_table(thd_1, table_4); table_cache_2->add_used_table(thd_2, table_5); table_cache_2->add_used_table(thd_2, table_6); EXPECT_EQ(4U, table_cache_1->cached_tables()); EXPECT_EQ(2U, table_cache_2->cached_tables()); EXPECT_EQ(6U, table_cache_manager.cached_tables()); table_cache_manager.free_all_unused_tables(); // All TABLE instances should stay around in caches as // all of them are used. EXPECT_EQ(4U, table_cache_1->cached_tables()); EXPECT_EQ(2U, table_cache_2->cached_tables()); EXPECT_EQ(6U, table_cache_manager.cached_tables()); table_cache_1->release_table(thd_1, table_1); table_cache_manager.free_all_unused_tables(); // One table should be freed. So there should be 3 + 2 TABLE instances. EXPECT_EQ(3U, table_cache_1->cached_tables()); EXPECT_EQ(2U, table_cache_2->cached_tables()); EXPECT_EQ(5U, table_cache_manager.cached_tables()); table_cache_1->release_table(thd_1, table_2); table_cache_1->release_table(thd_1, table_3); table_cache_2->release_table(thd_2, table_5); table_cache_manager.free_all_unused_tables(); // Now there should be 1 + 1 used TABLE instances left. EXPECT_EQ(1U, table_cache_1->cached_tables()); EXPECT_EQ(1U, table_cache_2->cached_tables()); EXPECT_EQ(2U, table_cache_manager.cached_tables()); table_cache_1->release_table(thd_1, table_4); table_cache_manager.free_all_unused_tables(); // There should be 0 + 1 TABLE instances around. EXPECT_EQ(0U, table_cache_1->cached_tables()); EXPECT_EQ(1U, table_cache_2->cached_tables()); EXPECT_EQ(1U, table_cache_manager.cached_tables()); table_cache_2->release_table(thd_2, table_6); table_cache_manager.free_all_unused_tables(); // All caches should become empty. EXPECT_EQ(0U, table_cache_1->cached_tables()); EXPECT_EQ(0U, table_cache_2->cached_tables()); EXPECT_EQ(0U, table_cache_manager.cached_tables()); table_cache_manager.unlock_all_and_tdc(); } /* Test for Table_cache_manager/Table_cache::cached_tables(). */ TEST_F(TableCacheDoubleCacheTest, ManagerCachedTables) { THD *thd_1= get_thd(0); THD *thd_2= get_thd(1); Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1); Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2); // There should be no TABLE instances in all cachea. EXPECT_EQ(0U, table_cache_1->cached_tables()); EXPECT_EQ(0U, table_cache_2->cached_tables()); EXPECT_EQ(0U, table_cache_manager.cached_tables()); Mock_share share_1("share_1"); Mock_share share_2("share_2"); TABLE *table_1= share_1.create_table(thd_1); TABLE *table_2= share_1.create_table(thd_1); TABLE *table_3= share_2.create_table(thd_1); TABLE *table_4= share_1.create_table(thd_2); TABLE *table_5= share_2.create_table(thd_2); table_cache_manager.lock_all_and_tdc(); table_cache_1->add_used_table(thd_1, table_1); table_cache_1->add_used_table(thd_1, table_2); table_cache_1->add_used_table(thd_1, table_3); // There should be 3 + 0 TABLE objects in cache EXPECT_EQ(3U, table_cache_1->cached_tables()); EXPECT_EQ(0U, table_cache_2->cached_tables()); EXPECT_EQ(3U, table_cache_manager.cached_tables()); table_cache_2->add_used_table(thd_2, table_4); table_cache_2->add_used_table(thd_2, table_5); // There should be 3 + 2 TABLE objects in cache EXPECT_EQ(3U, table_cache_1->cached_tables()); EXPECT_EQ(2U, table_cache_2->cached_tables()); EXPECT_EQ(5U, table_cache_manager.cached_tables()); table_cache_1->release_table(thd_1, table_1); table_cache_2->release_table(thd_2, table_4); // There should be the same number of TABLE objects - 3 + 2 EXPECT_EQ(3U, table_cache_1->cached_tables()); EXPECT_EQ(2U, table_cache_2->cached_tables()); EXPECT_EQ(5U, table_cache_manager.cached_tables()); table_cache_2->remove_table(table_5); // There should be 3 + 1 TABLE objects in cache EXPECT_EQ(3U, table_cache_1->cached_tables()); EXPECT_EQ(1U, table_cache_2->cached_tables()); EXPECT_EQ(4U, table_cache_manager.cached_tables()); table_cache_1->remove_table(table_1); table_cache_2->remove_table(table_4); // There should be 2 + 0 TABLE objects in cache EXPECT_EQ(2U, table_cache_1->cached_tables()); EXPECT_EQ(0U, table_cache_2->cached_tables()); EXPECT_EQ(2U, table_cache_manager.cached_tables()); table_cache_1->remove_table(table_2); table_cache_1->remove_table(table_3); // Caches should be empty EXPECT_EQ(0U, table_cache_1->cached_tables()); EXPECT_EQ(0U, table_cache_2->cached_tables()); EXPECT_EQ(0U, table_cache_manager.cached_tables()); table_cache_manager.unlock_all_and_tdc(); share_1.destroy_table(table_1); share_1.destroy_table(table_2); share_2.destroy_table(table_3); share_1.destroy_table(table_4); share_2.destroy_table(table_5); } /* Coverage for lock and unlock methods of Table_cache_manager class. */ TEST_F(TableCacheDoubleCacheDeathTest, ManagerLockAndUnlock) { // Nor caches nor LOCK_open should not be locked after initialization #ifdef SAFE_MUTEX EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all(), assert_string); EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all_and_tdc(), assert_string); #endif // And get locked after we call its lock_all_and_tdc() method. table_cache_manager.lock_all_and_tdc(); table_cache_manager.assert_owner_all(); table_cache_manager.assert_owner_all_and_tdc(); // In addition to Table_cache_manager method we check this by // calling Table_cache methods and asserting state of LOCK_open. Table_cache *cache_1= table_cache_manager.get_cache(get_thd(0)); Table_cache *cache_2= table_cache_manager.get_cache(get_thd(1)); cache_1->assert_owner(); cache_2->assert_owner(); mysql_mutex_assert_owner(&LOCK_open); // Locks should be unlocked after we call unlock method table_cache_manager.unlock_all_and_tdc(); #ifdef SAFE_MUTEX EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all(), assert_string); EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all_and_tdc(), assert_string); #endif } /* Coverage for Table_cache_manager::free_table(); */ TEST_F(TableCacheDoubleCacheDeathTest, ManagerFreeTable) { THD *thd_1= get_thd(0); THD *thd_2= get_thd(1); Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1); Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2); Mock_share share_1("share_1"); Mock_share share_2("share_2"); TABLE *table_1= share_1.create_table(thd_1); TABLE *table_2= share_1.create_table(thd_1); TABLE *table_3= share_2.create_table(thd_1); TABLE *table_4= share_1.create_table(thd_2); TABLE *table_5= share_2.create_table(thd_2); table_cache_manager.lock_all_and_tdc(); /* Coverage for TDC_RT_REMOVE_ALL case. */ table_cache_1->add_used_table(thd_1, table_1); table_cache_1->add_used_table(thd_1, table_2); table_cache_1->release_table(thd_1, table_2); table_cache_1->add_used_table(thd_1, table_3); table_cache_2->add_used_table(thd_2, table_4); table_cache_2->add_used_table(thd_2, table_5); EXPECT_EQ(5U, table_cache_manager.cached_tables()); // There should be assert failure since we are trying // to free all tables for share_1, while some tables // are in use. #ifndef DBUG_OFF EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_ALL, &share_1), ".*Assertion.*is_empty.*"); #endif table_cache_1->release_table(thd_1, table_1); table_cache_2->release_table(thd_2, table_4); // After all tables for share_1 marked as unused freeing // all tables should succeed. table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_ALL, &share_1); // We still should have 2 TABLE objects for share_2. EXPECT_EQ(2U, table_cache_manager.cached_tables()); /* Coverage for TDC_RT_REMOVE_NOT_OWN case. */ table_1= share_1.create_table(thd_1); table_2= share_1.create_table(thd_1); table_4= share_1.create_table(thd_2); table_cache_1->add_used_table(thd_1, table_1); table_cache_1->add_used_table(thd_1, table_2); table_cache_1->release_table(thd_1, table_2); table_cache_2->add_used_table(thd_2, table_4); EXPECT_EQ(5U, table_cache_manager.cached_tables()); // There should be assert failure since we are trying // to free all not own TABLEs for share_1, while thd_2 // has a TABLE object for it in used #ifndef DBUG_OFF EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_NOT_OWN, &share_1), ".*Assertion.*0.*"); #endif table_cache_2->release_table(thd_2, table_4); // After TABLE owned by thd_2 is marked as unused, the below // call should succeed. table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_NOT_OWN, &share_1); // We still have 1 TABLE object for share_1 in thd_1 and // 2 TABLE objects for share_2. EXPECT_EQ(3U, table_cache_manager.cached_tables()); /* Coverage for TDC_RT_REMOVE_UNUSED case. */ table_2= share_1.create_table(thd_1); table_4= share_1.create_table(thd_2); table_cache_1->add_used_table(thd_1, table_2); table_cache_1->release_table(thd_1, table_2); table_cache_2->add_used_table(thd_2, table_4); EXPECT_EQ(5U, table_cache_manager.cached_tables()); table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_UNUSED, &share_1); // The above call should have been freed only 1 table. EXPECT_EQ(4U, table_cache_manager.cached_tables()); // Mark all remaining TABLE objects for share_1 as unused table_cache_1->release_table(thd_1, table_1); table_cache_2->release_table(thd_2, table_4); table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_UNUSED, &share_1); // The above call should free all unused TABLE objects for share_1. // Therefore only 2 objects for share_2 should be remaining EXPECT_EQ(2U, table_cache_manager.cached_tables()); // Clean-up. table_cache_1->remove_table(table_3); table_cache_2->remove_table(table_5); share_2.destroy_table(table_3); share_2.destroy_table(table_5); table_cache_manager.unlock_all_and_tdc(); } /* Coverage for Table_cache_iterator */ TEST_F(TableCacheDoubleCacheTest, Iterator) { THD *thd_1= get_thd(0); THD *thd_2= get_thd(1); table_cache_manager.lock_all_and_tdc(); Mock_share share_1("share_1"); Mock_share share_2("share_2"); // There is no TABLE objects for share_1 so the below iterator // should not find anything. Table_cache_iterator it(&share_1); EXPECT_TRUE(it++ == NULL); // Attempt to iterate behind the end should not give anything. EXPECT_TRUE(it++ == NULL); Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1); Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2); TABLE *table_1= share_1.create_table(thd_1); TABLE *table_2= share_1.create_table(thd_1); TABLE *table_3= share_2.create_table(thd_1); TABLE *table_4= share_1.create_table(thd_2); TABLE *table_5= share_2.create_table(thd_2); table_cache_2->add_used_table(thd_2, table_4); // Now the iterato should see table_4. it.rewind(); TABLE *table_r1= it++; EXPECT_TRUE(table_r1 == table_4); // But only it. EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->add_used_table(thd_1, table_1); // Now we should see two tables: it.rewind(); table_r1= it++; EXPECT_TRUE(table_r1 != NULL); TABLE *table_r2= it++; EXPECT_TRUE(table_r2 != NULL); EXPECT_TRUE(table_r1 != table_r2); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->add_used_table(thd_1, table_2); // And now three ! it.rewind(); table_r1= it++; EXPECT_TRUE(table_r1 != NULL); table_r2= it++; EXPECT_TRUE(table_r2 != NULL); TABLE *table_r3= it++; EXPECT_TRUE(table_r3 != NULL); EXPECT_TRUE(table_r1 != table_r2 && table_r1 != table_r3 && table_r2 != table_r3); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->release_table(thd_1, table_1); // We should be seeing only used TABLE objects, so two tables now it.rewind(); table_r1= it++; EXPECT_TRUE(table_r1 != NULL); table_r2= it++; EXPECT_TRUE(table_r2 != NULL); EXPECT_TRUE(table_r1 != table_r2); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->add_used_table(thd_1, table_3); table_cache_2->add_used_table(thd_2, table_5); // We also should not be seeing TABLE objects for share_2 it.rewind(); table_r1= it++; EXPECT_TRUE(table_r1 != NULL); table_r2= it++; EXPECT_TRUE(table_r2 != NULL); EXPECT_TRUE(table_r1 != table_r2); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->remove_table(table_2); // Now we should se only one used TABLE it.rewind(); table_r1= it++; EXPECT_TRUE(table_r1 == table_4); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->remove_table(table_4); // And now no used TABLE objects for share_1 at all it.rewind(); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->remove_table(table_1); // Still the same it.rewind(); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_1->remove_table(table_3); table_cache_2->remove_table(table_5); // Cache is empty so iterator should not show any TABLE objects. it.rewind(); EXPECT_TRUE(it++ == NULL); EXPECT_TRUE(it++ == NULL); table_cache_manager.unlock_all_and_tdc(); share_1.destroy_table(table_1); share_1.destroy_table(table_2); share_2.destroy_table(table_3); share_1.destroy_table(table_4); share_2.destroy_table(table_5); } }