/* Copyright (c) 2011, 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, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "my_inttypes.h" #include "my_macros.h" #include "my_table_map.h" #include "sql/current_thd.h" #include "sql/mem_root_array.h" #include "sql/mysqld.h" // THR_MALLOC #include "sql/sql_optimizer.h" // Key_use_array /** WL#5774 Decrease number of malloc's for normal DML queries. One of the malloc's was due to DYNAMIC_ARRAY keyuse; We replace the DYNAMIC_ARRAY with a std::vector-like class Mem_root_array. Below are unit tests for comparing performance, and for testing functionality of Mem_root_array. */ namespace dynarray_unittest { // We generate some random data at startup, for testing of sorting. void generate_test_data(Key_use *keys, TABLE_LIST *tables, int n) { int ix; for (ix = 0; ix < n; ++ix) { tables[ix].set_tableno(ix % 3); keys[ix] = Key_use(&tables[ix], NULL, // Item *val 0, // table_map used_tables ix % 4, // uint key ix % 2, // uint keypart 0, // uint optimize 0, // keypart_map 0, // ha_rows ref_table_rows true, // bool null_rejecting NULL, // bool *cond_guard 0 // uint sj_pred_no ); } std::random_shuffle(&keys[0], &keys[n]); } constexpr int num_elements = 200; /* This class is for unit testing of Mem_root_array. */ class MemRootTest : public ::testing::Test { protected: MemRootTest() : m_mem_root_p(&m_mem_root), m_array_std(m_mem_root_p) {} virtual void SetUp() { init_sql_alloc(PSI_NOT_INSTRUMENTED, &m_mem_root, 1024, 0); THR_MALLOC = &m_mem_root_p; m_array_std.reserve(num_elements); destroy_counter = 0; } virtual void TearDown() { free_root(&m_mem_root, MYF(0)); } static void SetUpTestCase() { generate_test_data(test_data, table_list, num_elements); THR_MALLOC = nullptr; } static void TearDownTestCase() { THR_MALLOC = nullptr; } MEM_ROOT m_mem_root; MEM_ROOT *m_mem_root_p; Key_use_array m_array_std; public: static size_t destroy_counter; private: static Key_use test_data[num_elements]; static TABLE_LIST table_list[num_elements]; GTEST_DISALLOW_COPY_AND_ASSIGN_(MemRootTest); }; size_t MemRootTest::destroy_counter; Key_use MemRootTest::test_data[num_elements]; TABLE_LIST MemRootTest::table_list[num_elements]; // Test that Mem_root_array re-expanding works. TEST_F(MemRootTest, Reserve) { Mem_root_array intarr(m_mem_root_p); intarr.reserve(2); const uint num_pushes = 20; for (uint ix = 0; ix < num_pushes; ++ix) { EXPECT_EQ(ix, intarr.size()); EXPECT_FALSE(intarr.push_back(ix)); EXPECT_EQ(ix, intarr.at(ix)); } for (uint ix = 0; ix < num_pushes; ++ix) { EXPECT_EQ(ix, intarr.at(ix)); } EXPECT_EQ(sizeof(uint), intarr.element_size()); EXPECT_EQ(num_pushes, intarr.size()); EXPECT_LE(num_pushes, intarr.capacity()); } // Verify that we can move MEM_ROOT without any leaks. // Run with // valgrind --leak-check=full --gtest_filter='-*DeathTest*' > foo TEST_F(MemRootTest, MoveMemRoot) { Mem_root_array intarr(m_mem_root_p); MEM_ROOT own_root = std::move(*m_mem_root_p); intarr.set_mem_root(&own_root); intarr.push_back(42); *m_mem_root_p = std::move(own_root); } class DestroyCounter { public: DestroyCounter() : p_counter(&MemRootTest::destroy_counter) {} DestroyCounter(const DestroyCounter &rhs) : p_counter(rhs.p_counter) {} explicit DestroyCounter(size_t *p) : p_counter(p) {} DestroyCounter &operator=(const DestroyCounter &) = default; ~DestroyCounter() { (*p_counter) += 1; } private: size_t *p_counter; }; // Test chop() and clear() and that destructors are executed. TEST_F(MemRootTest, ChopAndClear) { Mem_root_array array(m_mem_root_p); const size_t nn = 4; array.reserve(nn); size_t counter = 0; DestroyCounter foo(&counter); for (size_t ix = 0; ix < array.capacity(); ++ix) array.push_back(foo); EXPECT_EQ(0U, counter); array.chop(nn / 2); EXPECT_EQ(nn / 2, counter); EXPECT_EQ(nn / 2, array.size()); array.clear(); EXPECT_EQ(nn, counter); } // Test that elements are destroyed if push_back() needs to call reserve(). TEST_F(MemRootTest, ReserveDestroy) { Mem_root_array array(m_mem_root_p); const size_t nn = 4; array.reserve(nn / 2); size_t counter = 0; DestroyCounter foo(&counter); for (size_t ix = 0; ix < nn; ++ix) array.push_back(foo); EXPECT_EQ(nn / 2, counter); EXPECT_EQ(nn, array.size()); counter = 0; array.clear(); EXPECT_EQ(nn, counter); } TEST_F(MemRootTest, ResizeSame) { Mem_root_array array(m_mem_root_p); array.reserve(100); size_t counter = 0; DestroyCounter foo(&counter); for (int ix = 0; ix < 10; ++ix) array.push_back(foo); EXPECT_EQ(10U, array.size()); array.resize(10U); EXPECT_EQ(10U, array.size()); array.clear(); EXPECT_EQ(10U, counter); } TEST_F(MemRootTest, ResizeGrow) { Mem_root_array array(m_mem_root_p); array.reserve(100); size_t counter = 0; DestroyCounter foo(&counter); array.resize(10, foo); EXPECT_EQ(0U, counter); array.clear(); EXPECT_EQ(0U, MemRootTest::destroy_counter); EXPECT_EQ(10U, counter); } TEST_F(MemRootTest, ResizeShrink) { size_t counter = 0; Mem_root_array array(m_mem_root_p); array.reserve(100); DestroyCounter foo(&counter); array.resize(10, foo); EXPECT_EQ(0U, counter); array.resize(5); EXPECT_EQ(5U, counter); } TEST_F(MemRootTest, Erase) { using A = Mem_root_array; size_t counter = 0; DestroyCounter foo(&counter); A array(m_mem_root_p); array.resize(10, foo); EXPECT_EQ(10U, array.size()); EXPECT_EQ(0U, counter); A::iterator it = array.erase(array.cbegin() + 2, array.cbegin() + 4); EXPECT_EQ(8U, array.size()); EXPECT_EQ(array.begin() + 2, it); EXPECT_EQ(2U, counter); it = array.erase(array.cend(), array.cend()); EXPECT_EQ(8U, array.size()); EXPECT_EQ(array.cend(), it); EXPECT_EQ(2U, counter); it = array.erase(array.cbegin(), array.cbegin()); EXPECT_EQ(8U, array.size()); EXPECT_EQ(array.cbegin(), it); EXPECT_EQ(2U, counter); it = array.erase(array.cbegin(), array.cend()); EXPECT_EQ(0U, array.size()); EXPECT_EQ(array.cbegin(), it); EXPECT_EQ(array.cend(), it); EXPECT_EQ(10U, counter); } TEST_F(MemRootTest, Erase2) { using A = Mem_root_array; size_t counter = 0; DestroyCounter foo(&counter); A array(m_mem_root_p); array.resize(10, foo); EXPECT_EQ(10U, array.size()); EXPECT_EQ(0U, counter); A::iterator it = array.erase(5); EXPECT_EQ(9U, array.size()); EXPECT_EQ(std::next(array.cbegin(), 5), it); EXPECT_EQ(1U, counter); it = array.erase(static_cast(0)); EXPECT_EQ(8U, array.size()); EXPECT_EQ(array.cbegin(), it); EXPECT_EQ(2U, counter); it = array.erase(7); EXPECT_EQ(7U, array.size()); EXPECT_EQ(array.cend(), it); EXPECT_EQ(3U, counter); } TEST_F(MemRootTest, Insert) { using A = Mem_root_array; A array(m_mem_root_p); A::iterator it = array.insert(array.cbegin(), 1); EXPECT_EQ(array.cbegin(), it); it = array.insert(array.cbegin(), 2); EXPECT_EQ(array.cbegin(), it); it = array.insert(array.cbegin() + 1, 3); EXPECT_EQ(array.cbegin() + 1, it); it = array.insert(array.cend(), 4); EXPECT_EQ(array.cend() - 1, it); EXPECT_EQ(4U, array.size()); EXPECT_EQ(2, array[0]); EXPECT_EQ(3, array[1]); EXPECT_EQ(1, array[2]); EXPECT_EQ(4, array[3]); } } // namespace dynarray_unittest