339 lines
11 KiB
C++
339 lines
11 KiB
C++
/* Copyright (c) 2018, 2019, 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 "storage/secondary_engine_mock/ha_mock.h"
|
|
|
|
#include <stddef.h>
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "lex_string.h"
|
|
#include "my_alloc.h"
|
|
#include "my_compiler.h"
|
|
#include "my_dbug.h"
|
|
#include "my_inttypes.h"
|
|
#include "my_sys.h"
|
|
#include "mysql/plugin.h"
|
|
#include "mysqld_error.h"
|
|
#include "sql/debug_sync.h"
|
|
#include "sql/handler.h"
|
|
#include "sql/sql_class.h"
|
|
#include "sql/sql_const.h"
|
|
#include "sql/sql_lex.h"
|
|
#include "sql/sql_optimizer.h"
|
|
#include "sql/table.h"
|
|
#include "template_utils.h"
|
|
#include "thr_lock.h"
|
|
|
|
namespace dd {
|
|
class Table;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct MockShare {
|
|
THR_LOCK lock;
|
|
MockShare() { thr_lock_init(&lock); }
|
|
~MockShare() { thr_lock_delete(&lock); }
|
|
|
|
// Not copyable. The THR_LOCK object must stay where it is in memory
|
|
// after it has been initialized.
|
|
MockShare(const MockShare &) = delete;
|
|
MockShare &operator=(const MockShare &) = delete;
|
|
};
|
|
|
|
// Map from (db_name, table_name) to the MockShare with table state.
|
|
class LoadedTables {
|
|
std::map<std::pair<std::string, std::string>, MockShare> m_tables;
|
|
std::mutex m_mutex;
|
|
|
|
public:
|
|
void add(const std::string &db, const std::string &table) {
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
m_tables.emplace(std::piecewise_construct, std::make_tuple(db, table),
|
|
std::make_tuple());
|
|
}
|
|
|
|
MockShare *get(const std::string &db, const std::string &table) {
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
auto it = m_tables.find(std::make_pair(db, table));
|
|
return it == m_tables.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
void erase(const std::string &db, const std::string &table) {
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
m_tables.erase(std::make_pair(db, table));
|
|
}
|
|
};
|
|
|
|
LoadedTables *loaded_tables{nullptr};
|
|
|
|
/**
|
|
Execution context class for the MOCK engine. It allocates some data
|
|
on the heap when it is constructed, and frees it when it is
|
|
destructed, so that LeakSanitizer and Valgrind can detect if the
|
|
server doesn't destroy the object when the query execution has
|
|
completed.
|
|
*/
|
|
class Mock_execution_context : public Secondary_engine_execution_context {
|
|
public:
|
|
Mock_execution_context() : m_data(std::make_unique<char[]>(10)) {}
|
|
/**
|
|
Checks if the specified cost is the lowest cost seen so far for executing
|
|
the given JOIN.
|
|
*/
|
|
bool BestPlanSoFar(const JOIN &join, double cost) {
|
|
if (&join != m_current_join) {
|
|
// No plan has been seen for this join. The current one is best so far.
|
|
m_current_join = &join;
|
|
m_best_cost = cost;
|
|
return true;
|
|
}
|
|
|
|
// Check if the current plan is the best seen so far.
|
|
const bool cheaper = cost < m_best_cost;
|
|
m_best_cost = std::min(m_best_cost, cost);
|
|
return cheaper;
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<char[]> m_data;
|
|
/// The JOIN currently being optimized.
|
|
const JOIN *m_current_join{nullptr};
|
|
/// The cost of the best plan seen so far for the current JOIN.
|
|
double m_best_cost;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
namespace mock {
|
|
|
|
ha_mock::ha_mock(handlerton *hton, TABLE_SHARE *table_share_arg)
|
|
: handler(hton, table_share_arg) {}
|
|
|
|
int ha_mock::open(const char *, int, unsigned int, const dd::Table *) {
|
|
MockShare *share =
|
|
loaded_tables->get(table_share->db.str, table_share->table_name.str);
|
|
if (share == nullptr) {
|
|
// The table has not been loaded into the secondary storage engine yet.
|
|
my_error(ER_SECONDARY_ENGINE_PLUGIN, MYF(0), "Table has not been loaded");
|
|
return HA_ERR_GENERIC;
|
|
}
|
|
thr_lock_data_init(&share->lock, &m_lock, nullptr);
|
|
return 0;
|
|
}
|
|
|
|
int ha_mock::info(unsigned int flags) {
|
|
// Get the cardinality statistics from the primary storage engine.
|
|
handler *primary = ha_get_primary_handler();
|
|
int ret = primary->info(flags);
|
|
if (ret == 0) {
|
|
stats.records = primary->stats.records;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
handler::Table_flags ha_mock::table_flags() const {
|
|
// Secondary engines do not support index access. Indexes are only used for
|
|
// cost estimates.
|
|
return HA_NO_INDEX_ACCESS;
|
|
}
|
|
|
|
unsigned long ha_mock::index_flags(unsigned int idx, unsigned int part,
|
|
bool all_parts) const {
|
|
const handler *primary = ha_get_primary_handler();
|
|
const unsigned long primary_flags =
|
|
primary == nullptr ? 0 : primary->index_flags(idx, part, all_parts);
|
|
|
|
// Inherit the following index flags from the primary handler, if they are
|
|
// set:
|
|
//
|
|
// HA_READ_RANGE - to signal that ranges can be read from the index, so that
|
|
// the optimizer can use the index to estimate the number of rows in a range.
|
|
//
|
|
// HA_KEY_SCAN_NOT_ROR - to signal if the index returns records in rowid
|
|
// order. Used to disable use of the index in the range optimizer if it is not
|
|
// in rowid order.
|
|
return ((HA_READ_RANGE | HA_KEY_SCAN_NOT_ROR) & primary_flags);
|
|
}
|
|
|
|
ha_rows ha_mock::records_in_range(unsigned int index, key_range *min_key,
|
|
key_range *max_key) {
|
|
// Get the number of records in the range from the primary storage engine.
|
|
return ha_get_primary_handler()->records_in_range(index, min_key, max_key);
|
|
}
|
|
|
|
THR_LOCK_DATA **ha_mock::store_lock(THD *, THR_LOCK_DATA **to,
|
|
thr_lock_type lock_type) {
|
|
if (lock_type != TL_IGNORE && m_lock.type == TL_UNLOCK)
|
|
m_lock.type = lock_type;
|
|
*to++ = &m_lock;
|
|
return to;
|
|
}
|
|
|
|
int ha_mock::prepare_load_table(const TABLE &table_arg) {
|
|
loaded_tables->add(table_arg.s->db.str, table_arg.s->table_name.str);
|
|
return 0;
|
|
}
|
|
|
|
int ha_mock::load_table(const TABLE &table_arg) {
|
|
DBUG_ASSERT(table_arg.file != nullptr);
|
|
if (loaded_tables->get(table_arg.s->db.str, table_arg.s->table_name.str) ==
|
|
nullptr) {
|
|
my_error(ER_NO_SUCH_TABLE, MYF(0), table_arg.s->db.str,
|
|
table_arg.s->table_name.str);
|
|
return HA_ERR_KEY_NOT_FOUND;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ha_mock::unload_table(const char *db_name, const char *table_name,
|
|
bool error_if_not_loaded) {
|
|
if (error_if_not_loaded &&
|
|
loaded_tables->get(db_name, table_name) == nullptr) {
|
|
my_error(ER_SECONDARY_ENGINE_PLUGIN, MYF(0),
|
|
"Table is not loaded on a secondary engine");
|
|
return 1;
|
|
} else {
|
|
loaded_tables->erase(db_name, table_name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} // namespace mock
|
|
|
|
static bool PrepareSecondaryEngine(THD *thd, LEX *lex) {
|
|
DBUG_EXECUTE_IF("secondary_engine_mock_prepare_error", {
|
|
my_error(ER_SECONDARY_ENGINE_PLUGIN, MYF(0), "");
|
|
return true;
|
|
});
|
|
|
|
auto context = new (thd->mem_root) Mock_execution_context;
|
|
if (context == nullptr) return true;
|
|
lex->set_secondary_engine_execution_context(context);
|
|
|
|
// Disable use of constant tables and evaluation of subqueries during
|
|
// optimization.
|
|
lex->add_statement_options(OPTION_NO_CONST_TABLES |
|
|
OPTION_NO_SUBQUERY_DURING_OPTIMIZATION);
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool OptimizeSecondaryEngine(THD *thd MY_ATTRIBUTE((unused)),
|
|
LEX *lex MY_ATTRIBUTE((unused))) {
|
|
// The context should have been set by PrepareSecondaryEngine.
|
|
DBUG_ASSERT(lex->secondary_engine_execution_context() != nullptr);
|
|
|
|
DBUG_EXECUTE_IF("secondary_engine_mock_optimize_error", {
|
|
my_error(ER_SECONDARY_ENGINE_PLUGIN, MYF(0), "");
|
|
return true;
|
|
});
|
|
|
|
DEBUG_SYNC(thd, "before_mock_optimize");
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool CompareJoinCost(
|
|
THD *thd, const JOIN &join,
|
|
const Candidate_table_order &table_order MY_ATTRIBUTE((unused)),
|
|
double optimizer_cost, bool *cheaper, double *secondary_engine_cost) {
|
|
DBUG_EXECUTE_IF("secondary_engine_mock_compare_cost_error", {
|
|
my_error(ER_SECONDARY_ENGINE_PLUGIN, MYF(0), "");
|
|
return true;
|
|
});
|
|
|
|
// Just use the cost calculated by the optimizer by default.
|
|
*secondary_engine_cost = optimizer_cost;
|
|
|
|
// This debug flag makes the cost function prefer orders where a table with
|
|
// the alias "X" is closer to the beginning.
|
|
DBUG_EXECUTE_IF("secondary_engine_mock_change_join_order", {
|
|
double cost = table_order.size();
|
|
for (size_t i = 0; i < table_order.size(); ++i) {
|
|
const TABLE_LIST *ref = table_order.table_ref(i);
|
|
if (std::string(ref->alias) == "X") {
|
|
cost += i;
|
|
}
|
|
}
|
|
*secondary_engine_cost = cost;
|
|
});
|
|
|
|
// Check if the calculated cost is cheaper than the best cost seen so far.
|
|
*cheaper = down_cast<Mock_execution_context *>(
|
|
thd->lex->secondary_engine_execution_context())
|
|
->BestPlanSoFar(join, *secondary_engine_cost);
|
|
|
|
return false;
|
|
}
|
|
|
|
static handler *Create(handlerton *hton, TABLE_SHARE *table_share, bool,
|
|
MEM_ROOT *mem_root) {
|
|
return new (mem_root) mock::ha_mock(hton, table_share);
|
|
}
|
|
|
|
static int Init(MYSQL_PLUGIN p) {
|
|
loaded_tables = new LoadedTables();
|
|
|
|
handlerton *hton = static_cast<handlerton *>(p);
|
|
hton->create = Create;
|
|
hton->state = SHOW_OPTION_YES;
|
|
hton->flags = HTON_IS_SECONDARY_ENGINE;
|
|
hton->db_type = DB_TYPE_UNKNOWN;
|
|
hton->prepare_secondary_engine = PrepareSecondaryEngine;
|
|
hton->optimize_secondary_engine = OptimizeSecondaryEngine;
|
|
hton->compare_secondary_engine_cost = CompareJoinCost;
|
|
return 0;
|
|
}
|
|
|
|
static int Deinit(MYSQL_PLUGIN) {
|
|
delete loaded_tables;
|
|
loaded_tables = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
static st_mysql_storage_engine mock_storage_engine{
|
|
MYSQL_HANDLERTON_INTERFACE_VERSION};
|
|
|
|
mysql_declare_plugin(mock){
|
|
MYSQL_STORAGE_ENGINE_PLUGIN,
|
|
&mock_storage_engine,
|
|
"MOCK",
|
|
"MySQL",
|
|
"Mock storage engine",
|
|
PLUGIN_LICENSE_GPL,
|
|
Init,
|
|
nullptr,
|
|
Deinit,
|
|
0x0001,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
0,
|
|
} mysql_declare_plugin_end;
|