3123 lines
113 KiB
C++
3123 lines
113 KiB
C++
/* Copyright (c) 2015, 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 "sql/dd/cache/dictionary_client.h"
|
|
|
|
#include <stdio.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include "lex_string.h"
|
|
#include "m_ctype.h"
|
|
#include "m_string.h"
|
|
#include "my_dbug.h"
|
|
#include "my_inttypes.h"
|
|
#include "my_sys.h"
|
|
#include "mysql/components/services/log_builtins.h"
|
|
#include "mysql_com.h"
|
|
#include "mysqld_error.h"
|
|
#include "sql/dd/cache/multi_map_base.h"
|
|
#include "sql/dd/dd_schema.h" // dd::Schema_MDL_locker
|
|
#include "sql/dd/impl/bootstrap/bootstrap_ctx.h" // bootstrap_stage
|
|
#include "sql/dd/impl/cache/shared_dictionary_cache.h" // get(), release(), ...
|
|
#include "sql/dd/impl/cache/storage_adapter.h" // store(), drop(), ...
|
|
#include "sql/dd/impl/dictionary_impl.h"
|
|
#include "sql/dd/impl/object_key.h"
|
|
#include "sql/dd/impl/raw/object_keys.h" // Primary_id_key, ...
|
|
#include "sql/dd/impl/raw/raw_record.h"
|
|
#include "sql/dd/impl/raw/raw_record_set.h" // Raw_record_set
|
|
#include "sql/dd/impl/raw/raw_table.h" // Raw_table
|
|
#include "sql/dd/impl/sdi.h" // dd::sdi::drop_after_update
|
|
#include "sql/dd/impl/tables/character_sets.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/check_constraints.h" // check_constraint_exists
|
|
#include "sql/dd/impl/tables/collations.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/column_statistics.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/events.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/foreign_keys.h"
|
|
#include "sql/dd/impl/tables/index_stats.h" // dd::Index_stats
|
|
#include "sql/dd/impl/tables/resource_groups.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/routines.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/schemata.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/spatial_reference_systems.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/table_partitions.h" // get_partition_table_id()
|
|
#include "sql/dd/impl/tables/table_stats.h" // dd::Table_stats
|
|
#include "sql/dd/impl/tables/tables.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/tablespaces.h" // create_name_key()
|
|
#include "sql/dd/impl/tables/triggers.h" // dd::tables::Triggers
|
|
#include "sql/dd/impl/tables/view_routine_usage.h" // create_name_key
|
|
#include "sql/dd/impl/tables/view_table_usage.h" // create_name_key
|
|
#include "sql/dd/impl/transaction_impl.h" // Transaction_ro
|
|
#include "sql/dd/impl/types/entity_object_impl.h" // Entity_object_impl
|
|
#include "sql/dd/impl/types/object_table_definition_impl.h" // fs_name_case()
|
|
#include "sql/dd/properties.h" // Properties
|
|
#include "sql/dd/types/abstract_table.h" // Abstract_table
|
|
#include "sql/dd/types/charset.h" // Charset
|
|
#include "sql/dd/types/collation.h" // Collation
|
|
#include "sql/dd/types/column_statistics.h" // Column_statistics
|
|
#include "sql/dd/types/event.h" // Event
|
|
#include "sql/dd/types/function.h" // Function
|
|
#include "sql/dd/types/index_stat.h" // Index_stat
|
|
#include "sql/dd/types/procedure.h" // Procedure
|
|
#include "sql/dd/types/resource_group.h" // Resource_group
|
|
#include "sql/dd/types/routine.h"
|
|
#include "sql/dd/types/schema.h" // Schema
|
|
#include "sql/dd/types/spatial_reference_system.h" // Spatial_reference_system
|
|
#include "sql/dd/types/table.h" // Table
|
|
#include "sql/dd/types/table_stat.h" // Table_stat
|
|
#include "sql/dd/types/tablespace.h" // Tablespace
|
|
#include "sql/dd/types/view.h" // View
|
|
#include "sql/dd/types/view_routine.h" // View_routine
|
|
#include "sql/dd/types/view_table.h" // View_table
|
|
#include "sql/debug_sync.h" // DEBUG_SYNC()
|
|
#include "sql/handler.h"
|
|
#include "sql/log.h"
|
|
#include "sql/mdl.h"
|
|
#include "sql/mysqld.h"
|
|
#include "sql/sql_class.h" // THD
|
|
#include "sql/sql_plugin_ref.h"
|
|
#include "sql/table.h"
|
|
#include "sql/tztime.h" // Time_zone, my_tz_OFFSET0
|
|
|
|
namespace {
|
|
|
|
/**
|
|
Helper class providing overloaded functions asserting that we have proper
|
|
MDL locks in place. Please note that the functions cannot be called
|
|
until after we have the name of the object, so if we acquire an object
|
|
by id, the asserts must be delayed until the object is retrieved.
|
|
|
|
@note Checking for MDL locks is disabled for the DD initialization
|
|
thread because the server is not multi threaded at this stage.
|
|
*/
|
|
|
|
class MDL_checker {
|
|
private:
|
|
/**
|
|
Private helper function for asserting MDL for tables.
|
|
|
|
@note For temporary tables, we have no locks.
|
|
|
|
@param thd Thread context.
|
|
@param schema_name Schema name to use in the MDL key.
|
|
@param object_name Object name to use in the MDL key.
|
|
@param mdl_namespace MDL key namespace to use.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const char *schema_name,
|
|
const char *object_name,
|
|
MDL_key::enum_mdl_namespace mdl_namespace,
|
|
enum_mdl_type lock_type) {
|
|
// For the schema name part, the behavior is dependent on whether
|
|
// the schema name is supplied explicitly in the sql statement
|
|
// or not. If it is, the case sensitive name is locked. If only
|
|
// the table name is supplied in the SQL statement, then the
|
|
// current schema is used as the schema part of the key, and in
|
|
// that case, the lowercase name is locked. This applies only
|
|
// when l_c_t_n == 2. To verify, we therefor use both variants
|
|
// of the schema name.
|
|
char schema_name_buf[NAME_LEN + 1];
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(
|
|
mdl_namespace, schema_name, object_name, lock_type) ||
|
|
thd->mdl_context.owns_equal_or_stronger_lock(
|
|
mdl_namespace,
|
|
dd::Object_table_definition_impl::fs_name_case(schema_name,
|
|
schema_name_buf),
|
|
object_name, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for tables.
|
|
|
|
@note We need to retrieve the schema name, since this is required
|
|
for the MDL key.
|
|
|
|
@param thd Thread context.
|
|
@param table Table object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Abstract_table *table,
|
|
enum_mdl_type lock_type) {
|
|
// The schema must be auto released to avoid disturbing the context
|
|
// at the origin of the function call.
|
|
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
|
|
const dd::Schema *schema = NULL;
|
|
|
|
// If the schema acquisition fails, we cannot assure that we have a lock,
|
|
// and therefore return false.
|
|
if (thd->dd_client()->acquire(table->schema_id(), &schema)) return false;
|
|
|
|
// Skip check for temporary tables.
|
|
if (!table || is_prefix(table->name().c_str(), tmp_file_prefix))
|
|
return true;
|
|
|
|
// Likewise, if there is no schema, we cannot have a proper lock.
|
|
// This may in theory happen during bootstrapping since the meta data for
|
|
// the system schema is not stored yet; however, this is prevented by
|
|
// surrounding code calling this function only if
|
|
// '!thd->is_dd_system_thread' i.e., this is not a bootstrapping thread.
|
|
DBUG_ASSERT(!thd->is_dd_system_thread());
|
|
DBUG_ASSERT(schema);
|
|
|
|
// We must take l_c_t_n into account when reconstructing the
|
|
// MDL key from the table name.
|
|
char table_name_buf[NAME_LEN + 1];
|
|
|
|
if (!my_strcasecmp(system_charset_info, schema->name().c_str(),
|
|
"information_schema"))
|
|
return is_locked(thd, schema->name().c_str(), table->name().c_str(),
|
|
MDL_key::TABLE, lock_type);
|
|
|
|
return is_locked(thd, schema->name().c_str(),
|
|
dd::Object_table_definition_impl::fs_name_case(
|
|
table->name(), table_name_buf),
|
|
MDL_key::TABLE, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for events.
|
|
|
|
@note We need to retrieve the schema name, since this is required
|
|
for the MDL key.
|
|
|
|
@param thd Thread context.
|
|
@param event Event object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Event *event,
|
|
enum_mdl_type lock_type) {
|
|
// The schema must be auto released to avoid disturbing the context
|
|
// at the origin of the function call.
|
|
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
|
|
const dd::Schema *schema = NULL;
|
|
|
|
// If the schema acquisition fails, we cannot assure that we have a lock,
|
|
// and therefore return false.
|
|
if (thd->dd_client()->acquire(event->schema_id(), &schema)) return false;
|
|
DBUG_ASSERT(schema);
|
|
|
|
MDL_key mdl_key;
|
|
char schema_name_buf[NAME_LEN + 1];
|
|
dd::Event::create_mdl_key(dd::Object_table_definition_impl::fs_name_case(
|
|
schema->name(), schema_name_buf),
|
|
event->name(), &mdl_key);
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(&mdl_key, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for routines.
|
|
|
|
@note We need to retrieve the schema name, since this is required
|
|
for the MDL key.
|
|
|
|
@param thd Thread context.
|
|
@param routine Routine object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Routine *routine,
|
|
enum_mdl_type lock_type) {
|
|
// The schema must be auto released to avoid disturbing the context
|
|
// at the origin of the function call.
|
|
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
|
|
const dd::Schema *schema = NULL;
|
|
|
|
// If the schema acquisition fails, we cannot assure that we have a lock,
|
|
// and therefore return false.
|
|
if (thd->dd_client()->acquire(routine->schema_id(), &schema)) return false;
|
|
|
|
DBUG_ASSERT(schema);
|
|
|
|
MDL_key mdl_key;
|
|
char schema_name_buf[NAME_LEN + 1];
|
|
dd::Routine::create_mdl_key(routine->type(),
|
|
dd::Object_table_definition_impl::fs_name_case(
|
|
schema->name(), schema_name_buf),
|
|
routine->name(), &mdl_key);
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(&mdl_key, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for schemata.
|
|
|
|
@param thd Thread context.
|
|
@param schema Schema object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Schema *schema,
|
|
enum_mdl_type lock_type) {
|
|
if (!schema) return true;
|
|
|
|
// We must take l_c_t_n into account when reconstructing the
|
|
// MDL key from the schema name.
|
|
char name_buf[NAME_LEN + 1];
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::SCHEMA,
|
|
dd::Object_table_definition_impl::fs_name_case(schema->name(),
|
|
name_buf),
|
|
"", lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for spatial reference systems.
|
|
|
|
@param thd Thread context.
|
|
@param srs Spatial reference system object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Spatial_reference_system *srs,
|
|
enum_mdl_type lock_type) {
|
|
if (!srs) return true;
|
|
|
|
// Check that the SRID is within the legal range to make sure we
|
|
// don't overflow id_str below. The ID is unsigned, so we only
|
|
// need to check the upper bound.
|
|
DBUG_ASSERT(srs->id() <= UINT_MAX32);
|
|
|
|
char id_str[11]; // uint32 => max 10 digits + \0
|
|
int10_to_str(static_cast<long>(srs->id()), id_str, 10);
|
|
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(MDL_key::SRID, "",
|
|
id_str, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for column statistics.
|
|
|
|
@param thd Thread context.
|
|
@param s Column statistic object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd,
|
|
const dd::Column_statistics *column_statistics,
|
|
enum_mdl_type lock_type) {
|
|
if (!column_statistics) return true; /* purecov: deadcode */
|
|
|
|
MDL_key mdl_key;
|
|
column_statistics->create_mdl_key(&mdl_key);
|
|
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(&mdl_key, lock_type);
|
|
}
|
|
|
|
/**
|
|
Private helper function for asserting MDL for tablespaces.
|
|
|
|
@note We need to retrieve the schema name, since this is required
|
|
for the MDL key.
|
|
|
|
@param thd Thread context.
|
|
@param tablespace Tablespace object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Tablespace *tablespace,
|
|
enum_mdl_type lock_type) {
|
|
if (!tablespace) return true;
|
|
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::TABLESPACE, "", tablespace->name().c_str(), lock_type);
|
|
}
|
|
|
|
public:
|
|
// Releasing arbitrary dictionary objects is not checked.
|
|
static bool is_release_locked(THD *, const dd::Entity_object *) {
|
|
return true;
|
|
}
|
|
|
|
// Reading a table object should be governed by MDL_SHARED.
|
|
static bool is_read_locked(THD *thd, const dd::Abstract_table *table) {
|
|
return thd->is_dd_system_thread() || is_locked(thd, table, MDL_SHARED);
|
|
}
|
|
|
|
// Writing a table object should be governed by MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd, const dd::Abstract_table *table) {
|
|
return thd->is_dd_system_thread() || is_locked(thd, table, MDL_EXCLUSIVE);
|
|
}
|
|
|
|
#ifdef EXTRA_DD_DEBUG // Too intrusive/expensive to have enabled by default.
|
|
// Releasing a table object should be covered in the same way as for reading.
|
|
static bool is_release_locked(THD *thd, const dd::Abstract_table *table) {
|
|
if (thd->is_dd_system_thread()) return true;
|
|
dd::Schema *schema = nullptr;
|
|
if (thd->dd_client()->acquire_uncached(table->schema_id(), &schema))
|
|
return false;
|
|
if (schema == nullptr) return false;
|
|
dd::Schema_MDL_locker mdl_locker(thd);
|
|
if (mdl_locker.ensure_locked(schema->name().c_str())) return false;
|
|
return is_read_locked(thd, table);
|
|
}
|
|
#endif // EXTRA_DD_DEBUG
|
|
|
|
// Reading a spatial reference system object should be governed by MDL_SHARED.
|
|
static bool is_read_locked(THD *thd,
|
|
const dd::Spatial_reference_system *srs) {
|
|
return thd->is_dd_system_thread() || is_locked(thd, srs, MDL_SHARED);
|
|
}
|
|
|
|
// Writing a spatial reference system object should be governed by
|
|
// MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd,
|
|
const dd::Spatial_reference_system *srs) {
|
|
return !mysqld_server_started || is_locked(thd, srs, MDL_EXCLUSIVE);
|
|
}
|
|
|
|
// Releasing a spatial reference system object should be covered
|
|
// in the same way as for reading.
|
|
static bool is_release_locked(THD *thd,
|
|
const dd::Spatial_reference_system *srs) {
|
|
return is_read_locked(thd, srs);
|
|
}
|
|
|
|
// Reading a column_statistics object should be governed by MDL_SHARED.
|
|
static bool is_read_locked(THD *thd,
|
|
const dd::Column_statistics *column_statistics) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, column_statistics, MDL_SHARED);
|
|
}
|
|
|
|
// Writing a column_statistics object should be governed by
|
|
// MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd,
|
|
const dd::Column_statistics *column_statistics) {
|
|
return !mysqld_server_started ||
|
|
is_locked(thd, column_statistics, MDL_EXCLUSIVE);
|
|
}
|
|
|
|
// Releasing a column_statistics object should be covered
|
|
// in the same way as for reading.
|
|
static bool is_release_locked(
|
|
THD *thd, const dd::Column_statistics *column_statistics) {
|
|
return is_read_locked(thd, column_statistics);
|
|
}
|
|
|
|
// No MDL namespace for character sets.
|
|
static bool is_read_locked(THD *, const dd::Charset *) { return true; }
|
|
|
|
// No MDL namespace for character sets.
|
|
static bool is_write_locked(THD *, const dd::Charset *) { return true; }
|
|
|
|
// No MDL namespace for collations.
|
|
static bool is_read_locked(THD *, const dd::Collation *) { return true; }
|
|
|
|
// No MDL namespace for collations.
|
|
static bool is_write_locked(THD *, const dd::Collation *) { return true; }
|
|
|
|
/*
|
|
Reading a schema object should be governed by at least
|
|
MDL_INTENTION_EXCLUSIVE. IX is acquired when a schema is
|
|
being accessed when creating/altering table; while opening
|
|
a table before we know whether the table exists, and when
|
|
explicitly acquiring a schema object for reading.
|
|
*/
|
|
static bool is_read_locked(THD *thd, const dd::Schema *schema) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, schema, MDL_INTENTION_EXCLUSIVE);
|
|
}
|
|
|
|
// Writing a schema object should be governed by MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd, const dd::Schema *schema) {
|
|
return thd->is_dd_system_thread() || is_locked(thd, schema, MDL_EXCLUSIVE);
|
|
}
|
|
|
|
// Releasing a schema object should be covered in the same way as for reading.
|
|
static bool is_release_locked(THD *thd, const dd::Schema *schema) {
|
|
return is_read_locked(thd, schema);
|
|
}
|
|
|
|
/*
|
|
Reading a tablespace object should be governed by at least
|
|
MDL_INTENTION_EXCLUSIVE. IX is acquired when a tablespace is
|
|
being accessed when creating/altering table.
|
|
*/
|
|
static bool is_read_locked(THD *thd, const dd::Tablespace *tablespace) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, tablespace, MDL_INTENTION_EXCLUSIVE);
|
|
}
|
|
|
|
// Writing a tablespace object should be governed by MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd, const dd::Tablespace *tablespace) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, tablespace, MDL_EXCLUSIVE);
|
|
}
|
|
|
|
// Releasing a tablespace object should be covered in the same way as for
|
|
// reading.
|
|
static bool is_release_locked(THD *thd, const dd::Tablespace *tablespace) {
|
|
return is_read_locked(thd, tablespace);
|
|
}
|
|
|
|
// Reading a Event object should be governed at least MDL_SHARED.
|
|
static bool is_read_locked(THD *thd, const dd::Event *event) {
|
|
return (thd->is_dd_system_thread() || is_locked(thd, event, MDL_SHARED));
|
|
}
|
|
|
|
// Writing a Event object should be governed by MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd, const dd::Event *event) {
|
|
return (thd->is_dd_system_thread() || is_locked(thd, event, MDL_EXCLUSIVE));
|
|
}
|
|
|
|
#ifdef EXTRA_DD_DEBUG // Too intrusive/expensive to have enabled by default.
|
|
// Releasing an Event object should be covered in the same way as for reading.
|
|
static bool is_release_locked(THD *thd, const dd::Event *event) {
|
|
if (thd->is_dd_system_thread()) return true;
|
|
dd::Schema *schema = nullptr;
|
|
if (thd->dd_client()->acquire_uncached(event->schema_id(), &schema))
|
|
return false;
|
|
if (schema == nullptr) return false;
|
|
dd::Schema_MDL_locker mdl_locker(thd);
|
|
if (mdl_locker.ensure_locked(schema->name().c_str())) return false;
|
|
return is_read_locked(thd, event);
|
|
}
|
|
#endif // EXTRA_DD_DEBUG
|
|
|
|
// Reading a Routine object should be governed at least MDL_SHARED.
|
|
static bool is_read_locked(THD *thd, const dd::Routine *routine) {
|
|
return (thd->is_dd_system_thread() || is_locked(thd, routine, MDL_SHARED));
|
|
}
|
|
|
|
// Writing a Routine object should be governed by MDL_EXCLUSIVE.
|
|
static bool is_write_locked(THD *thd, const dd::Routine *routine) {
|
|
return (thd->is_dd_system_thread() ||
|
|
is_locked(thd, routine, MDL_EXCLUSIVE));
|
|
}
|
|
|
|
#ifdef EXTRA_DD_DEBUG // Too intrusive/expensive to have enabled by default.
|
|
// Releasing a Routine object should be covered in the same way as for
|
|
// reading.
|
|
static bool is_release_locked(THD *thd, const dd::Routine *routine) {
|
|
if (thd->is_dd_system_thread()) return true;
|
|
dd::Schema *schema = nullptr;
|
|
if (thd->dd_client()->acquire_uncached(routine->schema_id(), &schema))
|
|
return false;
|
|
if (schema == nullptr) return false;
|
|
dd::Schema_MDL_locker mdl_locker(thd);
|
|
if (mdl_locker.ensure_locked(schema->name().c_str())) return false;
|
|
return is_read_locked(thd, routine);
|
|
}
|
|
#endif // EXTRA_DD_DEBUG
|
|
|
|
/**
|
|
Private helper function for asserting MDL for resource groups.
|
|
|
|
@param thd THD context.
|
|
@param resource_group DD Resource group object.
|
|
@param lock_type Weakest lock type accepted.
|
|
|
|
@return true if we have the required lock, otherwise false.
|
|
*/
|
|
|
|
static bool is_locked(THD *thd, const dd::Resource_group *resource_group,
|
|
enum_mdl_type lock_type) {
|
|
if (resource_group == nullptr) return true;
|
|
|
|
MDL_key mdl_key;
|
|
dd::Resource_group::create_mdl_key(resource_group->name(), &mdl_key);
|
|
|
|
return thd->mdl_context.owns_equal_or_stronger_lock(&mdl_key, lock_type);
|
|
}
|
|
|
|
/**
|
|
Check whether a resource group object holds at least
|
|
MDL_INTENTION_EXCLUSIVE. IX is acquired when a resource group is being
|
|
accessed when creating/altering a resource group.
|
|
|
|
@param thd THD context.
|
|
@param resource_group Pointer to DD resource group object.
|
|
|
|
@return true if required lock is held else false
|
|
*/
|
|
|
|
static bool is_read_locked(THD *thd,
|
|
const dd::Resource_group *resource_group) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, resource_group, MDL_INTENTION_EXCLUSIVE);
|
|
}
|
|
|
|
/**
|
|
Check if MDL_EXCLUSIVE lock is held by DD Resource group object.
|
|
Writing a resource group object should be governed by MDL_EXCLUSIVE.
|
|
|
|
@param thd THD context
|
|
@param resource_group Pointer to DD resource group object.
|
|
|
|
@return true if required lock is held else false.
|
|
*/
|
|
|
|
static bool is_write_locked(THD *thd,
|
|
const dd::Resource_group *resource_group) {
|
|
return thd->is_dd_system_thread() ||
|
|
is_locked(thd, resource_group, MDL_EXCLUSIVE);
|
|
}
|
|
};
|
|
|
|
using SPI_missing_status = std::bitset<2>;
|
|
enum class SPI_missing_type { TABLES, PARTITIONS };
|
|
using SPI_order = std::vector<dd::Object_id>;
|
|
using SPI_index = std::unordered_map<dd::Object_id, SPI_missing_status>;
|
|
|
|
template <size_t SIZE>
|
|
class SPI_lru_cache_templ {
|
|
SPI_order m_order;
|
|
unsigned int m_insix = 0;
|
|
SPI_index m_index;
|
|
|
|
#ifndef DBUG_OFF
|
|
mutable int m_hits;
|
|
mutable int m_misses = 1;
|
|
#endif /* DBUG_OFF */
|
|
|
|
public:
|
|
~SPI_lru_cache_templ() {
|
|
DBUG_EXECUTE_IF("spi_cache_stats", {
|
|
std::cout << "SPI_lru_cache stats; m_order.size(): " << m_order.size()
|
|
<< " m_hits: " << m_hits << " m_misses: " << m_misses;
|
|
if (m_hits > 0) {
|
|
std::cout << " hit rate: "
|
|
<< 100 * (static_cast<double>(m_hits) / (m_hits + m_misses))
|
|
<< " %";
|
|
}
|
|
std::cout << std::endl;
|
|
});
|
|
}
|
|
|
|
void insert(dd::Object_id id, SPI_missing_type t) {
|
|
if (m_order.size() < SIZE) {
|
|
m_order.push_back(id);
|
|
} else {
|
|
m_insix %= SIZE;
|
|
m_index.erase(m_order[m_insix]);
|
|
m_order[m_insix] = id;
|
|
}
|
|
++m_insix;
|
|
auto &status = m_index[id];
|
|
status[static_cast<size_t>(t)] = true;
|
|
}
|
|
|
|
bool is_cached(dd::Object_id id, SPI_missing_type t) const {
|
|
auto it = m_index.find(id);
|
|
bool ex = (it != m_index.end() && it->second[static_cast<size_t>(t)]);
|
|
DBUG_EXECUTE_IF("spi_cache_stats", {
|
|
if (ex) {
|
|
++m_hits;
|
|
} else {
|
|
++m_misses;
|
|
}
|
|
});
|
|
return ex;
|
|
}
|
|
};
|
|
|
|
// Fetch the names of all the components in the schema which match
|
|
// the criteria provided.
|
|
template <typename T>
|
|
bool fetch_schema_component_names_by_criteria(
|
|
THD *thd, const dd::Schema *schema, std::vector<dd::String_type> *names,
|
|
std::function<bool(dd::Raw_record *)> const &fetch_criteria) {
|
|
DBUG_ASSERT(names);
|
|
|
|
// Create the key based on the schema id.
|
|
std::unique_ptr<dd::Object_key> object_key(
|
|
T::DD_table::create_key_by_schema_id(schema->id()));
|
|
|
|
// Setup read only DD transaction.
|
|
dd::Transaction_ro trx(thd, ISO_READ_COMMITTED);
|
|
|
|
trx.otx.register_tables<T>();
|
|
dd::Raw_table *table = trx.otx.get_table<T>();
|
|
DBUG_ASSERT(table);
|
|
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<dd::Raw_record_set> rs;
|
|
if (table->open_record_set(object_key.get(), rs)) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
dd::Raw_record *r = rs->current_record();
|
|
dd::String_type s;
|
|
while (r) {
|
|
// Get the table name, if the fetch criteria satisfies.
|
|
if (fetch_criteria(r))
|
|
names->push_back(r->read_str(T::DD_table::FIELD_NAME));
|
|
|
|
if (rs->next(r)) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace dd {
|
|
namespace cache {
|
|
|
|
/**
|
|
Inherit from an instantiation of the template to allow
|
|
forward-declaring in Dictionary_client.
|
|
*/
|
|
class SPI_lru_cache : public SPI_lru_cache_templ<1024> {};
|
|
|
|
SPI_lru_cache_owner_ptr::~SPI_lru_cache_owner_ptr() {
|
|
if (m_spi_lru_cache != nullptr) {
|
|
delete m_spi_lru_cache;
|
|
}
|
|
}
|
|
|
|
SPI_lru_cache *SPI_lru_cache_owner_ptr::operator->() {
|
|
if (m_spi_lru_cache == nullptr) {
|
|
m_spi_lru_cache = new SPI_lru_cache{};
|
|
}
|
|
return m_spi_lru_cache;
|
|
}
|
|
|
|
bool is_cached(const SPI_lru_cache_owner_ptr &cache, Object_id id,
|
|
SPI_missing_type t) {
|
|
return (cache.is_nullptr() ? false : cache->is_cached(id, t));
|
|
}
|
|
|
|
// Transfer an object from the current to the previous auto releaser.
|
|
template <typename T>
|
|
void Dictionary_client::Auto_releaser::transfer_release(const T *object) {
|
|
DBUG_ASSERT(object);
|
|
// Remove the object, which must be present.
|
|
Cache_element<T> *element = NULL;
|
|
m_release_registry.get(object, &element);
|
|
DBUG_ASSERT(element);
|
|
m_release_registry.remove(element);
|
|
m_prev->auto_release(element);
|
|
}
|
|
|
|
// Remove an element from some auto releaser down the chain.
|
|
template <typename T>
|
|
Dictionary_client::Auto_releaser *Dictionary_client::Auto_releaser::remove(
|
|
Cache_element<T> *element) {
|
|
DBUG_ASSERT(element);
|
|
// Scan the auto releaser linked list and remove the element.
|
|
for (Auto_releaser *releaser = this; releaser != NULL;
|
|
releaser = releaser->m_prev) {
|
|
Cache_element<T> *e = NULL;
|
|
releaser->m_release_registry.get(element->object(), &e);
|
|
if (e == element) {
|
|
releaser->m_release_registry.remove(element);
|
|
return releaser;
|
|
}
|
|
}
|
|
// The element must be present in some auto releaser.
|
|
DBUG_ASSERT(false); /* purecov: deadcode */
|
|
return NULL;
|
|
}
|
|
|
|
// Create a new empty auto releaser.
|
|
Dictionary_client::Auto_releaser::Auto_releaser()
|
|
: m_client(NULL), m_prev(NULL) {}
|
|
|
|
// Create a new auto releaser and link it into the dictionary client
|
|
// as the current releaser.
|
|
Dictionary_client::Auto_releaser::Auto_releaser(Dictionary_client *client)
|
|
: m_client(client), m_prev(client->m_current_releaser) {
|
|
m_client->m_current_releaser = this;
|
|
}
|
|
|
|
// Release all objects registered and restore previous releaser.
|
|
Dictionary_client::Auto_releaser::~Auto_releaser() {
|
|
// Release all objects registered.
|
|
m_client->release<Abstract_table>(&m_release_registry);
|
|
m_client->release<Schema>(&m_release_registry);
|
|
m_client->release<Tablespace>(&m_release_registry);
|
|
m_client->release<Charset>(&m_release_registry);
|
|
m_client->release<Collation>(&m_release_registry);
|
|
m_client->release<Column_statistics>(&m_release_registry);
|
|
m_client->release<Event>(&m_release_registry);
|
|
m_client->release<Routine>(&m_release_registry);
|
|
m_client->release<Spatial_reference_system>(&m_release_registry);
|
|
m_client->release<Resource_group>(&m_release_registry);
|
|
|
|
// Restore the client's previous releaser.
|
|
m_client->m_current_releaser = m_prev;
|
|
|
|
// Delete any remaining uncommitted or uncached objects if we only have
|
|
// the default releaser left. If any objects remain, we probably aborted
|
|
// the transaction.
|
|
if (m_client->m_current_releaser == &m_client->m_default_releaser) {
|
|
// We should either have reported an error or have removed all
|
|
// uncommitted objects (typically committed them to the shared cache).
|
|
DBUG_ASSERT(m_client->m_thd->is_error() || m_client->m_thd->killed ||
|
|
(m_client->m_registry_uncommitted.size_all() == 0 &&
|
|
m_client->m_registry_dropped.size_all() == 0));
|
|
|
|
m_client->m_registry_uncommitted.erase_all();
|
|
m_client->m_registry_dropped.erase_all();
|
|
|
|
// Delete any objects retrieved by acquire_uncached() or
|
|
// acquire_for_modification().
|
|
delete_container_pointers(m_client->m_uncached_objects);
|
|
}
|
|
}
|
|
|
|
// Debug dump to stderr.
|
|
template <typename T>
|
|
void Dictionary_client::Auto_releaser::dump() const {
|
|
#ifndef DBUG_OFF
|
|
fprintf(stderr, "================================\n");
|
|
fprintf(stderr, "Auto releaser\n");
|
|
m_release_registry.dump<T>();
|
|
fprintf(stderr, "================================\n");
|
|
fflush(stderr);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
Class to fetch dd::Objects with GMT time.
|
|
|
|
When dictionary object is fetched to create dd::Objects, the timestamp
|
|
data should be according to GMT and independent of time_zone. Time_zone
|
|
data should be added to time column before using the data.
|
|
|
|
Any timestamp column in dictionary should implement the data retrieval
|
|
function to return GMT data to dictionary framework but consider time_zone
|
|
when returning data to server.
|
|
*/
|
|
|
|
class Timestamp_timezone_guard {
|
|
public:
|
|
Timestamp_timezone_guard(THD *thd) : m_thd(thd) {
|
|
m_tz = m_thd->variables.time_zone;
|
|
m_thd->variables.time_zone = my_tz_OFFSET0;
|
|
}
|
|
|
|
~Timestamp_timezone_guard() { m_thd->variables.time_zone = m_tz; }
|
|
|
|
private:
|
|
::Time_zone *m_tz;
|
|
THD *m_thd;
|
|
};
|
|
|
|
// Get a dictionary object.
|
|
template <typename K, typename T>
|
|
bool Dictionary_client::acquire(const K &key, const T **object,
|
|
bool *local_committed,
|
|
bool *local_uncommitted) {
|
|
DBUG_ASSERT(object);
|
|
DBUG_ASSERT(local_committed);
|
|
DBUG_ASSERT(local_uncommitted);
|
|
*object = NULL;
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
DBUG_EXECUTE_IF("fail_while_acquiring_dd_object", {
|
|
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
|
|
return true;
|
|
});
|
|
|
|
// Lookup in registry of uncommitted objects
|
|
T *uncommitted_object = nullptr;
|
|
bool dropped = false;
|
|
acquire_uncommitted(key, &uncommitted_object, &dropped);
|
|
if (uncommitted_object || dropped) {
|
|
*local_committed = false;
|
|
*local_uncommitted = true;
|
|
*object = uncommitted_object;
|
|
return false;
|
|
}
|
|
*local_uncommitted = false;
|
|
|
|
// Lookup in the registry of committed objects.
|
|
Cache_element<T> *element = NULL;
|
|
m_registry_committed.get(key, &element);
|
|
if (element) {
|
|
// Check if an uncommitted object with the same id exists.
|
|
// If so, the object has been renamed or dropped, and we should
|
|
// return nothing.
|
|
const typename T::Id_key id_key(element->object()->id());
|
|
acquire_uncommitted(id_key, &uncommitted_object, &dropped);
|
|
DBUG_ASSERT(!dropped);
|
|
if (uncommitted_object || dropped) return false;
|
|
|
|
// Object has not been renamed
|
|
*local_committed = true;
|
|
*object = element->object();
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_read_locked(m_thd, *object));
|
|
return false;
|
|
}
|
|
|
|
// The element is not present locally.
|
|
*local_committed = false;
|
|
|
|
// Get the object from the shared cache.
|
|
if (Shared_dictionary_cache::instance()->get(m_thd, key, &element)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Add the element to the local registry and assign the output object.
|
|
if (element) {
|
|
// Recheck that we haven't renamed or dropped this object.
|
|
const typename T::Id_key id_key(element->object()->id());
|
|
acquire_uncommitted(id_key, &uncommitted_object, &dropped);
|
|
if (uncommitted_object || dropped) {
|
|
// Here, we drop the object from the shared cache. If the
|
|
// object has been dropped, we would otherwise contaminate
|
|
// the shared cache. For simplicity, we drop it also in the
|
|
// case of a modified (i.e., renamed) object. This would also
|
|
// be handled in remove_uncommitted_objects() when the
|
|
// shared cache is updated with the modified objects.
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, element->object()));
|
|
Shared_dictionary_cache::instance()->drop(element);
|
|
return false;
|
|
}
|
|
|
|
DBUG_ASSERT(element->object() && element->object()->id());
|
|
// Sign up for auto release.
|
|
m_registry_committed.put(element);
|
|
m_current_releaser->auto_release(element);
|
|
*object = element->object();
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_read_locked(m_thd, *object));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename K, typename T>
|
|
void Dictionary_client::acquire_uncommitted(const K &key, T **object,
|
|
bool *dropped) {
|
|
DBUG_ASSERT(object);
|
|
DBUG_ASSERT(dropped);
|
|
*object = nullptr;
|
|
*dropped = false;
|
|
|
|
Object_id uncommitted_id = INVALID_OBJECT_ID;
|
|
Object_id dropped_id = INVALID_OBJECT_ID;
|
|
|
|
Cache_element<T> *element = NULL;
|
|
m_registry_uncommitted.get(key, &element);
|
|
if (element) {
|
|
*object = const_cast<T *>(element->object()); // TODO: Const cast
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_read_locked(m_thd, *object));
|
|
uncommitted_id = (*object)->id();
|
|
}
|
|
|
|
m_registry_dropped.get(key, &element);
|
|
if (element) {
|
|
const T *dropped_object = element->object();
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_read_locked(m_thd, dropped_object));
|
|
dropped_id = dropped_object->id();
|
|
}
|
|
|
|
// The object should never be present in both registries with the same id.
|
|
DBUG_ASSERT(uncommitted_id != dropped_id || dropped_id == INVALID_OBJECT_ID);
|
|
|
|
*dropped =
|
|
(dropped_id != INVALID_OBJECT_ID && uncommitted_id == INVALID_OBJECT_ID);
|
|
|
|
// If dropped, we return nullptr.
|
|
if (*dropped) *object = nullptr;
|
|
}
|
|
|
|
// Mark all objects of a certain type as not being used by this client.
|
|
template <typename T>
|
|
size_t Dictionary_client::release(Object_registry *registry) {
|
|
DBUG_ASSERT(registry);
|
|
size_t num_released = 0;
|
|
|
|
// Iterate over all elements in the registry partition.
|
|
typename Multi_map_base<T>::Const_iterator it;
|
|
for (it = registry->begin<T>(); it != registry->end<T>(); ++num_released) {
|
|
DBUG_ASSERT(it->second);
|
|
DBUG_ASSERT(it->second->object());
|
|
|
|
// Make sure we handle iterator invalidation: Increment
|
|
// before erasing.
|
|
Cache_element<T> *element = it->second;
|
|
++it;
|
|
|
|
// Remove the element from the actual registry.
|
|
registry->remove(element);
|
|
|
|
// Remove the element from the client's object registry.
|
|
if (registry != &m_registry_committed)
|
|
m_registry_committed.remove(element);
|
|
else
|
|
(void)m_current_releaser->remove(element);
|
|
|
|
// Clone the object before releasing it. The object is needed for checking
|
|
// the meta data lock afterwards. This is an expensive check, so only
|
|
// do it if EXTRA_DD_DEBUG is set.
|
|
#ifdef EXTRA_DD_DEBUG
|
|
std::unique_ptr<const T> object_clone(element->object()->clone());
|
|
#endif
|
|
|
|
// Release the element from the shared cache.
|
|
Shared_dictionary_cache::instance()->release(element);
|
|
|
|
// Make sure we still have some meta data lock. This is checked to
|
|
// catch situations where we have released the lock before releasing
|
|
// the cached element. This will happen if we, e.g., declare a
|
|
// Schema_MDL_locker after the Auto_releaser which keeps track of when
|
|
// the elements are to be released. In that case, the instances will
|
|
// be deleted in the opposite order, hence there will be a short period
|
|
// where the schema locker is deleted (and hence, its MDL ticket is
|
|
// released) while the actual schema object is still not released. This
|
|
// means that there may be situations where we have a different thread
|
|
// getting an X meta data lock on the schema name, while the reference
|
|
// counter of the corresponding cache element is already > 0, which may
|
|
// again trigger asserts in the shared cache and allow for improper object
|
|
// usage.
|
|
#ifdef EXTRA_DD_DEBUG
|
|
DBUG_ASSERT(MDL_checker::is_release_locked(m_thd, object_clone.get()));
|
|
#endif
|
|
}
|
|
return num_released;
|
|
}
|
|
|
|
// Release all objects in the submitted object registry.
|
|
size_t Dictionary_client::release(Object_registry *registry) {
|
|
return release<Abstract_table>(registry) + release<Schema>(registry) +
|
|
release<Tablespace>(registry) + release<Charset>(registry) +
|
|
release<Collation>(registry) + release<Event>(registry) +
|
|
release<Resource_group>(registry) + release<Routine>(registry) +
|
|
release<Spatial_reference_system>(registry) +
|
|
release<Column_statistics>(registry);
|
|
}
|
|
|
|
// Initialize an instance with a default auto releaser.
|
|
Dictionary_client::Dictionary_client(THD *thd)
|
|
: m_thd(thd), m_current_releaser(&m_default_releaser) {
|
|
DBUG_ASSERT(m_thd);
|
|
// We cannot fully initialize the m_default_releaser in the member
|
|
// initialization list since 'this' isn't fully initialized at that point.
|
|
// Thus, we do it here.
|
|
m_default_releaser.m_client = this;
|
|
}
|
|
|
|
// Make sure all objects are released.
|
|
Dictionary_client::~Dictionary_client() {
|
|
// Release the objects left in the object registry (should be empty).
|
|
size_t num_released = release(&m_registry_committed);
|
|
DBUG_ASSERT(num_released == 0);
|
|
if (num_released > 0) {
|
|
LogErr(WARNING_LEVEL, ER_DD_OBJECT_REMAINS);
|
|
}
|
|
|
|
// Delete the additional releasers (should be none).
|
|
while (m_current_releaser && m_current_releaser != &m_default_releaser) {
|
|
/* purecov: begin deadcode */
|
|
LogErr(WARNING_LEVEL, ER_DD_OBJECT_RELEASER_REMAINS);
|
|
DBUG_ASSERT(false);
|
|
delete m_current_releaser;
|
|
/* purecov: end */
|
|
}
|
|
|
|
// Finally, release the objects left in the default releaser
|
|
// (should be empty).
|
|
num_released = release(&m_default_releaser.m_release_registry);
|
|
DBUG_ASSERT(num_released == 0);
|
|
if (num_released > 0) {
|
|
LogErr(WARNING_LEVEL, ER_DD_OBJECT_REMAINS_IN_RELEASER);
|
|
}
|
|
}
|
|
|
|
// Retrieve an object by its object id.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire(Object_id id, const T **object) {
|
|
const typename T::Id_key key(id);
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
// We must be sure the object is released correctly if dynamic cast fails.
|
|
Auto_releaser releaser(this);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
bool error =
|
|
acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
*object = dynamic_cast<const T *>(cached_object);
|
|
|
|
// Don't auto release the object here if it is returned.
|
|
if (!local_committed && !local_uncommitted && *object)
|
|
releaser.transfer_release(cached_object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_for_modification(Object_id id, T **object) {
|
|
const typename T::Id_key key(id);
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
// We must be sure the object is released correctly if dynamic cast fails.
|
|
Auto_releaser releaser(this);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
bool error =
|
|
acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
const T *casted = dynamic_cast<const T *>(cached_object);
|
|
|
|
if (!casted)
|
|
*object = nullptr;
|
|
else {
|
|
*object = casted->clone();
|
|
auto_delete<T>(*object);
|
|
}
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve an object by its object id without caching it.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_uncached(Object_id id, T **object) {
|
|
const typename T::Id_key key(id);
|
|
const typename T::Cache_partition *stored_object = NULL;
|
|
|
|
// Read the uncached dictionary object.
|
|
bool error = Shared_dictionary_cache::instance()->get_uncached(
|
|
m_thd, key, ISO_READ_COMMITTED, &stored_object);
|
|
if (!error) {
|
|
// We do not verify proper MDL locking here since the
|
|
// returned object is owned by the caller.
|
|
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
// TODO: Replace const_cast by directly using Storage_adapter
|
|
*object = const_cast<T *>(dynamic_cast<const T *>(stored_object));
|
|
|
|
// Delete the object if dynamic cast fails.
|
|
if (stored_object && !*object)
|
|
delete stored_object;
|
|
else
|
|
auto_delete<T>(*object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve an object by its object id without caching it.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_uncached_uncommitted(Object_id id, T **object) {
|
|
const typename T::Id_key key(id);
|
|
DBUG_ASSERT(object);
|
|
|
|
// First get the object from acquire_uncommitted. This should be safe
|
|
// even without MDL, since the object is only available to this thread.
|
|
typename T::Cache_partition *uncommitted_object = nullptr;
|
|
bool dropped = false;
|
|
acquire_uncommitted(key, &uncommitted_object, &dropped);
|
|
|
|
// In this case, if the object has been dropped, we return nullptr since
|
|
// this is in line with the isolation level for the disk access.
|
|
if (dropped) {
|
|
*object = nullptr;
|
|
return false;
|
|
}
|
|
|
|
if (uncommitted_object != nullptr) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return, but in this
|
|
// case, we cannot delete the stored_object since it is present
|
|
// in the uncommitted registry. The returned object, however,
|
|
// must be auto deleted.
|
|
*object =
|
|
const_cast<T *>(dynamic_cast<const T *>(uncommitted_object->clone()));
|
|
if (*object != nullptr) auto_delete<T>(*object);
|
|
return false;
|
|
}
|
|
|
|
// Read the uncached dictionary object using ISO_READ_UNCOMMITTED
|
|
// isolation level.
|
|
const typename T::Cache_partition *stored_object = nullptr;
|
|
bool error = Shared_dictionary_cache::instance()->get_uncached(
|
|
m_thd, key, ISO_READ_UNCOMMITTED, &stored_object);
|
|
if (!error) {
|
|
// Here, stored_object is a newly created instance, so we do not need to
|
|
// clone() it, but we must delete it if dynamic cast fails.
|
|
*object = const_cast<T *>(dynamic_cast<const T *>(stored_object));
|
|
if (stored_object && !*object)
|
|
delete stored_object;
|
|
else
|
|
auto_delete<T>(*object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve an object by its name.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire(const String_type &object_name,
|
|
const T **object) {
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
bool error = T::update_name_key(&key, object_name);
|
|
if (error) {
|
|
my_error(ER_INVALID_DD_OBJECT_NAME, MYF(0), object_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
// We must be sure the object is released correctly if dynamic cast fails.
|
|
Auto_releaser releaser(this);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
*object = dynamic_cast<const T *>(cached_object);
|
|
|
|
// Don't auto release the object here if it is returned.
|
|
if (!local_committed && !local_uncommitted && *object)
|
|
releaser.transfer_release(cached_object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_for_modification(const String_type &object_name,
|
|
T **object) {
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
bool error = T::update_name_key(&key, object_name);
|
|
if (error) {
|
|
my_error(ER_INVALID_DD_OBJECT_NAME, MYF(0), object_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
// We must be sure the object is released correctly if dynamic cast fails.
|
|
Auto_releaser releaser(this);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
const T *casted = dynamic_cast<const T *>(cached_object);
|
|
|
|
if (!casted)
|
|
*object = nullptr;
|
|
else {
|
|
*object = casted->clone();
|
|
auto_delete<T>(*object);
|
|
}
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve an object by its schema- and object name.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire(const String_type &schema_name,
|
|
const String_type &object_name,
|
|
const T **object) {
|
|
// We must make sure the schema is released and unlocked in the right order.
|
|
Schema_MDL_locker mdl_locker(m_thd);
|
|
Auto_releaser releaser(this);
|
|
|
|
DBUG_ASSERT(object);
|
|
*object = NULL;
|
|
|
|
// Get the schema object by name.
|
|
const Schema *schema = NULL;
|
|
bool error = mdl_locker.ensure_locked(schema_name.c_str()) ||
|
|
acquire(schema_name, &schema);
|
|
|
|
// If there was an error, or if we found no valid schema, return here.
|
|
if (error) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// A non existing schema is not reported as an error.
|
|
if (!schema) return false;
|
|
|
|
DEBUG_SYNC(m_thd, "acquired_schema_while_acquiring_table");
|
|
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
T::update_name_key(&key, schema->id(), object_name);
|
|
|
|
// Acquire the dictionary object.
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
*object = dynamic_cast<const T *>(cached_object);
|
|
|
|
// Don't auto release the object here if it is returned.
|
|
if (!local_committed && !local_uncommitted && *object)
|
|
releaser.transfer_release(cached_object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_for_modification(const String_type &schema_name,
|
|
const String_type &object_name,
|
|
T **object) {
|
|
// We must make sure the schema is released and unlocked in the right order.
|
|
Schema_MDL_locker mdl_locker(m_thd);
|
|
Auto_releaser releaser(this);
|
|
|
|
DBUG_ASSERT(object);
|
|
*object = NULL;
|
|
|
|
// Get the schema object by name.
|
|
const Schema *schema = NULL;
|
|
bool error = mdl_locker.ensure_locked(schema_name.c_str()) ||
|
|
acquire(schema_name, &schema);
|
|
|
|
// If there was an error, or if we found no valid schema, return here.
|
|
if (error) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// A non existing schema is not reported as an error.
|
|
if (!schema) return false;
|
|
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
T::update_name_key(&key, schema->id(), object_name);
|
|
|
|
// Acquire the dictionary object.
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return NULL if we e.g. asked
|
|
// for a dd::Table and got a dd::View in return.
|
|
DBUG_ASSERT(object);
|
|
const T *casted = dynamic_cast<const T *>(cached_object);
|
|
|
|
if (!casted)
|
|
*object = nullptr;
|
|
else {
|
|
*object = casted->clone();
|
|
auto_delete<T>(*object);
|
|
}
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve an object by its schema- and object name. Return as double
|
|
// pointer to base type.
|
|
template <typename T>
|
|
bool Dictionary_client::acquire(const String_type &schema_name,
|
|
const String_type &object_name,
|
|
const typename T::Cache_partition **object) {
|
|
// We must make sure the schema is released and unlocked in the right order.
|
|
Schema_MDL_locker mdl_locker(m_thd);
|
|
Auto_releaser releaser(this);
|
|
|
|
DBUG_ASSERT(object);
|
|
*object = NULL;
|
|
|
|
// Get the schema object by name.
|
|
const Schema *schema = NULL;
|
|
bool error = mdl_locker.ensure_locked(schema_name.c_str()) ||
|
|
acquire(schema_name, &schema);
|
|
|
|
// If there was an error, or if we found no valid schema, return here.
|
|
if (error) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
// A non existing schema is not reported as an error.
|
|
if (!schema) return false;
|
|
|
|
DEBUG_SYNC(m_thd, "acquired_schema_while_acquiring_table");
|
|
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
T::update_name_key(&key, schema->id(), object_name);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
// Acquire the dictionary object.
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// No downcasting is necessary here.
|
|
// Don't auto release the object here if it is returned.
|
|
if (!local_committed && !local_uncommitted && *object)
|
|
releaser.transfer_release(*object);
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
|
|
return error;
|
|
}
|
|
|
|
template <typename T>
|
|
bool Dictionary_client::acquire_for_modification(
|
|
const String_type &schema_name, const String_type &object_name,
|
|
typename T::Cache_partition **object) {
|
|
// We must make sure the schema is released and unlocked in the right order.
|
|
Schema_MDL_locker mdl_locker(m_thd);
|
|
Auto_releaser releaser(this);
|
|
|
|
DBUG_ASSERT(object);
|
|
*object = NULL;
|
|
|
|
// Get the schema object by name.
|
|
const Schema *schema = NULL;
|
|
bool error = mdl_locker.ensure_locked(schema_name.c_str()) ||
|
|
acquire(schema_name, &schema);
|
|
|
|
// If there was an error, or if we found no valid schema, return here.
|
|
if (error) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// A non existing schema is not reported as an error.
|
|
if (!schema) return false;
|
|
|
|
// Create the name key for the object.
|
|
typename T::Name_key key;
|
|
T::update_name_key(&key, schema->id(), object_name);
|
|
|
|
// Cache dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
// Acquire the dictionary object.
|
|
const typename T::Cache_partition *cached_object = NULL;
|
|
|
|
bool local_committed = false;
|
|
bool local_uncommitted = false;
|
|
error = acquire(key, &cached_object, &local_committed, &local_uncommitted);
|
|
|
|
if (!error) {
|
|
// Cast not necessary here since we return the T::Cache_partition.
|
|
if (cached_object != nullptr) {
|
|
*object = cached_object->clone();
|
|
auto_delete(*object);
|
|
}
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
|
|
return error;
|
|
}
|
|
|
|
// Retrieve a table object by its se private id.
|
|
bool Dictionary_client::acquire_uncached_table_by_se_private_id(
|
|
const String_type &engine, Object_id se_private_id, Table **table) {
|
|
DBUG_ASSERT(table);
|
|
*table = NULL;
|
|
bool no_table =
|
|
is_cached(m_no_table_spids, se_private_id, SPI_missing_type::TABLES);
|
|
|
|
if (no_table) {
|
|
return false;
|
|
}
|
|
|
|
// Create se private key.
|
|
Table::Aux_key key;
|
|
Table::update_aux_key(&key, engine, se_private_id);
|
|
|
|
const Table::Cache_partition *stored_object = NULL;
|
|
|
|
// Read the uncached dictionary object.
|
|
if (Shared_dictionary_cache::instance()->get_uncached(
|
|
m_thd, key, ISO_READ_COMMITTED, &stored_object)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// If object was not found.
|
|
if (stored_object == NULL) {
|
|
m_no_table_spids->insert(se_private_id, SPI_missing_type::TABLES);
|
|
return false;
|
|
}
|
|
DBUG_ASSERT(no_table == false);
|
|
|
|
// Dynamic cast may legitimately return NULL only if the stored object
|
|
// was NULL, i.e., the object did not exist.
|
|
// TODO: Replace const_cast by directly using Storage_adapter
|
|
*table = const_cast<Table *>(dynamic_cast<const Table *>(stored_object));
|
|
|
|
// Delete the object and report error if dynamic cast fails.
|
|
if (!*table) {
|
|
my_error(ER_INVALID_DD_OBJECT, MYF(0),
|
|
Table::DD_table::instance().name().c_str(), "Not a table object.");
|
|
delete stored_object;
|
|
return true;
|
|
} else
|
|
auto_delete<Table>(*table);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Retrieve a table object by its partition se private id.
|
|
bool Dictionary_client::acquire_uncached_table_by_partition_se_private_id(
|
|
const String_type &engine, Object_id se_partition_id, Table **table) {
|
|
DBUG_ASSERT(table);
|
|
*table = NULL;
|
|
bool no_table = is_cached(m_no_table_spids, se_partition_id,
|
|
SPI_missing_type::PARTITIONS);
|
|
if (no_table) {
|
|
return false;
|
|
}
|
|
|
|
// Read record directly from the tables.
|
|
Object_id table_id;
|
|
if (tables::Table_partitions::get_partition_table_id(
|
|
m_thd, engine, se_partition_id, &table_id)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (table_id == INVALID_OBJECT_ID) {
|
|
m_no_table_spids->insert(se_partition_id, SPI_missing_type::PARTITIONS);
|
|
return false;
|
|
}
|
|
|
|
if (acquire_uncached(table_id, table)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (*table == NULL) {
|
|
m_no_table_spids->insert(se_partition_id, SPI_missing_type::PARTITIONS);
|
|
return false;
|
|
}
|
|
DBUG_ASSERT(no_table == false);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Get names of index and column names from index statistics entries.
|
|
static bool get_index_statistics_entries(
|
|
THD *thd, const String_type &schema_name, const String_type &table_name,
|
|
std::vector<String_type> &index_names,
|
|
std::vector<String_type> &column_names) {
|
|
/*
|
|
Use READ UNCOMMITTED isolation, so this method works correctly when
|
|
called from the middle of atomic ALTER TABLE statement.
|
|
*/
|
|
dd::Transaction_ro trx(thd, ISO_READ_UNCOMMITTED);
|
|
|
|
// Open the DD tables holding dynamic table statistics.
|
|
trx.otx.register_tables<dd::Table_stat>();
|
|
trx.otx.register_tables<dd::Index_stat>();
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(thd->is_error() || thd->killed);
|
|
return true;
|
|
}
|
|
|
|
// Create the range key based on schema and table name.
|
|
std::unique_ptr<Object_key> object_key(
|
|
dd::tables::Index_stats::create_range_key_by_table_name(schema_name,
|
|
table_name));
|
|
|
|
Raw_table *table = trx.otx.get_table<dd::Index_stat>();
|
|
DBUG_ASSERT(table);
|
|
|
|
// Start the scan.
|
|
std::unique_ptr<Raw_record_set> rs;
|
|
if (table->open_record_set(object_key.get(), rs)) {
|
|
DBUG_ASSERT(thd->is_error() || thd->killed);
|
|
return true;
|
|
}
|
|
|
|
// Read each index entry.
|
|
Raw_record *r = rs->current_record();
|
|
while (r) {
|
|
// Read index and column names.
|
|
index_names.push_back(r->read_str(tables::Index_stats::FIELD_INDEX_NAME));
|
|
column_names.push_back(r->read_str(tables::Index_stats::FIELD_COLUMN_NAME));
|
|
|
|
if (rs->next(r)) {
|
|
DBUG_ASSERT(thd->is_error() || thd->killed);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Remove the dynamic statistics stored in mysql.table_stats and
|
|
mysql.index_stats.
|
|
*/
|
|
bool Dictionary_client::remove_table_dynamic_statistics(
|
|
const String_type &schema_name, const String_type &table_name) {
|
|
//
|
|
// Get list of index statistics entries.
|
|
//
|
|
|
|
std::vector<String_type> index_names, column_names;
|
|
if (get_index_statistics_entries(m_thd, schema_name, table_name, index_names,
|
|
column_names)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Drop index statistics entries for the table.
|
|
//
|
|
|
|
// Iterate and drop each index statistic entry, if exists.
|
|
if (!index_names.empty()) {
|
|
const Index_stat *idx_stat = nullptr;
|
|
std::vector<String_type>::iterator it_idxs = index_names.begin();
|
|
std::vector<String_type>::iterator it_cols = column_names.begin();
|
|
while (it_idxs != index_names.end()) {
|
|
// Fetch the entry.
|
|
std::unique_ptr<Index_stat::Name_key> key(
|
|
tables::Index_stats::create_object_key(schema_name, table_name,
|
|
*it_idxs, *it_cols));
|
|
|
|
/*
|
|
Use READ UNCOMMITTED isolation, so this method works correctly when
|
|
called from the middle of atomic ALTER TABLE statement.
|
|
*/
|
|
if (Storage_adapter::get(m_thd, *key, ISO_READ_UNCOMMITTED, false,
|
|
&idx_stat)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
// Drop the entry.
|
|
if (idx_stat &&
|
|
Storage_adapter::drop(m_thd, const_cast<Index_stat *>(idx_stat))) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
delete idx_stat;
|
|
idx_stat = nullptr;
|
|
|
|
it_idxs++;
|
|
it_cols++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Drop the table statistics entry.
|
|
//
|
|
|
|
// Fetch the entry.
|
|
std::unique_ptr<Table_stat::Name_key> key(
|
|
tables::Table_stats::create_object_key(schema_name, table_name));
|
|
|
|
/*
|
|
Use READ UNCOMMITTED isolation, so this method works correctly when
|
|
called from the middle of atomic ALTER TABLE statement.
|
|
*/
|
|
const Table_stat *tab_stat = nullptr;
|
|
if (Storage_adapter::get(m_thd, *key, ISO_READ_UNCOMMITTED, false,
|
|
&tab_stat)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
// Drop the entry.
|
|
if (tab_stat &&
|
|
Storage_adapter::drop(m_thd, const_cast<Table_stat *>(tab_stat))) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
delete tab_stat;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Retrieve a schema- and table name by the se private id of the table.
|
|
bool Dictionary_client::get_table_name_by_se_private_id(
|
|
const String_type &engine, Object_id se_private_id,
|
|
String_type *schema_name, String_type *table_name) {
|
|
// Objects to be acquired.
|
|
Table *tab_obj = NULL;
|
|
Schema *sch_obj = NULL;
|
|
|
|
// Store empty in OUT params.
|
|
DBUG_ASSERT(schema_name && table_name);
|
|
schema_name->clear();
|
|
table_name->clear();
|
|
|
|
// Acquire the table uncached, because we cannot acquire a meta data
|
|
// lock since we do not know the table name.
|
|
if (acquire_uncached_table_by_se_private_id(engine, se_private_id,
|
|
&tab_obj)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Object not found.
|
|
if (!tab_obj) return false;
|
|
|
|
// Acquire the schema uncached to get the schema name. Like above, we
|
|
// cannot lock it in advance since we do not know its name.
|
|
if (acquire_uncached(tab_obj->schema_id(), &sch_obj)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (!sch_obj) {
|
|
my_error(ER_BAD_DB_ERROR, MYF(0), schema_name->c_str());
|
|
return true;
|
|
}
|
|
|
|
// Now, we have both objects, and can assign the names.
|
|
*schema_name = sch_obj->name();
|
|
*table_name = tab_obj->name();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Retrieve a schema- and table name by the se private id of the partition.
|
|
bool Dictionary_client::get_table_name_by_partition_se_private_id(
|
|
const String_type &engine, Object_id se_partition_id,
|
|
String_type *schema_name, String_type *table_name) {
|
|
Table *tab_obj = NULL;
|
|
Schema *sch_obj = NULL;
|
|
|
|
// Store empty in OUT params.
|
|
DBUG_ASSERT(schema_name && table_name);
|
|
schema_name->clear();
|
|
table_name->clear();
|
|
|
|
if (acquire_uncached_table_by_partition_se_private_id(engine, se_partition_id,
|
|
&tab_obj)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Object not found.
|
|
if (!tab_obj) return false;
|
|
|
|
// Acquire the schema to get the schema name.
|
|
if (acquire_uncached(tab_obj->schema_id(), &sch_obj)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (!sch_obj) {
|
|
my_error(ER_BAD_DB_ERROR, MYF(0), schema_name->c_str());
|
|
return true;
|
|
}
|
|
|
|
// Now, we have both objects, and can assign the names.
|
|
*schema_name = sch_obj->name();
|
|
*table_name = tab_obj->name();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Dictionary_client::get_table_name_by_trigger_name(
|
|
const Schema &schema, const String_type &trigger_name,
|
|
String_type *table_name) {
|
|
DBUG_ASSERT(table_name != nullptr);
|
|
*table_name = "";
|
|
|
|
// Read record directly from the tables.
|
|
Object_id table_id;
|
|
if (tables::Triggers::get_trigger_table_id(m_thd, schema.id(), trigger_name,
|
|
&table_id)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
const Table::Id_key key(table_id);
|
|
const Table::Cache_partition *stored_object = nullptr;
|
|
|
|
bool error = Shared_dictionary_cache::instance()->get_uncached(
|
|
m_thd, key, ISO_READ_COMMITTED, &stored_object);
|
|
|
|
if (!error) {
|
|
// Dynamic cast may legitimately return nullptr if the stored
|
|
// object was nullptr, i.e., the object did not exist.
|
|
const Table *table = dynamic_cast<const Table *>(stored_object);
|
|
|
|
// Delete the object and report error if dynamic cast fails.
|
|
if (stored_object != nullptr && table == nullptr) {
|
|
my_error(ER_INVALID_DD_OBJECT, MYF(0),
|
|
Table::DD_table::instance().name().c_str(),
|
|
"Not a table object.");
|
|
delete stored_object;
|
|
return true;
|
|
}
|
|
|
|
// Copy the table name to OUT param.
|
|
if (table != nullptr) {
|
|
*table_name = table->name();
|
|
delete stored_object;
|
|
}
|
|
} else
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
|
|
return error;
|
|
}
|
|
|
|
bool Dictionary_client::check_foreign_key_exists(
|
|
const Schema &schema, const String_type &foreign_key_name, bool *exists) {
|
|
#ifndef DBUG_OFF
|
|
char schema_name_buf[NAME_LEN + 1];
|
|
char fk_name_buff[NAME_LEN + 1];
|
|
my_stpcpy(fk_name_buff, foreign_key_name.c_str());
|
|
my_casedn_str(system_charset_info, fk_name_buff);
|
|
|
|
DBUG_ASSERT(m_thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::FOREIGN_KEY,
|
|
dd::Object_table_definition_impl::fs_name_case(schema.name(),
|
|
schema_name_buf),
|
|
fk_name_buff, MDL_EXCLUSIVE));
|
|
#endif
|
|
|
|
// Get info directly from the tables.
|
|
if (tables::Foreign_keys::check_foreign_key_exists(
|
|
m_thd, schema.id(), foreign_key_name, exists)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Dictionary_client::check_constraint_exists(
|
|
const Schema &schema, const String_type &check_cons_name, bool *exists) {
|
|
#ifndef DBUG_OFF
|
|
char schema_name_buf[NAME_LEN + 1];
|
|
char check_cons_name_buff[NAME_LEN + 1];
|
|
my_stpcpy(check_cons_name_buff, check_cons_name.c_str());
|
|
my_casedn_str(system_charset_info, check_cons_name_buff);
|
|
|
|
DBUG_ASSERT(m_thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::CHECK_CONSTRAINT,
|
|
dd::Object_table_definition_impl::fs_name_case(schema.name(),
|
|
schema_name_buf),
|
|
check_cons_name_buff, MDL_EXCLUSIVE));
|
|
#endif
|
|
|
|
// Get info directly from the tables.
|
|
if (tables::Check_constraints::check_constraint_exists(
|
|
m_thd, schema.id(), check_cons_name, exists)) {
|
|
DBUG_ASSERT(m_thd->is_error() || m_thd->killed);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool fetch_raw_record(THD *thd,
|
|
std::function<bool(Raw_record *)> const &processor) {
|
|
Transaction_ro trx(thd, ISO_READ_COMMITTED);
|
|
|
|
trx.otx.register_tables<T>();
|
|
Raw_table *table = trx.otx.get_table<T>();
|
|
DBUG_ASSERT(table);
|
|
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<Raw_record_set> rs;
|
|
if (table->open_record_set(nullptr, rs)) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
Raw_record *r = rs->current_record();
|
|
String_type s;
|
|
while (r) {
|
|
if (processor(r) || rs->next(r)) {
|
|
DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch the ids of all the components.
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_global_component_ids(
|
|
std::vector<Object_id> *ids) const {
|
|
DBUG_ASSERT(ids);
|
|
|
|
auto processor = [&](Raw_record *r) -> bool {
|
|
ids->push_back(r->read_int(0));
|
|
return false;
|
|
};
|
|
|
|
if (fetch_raw_record<T>(m_thd, processor)) {
|
|
ids->clear();
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch the names of all the components.
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_global_component_names(
|
|
std::vector<String_type> *names) const {
|
|
DBUG_ASSERT(names);
|
|
|
|
auto processor = [&](Raw_record *r) -> bool {
|
|
names->push_back(r->read_str(T::DD_table::FIELD_NAME));
|
|
return false;
|
|
};
|
|
|
|
if (fetch_raw_record<T>(m_thd, processor)) {
|
|
names->clear();
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch the table names that belong to schema with specific engine.
|
|
bool Dictionary_client::fetch_schema_table_names_by_engine(
|
|
const Schema *schema, const dd::String_type &engine,
|
|
std::vector<String_type> *names) const {
|
|
auto fetch_criteria = [&](Raw_record *r) -> bool {
|
|
auto table_type = static_cast<dd::Abstract_table::enum_hidden_type>(
|
|
r->read_int(dd::tables::Tables::FIELD_HIDDEN));
|
|
dd::String_type engine_name = r->read_str(dd::tables::Tables::FIELD_ENGINE);
|
|
|
|
// Select visible tables names.
|
|
return (table_type == dd::Abstract_table::HT_VISIBLE &&
|
|
(my_strcasecmp(system_charset_info, engine_name.c_str(),
|
|
engine.c_str()) == 0));
|
|
};
|
|
return fetch_schema_component_names_by_criteria<Abstract_table>(
|
|
m_thd, schema, names, fetch_criteria);
|
|
}
|
|
|
|
// Fetch the server table names that belong to schema, except for SE
|
|
// specific tables.
|
|
bool Dictionary_client::fetch_schema_table_names_not_hidden_by_se(
|
|
const Schema *schema, std::vector<String_type> *names) const {
|
|
auto fetch_criteria = [&](Raw_record *r) -> bool {
|
|
return static_cast<dd::Abstract_table::enum_hidden_type>(
|
|
r->read_int(dd::tables::Tables::FIELD_HIDDEN)) !=
|
|
dd::Abstract_table::HT_HIDDEN_SE;
|
|
};
|
|
return fetch_schema_component_names_by_criteria<Abstract_table>(
|
|
m_thd, schema, names, fetch_criteria);
|
|
}
|
|
|
|
// Fetch the table names that belong to schema, except for HIDDEN tables.
|
|
template <>
|
|
bool Dictionary_client::fetch_schema_component_names<Abstract_table>(
|
|
const Schema *schema, std::vector<String_type> *names) const {
|
|
auto fetch_criteria = [&](Raw_record *r) -> bool {
|
|
return static_cast<dd::Abstract_table::enum_hidden_type>(
|
|
r->read_int(dd::tables::Tables::FIELD_HIDDEN)) ==
|
|
dd::Abstract_table::HT_VISIBLE; // Select visible tables names.
|
|
};
|
|
return fetch_schema_component_names_by_criteria<Abstract_table>(
|
|
m_thd, schema, names, fetch_criteria);
|
|
}
|
|
|
|
// Fetch the names of object type T that belong to schema.
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_schema_component_names(
|
|
const Schema *schema, std::vector<String_type> *names) const {
|
|
auto fetch_criteria = [&](Raw_record *) -> bool {
|
|
return true; // Select all names.
|
|
};
|
|
return fetch_schema_component_names_by_criteria<T>(m_thd, schema, names,
|
|
fetch_criteria);
|
|
}
|
|
|
|
// Fetch objects from DD tables that match the supplied key.
|
|
template <typename Object_type>
|
|
bool Dictionary_client::fetch(Const_ptr_vec<Object_type> *coll,
|
|
const Object_key *object_key) {
|
|
// Since we clear the vector on failure, it should be empty
|
|
// when we start.
|
|
DBUG_ASSERT(coll->empty());
|
|
|
|
Transaction_ro trx(m_thd, ISO_READ_COMMITTED);
|
|
trx.otx.register_tables<typename Object_type::Cache_partition>();
|
|
Raw_table *table = trx.otx.get_table<typename Object_type::Cache_partition>();
|
|
DBUG_ASSERT(table);
|
|
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Retrieve the objects in a nested scope to make sure the
|
|
// record set is deleted before the transaction is committed
|
|
// (a dependency in the Raw_record_set destructor).
|
|
{
|
|
std::unique_ptr<Raw_record_set> rs;
|
|
if (table->open_record_set(object_key, rs)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
Raw_record *r = rs->current_record();
|
|
while (r) {
|
|
Object_type *object = nullptr;
|
|
Entity_object *new_object = nullptr;
|
|
const Entity_object_table &dd_table = Object_type::DD_table::instance();
|
|
|
|
// Restore the object from the record. We must do this with another
|
|
// transaction to avoid opening the same index twice, which we would
|
|
// otherwise do for e.g. tables.
|
|
{
|
|
Transaction_ro sub_trx(m_thd, ISO_READ_COMMITTED);
|
|
sub_trx.otx.register_tables<typename Object_type::Cache_partition>();
|
|
|
|
if (sub_trx.otx.open_tables()) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (r && dd_table.restore_object_from_record(&sub_trx.otx, *r,
|
|
&new_object)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
coll->clear();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Delete the new object if dynamic cast fails. Here, a failing dynamic
|
|
// cast is a legitimate situation; if we e.g. scan for tables, some
|
|
// of the objects will be views.
|
|
if (new_object) {
|
|
object = dynamic_cast<Object_type *>(new_object);
|
|
if (object == nullptr) {
|
|
delete new_object;
|
|
} else {
|
|
// Sign up the object for being auto deleted.
|
|
auto_delete<Object_type>(object);
|
|
coll->push_back(object);
|
|
}
|
|
}
|
|
|
|
if (rs->next(r)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
coll->clear();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch all components in the schema.
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_schema_components(const Schema *schema,
|
|
Const_ptr_vec<T> *coll) {
|
|
std::unique_ptr<Object_key> k(
|
|
T::DD_table::create_key_by_schema_id(schema->id()));
|
|
|
|
if (fetch(coll, k.get())) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
DBUG_ASSERT(coll->empty());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fetch all global components of the given type.
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_global_components(Const_ptr_vec<T> *coll) {
|
|
if (fetch(coll, NULL)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
DBUG_ASSERT(coll->empty());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool Dictionary_client::fetch_referencing_views_object_id(
|
|
const char *schema, const char *tbl_or_sf_name,
|
|
std::vector<Object_id> *view_ids) const {
|
|
/*
|
|
Use READ UNCOMMITTED isolation, so this method works correctly when
|
|
called from the middle of atomic DROP TABLE/DATABASE or
|
|
RENAME TABLE statements.
|
|
*/
|
|
dd::Transaction_ro trx(m_thd, ISO_READ_UNCOMMITTED);
|
|
|
|
// Register View_table_usage/View_routine_usage.
|
|
trx.otx.register_tables<T>();
|
|
Raw_table *view_usage_table = trx.otx.get_table<T>();
|
|
DBUG_ASSERT(view_usage_table);
|
|
|
|
// Open registered tables.
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Create the key based on the base table/ view/ stored function name.
|
|
std::unique_ptr<Object_key> object_key(T::DD_table::create_key_by_name(
|
|
String_type(Dictionary_impl::default_catalog_name()), String_type(schema),
|
|
String_type(tbl_or_sf_name)));
|
|
std::unique_ptr<Raw_record_set> rs;
|
|
if (view_usage_table->open_record_set(object_key.get(), rs)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
Raw_record *vtr = rs->current_record();
|
|
while (vtr) {
|
|
/* READ VIEW ID */
|
|
Object_id id = vtr->read_int(T::DD_table::FIELD_VIEW_ID);
|
|
view_ids->push_back(id);
|
|
|
|
if (rs->next(vtr)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Dictionary_client::fetch_fk_children_uncached(
|
|
const String_type &parent_schema, const String_type &parent_name,
|
|
const String_type &parent_engine, bool uncommitted,
|
|
std::vector<String_type> *children_schemas,
|
|
std::vector<String_type> *children_names) {
|
|
dd::Transaction_ro trx(
|
|
m_thd, uncommitted ? ISO_READ_UNCOMMITTED : ISO_READ_COMMITTED);
|
|
|
|
trx.otx.register_tables<Foreign_key>();
|
|
Raw_table *foreign_keys_table = trx.otx.get_table<Foreign_key>();
|
|
DBUG_ASSERT(foreign_keys_table);
|
|
|
|
if (trx.otx.open_tables()) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Create the key based on the parent table name.
|
|
std::unique_ptr<Object_key> object_key(
|
|
tables::Foreign_keys::create_key_by_referenced_name(
|
|
String_type(Dictionary_impl::default_catalog_name()), parent_schema,
|
|
parent_name));
|
|
|
|
std::unique_ptr<Raw_record_set> rs;
|
|
if (foreign_keys_table->open_record_set(object_key.get(), rs)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
Raw_record *r = rs->current_record();
|
|
while (r) {
|
|
/* READ TABLE ID */
|
|
Object_id id = r->read_int(tables::Foreign_keys::FIELD_TABLE_ID);
|
|
|
|
dd::Table *table = nullptr;
|
|
dd::Schema *schema = nullptr;
|
|
dd::cache::Dictionary_client::Auto_releaser releaser(this);
|
|
if (uncommitted) {
|
|
if (acquire_uncached_uncommitted(id, &table)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
} else {
|
|
if (acquire_uncached(id, &table)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (table) {
|
|
// Filter out children in different SEs. This is not supported.
|
|
if (my_strcasecmp(system_charset_info, table->engine().c_str(),
|
|
parent_engine.c_str()) == 0) {
|
|
if (uncommitted) {
|
|
if (acquire_uncached_uncommitted(table->schema_id(), &schema)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
} else {
|
|
if (acquire_uncached(table->schema_id(), &schema)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (schema) {
|
|
children_schemas->push_back(schema->name());
|
|
children_names->push_back(table->name());
|
|
}
|
|
}
|
|
}
|
|
// If table/schema is not found, it was deleted since we retrieved the ID.
|
|
// That can happen since we don't have a lock and is not an error.
|
|
|
|
if (rs->next(r)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Invalidate a table entry in the shared cache based on schema qualified name.
|
|
bool Dictionary_client::invalidate(const String_type &schema_name,
|
|
const String_type &table_name) {
|
|
// Verify metadata lock (should hold even for non-existing tables).
|
|
DBUG_ASSERT(m_thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::TABLE, schema_name.c_str(), table_name.c_str(), MDL_EXCLUSIVE));
|
|
|
|
// There should also be an IX lock on the schema name at this point.
|
|
DBUG_ASSERT(m_thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::SCHEMA, schema_name.c_str(), "", MDL_INTENTION_EXCLUSIVE));
|
|
|
|
Auto_releaser releaser(this);
|
|
const Table *table;
|
|
if (acquire(schema_name, table_name, &table)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
if (table != nullptr) {
|
|
invalidate(table);
|
|
}
|
|
|
|
// Invalidation of a non-existing object is not treated as an error.
|
|
return false;
|
|
}
|
|
|
|
// Invalidate an entry in the shared cache.
|
|
template <typename T>
|
|
void Dictionary_client::invalidate(const T *object) {
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, object));
|
|
|
|
// Invalidate the shared cache. This is safe since we have an
|
|
// exclusive meta data lock on the name.
|
|
// NOTE: When invalidating directly from the server code, further
|
|
// acquisition from this thread will pollute the shared cache.
|
|
// However, when invalidation is done in the context of drop()
|
|
// (see below), the object will be added to the registry of dropped
|
|
// objects, preventing subsequent acquisition even from this thread.
|
|
|
|
// Uncommitted object which was acquired for modification might have
|
|
// corrupted name.... So lookup by id. (see mysql_rename_table()
|
|
// problem)
|
|
Cache_element<typename T::Cache_partition> *element = nullptr;
|
|
const typename T::Id_key id_key(object->id());
|
|
m_registry_committed.get(id_key, &element);
|
|
|
|
if (element) {
|
|
// Remove the element from the chain of auto releasers.
|
|
(void)m_current_releaser->remove(element);
|
|
// Remove the element from the local registry.
|
|
m_registry_committed.remove(element);
|
|
// Remove the element from the cache, delete the wrapper and the object.
|
|
Shared_dictionary_cache::instance()->drop(element);
|
|
} else
|
|
Shared_dictionary_cache::instance()
|
|
->drop_if_present<typename T::Id_key, typename T::Cache_partition>(
|
|
id_key);
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
|
|
/**
|
|
Check whether protection against the backup- and global read
|
|
lock has been acquired.
|
|
|
|
@param[in] thd Thread context.
|
|
|
|
@return true protection against the backup lock and
|
|
global read lock has been acquired, or
|
|
the thread is a DD system thread, or the
|
|
server is not done starting.
|
|
false otherwise.
|
|
*/
|
|
|
|
template <typename T>
|
|
static bool is_backup_lock_and_grl_acquired(THD *thd) {
|
|
return !mysqld_server_started || thd->is_dd_system_thread() ||
|
|
(thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::BACKUP_LOCK, "", "", MDL_INTENTION_EXCLUSIVE) &&
|
|
thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
|
|
}
|
|
|
|
template <>
|
|
bool is_backup_lock_and_grl_acquired<Charset>(THD *) {
|
|
return true;
|
|
}
|
|
|
|
template <>
|
|
bool is_backup_lock_and_grl_acquired<Collation>(THD *) {
|
|
return true;
|
|
}
|
|
|
|
template <>
|
|
bool is_backup_lock_and_grl_acquired<Column_statistics>(THD *) {
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
// Remove and delete an object from the shared cache and the dd tables.
|
|
template <typename T>
|
|
bool Dictionary_client::drop(const T *object) {
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, object));
|
|
|
|
DBUG_ASSERT(is_backup_lock_and_grl_acquired<T>(m_thd));
|
|
|
|
if (Storage_adapter::drop(m_thd, object)) {
|
|
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
|
|
m_thd->is_error());
|
|
return true;
|
|
}
|
|
|
|
// Prepare an instance to be added to the dropped registry. This must be done
|
|
// prior to cleaning up the committed registry since the instance we drop
|
|
// might be present there (since we are allowed to drop const object coming
|
|
// from acquire()).
|
|
T *dropped_object = object->clone();
|
|
|
|
// Invalidate the entry in the shared cache (if present).
|
|
invalidate(object);
|
|
|
|
// Finally, add a clone to the dropped registry. Note that we are allowed to
|
|
// drop a const object, e.g. coming from acquire(). This means that the
|
|
// object instance, or the same id in a different instance, may be present in
|
|
// the uncommitted registry. This is handled inside register_dropped_object(),
|
|
// where we ensure that the uncommitted and dropped registries are consistent.
|
|
register_dropped_object(dropped_object);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Store a new dictionary object.
|
|
template <typename T>
|
|
bool Dictionary_client::store(T *object) {
|
|
#ifndef DBUG_OFF
|
|
// Make sure the object is not being used by this client.
|
|
Cache_element<typename T::Cache_partition> *element = NULL;
|
|
m_registry_committed.get(
|
|
static_cast<const typename T::Cache_partition *>(object), &element);
|
|
DBUG_ASSERT(!element);
|
|
#endif
|
|
|
|
DBUG_ASSERT(is_backup_lock_and_grl_acquired<T>(m_thd));
|
|
|
|
// Store dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
// Most DD objects have invalid object IDs when they're created. The ID is
|
|
// replaced with an auto-generated (AUTO_INCREMENT) value when they are
|
|
// stored.
|
|
//
|
|
// Spatial reference systems are different since the ID is the SRID, which is
|
|
// a user specified value. Therefore, spatial reference systems have valid IDs
|
|
// at this point, while other objects do not.
|
|
DBUG_ASSERT(object->id() == INVALID_OBJECT_ID ||
|
|
dynamic_cast<Spatial_reference_system *>(object));
|
|
|
|
// Check proper MDL lock.
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, object));
|
|
if (Storage_adapter::store(m_thd, object)) return true;
|
|
|
|
DBUG_ASSERT(object->id() != INVALID_OBJECT_ID);
|
|
register_uncommitted_object(object->clone());
|
|
return false;
|
|
}
|
|
|
|
// Store a new dictionary object.
|
|
template <>
|
|
bool Dictionary_client::store(Table_stat *object) {
|
|
// Store dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
return Storage_adapter::store<Table_stat>(m_thd, object);
|
|
}
|
|
|
|
template <>
|
|
bool Dictionary_client::store(Index_stat *object) {
|
|
// Store dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
return Storage_adapter::store<Index_stat>(m_thd, object);
|
|
}
|
|
|
|
// Update a persisted dictionary object, but keep the shared cache unchanged.
|
|
template <typename T>
|
|
bool Dictionary_client::update(T *new_object) {
|
|
DBUG_ASSERT(new_object);
|
|
|
|
// Make sure the object has a valid object id.
|
|
DBUG_ASSERT(new_object->id() != INVALID_OBJECT_ID);
|
|
|
|
// Avoid updating DD object that modifies m_registry_uncommitted cache
|
|
// during attachable read-write transaction.
|
|
DBUG_ASSERT(!m_thd->is_attachable_rw_transaction_active());
|
|
|
|
// The new_object instance should not be present in the committed registry.
|
|
Cache_element<typename T::Cache_partition> *element = NULL;
|
|
|
|
#ifndef DBUG_OFF
|
|
m_registry_committed.get(
|
|
static_cast<const typename T::Cache_partition *>(new_object), &element);
|
|
DBUG_ASSERT(!element);
|
|
#endif
|
|
|
|
DBUG_ASSERT(is_backup_lock_and_grl_acquired<T>(m_thd));
|
|
|
|
// Store dictionary objects with UTC time
|
|
Timestamp_timezone_guard ts(m_thd);
|
|
|
|
// new_object->id() may or may not be reflected in the uncommitted registry.
|
|
const typename T::Id_key id_key(new_object->id());
|
|
const T *old_object = nullptr;
|
|
m_registry_uncommitted.get(id_key, &element);
|
|
|
|
if (element) {
|
|
// If new_object->id() is present in the uncommitted registry, then
|
|
// that object is the previously stored object for this id, since the
|
|
// only way to enter the uncommitted registry is through store() or
|
|
// update().
|
|
old_object = dynamic_cast<const T *>(element->object());
|
|
} else {
|
|
// If not present, then the previously stored object can be acquire()'d
|
|
// in the usual way (a cache miss handled by ISO_READ_COMMITTED is fine,
|
|
// since the object hasn't been stored by this transaction yet anyway).
|
|
if (acquire(new_object->id(), &old_object)) return true;
|
|
}
|
|
|
|
// Either way, we now should have the previously stored object.
|
|
DBUG_ASSERT(old_object);
|
|
|
|
// Check proper MDL locks.
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, old_object));
|
|
DBUG_ASSERT(MDL_checker::is_write_locked(m_thd, new_object));
|
|
|
|
/*
|
|
We first store the new object. If store() fails, there is not a
|
|
lot to do except returning true.
|
|
*/
|
|
if (Storage_adapter::store(m_thd, new_object)) return true;
|
|
|
|
/*
|
|
Remove the old sdi after store() has successfully written the
|
|
new one. Note that this is a noop unless we are writing the SDI
|
|
to file and the name of the new object is different. (If the
|
|
names are identical the old file will be over-written by
|
|
store(). If we are storing the SDI in a tablespace the key
|
|
does not depend on the name and the store is a transactional
|
|
update).
|
|
*/
|
|
if (sdi::drop_after_update(m_thd, old_object, new_object)) {
|
|
return true;
|
|
}
|
|
|
|
if (element) {
|
|
// Remove and delete the old uncommitted object.
|
|
m_registry_uncommitted.remove(element);
|
|
// If we update an already updated object, don't delete it.
|
|
if (element->object() != new_object) {
|
|
delete element->object();
|
|
element->set_object(new_object);
|
|
}
|
|
element->recreate_keys();
|
|
m_registry_uncommitted.put(element);
|
|
} else {
|
|
register_uncommitted_object(new_object);
|
|
}
|
|
|
|
// Remove the new object from the auto deleter.
|
|
no_auto_delete<T>(new_object);
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
void Dictionary_client::register_uncommitted_object(T *object) {
|
|
Cache_element<typename T::Cache_partition> *element = nullptr;
|
|
|
|
// Avoid registering uncommitted object during attachable read-write
|
|
// transaction processing.
|
|
DBUG_ASSERT(!m_thd->is_attachable_rw_transaction_active());
|
|
|
|
#ifndef DBUG_OFF
|
|
// Make sure we do not sign up a shared object for auto delete.
|
|
m_registry_committed.get(
|
|
static_cast<const typename T::Cache_partition *>(object), &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
|
|
// We need a top level auto releaser to make sure the uncommitted objects
|
|
// are removed. This is done in the auto releaser destructor. When
|
|
// renove_uncommitted_objects() is called implicitly as part of commit/
|
|
// rollback, this should not be necessary.
|
|
DBUG_ASSERT(m_current_releaser != &m_default_releaser);
|
|
|
|
// store() should have been called before if this is a
|
|
// new object so that it has a proper ID already.
|
|
DBUG_ASSERT(object->id() != INVALID_OBJECT_ID);
|
|
|
|
// Make sure the same id is not present in the dropped registry.
|
|
const typename T::Id_key id_key(object->id());
|
|
m_registry_dropped.get(id_key, &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
#endif
|
|
|
|
element = new Cache_element<typename T::Cache_partition>();
|
|
element->set_object(object);
|
|
element->recreate_keys();
|
|
m_registry_uncommitted.put(element);
|
|
}
|
|
|
|
template <typename T>
|
|
void Dictionary_client::register_dropped_object(T *object) {
|
|
Cache_element<typename T::Cache_partition> *element = nullptr;
|
|
#ifndef DBUG_OFF
|
|
// Make sure we do not sign up a shared object for auto delete.
|
|
m_registry_committed.get(
|
|
static_cast<const typename T::Cache_partition *>(object), &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
|
|
// We need a top level auto releaser to make sure the dropped objects
|
|
// are removed. This is done in the auto releaser destructor. When
|
|
// renove_uncommitted_objects() is called implicitly as part of commit/
|
|
// rollback, this should not be necessary.
|
|
DBUG_ASSERT(m_current_releaser != &m_default_releaser);
|
|
|
|
// store() should have been called before if this is a
|
|
// new object so that it has a proper ID already.
|
|
DBUG_ASSERT(object->id() != INVALID_OBJECT_ID);
|
|
#endif
|
|
|
|
// Could be in the uncommitted registry, remove and delete.
|
|
const typename T::Id_key id_key(object->id());
|
|
typename T::Cache_partition *modified = nullptr;
|
|
bool dropped = false;
|
|
acquire_uncommitted(id_key, &modified, &dropped);
|
|
DBUG_ASSERT(!dropped);
|
|
if (modified != nullptr) {
|
|
m_registry_uncommitted.get(
|
|
static_cast<const typename T::Cache_partition *>(modified), &element);
|
|
DBUG_ASSERT(element != nullptr);
|
|
m_registry_uncommitted.remove(element);
|
|
DBUG_ASSERT(element->object() != object);
|
|
delete element->object();
|
|
// The element is reused below, so we don't delete it.
|
|
}
|
|
|
|
if (element == nullptr)
|
|
element = new Cache_element<typename T::Cache_partition>();
|
|
|
|
element->set_object(object);
|
|
element->recreate_keys();
|
|
|
|
// Check if the object is already registered. This could happen if
|
|
// the object is dropped twice in the same statement. Currently
|
|
// this is possible when updating view metadata since alter view
|
|
// is implemented as drop+create. We have to look up on name since
|
|
// the ID has changed.
|
|
Cache_element<typename T::Cache_partition> *dropped_ele = nullptr;
|
|
m_registry_dropped.get(*element->name_key(), &dropped_ele);
|
|
if (dropped_ele != nullptr) {
|
|
// We have dropped an object with the same name earlier.
|
|
// Remove the old object so that we can insert the new
|
|
// object without getting key conflicts.
|
|
// Note: This means that the previously dropped object can
|
|
// now be retrieved again with the old ID!
|
|
m_registry_dropped.remove(dropped_ele);
|
|
delete dropped_ele->object();
|
|
delete dropped_ele;
|
|
}
|
|
|
|
m_registry_dropped.put(element);
|
|
}
|
|
|
|
template <typename T>
|
|
void Dictionary_client::remove_uncommitted_objects(
|
|
bool commit_to_shared_cache) {
|
|
#ifndef DBUG_OFF
|
|
// Note: The ifdef'ed block below is only for consistency checks in
|
|
// debug builds.
|
|
typename Multi_map_base<typename T::Cache_partition>::Const_iterator it;
|
|
for (it = m_registry_dropped.begin<typename T::Cache_partition>();
|
|
it != m_registry_dropped.end<typename T::Cache_partition>(); it++) {
|
|
const typename T::Cache_partition *dropped_object = it->second->object();
|
|
DBUG_ASSERT(dropped_object != nullptr);
|
|
|
|
// Checking proper MDL lock is skipped here because when dropping a
|
|
// schema, the implementation of the MDL checking does not work properly.
|
|
|
|
// Make sure that dropped object ids are not present persistently with
|
|
// isolation level READ UNCOMMITTED.
|
|
const typename T::Id_key id_key(dropped_object->id());
|
|
|
|
// Fetch the dictionary object by PK from the DD tables, and verify that
|
|
// it's not available, but only if:
|
|
// - This is not a DD system thread (due to SE being faked).
|
|
// - The transaction is being committed, not rolled back.
|
|
// - We're not allowing direct access to DD tables.
|
|
if (!m_thd->is_dd_system_thread() && commit_to_shared_cache &&
|
|
DBUG_EVALUATE_IF("skip_dd_table_access_check", false, true)) {
|
|
const typename T::Cache_partition *stored_object = nullptr;
|
|
if (!Shared_dictionary_cache::instance()->get_uncached(
|
|
m_thd, id_key, ISO_READ_UNCOMMITTED, &stored_object))
|
|
DBUG_ASSERT(stored_object == nullptr);
|
|
}
|
|
|
|
// Make sure that dropped object ids are not present in the shared cache.
|
|
DBUG_ASSERT(
|
|
!(Shared_dictionary_cache::instance()
|
|
->available<typename T::Id_key, typename T::Cache_partition>(
|
|
id_key)));
|
|
|
|
// Make sure that dropped object ids are not present in the uncommitted
|
|
// registry.
|
|
Cache_element<typename T::Cache_partition> *element = nullptr;
|
|
m_registry_uncommitted.get(id_key, &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
|
|
// Make sure that dropped object ids are not present in the committed
|
|
// registry.
|
|
m_registry_committed.get(id_key, &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
}
|
|
#endif
|
|
if (commit_to_shared_cache) {
|
|
typename Multi_map_base<typename T::Cache_partition>::Const_iterator it;
|
|
for (it = m_registry_uncommitted.begin<typename T::Cache_partition>();
|
|
it != m_registry_uncommitted.end<typename T::Cache_partition>();
|
|
it++) {
|
|
typename T::Cache_partition *uncommitted_object =
|
|
const_cast<typename T::Cache_partition *>(it->second->object());
|
|
DBUG_ASSERT(uncommitted_object != nullptr);
|
|
|
|
// Update the DD object in the core registry if applicable.
|
|
// Currently only for the dd tablespace to allow it to be encrypted.
|
|
dd::cache::Storage_adapter::instance()->core_update(it->second->object());
|
|
|
|
// Invalidate the entry in the shared cache (if present).
|
|
invalidate(uncommitted_object);
|
|
|
|
#ifndef DBUG_OFF
|
|
// Make sure the uncommitted id is not present in the dropped registry.
|
|
const typename T::Id_key key(uncommitted_object->id());
|
|
Cache_element<typename T::Cache_partition> *element = NULL;
|
|
m_registry_committed.get(key, &element);
|
|
DBUG_ASSERT(element == nullptr);
|
|
#endif
|
|
}
|
|
|
|
// The modified objects are evicted from the cache above. On the next
|
|
// acquisition, this will lead to a cache miss, which will make sure
|
|
// that e.g. the de-normalized FK parent information is corrected.
|
|
|
|
// However, if this is a DD system thread, and we are initializing the
|
|
// DD, the changes must be made visible in the shared cache. Otherwise,
|
|
// adding the cyclic FKs for the character sets table will fail.
|
|
if (m_thd->is_dd_system_thread() &&
|
|
bootstrap::DD_bootstrap_ctx::instance().get_stage() <
|
|
bootstrap::Stage::FINISHED) {
|
|
// We must do this in two iterations to handle situations where two
|
|
// uncommitted objects swap names.
|
|
for (it = m_registry_uncommitted.begin<typename T::Cache_partition>();
|
|
it != m_registry_uncommitted.end<typename T::Cache_partition>();
|
|
it++) {
|
|
typename T::Cache_partition *uncommitted_object =
|
|
const_cast<typename T::Cache_partition *>(it->second->object());
|
|
DBUG_ASSERT(uncommitted_object != nullptr);
|
|
|
|
Cache_element<typename T::Cache_partition> *element = NULL;
|
|
|
|
// In put, the reference counter is stepped up, so this is safe.
|
|
Shared_dictionary_cache::instance()->put(
|
|
static_cast<const typename T::Cache_partition *>(
|
|
uncommitted_object->clone()),
|
|
&element);
|
|
|
|
m_registry_committed.put(element);
|
|
// Sign up for auto release.
|
|
m_current_releaser->auto_release(element);
|
|
}
|
|
}
|
|
} // commit_to_shared_cache
|
|
m_registry_uncommitted.erase<typename T::Cache_partition>();
|
|
m_registry_dropped.erase<typename T::Cache_partition>();
|
|
}
|
|
|
|
void Dictionary_client::rollback_modified_objects() {
|
|
remove_uncommitted_objects<Abstract_table>(false);
|
|
remove_uncommitted_objects<Schema>(false);
|
|
remove_uncommitted_objects<Tablespace>(false);
|
|
remove_uncommitted_objects<Charset>(false);
|
|
remove_uncommitted_objects<Collation>(false);
|
|
remove_uncommitted_objects<Column_statistics>(false);
|
|
remove_uncommitted_objects<Event>(false);
|
|
remove_uncommitted_objects<Routine>(false);
|
|
remove_uncommitted_objects<Spatial_reference_system>(false);
|
|
remove_uncommitted_objects<Resource_group>(false);
|
|
}
|
|
|
|
void Dictionary_client::commit_modified_objects() {
|
|
remove_uncommitted_objects<Abstract_table>(true);
|
|
remove_uncommitted_objects<Schema>(true);
|
|
remove_uncommitted_objects<Tablespace>(true);
|
|
remove_uncommitted_objects<Charset>(true);
|
|
remove_uncommitted_objects<Collation>(true);
|
|
remove_uncommitted_objects<Column_statistics>(true);
|
|
remove_uncommitted_objects<Event>(true);
|
|
remove_uncommitted_objects<Routine>(true);
|
|
remove_uncommitted_objects<Spatial_reference_system>(true);
|
|
remove_uncommitted_objects<Resource_group>(true);
|
|
}
|
|
|
|
// Debug dump of the client and its registry to stderr.
|
|
/* purecov: begin inspected */
|
|
template <typename T>
|
|
void Dictionary_client::dump() const {
|
|
#ifndef DBUG_OFF
|
|
fprintf(stderr, "================================\n");
|
|
fprintf(stderr, "Dictionary client (committed)\n");
|
|
m_registry_committed.dump<T>();
|
|
fprintf(stderr, "Dictionary client (uncommitted)\n");
|
|
m_registry_uncommitted.dump<T>();
|
|
fprintf(stderr, "Dictionary client (dropped)\n");
|
|
m_registry_dropped.dump<T>();
|
|
fprintf(stderr, "Dictionary client (uncached)\n");
|
|
for (std::vector<Entity_object *>::const_iterator it =
|
|
m_uncached_objects.begin();
|
|
it != m_uncached_objects.end(); it++) {
|
|
if (*it != nullptr)
|
|
fprintf(stderr, "id=%llu, name= %s\n", (*it)->id(),
|
|
(*it)->name().c_str());
|
|
else
|
|
fprintf(stderr, "nullptr\n");
|
|
}
|
|
fprintf(stderr, "================================\n");
|
|
#endif
|
|
}
|
|
/* purecov: end */
|
|
|
|
// The explicit instantiation of the template members below
|
|
// is not handled well by doxygen, so we enclose this in a
|
|
// cond/endcon block. Documenting these does not add much
|
|
// value anyway, if the member definitions were in a header
|
|
// file, the compiler would do the instantiation for us.
|
|
|
|
/**
|
|
@cond
|
|
*/
|
|
|
|
// Explicitly instantiate the types for the various usages.
|
|
template bool Dictionary_client::fetch_schema_components(
|
|
const Schema *, std::vector<const Abstract_table *> *);
|
|
|
|
template bool Dictionary_client::fetch_schema_components(
|
|
const Schema *, std::vector<const Table *> *);
|
|
|
|
template bool Dictionary_client::fetch_schema_components(
|
|
const Schema *, std::vector<const View *> *);
|
|
|
|
template bool Dictionary_client::fetch_schema_components(
|
|
const Schema *, std::vector<const Event *> *);
|
|
|
|
template bool Dictionary_client::fetch_schema_components(
|
|
const Schema *, std::vector<const Routine *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Charset *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Collation *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Schema *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Tablespace *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Table *> *);
|
|
|
|
template bool Dictionary_client::fetch_global_components(
|
|
std::vector<const Resource_group *> *);
|
|
|
|
template bool Dictionary_client::fetch_schema_component_names<Event>(
|
|
const Schema *, std::vector<String_type> *) const;
|
|
|
|
template bool Dictionary_client::fetch_schema_component_names<Trigger>(
|
|
const Schema *, std::vector<String_type> *) const;
|
|
|
|
template bool Dictionary_client::fetch_global_component_ids<Table>(
|
|
std::vector<Object_id> *) const;
|
|
|
|
template bool Dictionary_client::fetch_global_component_names<Tablespace>(
|
|
std::vector<String_type> *) const;
|
|
|
|
template bool Dictionary_client::fetch_global_component_names<Schema>(
|
|
std::vector<String_type> *) const;
|
|
|
|
template bool Dictionary_client::fetch_referencing_views_object_id<View_table>(
|
|
const char *schema, const char *tbl_or_sf_name,
|
|
std::vector<Object_id> *view_ids) const;
|
|
|
|
template bool Dictionary_client::fetch_referencing_views_object_id<
|
|
View_routine>(const char *schema, const char *tbl_or_sf_name,
|
|
std::vector<Object_id> *view_ids) const;
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Abstract_table **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &,
|
|
const Abstract_table **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
const String_type &,
|
|
Abstract_table **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Abstract_table>(
|
|
bool);
|
|
template bool Dictionary_client::drop(const Abstract_table *);
|
|
template bool Dictionary_client::store(Abstract_table *);
|
|
template bool Dictionary_client::update(Abstract_table *);
|
|
template void Dictionary_client::dump<Abstract_table>() const;
|
|
|
|
template bool Dictionary_client::acquire(Object_id, dd::Charset const **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
dd::Charset **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Charset>(bool);
|
|
template bool Dictionary_client::acquire(String_type const &, Charset const **);
|
|
template bool Dictionary_client::acquire(String_type const &, Schema const **);
|
|
template bool Dictionary_client::acquire_for_modification(String_type const &,
|
|
dd::Charset **);
|
|
|
|
template bool Dictionary_client::drop(const Charset *);
|
|
template bool Dictionary_client::store(Charset *);
|
|
template bool Dictionary_client::update(Charset *);
|
|
template void Dictionary_client::dump<Charset>() const;
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Charset **);
|
|
template bool Dictionary_client::acquire(Object_id, dd::Collation const **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
dd::Collation **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Collation>(bool);
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Collation **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const Collation **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
Collation **);
|
|
template bool Dictionary_client::drop(const Collation *);
|
|
template bool Dictionary_client::store(Collation *);
|
|
template bool Dictionary_client::update(Collation *);
|
|
template void Dictionary_client::dump<Collation>() const;
|
|
|
|
template bool Dictionary_client::acquire(Object_id, Schema const **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id, Schema **);
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Schema **);
|
|
template bool Dictionary_client::acquire_uncached_uncommitted(Object_id,
|
|
Schema **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
Schema **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Schema>(bool);
|
|
|
|
template bool Dictionary_client::drop(const Schema *);
|
|
template bool Dictionary_client::store(Schema *);
|
|
template bool Dictionary_client::update(Schema *);
|
|
template void Dictionary_client::dump<Schema>() const;
|
|
|
|
template bool Dictionary_client::acquire(Object_id,
|
|
const Spatial_reference_system **);
|
|
template bool Dictionary_client::acquire_for_modification(
|
|
Object_id, Spatial_reference_system **);
|
|
template bool Dictionary_client::acquire_uncached(Object_id,
|
|
Spatial_reference_system **);
|
|
template bool Dictionary_client::drop(const Spatial_reference_system *);
|
|
template bool Dictionary_client::store(Spatial_reference_system *);
|
|
template bool Dictionary_client::update(Spatial_reference_system *);
|
|
template void Dictionary_client::dump<Spatial_reference_system>() const;
|
|
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const Column_statistics **);
|
|
template bool Dictionary_client::acquire(Object_id, const Column_statistics **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
Column_statistics **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
Column_statistics **);
|
|
template bool Dictionary_client::acquire_uncached(Object_id,
|
|
Column_statistics **);
|
|
template bool Dictionary_client::drop(const Column_statistics *);
|
|
template bool Dictionary_client::store(Column_statistics *);
|
|
template bool Dictionary_client::update(Column_statistics *);
|
|
template void Dictionary_client::dump<Column_statistics>() const;
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Table **);
|
|
template bool Dictionary_client::acquire(Object_id, const Table **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id, Table **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &, const Table **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
const String_type &,
|
|
Table **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Table>(bool);
|
|
template bool Dictionary_client::drop(const Table *);
|
|
template bool Dictionary_client::store(Table *);
|
|
template bool Dictionary_client::update(Table *);
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Tablespace **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const Tablespace **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
Tablespace **);
|
|
template bool Dictionary_client::acquire(Object_id, const Tablespace **);
|
|
template bool Dictionary_client::acquire_uncached_uncommitted(Object_id,
|
|
Tablespace **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
Tablespace **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Tablespace>(bool);
|
|
template bool Dictionary_client::drop(const Tablespace *);
|
|
template bool Dictionary_client::store(Tablespace *);
|
|
template bool Dictionary_client::update(Tablespace *);
|
|
template void Dictionary_client::dump<Tablespace>() const;
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, View **);
|
|
template bool Dictionary_client::acquire_uncached_uncommitted(Object_id,
|
|
View **);
|
|
template bool Dictionary_client::acquire(Object_id, const View **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id, View **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &, const View **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
const String_type &,
|
|
View **);
|
|
template void Dictionary_client::remove_uncommitted_objects<View>(bool);
|
|
template bool Dictionary_client::drop(const View *);
|
|
template bool Dictionary_client::store(View *);
|
|
template bool Dictionary_client::update(View *);
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Event **);
|
|
template bool Dictionary_client::acquire(Object_id, const Event **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id, Event **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Event>(bool);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &, const Event **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
const String_type &,
|
|
Event **);
|
|
template bool Dictionary_client::drop(const Event *);
|
|
template bool Dictionary_client::store(Event *);
|
|
template bool Dictionary_client::update(Event *);
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Function **);
|
|
template bool Dictionary_client::acquire(Object_id, const Function **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &,
|
|
const Function **);
|
|
template bool Dictionary_client::drop(const Function *);
|
|
template bool Dictionary_client::store(Function *);
|
|
template bool Dictionary_client::update(Function *);
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Procedure **);
|
|
template bool Dictionary_client::acquire(Object_id, const Procedure **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
Procedure **);
|
|
template void Dictionary_client::remove_uncommitted_objects<Procedure>(bool);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const String_type &,
|
|
const Procedure **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
const String_type &,
|
|
Procedure **);
|
|
template bool Dictionary_client::drop(const Procedure *);
|
|
template bool Dictionary_client::store(Procedure *);
|
|
template bool Dictionary_client::update(Procedure *);
|
|
|
|
template bool Dictionary_client::drop(const Routine *);
|
|
template void Dictionary_client::remove_uncommitted_objects<Routine>(bool);
|
|
template bool Dictionary_client::update(Routine *);
|
|
|
|
template bool Dictionary_client::acquire<Function>(
|
|
const String_type &, const String_type &,
|
|
const Function::Cache_partition **);
|
|
template bool Dictionary_client::acquire<Procedure>(
|
|
const String_type &, const String_type &,
|
|
const Procedure::Cache_partition **);
|
|
template bool Dictionary_client::acquire_for_modification<Function>(
|
|
const String_type &, const String_type &, Function::Cache_partition **);
|
|
template bool Dictionary_client::acquire_for_modification<Procedure>(
|
|
const String_type &, const String_type &, Procedure::Cache_partition **);
|
|
|
|
template bool Dictionary_client::acquire_uncached(Object_id, Routine **);
|
|
template bool Dictionary_client::acquire_for_modification(Object_id,
|
|
Routine **);
|
|
template bool Dictionary_client::acquire(const String_type &,
|
|
const Resource_group **);
|
|
template bool Dictionary_client::acquire_for_modification(const String_type &,
|
|
Resource_group **);
|
|
template bool Dictionary_client::drop(const Resource_group *);
|
|
template bool Dictionary_client::store(Resource_group *);
|
|
template void Dictionary_client::remove_uncommitted_objects<Resource_group>(
|
|
bool);
|
|
template bool Dictionary_client::update(Resource_group *);
|
|
/**
|
|
@endcond
|
|
*/
|
|
|
|
} // namespace cache
|
|
} // namespace dd
|
|
|
|
namespace dd_cache_unittest {
|
|
void insert(dd::cache::SPI_lru_cache_owner_ptr &c, dd::Object_id id) {
|
|
c->insert(id, SPI_missing_type::TABLES);
|
|
}
|
|
bool is_cached(const dd::cache::SPI_lru_cache_owner_ptr &c, dd::Object_id id) {
|
|
return dd::cache::is_cached(c, id, SPI_missing_type::TABLES);
|
|
}
|
|
} // namespace dd_cache_unittest
|