polardbxengine/storage/innobase/dict/dict0dd.cc

6228 lines
198 KiB
C++

/*****************************************************************************
Copyright (c) 2017, 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
*****************************************************************************/
/** @file dict/dict0dd.cc
Data dictionary interface */
#ifndef UNIV_HOTBACKUP
#include <auto_thd.h>
#include <current_thd.h>
#include <sql/thd_raii.h>
#include <sql_backup_lock.h>
#include <sql_class.h>
#include <sql_thd_internal_api.h>
#include "item.h"
#else /* !UNIV_HOTBACKUP */
#include <my_base.h>
#endif /* !UNIV_HOTBACKUP */
#include <dd/properties.h>
#include "dict0crea.h"
#include "dict0dd.h"
#include "dict0dict.h"
#include "dict0mem.h"
#include "dict0priv.h"
#ifndef UNIV_HOTBACKUP
#include "dict0stats.h"
#endif /* !UNIV_HOTBACKUP */
#include "data0type.h"
#include "dict0dict.h"
#include "fil0fil.h"
#include "mach0data.h"
#include "rem0rec.h"
#ifndef UNIV_HOTBACKUP
#include "fts0priv.h"
#include "gis/rtree_support.h" // fetch_srs
#endif /* !UNIV_HOTBACKUP */
#include "srv0start.h"
#include "ut0crc32.h"
#ifndef UNIV_HOTBACKUP
#include "btr0sea.h"
#include "derror.h"
#include "fts0plugin.h"
#include "ha_innodb.h"
#include "ha_innopart.h"
#include "ha_prototypes.h"
#include "mysql/plugin.h"
#include "query_options.h"
#include "sql_base.h"
#include "sql_table.h"
#endif /* !UNIV_HOTBACKUP */
#include "lizard0dict.h"
#include "lizard0row.h"
#include "lizard0page.h"
#include "lizard0data0types.h"
const char *DD_instant_col_val_coder::encode(const byte *stream, size_t in_len,
size_t *out_len) {
cleanup();
m_result = UT_NEW_ARRAY_NOKEY(byte, in_len * 2);
char *result = reinterpret_cast<char *>(m_result);
for (size_t i = 0; i < in_len; ++i) {
uint8_t v1 = ((stream[i] & 0xF0) >> 4);
uint8_t v2 = (stream[i] & 0x0F);
result[i * 2] = (v1 < 10 ? '0' + v1 : 'a' + v1 - 10);
result[i * 2 + 1] = (v2 < 10 ? '0' + v2 : 'a' + v2 - 10);
}
*out_len = in_len * 2;
return (result);
}
const byte *DD_instant_col_val_coder::decode(const char *stream, size_t in_len,
size_t *out_len) {
ut_ad(in_len % 2 == 0);
cleanup();
m_result = UT_NEW_ARRAY_NOKEY(byte, in_len / 2);
for (size_t i = 0; i < in_len / 2; ++i) {
char c1 = stream[i * 2];
char c2 = stream[i * 2 + 1];
ut_ad(isdigit(c1) || (c1 >= 'a' && c1 <= 'f'));
ut_ad(isdigit(c2) || (c2 >= 'a' && c2 <= 'f'));
m_result[i] = ((isdigit(c1) ? c1 - '0' : c1 - 'a' + 10) << 4) +
((isdigit(c2) ? c2 - '0' : c2 - 'a' + 10));
}
*out_len = in_len / 2;
return (m_result);
}
#ifndef UNIV_HOTBACKUP
/** Check if the InnoDB index is consistent with dd::Index
@param[in] index InnoDB index
@param[in] dd_index dd::Index or dd::Partition_index
@return true if match
@retval false if not match */
template <typename Index>
static bool dd_index_match(const dict_index_t *index, const Index *dd_index) {
bool match = true;
/* Don't check the name for primary index, since internal index
name could be variant */
if (my_strcasecmp(system_charset_info, index->name(),
dd_index->name().c_str()) != 0 &&
strcmp(dd_index->name().c_str(), "PRIMARY") != 0) {
ib::warn(ER_IB_MSG_162)
<< "Index name in InnoDB is " << index->name()
<< " while index name in global DD is " << dd_index->name();
match = false;
}
const dd::Properties &p = dd_index->se_private_data();
uint64 id;
uint32 root;
uint64 trx_id;
ut_ad(p.exists(dd_index_key_strings[DD_INDEX_ID]));
p.get(dd_index_key_strings[DD_INDEX_ID], &id);
if (id != index->id) {
ib::warn(ER_IB_MSG_163)
<< "Index id in InnoDB is " << index->id << " while index id in"
<< " global DD is " << id;
match = false;
}
ut_ad(p.exists(dd_index_key_strings[DD_INDEX_ROOT]));
p.get(dd_index_key_strings[DD_INDEX_ROOT], &root);
if (root != index->page) {
ib::warn(ER_IB_MSG_164)
<< "Index root in InnoDB is " << index->page << " while index root in"
<< " global DD is " << root;
match = false;
}
ut_ad(p.exists(dd_index_key_strings[DD_INDEX_TRX_ID]));
p.get(dd_index_key_strings[DD_INDEX_TRX_ID], &trx_id);
/* For DD tables, the trx_id=0 is got from get_se_private_id().
TODO: index->trx_id is not expected to be 0 once Bug#25730513 is fixed*/
if (trx_id != 0 && index->trx_id != 0 && trx_id != index->trx_id) {
ib::warn(ER_IB_MSG_165) << "Index transaction id in InnoDB is "
<< index->trx_id << " while index transaction"
<< " id in global DD is " << trx_id;
match = false;
}
return (match);
}
/** Check if the InnoDB table is consistent with dd::Table
@param[in] table InnoDB table
@param[in] dd_table dd::Table or dd::Partition
@return true if match
@retval false if not match */
template <typename Table>
bool dd_table_match(const dict_table_t *table, const Table *dd_table) {
/* Temporary table has no metadata written */
if (dd_table == nullptr || table->is_temporary()) {
return (true);
}
bool match = true;
if (dd_table->se_private_id() != table->id) {
ib::warn(ER_IB_MSG_166)
<< "Table id in InnoDB is " << table->id
<< " while the id in global DD is " << dd_table->se_private_id();
match = false;
}
/* If tablespace is discarded, no need to check indexes */
if (dict_table_is_discarded(table)) {
return (match);
}
for (const auto dd_index : dd_table->indexes()) {
if (dd_table->tablespace_id() == dict_sys_t::s_dd_sys_space_id &&
dd_index->tablespace_id() != dd_table->tablespace_id()) {
ib::warn(ER_IB_MSG_167)
<< "Tablespace id in table is " << dd_table->tablespace_id()
<< ", while tablespace id in index " << dd_index->name() << " is "
<< dd_index->tablespace_id();
}
const dict_index_t *index = dd_find_index(table, dd_index);
ut_ad(index != nullptr);
if (!dd_index_match(index, dd_index)) {
match = false;
}
}
/* Tablespace and options can be checked here too */
return (match);
}
template bool dd_table_match<dd::Table>(const dict_table_t *,
const dd::Table *);
template bool dd_table_match<dd::Partition>(const dict_table_t *,
const dd::Partition *);
/** Release a metadata lock.
@param[in,out] thd current thread
@param[in,out] mdl metadata lock */
void dd_mdl_release(THD *thd, MDL_ticket **mdl) {
if (*mdl == nullptr) {
return;
}
dd::release_mdl(thd, *mdl);
*mdl = nullptr;
}
THD *dd_thd_for_undo(const trx_t *trx) {
return trx->mysql_thd == nullptr ? current_thd : trx->mysql_thd;
}
/** Check if current undo needs a MDL or not
@param[in] trx transaction
@return true if MDL is necessary, otherwise false */
bool dd_mdl_for_undo(const trx_t *trx) {
/* Try best to find a valid THD for checking, in case in background
rollback thread, trx doens't hold a mysql_thd */
THD *thd = dd_thd_for_undo(trx);
/* There are four cases for the undo to check here:
1. In recovery phase, binlog recover, there is no concurrent
user queries, so MDL is no necessary. In this case, thd is NULL.
2. In background rollback thread, there could be concurrent
user queries, so MDL is needed. In this case, thd is not NULL
3. In runtime transaction rollback, no need for MDL.
THD::transaction_rollback_request would be set.
4. In runtime asynchronous rollback, no need for MDL.
Check TRX_FORCE_ROLLBACK. */
return (thd != nullptr && !thd->transaction_rollback_request &&
((trx->in_innodb & TRX_FORCE_ROLLBACK) == 0));
}
/** Instantiate an InnoDB in-memory table metadata (dict_table_t)
based on a Global DD object.
@param[in,out] client data dictionary client
@param[in] dd_table Global DD table object
@param[in] dd_part Global DD partition or subpartition, or NULL
@param[in] tbl_name table name, or NULL if not known
@param[out] table InnoDB table (NULL if not found or loadable)
@param[in] thd Thread THD
@return error code
@retval 0 on success (DD_SUCCESS) */
int dd_table_open_on_dd_obj(dd::cache::Dictionary_client *client,
const dd::Table &dd_table,
const dd::Partition *dd_part, const char *tbl_name,
dict_table_t *&table, THD *thd) {
#ifdef UNIV_DEBUG
if (dd_part != nullptr) {
ut_ad(&dd_part->table() == &dd_table);
ut_ad(dd_table.se_private_id() == dd::INVALID_OBJECT_ID);
ut_ad(dd_table_is_partitioned(dd_table));
ut_ad(dd_part->parent_partition_id() == dd::INVALID_OBJECT_ID ||
dd_part->parent() != nullptr);
ut_ad(((dd_part->table().subpartition_type() != dd::Table::ST_NONE) ==
(dd_part->parent() != nullptr)));
}
#endif /* UNIV_DEBUG */
int error = 0;
const table_id_t table_id =
dd_part == nullptr ? dd_table.se_private_id() : dd_part->se_private_id();
const ulint fold = ut_fold_ull(table_id);
ut_ad(table_id != dd::INVALID_OBJECT_ID);
mutex_enter(&dict_sys->mutex);
HASH_SEARCH(id_hash, dict_sys->table_id_hash, fold, dict_table_t *, table,
ut_ad(table->cached), table->id == table_id);
if (table != nullptr) {
table->acquire();
}
mutex_exit(&dict_sys->mutex);
if (table != nullptr) {
return (0);
}
#ifdef UNIV_DEBUG
/* If this is a internal temporary table, it's impossible
to verify the MDL against the table name, because both the
database name and table name may be invalid for MDL */
if (tbl_name && !row_is_mysql_tmp_table_name(tbl_name)) {
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(tbl_name, db_buf, tbl_buf, nullptr, nullptr, nullptr);
if (dd_table_is_partitioned(dd_table)) {
/* If partitioned table, only double check the prefix of table name.
No need to check the partition separator because the '#' would be
converted to '?' by dd_parse_tbl_name(). */
tbl_buf[strlen(dd_table.name().c_str())] = '\0';
}
ut_ad(innobase_strcasecmp(dd_table.name().c_str(), tbl_buf) == 0);
}
#endif /* UNIV_DEBUG */
TABLE_SHARE ts;
dd::Schema *schema;
const char *table_cache_key;
size_t table_cache_key_len;
if (tbl_name != nullptr) {
schema = nullptr;
table_cache_key = tbl_name;
table_cache_key_len = dict_get_db_name_len(tbl_name);
} else {
error = client->acquire_uncached<dd::Schema>(dd_table.schema_id(), &schema);
if (error != 0) {
return (error);
}
table_cache_key = schema->name().c_str();
table_cache_key_len = schema->name().size();
}
init_tmp_table_share(thd, &ts, table_cache_key, table_cache_key_len,
dd_table.name().c_str(), "" /* file name */, nullptr);
error = open_table_def_suppress_invalid_meta_data(thd, &ts, dd_table);
if (error == 0) {
TABLE td;
error = open_table_from_share(thd, &ts, dd_table.name().c_str(), 0,
SKIP_NEW_HANDLER, 0, &td, false, &dd_table);
if (error == 0) {
char tmp_name[MAX_FULL_NAME_LEN + 1];
const char *tab_namep;
if (tbl_name) {
tab_namep = tbl_name;
} else {
char tmp_schema[MAX_DATABASE_NAME_LEN + 1];
char tmp_tablename[MAX_TABLE_NAME_LEN + 1];
tablename_to_filename(schema->name().c_str(), tmp_schema,
MAX_DATABASE_NAME_LEN + 1);
tablename_to_filename(dd_table.name().c_str(), tmp_tablename,
MAX_TABLE_NAME_LEN + 1);
snprintf(tmp_name, sizeof tmp_name, "%s/%s", tmp_schema, tmp_tablename);
tab_namep = tmp_name;
}
if (dd_part == nullptr) {
table = dd_open_table(client, &td, tab_namep, &dd_table, thd);
} else {
table = dd_open_table(client, &td, tab_namep, dd_part, thd);
}
closefrm(&td, false);
}
}
free_table_share(&ts);
return (error);
}
/** Load an InnoDB table definition by InnoDB table ID.
@param[in,out] thd current thread
@param[in,out] mdl metadata lock;
nullptr if we are resurrecting table IX locks in recovery
@param[in] table_id InnoDB table or partition ID
@return InnoDB table
@retval nullptr if the table is not found, or there was an error */
static dict_table_t *dd_table_open_on_id_low(THD *thd, MDL_ticket **mdl,
table_id_t table_id) {
char part_name[FN_REFLEN * 2];
const char *name_to_open = nullptr;
ut_ad(thd == nullptr || thd == current_thd);
#ifdef UNIV_DEBUG
btrsea_sync_check check(false);
ut_ad(!sync_check_iterate(check));
#endif
ut_ad(!srv_is_being_shutdown);
if (thd == nullptr) {
ut_ad(mdl == nullptr);
thd = current_thd;
}
/* During server startup, while recovering XA transaction we don't have THD.
The table should have been already in innodb cache if present in DD while
resurrecting transaction. We assume the table is not in DD and return. We
cannot continue anyway here with NULL THD. */
if (thd == nullptr) {
return (nullptr);
}
const dd::Table *dd_table;
const dd::Partition *dd_part = nullptr;
dd::cache::Dictionary_client *dc = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(dc);
/* Since we start with table se_private_id, and we do not have
table name, so we cannot MDL lock the table(name). So we will
try to get the table name without MDL protection, and verify later,
after we got the table name and MDL lock it. Thus a loop is needed
in case the verification failed, and another attempt is made until
all things matches */
for (;;) {
dd::String_type schema;
dd::String_type tablename;
if (dc->get_table_name_by_se_private_id(handler_name, table_id, &schema,
&tablename)) {
return (nullptr);
}
const bool not_table = schema.empty();
if (not_table) {
if (dc->get_table_name_by_partition_se_private_id(handler_name, table_id,
&schema, &tablename) ||
schema.empty()) {
return (nullptr);
}
}
/* Now we have tablename, and MDL locked it if necessary. */
if (mdl != nullptr) {
if (*mdl == nullptr &&
dd_mdl_acquire(thd, mdl, schema.c_str(), tablename.c_str())) {
return (nullptr);
}
ut_ad(*mdl != nullptr);
}
if (dc->acquire(schema, tablename, &dd_table) || dd_table == nullptr) {
if (mdl != nullptr) {
dd_mdl_release(thd, mdl);
}
return (nullptr);
}
const bool is_part = dd_table_is_partitioned(*dd_table);
/* Verify facts between dd_table and facts we know
1) Partiton table or not
2) Table ID matches or not
3) Table in InnoDB */
bool same_name = not_table == is_part &&
(not_table || dd_table->se_private_id() == table_id) &&
dd_table->engine() == handler_name;
/* Do more verification for partition table */
if (same_name && is_part) {
auto end = dd_table->leaf_partitions().end();
auto i =
std::search_n(dd_table->leaf_partitions().begin(), end, 1, table_id,
[](const dd::Partition *p, table_id_t id) {
return (p->se_private_id() == id);
});
if (i == end) {
same_name = false;
} else {
size_t name_len;
dd_part = *i;
ut_ad(dd_part_is_stored(dd_part));
/* For partition, we need to compose the
name. */
char tmp_schema[MAX_DATABASE_NAME_LEN + 1];
char tmp_tablename[MAX_TABLE_NAME_LEN + 1];
tablename_to_filename(schema.c_str(), tmp_schema,
MAX_DATABASE_NAME_LEN + 1);
tablename_to_filename(tablename.c_str(), tmp_tablename,
MAX_TABLE_NAME_LEN + 1);
snprintf(part_name, sizeof part_name, "%s/%s", tmp_schema,
tmp_tablename);
name_len = strlen(part_name);
Ha_innopart_share::create_partition_postfix(
part_name + name_len, FN_REFLEN - name_len, dd_part);
name_to_open = part_name;
}
}
/* facts do not match, retry */
if (!same_name) {
if (mdl != nullptr) {
dd_mdl_release(thd, mdl);
}
continue;
}
ut_ad(same_name);
break;
}
ut_ad(dd_part != nullptr || dd_table->se_private_id() == table_id);
ut_ad(dd_part == nullptr || dd_table == &dd_part->table());
ut_ad(dd_part == nullptr || dd_part->se_private_id() == table_id);
dict_table_t *ib_table = nullptr;
dd_table_open_on_dd_obj(dc, *dd_table, dd_part, name_to_open, ib_table, thd);
if (mdl && ib_table == nullptr) {
dd_mdl_release(thd, mdl);
}
return (ib_table);
}
#endif /* !UNIV_HOTBACKUP */
/** Check if access to a table should be refused.
@param[in,out] table InnoDB table or partition
@return error code
@retval 0 on success (DD_SUVCCESS) */
static MY_ATTRIBUTE((warn_unused_result)) int dd_check_corrupted(
dict_table_t *&table) {
if (table->is_corrupted()) {
if (dict_table_is_sdi(table->id)
#ifndef UNIV_HOTBACKUP
|| dict_table_is_system(table->id)
#endif /* !UNIV_HOTBACKUP */
) {
#ifndef UNIV_HOTBACKUP
my_error(ER_TABLE_CORRUPT, MYF(0), "", table->name.m_name);
#else /* !UNIV_HOTBACKUP */
ib::fatal(ER_IB_MSG_168) << "table is corrupt: " << table->name.m_name;
#endif /* !UNIV_HOTBACKUP */
} else {
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
#ifndef UNIV_HOTBACKUP
dd_parse_tbl_name(table->name.m_name, db_buf, tbl_buf, nullptr, nullptr,
nullptr);
my_error(ER_TABLE_CORRUPT, MYF(0), db_buf, tbl_buf);
#else /* !UNIV_HOTBACKUP */
ib::fatal(ER_IB_MSG_169) << "table is corrupt: " << table->name.m_name;
#endif /* !UNIV_HOTBACKUP */
}
table = nullptr;
return (HA_ERR_TABLE_CORRUPT);
}
dict_index_t *index = table->first_index();
if (!dict_table_is_sdi(table->id) && fil_space_get(index->space) == nullptr) {
#ifndef UNIV_HOTBACKUP
my_error(ER_TABLESPACE_MISSING, MYF(0), table->name.m_name);
#else /* !UNIV_HOTBACKUP */
ib::fatal(ER_IB_MSG_170)
<< "table space is missing: " << table->name.m_name;
#endif /* !UNIV_HOTBACKUP */
table = nullptr;
return (HA_ERR_TABLESPACE_MISSING);
}
/* Ignore missing tablespaces for secondary indexes. */
while ((index = index->next())) {
if (!index->is_corrupted() && fil_space_get(index->space) == nullptr) {
dict_set_corrupted(index);
}
}
return (0);
}
/** Open a persistent InnoDB table based on InnoDB table id, and
hold Shared MDL lock on it.
@param[in] table_id table identifier
@param[in,out] thd current MySQL connection (for mdl)
@param[in,out] mdl metadata lock (*mdl set if
table_id was found)
@param[in] dict_locked dict_sys mutex is held
@param[in] check_corruption check if the table is corrupted or not.
mdl=NULL if we are resurrecting table IX locks in recovery
@return table
@retval NULL if the table does not exist or cannot be opened */
dict_table_t *dd_table_open_on_id(table_id_t table_id, THD *thd,
MDL_ticket **mdl, bool dict_locked,
bool check_corruption) {
dict_table_t *ib_table;
const ulint fold = ut_fold_ull(table_id);
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
char full_name[MAX_FULL_NAME_LEN + 1];
if (!dict_locked) {
mutex_enter(&dict_sys->mutex);
}
HASH_SEARCH(id_hash, dict_sys->table_id_hash, fold, dict_table_t *, ib_table,
ut_ad(ib_table->cached), ib_table->id == table_id);
reopen:
if (ib_table == nullptr) {
#ifndef UNIV_HOTBACKUP
if (dict_table_is_sdi(table_id)) {
/* The table is SDI table */
space_id_t space_id = dict_sdi_get_space_id(table_id);
/* Create in-memory table oject for SDI table */
dict_index_t *sdi_index =
dict_sdi_create_idx_in_mem(space_id, false, 0, false);
if (sdi_index == nullptr) {
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
return (nullptr);
}
ib_table = sdi_index->table;
ut_ad(ib_table != nullptr);
ib_table->acquire();
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
} else {
mutex_exit(&dict_sys->mutex);
ib_table = dd_table_open_on_id_low(thd, mdl, table_id);
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
}
#else /* !UNIV_HOTBACKUP */
/* PRELIMINARY TEMPORARY WORKAROUND: is this ever used? */
bool not_hotbackup = false;
ut_a(not_hotbackup);
#endif /* !UNIV_HOTBACKUP */
} else if (mdl == nullptr || ib_table->is_temporary() ||
dict_table_is_sdi(ib_table->id)) {
if (dd_check_corrupted(ib_table)) {
ut_ad(ib_table == nullptr);
} else {
ib_table->acquire();
}
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
} else {
for (;;) {
#ifndef UNIV_HOTBACKUP
bool ret = dd_parse_tbl_name(ib_table->name.m_name, db_buf, tbl_buf,
nullptr, nullptr, nullptr);
#endif /* !UNIV_HOTBACKUP */
memset(full_name, 0, MAX_FULL_NAME_LEN + 1);
strcpy(full_name, ib_table->name.m_name);
ut_ad(!ib_table->is_temporary());
mutex_exit(&dict_sys->mutex);
#ifndef UNIV_HOTBACKUP
if (ret == false) {
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
return (nullptr);
}
if (dd_mdl_acquire(thd, mdl, db_buf, tbl_buf)) {
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
return (nullptr);
}
#endif /* !UNIV_HOTBACKUP */
/* Re-lookup the table after acquiring MDL. */
mutex_enter(&dict_sys->mutex);
HASH_SEARCH(id_hash, dict_sys->table_id_hash, fold, dict_table_t *,
ib_table, ut_ad(ib_table->cached), ib_table->id == table_id);
if (ib_table != nullptr) {
ulint namelen = strlen(ib_table->name.m_name);
/* The table could have been renamed. After
we release dict mutex before the old table
name is MDL locked. So we need to go back
to MDL lock the new name. */
if (namelen != strlen(full_name) ||
memcmp(ib_table->name.m_name, full_name, namelen)) {
#ifndef UNIV_HOTBACKUP
dd_mdl_release(thd, mdl);
#endif /* !UNIV_HOTBACKUP */
continue;
} else if (check_corruption && dd_check_corrupted(ib_table)) {
ut_ad(ib_table == nullptr);
} else if (ib_table->discard_after_ddl) {
#ifndef UNIV_HOTBACKUP
btr_drop_ahi_for_table(ib_table);
dict_table_remove_from_cache(ib_table);
#endif /* !UNIV_HOTBACKUP */
ib_table = nullptr;
#ifndef UNIV_HOTBACKUP
dd_mdl_release(thd, mdl);
#endif /* !UNIV_HOTBACKUP */
goto reopen;
} else {
ib_table->acquire_with_lock();
}
}
mutex_exit(&dict_sys->mutex);
break;
}
#ifndef UNIV_HOTBACKUP
ut_ad(*mdl != nullptr);
/* Now the table can't be found, release MDL,
let dd_table_open_on_id_low() do the lock, as table
name could be changed */
if (ib_table == nullptr) {
dd_mdl_release(thd, mdl);
ib_table = dd_table_open_on_id_low(thd, mdl, table_id);
if (ib_table == nullptr && *mdl != nullptr) {
dd_mdl_release(thd, mdl);
}
}
#else /* !UNIV_HOTBACKUP */
/* PRELIMINARY TEMPORARY WORKAROUND: is this ever used? */
bool not_hotbackup = false;
ut_a(not_hotbackup);
#endif /* !UNIV_HOTBACKUP */
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
}
ut_ad(dict_locked == mutex_own(&dict_sys->mutex));
return (ib_table);
}
#ifndef UNIV_HOTBACKUP
/** Set the discard flag for a non-partitioned dd table.
@param[in,out] thd current thread
@param[in] table InnoDB table
@param[in,out] table_def MySQL dd::Table to update
@param[in] discard discard flag
@return true if success
@retval false if fail. */
bool dd_table_discard_tablespace(THD *thd, const dict_table_t *table,
dd::Table *table_def, bool discard) {
bool ret = false;
DBUG_TRACE;
ut_ad(thd == current_thd);
#ifdef UNIV_DEBUG
btrsea_sync_check check(false);
ut_ad(!sync_check_iterate(check));
#endif /* UNIV_DEBUG */
ut_ad(!srv_is_being_shutdown);
if (table_def->se_private_id() != dd::INVALID_OBJECT_ID) {
ut_ad(table_def->table().leaf_partitions()->empty());
/* For discarding, we need to set new private
id to dd_table */
if (discard) {
/* Set the new private id to dd_table object. */
table_def->set_se_private_id(table->id);
} else {
ut_ad(table_def->se_private_id() == table->id);
}
/* Set index root page. */
for (auto dd_index : *table_def->indexes()) {
const dict_index_t *index = dd_find_index(table, dd_index);
ut_ad(index != nullptr);
dd::Properties &p = dd_index->se_private_data();
p.set(dd_index_key_strings[DD_INDEX_ROOT], index->page);
}
/* Set new table id for dd columns when it's importing
tablespace. */
if (!discard) {
for (auto dd_column : *table_def->columns()) {
dd_column->se_private_data().set(dd_index_key_strings[DD_TABLE_ID],
table->id);
}
}
/* Set discard flag. */
dd::Properties &p = table_def->se_private_data();
p.set(dd_table_key_strings[DD_TABLE_DISCARD], discard);
using Client = dd::cache::Dictionary_client;
using Releaser = dd::cache::Dictionary_client::Auto_releaser;
/* Get Tablespace object */
dd::Tablespace *dd_space = nullptr;
Client *client = dd::get_dd_client(thd);
Releaser releaser{client};
dd::Object_id dd_space_id =
(*table_def->indexes()->begin())->tablespace_id();
std::string space_name;
dd_filename_to_spacename(table->name.m_name, &space_name);
if (dd_tablespace_get_mdl(space_name.c_str())) {
ut_a(false);
}
if (client->acquire_for_modification(dd_space_id, &dd_space)) {
ut_a(false);
}
ut_a(dd_space != NULL);
dd_tablespace_set_state(
dd_space, discard ? DD_SPACE_STATE_DISCARDED : DD_SPACE_STATE_NORMAL);
if (client->update(dd_space)) {
ut_ad(0);
}
ret = true;
} else {
ret = false;
}
return ret;
}
/** Open an internal handle to a persistent InnoDB table by name.
@param[in,out] thd current thread
@param[out] mdl metadata lock
@param[in] name InnoDB table name
@param[in] dict_locked has dict_sys mutex locked
@param[in] ignore_err whether to ignore err
@return handle to non-partitioned table
@retval NULL if the table does not exist */
dict_table_t *dd_table_open_on_name(THD *thd, MDL_ticket **mdl,
const char *name, bool dict_locked,
ulint ignore_err) {
DBUG_TRACE;
#ifdef UNIV_DEBUG
btrsea_sync_check check(false);
ut_ad(!sync_check_iterate(check));
#endif
ut_ad(!srv_is_being_shutdown);
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
char part_buf[MAX_TABLE_NAME_LEN + 1];
char sub_buf[MAX_TABLE_NAME_LEN + 1];
bool skip_mdl = !(thd && mdl);
dict_table_t *table = nullptr;
/* Get pointer to a table object in InnoDB dictionary cache.
For intrinsic table, get it from session private data */
if (thd) {
table = thd_to_innodb_session(thd)->lookup_table_handler(name);
}
if (table != nullptr) {
table->acquire();
return table;
}
db_buf[0] = tbl_buf[0] = part_buf[0] = sub_buf[0] = '\0';
if (!dd_parse_tbl_name(name, db_buf, tbl_buf, part_buf, sub_buf, nullptr)) {
return nullptr;
}
if (!skip_mdl && dd_mdl_acquire(thd, mdl, db_buf, tbl_buf)) {
return nullptr;
}
if (!dict_locked) {
mutex_enter(&dict_sys->mutex);
}
table = dict_table_check_if_in_cache_low(name);
if (table != nullptr) {
table->acquire_with_lock();
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
return table;
}
mutex_exit(&dict_sys->mutex);
const dd::Table *dd_table = nullptr;
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
if (client->acquire(db_buf, tbl_buf, &dd_table) || dd_table == nullptr ||
dd_table->engine() != innobase_hton_name) {
/* The checking for engine should be only useful(valid)
for getting table statistics for IS. Two relevant API
functions are:
1. innobase_get_table_statistics
2. innobase_get_index_column_cardinality */
table = nullptr;
} else {
if (dd_table->se_private_id() == dd::INVALID_OBJECT_ID) {
ut_ad(!dd_table->leaf_partitions().empty());
if (strlen(part_buf) != 0) {
const dd::Partition *dd_part = nullptr;
for (auto part : dd_table->leaf_partitions()) {
if (part->parent() != nullptr) {
ut_ad(strlen(sub_buf) != 0);
if (part->name() == sub_buf && part->parent()->name() == part_buf) {
dd_part = part;
break;
}
} else if (part->name() == part_buf) {
dd_part = part;
break;
}
}
ut_ad(dd_part != nullptr);
dd_table_open_on_dd_obj(client, *dd_table, dd_part, name, table, thd);
} else {
/* FIXME: Once FK functions will not open
partitioned table in current improper way,
just assert this false */
table = nullptr;
}
} else {
ut_ad(dd_table->leaf_partitions().empty());
dd_table_open_on_dd_obj(client, *dd_table, nullptr, name, table, thd);
}
}
if (table && table->is_corrupted() &&
!(ignore_err & DICT_ERR_IGNORE_CORRUPT)) {
mutex_enter(&dict_sys->mutex);
table->release();
dict_table_remove_from_cache(table);
table = nullptr;
mutex_exit(&dict_sys->mutex);
}
if (table == nullptr && mdl) {
dd_mdl_release(thd, mdl);
*mdl = nullptr;
}
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
return table;
}
#endif /* !UNIV_HOTBACKUP */
/** Close an internal InnoDB table handle.
@param[in,out] table InnoDB table handle
@param[in,out] thd current MySQL connection (for mdl)
@param[in,out] mdl metadata lock (will be set NULL)
@param[in] dict_locked whether we hold dict_sys mutex */
void dd_table_close(dict_table_t *table, THD *thd, MDL_ticket **mdl,
bool dict_locked) {
dict_table_close(table, dict_locked, false);
#ifndef UNIV_HOTBACKUP
if (mdl != nullptr && *mdl != nullptr) {
ut_ad(!table->is_temporary());
dd_mdl_release(thd, mdl);
}
#endif /* !UNIV_HOTBACKUP */
}
#ifndef UNIV_HOTBACKUP
/** Replace the tablespace name in the file name.
@param[in] dd_file the tablespace file object.
@param[in] new_space_name new table space name to be updated in file name.
It must have already been converted to the
filename_charset such that
`d1/d2\d3`.`t3\t4/t5`
should look like:
d1@002fd2@005cd3/t3@005ct4@002ft5
both on Windows and on Linux.
@return None. */
static void replace_space_name_in_file_name(dd::Tablespace_file *dd_file,
dd::String_type new_space_name) {
ut_ad(std::count(new_space_name.begin(), new_space_name.end(),
Fil_path::DB_SEPARATOR) == 1);
/* Obtain the old tablespace file name. */
dd::String_type old_file_name = dd_file->filename();
/* We assume that old_file_name ends with:
OS_PATH_SEPARATOR + db_name + OS_PATH_SEPARATOR + table_name + dot_ext[IBD],
so on Windows it can look like:
.\d1@002fd2@005cd3\t1@002ft2@005ct3.ibd
and on Linux it could be:
./d1@002fd2@005cd3/t1@002ft2@005ct3.ibd */
ut_ad(std::count(old_file_name.begin(), old_file_name.end(),
OS_PATH_SEPARATOR) >= 2);
ut_ad(old_file_name.rfind(dot_ext[IBD]) ==
old_file_name.length() - strlen(dot_ext[IBD]));
/* Strip the last two components of the path (keep the slash) */
auto last_separator_pos = old_file_name.find_last_of(OS_PATH_SEPARATOR);
auto previous_separator_pos =
old_file_name.find_last_of(OS_PATH_SEPARATOR, last_separator_pos - 1);
old_file_name.resize(previous_separator_pos + 1);
/* Take care of path separators */
std::replace(new_space_name.begin(), new_space_name.end(),
Fil_path::DB_SEPARATOR, OS_PATH_SEPARATOR);
old_file_name += new_space_name + dot_ext[IBD];
/* Update the file name path */
dd_file->set_filename(old_file_name);
}
/** Update filename of dd::Tablespace
@param[in] dd_space_id DD tablespace id
@param[in] new_space_name New tablespace name
@param[in] new_path New data file path
@retval DB_SUCCESS on success. */
dberr_t dd_tablespace_rename(dd::Object_id dd_space_id,
const char *new_space_name, const char *new_path) {
THD *thd = current_thd;
std::string tablespace_name;
DBUG_TRACE;
#ifdef UNIV_DEBUG
btrsea_sync_check check(false);
ut_ad(!sync_check_iterate(check));
#endif /* UNIV_DEBUG */
ut_ad(!srv_is_being_shutdown);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
dd::Tablespace *dd_space = nullptr;
/* Get the dd tablespace */
if (client->acquire_uncached_uncommitted<dd::Tablespace>(dd_space_id,
&dd_space) ||
dd_space == nullptr) {
ut_ad(false);
return DB_ERROR;
}
MDL_ticket *src_ticket = nullptr;
if (dd_tablespace_get_mdl(dd_space->name().c_str(), &src_ticket)) {
ut_ad(false);
return DB_ERROR;
}
dd_filename_to_spacename(new_space_name, &tablespace_name);
MDL_ticket *dst_ticket = nullptr;
if (dd_tablespace_get_mdl(tablespace_name.c_str(), &dst_ticket)) {
ut_ad(false);
return DB_ERROR;
}
dd::Tablespace *new_space = nullptr;
/* Acquire the new dd tablespace for modification */
if (client->acquire_for_modification<dd::Tablespace>(dd_space_id,
&new_space)) {
ut_ad(false);
return DB_ERROR;
}
ut_ad(new_space->files().size() == 1);
dd::String_type old_space_name = new_space->name();
new_space->set_name(tablespace_name.c_str());
dd::Tablespace_file *dd_file =
const_cast<dd::Tablespace_file *>(*(new_space->files().begin()));
if (new_path != nullptr) {
dd_file->set_filename(new_path);
} else {
replace_space_name_in_file_name(dd_file, new_space_name);
ut_ad(dd_tablespace_get_state_enum(dd_space) == DD_SPACE_STATE_DISCARDED);
}
bool fail = client->update(new_space);
ut_ad(!fail);
dd::rename_tablespace_mdl_hook(thd, src_ticket, dst_ticket);
return fail ? DB_ERROR : DB_SUCCESS;
}
/** Validate the table format options.
@param[in] thd THD instance
@param[in] form MySQL table definition
@param[in] real_type real row type if it's not ROW_TYPE_NOT_USED
@param[in] zip_allowed whether ROW_FORMAT=COMPRESSED is OK
@param[in] strict whether innodb_strict_mode=ON
@param[out] is_redundant whether ROW_FORMAT=REDUNDANT
@param[out] blob_prefix whether ROW_FORMAT=DYNAMIC
or ROW_FORMAT=COMPRESSED
@param[out] zip_ssize log2(compressed page size),
or 0 if not ROW_FORMAT=COMPRESSED
@param[out] is_implicit if tablespace is implicit
@retval true if invalid (my_error will have been called)
@retval false if valid */
static bool format_validate(THD *thd, const TABLE *form, row_type real_type,
bool zip_allowed, bool strict, bool *is_redundant,
bool *blob_prefix, ulint *zip_ssize,
bool is_implicit) {
bool is_temporary = false;
ut_ad(thd != nullptr);
ut_ad(!zip_allowed || srv_page_size <= UNIV_ZIP_SIZE_MAX);
/* 1+log2(compressed_page_size), or 0 if not compressed */
*zip_ssize = 0;
const ulint zip_ssize_max =
std::min((ulint)UNIV_PAGE_SSIZE_MAX, (ulint)PAGE_ZIP_SSIZE_MAX);
const char *zip_refused = zip_allowed ? nullptr
: srv_page_size <= UNIV_ZIP_SIZE_MAX
? "innodb_file_per_table=OFF"
: "innodb_page_size>16k";
bool invalid = false;
if (real_type == ROW_TYPE_NOT_USED) {
real_type = form->s->real_row_type;
}
if (auto key_block_size = form->s->key_block_size) {
unsigned valid_zssize = 0;
char kbs[MY_INT32_NUM_DECIMAL_DIGITS + sizeof "KEY_BLOCK_SIZE=" + 1];
snprintf(kbs, sizeof kbs, "KEY_BLOCK_SIZE=%u", key_block_size);
for (unsigned kbsize = 1, zssize = 1; zssize <= zip_ssize_max;
zssize++, kbsize <<= 1) {
if (kbsize == key_block_size) {
valid_zssize = zssize;
break;
}
}
if (valid_zssize == 0) {
if (strict) {
my_error(ER_WRONG_VALUE, MYF(0), "KEY_BLOCK_SIZE",
kbs + sizeof "KEY_BLOCK_SIZE");
invalid = true;
} else {
push_warning_printf(thd, Sql_condition::SL_WARNING, ER_WRONG_VALUE,
ER_DEFAULT(ER_WRONG_VALUE), "KEY_BLOCK_SIZE",
kbs + sizeof "KEY_BLOCK_SIZE");
}
} else if (!zip_allowed) {
int error = is_temporary ? ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE
: ER_ILLEGAL_HA_CREATE_OPTION;
if (strict) {
my_error(error, MYF(0), innobase_hton_name, kbs, zip_refused);
invalid = true;
} else {
push_warning_printf(thd, Sql_condition::SL_WARNING, error,
ER_DEFAULT_NONCONST(error), innobase_hton_name, kbs,
zip_refused);
}
} else if (real_type != ROW_TYPE_COMPRESSED) {
/* This could happen when
1. There was an ALTER TABLE ... COPY to move
the table from COMPRESSED into DYNAMIC, etc.
2. For partitioned table, some partitions of which
could be of different row format from the specified
one */
} else if (form->s->row_type == ROW_TYPE_DEFAULT ||
form->s->row_type == ROW_TYPE_COMPRESSED) {
ut_ad(real_type == ROW_TYPE_COMPRESSED);
*zip_ssize = valid_zssize;
} else {
int error = is_temporary ? ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE
: ER_ILLEGAL_HA_CREATE_OPTION;
const char *conflict = get_row_format_name(form->s->row_type);
if (strict) {
my_error(error, MYF(0), innobase_hton_name, kbs, conflict);
invalid = true;
} else {
push_warning_printf(thd, Sql_condition::SL_WARNING, error,
ER_DEFAULT_NONCONST(error), innobase_hton_name, kbs,
conflict);
}
}
} else if (form->s->row_type != ROW_TYPE_COMPRESSED || !is_temporary) {
/* not ROW_FORMAT=COMPRESSED (nor KEY_BLOCK_SIZE),
or not TEMPORARY TABLE */
} else if (strict) {
my_error(ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE, MYF(0));
invalid = true;
} else {
push_warning(thd, Sql_condition::SL_WARNING,
ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE,
ER_THD(thd, ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE));
}
/* Check for a valid InnoDB ROW_FORMAT specifier and
other incompatibilities. */
rec_format_t innodb_row_format = REC_FORMAT_DYNAMIC;
switch (form->s->row_type) {
case ROW_TYPE_DYNAMIC:
ut_ad(*zip_ssize == 0);
/* If non strict_mode, row type can be converted between
COMPRESSED and DYNAMIC */
ut_ad(real_type == ROW_TYPE_DYNAMIC || real_type == ROW_TYPE_COMPRESSED);
break;
case ROW_TYPE_COMPACT:
ut_ad(*zip_ssize == 0);
ut_ad(real_type == ROW_TYPE_COMPACT);
innodb_row_format = REC_FORMAT_COMPACT;
break;
case ROW_TYPE_REDUNDANT:
ut_ad(*zip_ssize == 0);
ut_ad(real_type == ROW_TYPE_REDUNDANT);
innodb_row_format = REC_FORMAT_REDUNDANT;
break;
case ROW_TYPE_FIXED:
case ROW_TYPE_PAGED:
case ROW_TYPE_NOT_USED: {
const char *name = get_row_format_name(form->s->row_type);
if (strict) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), innobase_hton_name, name);
invalid = true;
} else {
push_warning_printf(
thd, Sql_condition::SL_WARNING, ER_ILLEGAL_HA_CREATE_OPTION,
ER_DEFAULT(ER_ILLEGAL_HA_CREATE_OPTION), innobase_hton_name, name);
}
}
/* fall through */
case ROW_TYPE_DEFAULT:
switch (real_type) {
case ROW_TYPE_FIXED:
case ROW_TYPE_PAGED:
case ROW_TYPE_NOT_USED:
case ROW_TYPE_DEFAULT:
/* get_real_row_type() should not return these */
ut_ad(0);
/* fall through */
case ROW_TYPE_DYNAMIC:
ut_ad(*zip_ssize == 0);
break;
case ROW_TYPE_COMPACT:
ut_ad(*zip_ssize == 0);
innodb_row_format = REC_FORMAT_COMPACT;
break;
case ROW_TYPE_REDUNDANT:
ut_ad(*zip_ssize == 0);
innodb_row_format = REC_FORMAT_REDUNDANT;
break;
case ROW_TYPE_COMPRESSED:
innodb_row_format = REC_FORMAT_COMPRESSED;
break;
}
if (*zip_ssize == 0) {
/* No valid KEY_BLOCK_SIZE was specified,
so do not imply ROW_FORMAT=COMPRESSED. */
if (innodb_row_format == REC_FORMAT_COMPRESSED) {
innodb_row_format = REC_FORMAT_DYNAMIC;
}
break;
}
/* fall through */
case ROW_TYPE_COMPRESSED:
if (is_temporary) {
if (strict) {
invalid = true;
}
/* ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE
was already reported. */
ut_ad(real_type == ROW_TYPE_DYNAMIC);
break;
} else if (zip_allowed && real_type == ROW_TYPE_COMPRESSED) {
/* ROW_FORMAT=COMPRESSED without KEY_BLOCK_SIZE
implies half the maximum compressed page size. */
if (*zip_ssize == 0) {
*zip_ssize = zip_ssize_max - 1;
}
innodb_row_format = REC_FORMAT_COMPRESSED;
break;
}
if (strict) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), innobase_hton_name,
"ROW_FORMAT=COMPRESSED", zip_refused);
invalid = true;
}
}
if (const char *algorithm =
form->s->compress.length > 0 ? form->s->compress.str : nullptr) {
Compression compression;
dberr_t err = Compression::check(algorithm, &compression);
if (err == DB_UNSUPPORTED) {
my_error(ER_WRONG_VALUE, MYF(0), "COMPRESSION", algorithm);
invalid = true;
} else if (compression.m_type != Compression::NONE) {
if (*zip_ssize != 0) {
if (strict) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), innobase_hton_name,
"COMPRESSION",
form->s->key_block_size ? "KEY_BLOCK_SIZE"
: "ROW_FORMAT=COMPRESSED");
invalid = true;
}
}
if (is_temporary) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), innobase_hton_name,
"COMPRESSION", "TEMPORARY");
invalid = true;
} else if (!is_implicit && strict) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), innobase_hton_name,
"COMPRESSION", "TABLESPACE");
invalid = true;
}
}
}
/* Check if there are any FTS indexes defined on this table. */
for (uint i = 0; i < form->s->keys; i++) {
const KEY *key = &form->key_info[i];
if ((key->flags & HA_FULLTEXT) && is_temporary) {
/* We don't support FTS indexes in temporary
tables. */
my_error(ER_INNODB_NO_FT_TEMP_TABLE, MYF(0));
return (true);
}
}
ut_ad((*zip_ssize == 0) == (innodb_row_format != REC_FORMAT_COMPRESSED));
*is_redundant = false;
*blob_prefix = false;
switch (innodb_row_format) {
case REC_FORMAT_REDUNDANT:
*is_redundant = true;
*blob_prefix = true;
break;
case REC_FORMAT_COMPACT:
*blob_prefix = true;
break;
case REC_FORMAT_COMPRESSED:
ut_ad(!is_temporary);
break;
case REC_FORMAT_DYNAMIC:
break;
}
return (invalid);
}
/** Set the AUTO_INCREMENT attribute.
@param[in,out] se_private_data dd::Table::se_private_data
@param[in] autoinc the auto-increment value */
void dd_set_autoinc(dd::Properties &se_private_data, uint64 autoinc) {
/* The value of "autoinc" here is the AUTO_INCREMENT attribute
specified at table creation. AUTO_INCREMENT=0 will silently
be treated as AUTO_INCREMENT=1. Likewise, if no AUTO_INCREMENT
attribute was specified, the value would be 0. */
if (autoinc > 0) {
/* InnoDB persists the "previous" AUTO_INCREMENT value. */
autoinc--;
}
uint64 version = 0;
if (se_private_data.exists(dd_table_key_strings[DD_TABLE_AUTOINC])) {
/* Increment the dynamic metadata version, so that
any previously buffered persistent dynamic metadata
will be ignored after this transaction commits. */
if (!se_private_data.get(dd_table_key_strings[DD_TABLE_VERSION],
&version)) {
version++;
} else {
/* incomplete se_private_data */
ut_ad(false);
}
}
se_private_data.set(dd_table_key_strings[DD_TABLE_VERSION], version);
se_private_data.set(dd_table_key_strings[DD_TABLE_AUTOINC], autoinc);
}
/** Copy the AUTO_INCREMENT and version attribute if exist.
@param[in] src dd::Table::se_private_data to copy from
@param[out] dest dd::Table::se_private_data to copy to */
void dd_copy_autoinc(const dd::Properties &src, dd::Properties &dest) {
uint64_t autoinc = 0;
uint64_t version = 0;
if (!src.exists(dd_table_key_strings[DD_TABLE_AUTOINC])) {
return;
}
if (src.get(dd_table_key_strings[DD_TABLE_AUTOINC],
reinterpret_cast<uint64 *>(&autoinc)) ||
src.get(dd_table_key_strings[DD_TABLE_VERSION],
reinterpret_cast<uint64 *>(&version))) {
ut_ad(0);
return;
}
dest.set(dd_table_key_strings[DD_TABLE_VERSION], version);
dest.set(dd_table_key_strings[DD_TABLE_AUTOINC], autoinc);
}
/** Copy the metadata of a table definition if there was an instant
ADD COLUMN happened. This should be done when it's not an ALTER TABLE
with rebuild.
@param[in,out] new_table New table definition
@param[in] old_table Old table definition */
void dd_copy_instant_n_cols(dd::Table &new_table, const dd::Table &old_table) {
ut_ad(dd_table_has_instant_cols(old_table));
if (!dd_table_has_instant_cols(new_table)) {
uint32_t cols;
old_table.se_private_data().get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&cols);
new_table.se_private_data().set(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
cols);
}
#ifdef UNIV_DEBUG
else {
uint32_t old_cols, new_cols;
old_table.se_private_data().get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&old_cols);
new_table.se_private_data().get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&new_cols);
ut_ad(old_cols == new_cols);
}
#endif /* UNIV_DEBUG */
}
/** Copy the engine-private parts of a table or partition definition
when the change does not affect InnoDB. This mainly copies the common
private data between dd::Table and dd::Partition
@tparam Table dd::Table or dd::Partition
@param[in,out] new_table Copy of old table or partition definition
@param[in] old_table Old table or partition definition */
template <typename Table>
void dd_copy_private(Table &new_table, const Table &old_table) {
uint64 autoinc = 0;
uint64 version = 0;
bool reset = false;
dd::Properties &se_private_data = new_table.se_private_data();
/* AUTOINC metadata could be set at the beginning for
non-partitioned tables. So already set metadata should be kept */
if (se_private_data.exists(dd_table_key_strings[DD_TABLE_AUTOINC])) {
se_private_data.get(dd_table_key_strings[DD_TABLE_AUTOINC], &autoinc);
se_private_data.get(dd_table_key_strings[DD_TABLE_VERSION], &version);
reset = true;
}
new_table.se_private_data().clear();
new_table.set_se_private_id(old_table.se_private_id());
new_table.set_se_private_data(old_table.se_private_data());
if (reset) {
se_private_data.set(dd_table_key_strings[DD_TABLE_VERSION], version);
se_private_data.set(dd_table_key_strings[DD_TABLE_AUTOINC], autoinc);
}
ut_ad(new_table.indexes()->size() == old_table.indexes().size());
/* Note that server could provide old and new dd::Table with
different index order in this case, so always do a double loop */
for (const auto old_index : old_table.indexes()) {
auto idx = new_table.indexes()->begin();
for (; (*idx)->name() != old_index->name(); ++idx)
;
ut_ad(idx != new_table.indexes()->end());
auto new_index = *idx;
ut_ad(!old_index->se_private_data().empty());
ut_ad(new_index != nullptr);
ut_ad(new_index->se_private_data().empty());
ut_ad(new_index->name() == old_index->name());
new_index->set_se_private_data(old_index->se_private_data());
new_index->set_tablespace_id(old_index->tablespace_id());
new_index->options().clear();
new_index->set_options(old_index->options());
}
new_table.table().set_row_format(old_table.table().row_format());
new_table.options().clear();
new_table.set_options(old_table.options());
}
template void dd_copy_private<dd::Table>(dd::Table &, const dd::Table &);
template void dd_copy_private<dd::Partition>(dd::Partition &,
const dd::Partition &);
/** Copy the engine-private parts of column definitions of a table.
@param[in,out] new_table Copy of old table
@param[in] old_table Old table */
void dd_copy_table_columns(dd::Table &new_table, const dd::Table &old_table) {
/* Columns in new table maybe more than old tables, when this is
called for adding instant columns. Also adding and dropping
virtual columns instantly is another case. */
for (const auto old_col : old_table.columns()) {
dd::Column *new_col = const_cast<dd::Column *>(
dd_find_column(&new_table, old_col->name().c_str()));
if (new_col == nullptr) {
ut_ad(old_col->is_virtual());
continue;
}
if (!old_col->se_private_data().empty()) {
if (!new_col->se_private_data().empty())
new_col->se_private_data().clear();
new_col->set_se_private_data(old_col->se_private_data());
}
}
}
void dd_part_adjust_table_id(dd::Table *new_table) {
ut_ad(dd_table_is_partitioned(*new_table));
auto part = new_table->leaf_partitions()->begin();
table_id_t table_id = (*part)->se_private_id();
for (auto dd_column : *new_table->table().columns()) {
dd_column->se_private_data().set(dd_index_key_strings[DD_TABLE_ID],
table_id);
}
}
/** Clear the instant ADD COLUMN information of a table
@param[in,out] dd_table dd::Table */
void dd_clear_instant_table(dd::Table &dd_table) {
ut_ad(dd_table_has_instant_cols(dd_table));
dd_table.se_private_data().remove(
dd_table_key_strings[DD_TABLE_INSTANT_COLS]);
ut_d(bool found = false);
for (auto col : *dd_table.columns()) {
dd::Properties &col_private = col->se_private_data();
if (col_private.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL])) {
ut_d(found = true);
col_private.remove(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL]);
} else if (col_private.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
ut_d(found = true);
col_private.remove(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT]);
}
}
ut_ad(found);
}
/** Clear the instant ADD COLUMN information of a partition, to make it
as a normal partition
@param[in,out] dd_part dd::Partition */
void dd_clear_instant_part(dd::Partition &dd_part) {
ut_ad(dd_part_has_instant_cols(dd_part));
dd_part.se_private_data().remove(
dd_partition_key_strings[DD_PARTITION_INSTANT_COLS]);
}
#ifdef UNIV_DEBUG
bool dd_instant_columns_exist(const dd::Table &dd_table) {
uint32_t n_cols = 0;
uint32_t non_instant_cols = 0;
bool found = false;
ut_ad(dd_table.se_private_data().exists(
dd_table_key_strings[DD_TABLE_INSTANT_COLS]));
dd_table.se_private_data().get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&n_cols);
for (auto col : dd_table.columns()) {
if (col->is_virtual() || col->is_se_hidden()) {
continue;
}
const dd::Properties &col_private = col->se_private_data();
if (col_private.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL]) ||
col_private.exists(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
found = true;
continue;
}
++non_instant_cols;
}
ut_ad(found);
/* Please note that n_cols could be 0 if the table only had some virtual
columns before instant ADD COLUMN. So below check should be sufficient */
ut_ad(non_instant_cols == n_cols);
return (found && non_instant_cols == n_cols);
}
#endif /* UNIV_DEBUG */
/** Add column default values for new instantly added columns
@param[in] old_table MySQL table as it is before the ALTER operation
@param[in] altered_table MySQL table that is being altered
@param[in,out] new_dd_table New dd::Table
@param[in] new_table New InnoDB table object */
void dd_add_instant_columns(const TABLE *old_table, const TABLE *altered_table,
dd::Table *new_dd_table,
const dict_table_t *new_table) {
DD_instant_col_val_coder coder;
uint32_t old_n_stored_cols = 0;
uint32_t old_cols = 0;
uint32_t new_cols = 0;
ut_d(uint32_t n_stored_checked = 0);
ut_d(uint16_t num_instant_cols = 0);
for (uint32_t i = 0; i < old_table->s->fields; ++i) {
if (!innobase_is_v_fld(old_table->field[i])) {
++old_n_stored_cols;
}
}
ut_ad(old_n_stored_cols <= old_table->s->fields);
ut_ad(altered_table->s->fields > old_table->s->fields);
/* Note that only the order of stored columns are cared, which means
1. Adding stored columns(along with virtual columns) at the end of the
table is absolutely fine.
2. Adding virtual columns before or after any existing stored columns
is fine.
3. Adding stored columns(along with virtual columns) before existing
trailing virtual columns
(especially for adding stored columns at the end of a table with functional
indexes) is also fine.
So need to find out which is the first stored column to be added. */
while (old_cols < old_table->s->fields &&
new_cols < altered_table->s->fields) {
if (innobase_is_v_fld(old_table->field[old_cols])) {
++old_cols;
continue;
}
if (innobase_is_v_fld(altered_table->field[new_cols])) {
++new_cols;
continue;
}
ut_ad(strcmp(old_table->field[old_cols]->field_name,
altered_table->field[new_cols]->field_name) == 0);
++old_cols;
++new_cols;
ut_d(++n_stored_checked);
}
ut_ad(old_cols == old_table->s->fields);
ut_ad(new_cols < altered_table->s->fields);
ut_ad(n_stored_checked == old_n_stored_cols);
for (uint32_t i = new_cols; i < altered_table->s->fields; ++i) {
Field *field = altered_table->field[i];
if (innobase_is_v_fld(field)) {
continue;
}
/* The MySQL type code has to fit in 8 bits
in the metadata stored in the InnoDB change buffer. */
ut_ad(field->charset() == nullptr ||
field->charset()->number <= MAX_CHAR_COLL_NUM);
ut_ad(field->charset() == nullptr || field->charset()->number > 0);
dd::Column *column = const_cast<dd::Column *>(
dd_find_column(new_dd_table, field->field_name));
ut_ad(column != nullptr);
dd::Properties &se_private = column->se_private_data();
ut_d(++num_instant_cols);
se_private.set(dd_index_key_strings[DD_TABLE_ID], new_table->id);
if (field->is_real_null()) {
se_private.set(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL],
true);
continue;
}
/* Get the mtype and prtype of this field. Keep this same
with the code in dd_fill_dict_table(), except FTS check */
ulint prtype = 0;
unsigned col_len = field->pack_length();
ulint nulls_allowed;
ulint unsigned_type;
ulint binary_type;
ulint long_true_varchar;
ulint charset_no;
ulint mtype = get_innobase_type_from_mysql_type(&unsigned_type, field);
nulls_allowed = field->real_maybe_null() ? 0 : DATA_NOT_NULL;
binary_type = field->binary() ? DATA_BINARY_TYPE : 0;
charset_no = 0;
if (dtype_is_string_type(mtype)) {
charset_no = static_cast<ulint>(field->charset()->number);
}
long_true_varchar = 0;
if (field->type() == MYSQL_TYPE_VARCHAR) {
col_len -= ((Field_varstring *)field)->length_bytes;
if (((Field_varstring *)field)->length_bytes == 2) {
long_true_varchar = DATA_LONG_TRUE_VARCHAR;
}
}
prtype =
dtype_form_prtype((ulint)field->type() | nulls_allowed | unsigned_type |
binary_type | long_true_varchar,
charset_no);
dict_col_t col;
memset(&col, 0, sizeof(dict_col_t));
/* Set a fake col_pos, since this should be useless */
dict_mem_fill_column_struct(&col, 0, mtype, prtype, col_len);
dfield_t dfield;
col.copy_type(dfield_get_type(&dfield));
ulint size = field->pack_length();
uint64_t buf;
const byte *mysql_data = field->ptr;
row_mysql_store_col_in_innobase_format(
&dfield, reinterpret_cast<byte *>(&buf), true, mysql_data, size,
dict_table_is_comp(new_table));
size_t length = 0;
const char *value = coder.encode(reinterpret_cast<byte *>(dfield.data),
dfield.len, &length);
dd::String_type default_value;
default_value.assign(dd::String_type(value, length));
se_private.set(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT],
default_value);
}
ut_ad(num_instant_cols > 0);
}
/** Compare the default values between imported column and column defined
in the server. Note that it's absolutely OK if there is no default value
in the column defined in server, since it can be filled in later.
@param[in] dd_col dd::Column
@param[in] col InnoDB column object
@return true The default values match
@retval false Not match */
bool dd_match_default_value(const dd::Column *dd_col, const dict_col_t *col) {
ut_ad(col->instant_default != nullptr);
const dd::Properties &private_data = dd_col->se_private_data();
if (private_data.exists(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
dd::String_type value;
const byte *default_value;
size_t len;
bool match;
DD_instant_col_val_coder coder;
private_data.get(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT], &value);
default_value = coder.decode(value.c_str(), value.length(), &len);
match = col->instant_default->len == len &&
memcmp(col->instant_default->value, default_value, len) == 0;
return (match);
} else if (private_data.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL])) {
return (col->instant_default->len == UNIV_SQL_NULL);
}
return (true);
}
/** Write default value of a column to dd::Column
@param[in] col default value of this column to write
@param[in,out] dd_col where to store the default value */
void dd_write_default_value(const dict_col_t *col, dd::Column *dd_col) {
if (col->instant_default->len == UNIV_SQL_NULL) {
dd_col->se_private_data().set(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL], true);
} else {
dd::String_type default_value;
size_t length = 0;
DD_instant_col_val_coder coder;
const char *value = coder.encode(col->instant_default->value,
col->instant_default->len, &length);
default_value.assign(dd::String_type(value, length));
dd_col->se_private_data().set(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT], default_value);
}
}
/** Parse the default value from dd::Column::se_private to dict_col_t
@param[in] se_private_data dd::Column::se_private
@param[in,out] col InnoDB column object
@param[in,out] heap Heap to store the default value */
static void dd_parse_default_value(const dd::Properties &se_private_data,
dict_col_t *col, mem_heap_t *heap) {
if (se_private_data.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL])) {
col->set_default(nullptr, UNIV_SQL_NULL, heap);
} else if (se_private_data.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
const byte *default_value;
size_t len;
dd::String_type value;
DD_instant_col_val_coder coder;
se_private_data.get(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT],
&value);
default_value = coder.decode(value.c_str(), value.length(), &len);
col->set_default(default_value, len, heap);
}
}
/** Import all metadata which is related to instant ADD COLUMN of a table
to dd::Table. This is used for IMPORT.
@param[in] table InnoDB table object
@param[in,out] dd_table dd::Table */
void dd_import_instant_add_columns(const dict_table_t *table,
dd::Table *dd_table) {
ut_ad(table->has_instant_cols());
ut_ad(dict_table_is_partition(table) == dd_table_is_partitioned(*dd_table));
if (!dd_table_is_partitioned(*dd_table)) {
dd_table->se_private_data().set(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
table->get_instant_cols());
} else {
uint32_t instant_cols = std::numeric_limits<uint32_t>::max();
if (dd_table->se_private_data().exists(
dd_table_key_strings[DD_TABLE_INSTANT_COLS])) {
dd_table->se_private_data().get(
dd_table_key_strings[DD_TABLE_INSTANT_COLS], &instant_cols);
}
if (instant_cols > table->get_instant_cols()) {
dd_table->se_private_data().set(
dd_table_key_strings[DD_TABLE_INSTANT_COLS],
table->get_instant_cols());
}
char postfix_name[FN_REFLEN];
dd::Partition *partition = nullptr;
for (const auto dd_part : *dd_table->leaf_partitions()) {
ut_d(size_t len =) Ha_innopart_share::create_partition_postfix(
postfix_name, FN_REFLEN, dd_part);
ut_ad(len < FN_REFLEN);
if (strstr(table->name.m_name, postfix_name) != 0) {
partition = dd_part;
break;
}
}
ut_ad(partition != nullptr);
partition->se_private_data().set(
dd_partition_key_strings[DD_PARTITION_INSTANT_COLS],
table->get_instant_cols());
}
/* Copy all default values if necessary */
ut_d(bool first_instant = false);
for (uint16_t i = 0; i < table->get_n_user_cols(); ++i) {
dict_col_t *col = table->get_col(i);
if (col->instant_default == nullptr) {
ut_ad(!first_instant);
continue;
}
ut_d(first_instant = true);
dd::Column *dd_col = const_cast<dd::Column *>(
dd_find_column(dd_table, table->get_col_name(i)));
ut_ad(dd_col != nullptr);
/* Default values mismatch should have been done.
So only write default value when it's not ever recorded */
if (!dd_col->se_private_data().exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL]) &&
!dd_col->se_private_data().exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
dd_write_default_value(col, dd_col);
}
}
}
/** Write metadata of a index to dd::Index
@param[in] dd_space_id Tablespace id, which server allocates
@param[in,out] dd_index dd::Index
@param[in] index InnoDB index object */
template <typename Index>
static void dd_write_index(dd::Object_id dd_space_id, Index *dd_index,
const dict_index_t *index) {
ut_ad(index->id != 0);
ut_ad(index->page >= FSP_FIRST_INODE_PAGE_NO);
dd_index->set_tablespace_id(dd_space_id);
dd::Properties &p = dd_index->se_private_data();
p.set(dd_index_key_strings[DD_INDEX_ID], index->id);
p.set(dd_index_key_strings[DD_INDEX_SPACE_ID], index->space);
p.set(dd_index_key_strings[DD_TABLE_ID], index->table->id);
p.set(dd_index_key_strings[DD_INDEX_ROOT], index->page);
p.set(dd_index_key_strings[DD_INDEX_TRX_ID], index->trx_id);
p.set(dd_index_key_strings[DD_INDEX_UBA], index->txn.uba);
p.set(dd_index_key_strings[DD_INDEX_SCN], index->txn.scn.load());
p.set(dd_index_key_strings[DD_INDEX_GCN], index->txn.gcn.load());
}
template void dd_write_index<dd::Index>(dd::Object_id, dd::Index *,
const dict_index_t *);
template void dd_write_index<dd::Partition_index>(dd::Object_id,
dd::Partition_index *,
const dict_index_t *);
/** Write metadata of a table to dd::Table
@tparam Table dd::Table or dd::Partition
@param[in] dd_space_id Tablespace id, which server allocates
@param[in,out] dd_table dd::Table or dd::Partition
@param[in] table InnoDB table object */
template <typename Table>
void dd_write_table(dd::Object_id dd_space_id, Table *dd_table,
const dict_table_t *table) {
/* Only set the tablespace id for tables in innodb_system tablespace */
if (dd_space_id == dict_sys_t::s_dd_sys_space_id) {
dd_table->set_tablespace_id(dd_space_id);
}
dd_table->set_se_private_id(table->id);
if (DICT_TF_HAS_DATA_DIR(table->flags)) {
ut_ad(dict_table_is_file_per_table(table));
dd_table->se_private_data().set(
dd_table_key_strings[DD_TABLE_DATA_DIRECTORY], true);
}
for (auto dd_index : *dd_table->indexes()) {
/* Don't assume the index orders are the same, even on
CREATE TABLE. This could be called from TRUNCATE path,
which would do some adjustment on FULLTEXT index, thus
the out-of-sync order */
const dict_index_t *index = dd_find_index(table, dd_index);
ut_ad(index != nullptr);
dd_write_index(dd_space_id, dd_index, index);
}
if (!dd_table_is_partitioned(dd_table->table()) ||
dd_part_is_first(reinterpret_cast<dd::Partition *>(dd_table))) {
for (auto dd_column : *dd_table->table().columns()) {
dd_column->se_private_data().set(dd_index_key_strings[DD_TABLE_ID],
table->id);
}
}
}
template void dd_write_table<dd::Table>(dd::Object_id, dd::Table *,
const dict_table_t *);
template void dd_write_table<dd::Partition>(dd::Object_id, dd::Partition *,
const dict_table_t *);
/** Set options of dd::Table according to InnoDB table object
@tparam Table dd::Table or dd::Partition
@param[in,out] dd_table dd::Table or dd::Partition
@param[in] table InnoDB table object */
template <typename Table>
void dd_set_table_options(Table *dd_table, const dict_table_t *table) {
dd::Table *dd_table_def = &(dd_table->table());
enum row_type type = ROW_TYPE_DEFAULT;
dd::Table::enum_row_format format = dd::Table::RF_DYNAMIC;
dd::Properties &options = dd_table_def->options();
switch (dict_tf_get_rec_format(table->flags)) {
case REC_FORMAT_REDUNDANT:
format = dd::Table::RF_REDUNDANT;
type = ROW_TYPE_REDUNDANT;
break;
case REC_FORMAT_COMPACT:
format = dd::Table::RF_COMPACT;
type = ROW_TYPE_COMPACT;
break;
case REC_FORMAT_COMPRESSED:
format = dd::Table::RF_COMPRESSED;
type = ROW_TYPE_COMPRESSED;
break;
case REC_FORMAT_DYNAMIC:
format = dd::Table::RF_DYNAMIC;
type = ROW_TYPE_DYNAMIC;
break;
default:
ut_a(0);
}
if (!dd_table_is_partitioned(*dd_table_def)) {
if (auto zip_ssize = DICT_TF_GET_ZIP_SSIZE(table->flags)) {
uint32 old_size;
if (!options.get("key_block_size", &old_size) && old_size != 0) {
options.set("key_block_size", 1 << (zip_ssize - 1));
}
} else {
options.set("key_block_size", 0);
/* It's possible that InnoDB ignores the specified
key_block_size, so check the block_size for every index.
Server assumes if block_size = 0, there should be no
option found, so remove it when found */
for (auto dd_index : *dd_table_def->indexes()) {
if (dd_index->options().exists("block_size")) {
dd_index->options().remove("block_size");
}
}
}
dd_table_def->set_row_format(format);
if (options.exists("row_type")) {
options.set("row_type", type);
}
} else if (dd_table_def->row_format() != format) {
dd_table->se_private_data().set(
dd_partition_key_strings[DD_PARTITION_ROW_FORMAT], format);
}
}
template void dd_set_table_options<dd::Table>(dd::Table *,
const dict_table_t *);
template void dd_set_table_options<dd::Partition>(dd::Partition *,
const dict_table_t *);
void dd_update_v_cols(dd::Table *dd_table, table_id_t id) {
for (auto dd_column : *dd_table->columns()) {
#ifdef UNIV_DEBUG
if (dd_column->se_private_data().exists(
dd_index_key_strings[DD_TABLE_ID])) {
table_id_t table_id;
dd_column->se_private_data().get(dd_index_key_strings[DD_TABLE_ID],
reinterpret_cast<uint64 *>(&table_id));
ut_ad(table_id == id);
}
#endif /* UNIV_DEBUG */
if (!dd_column->is_virtual()) {
continue;
}
dd::Properties &p = dd_column->se_private_data();
if (!p.exists(dd_index_key_strings[DD_TABLE_ID])) {
p.set(dd_index_key_strings[DD_TABLE_ID], id);
}
}
}
/** Write metadata of a tablespace to dd::Tablespace
@param[in,out] dd_space dd::Tablespace
@param[in] space_id InnoDB tablespace ID
@param[in] fsp_flags InnoDB tablespace flags
@param[in] state InnoDB tablespace state */
void dd_write_tablespace(dd::Tablespace *dd_space, space_id_t space_id,
uint32_t fsp_flags, dd_space_states state) {
dd::Properties &p = dd_space->se_private_data();
p.set(dd_space_key_strings[DD_SPACE_ID], space_id);
p.set(dd_space_key_strings[DD_SPACE_FLAGS], static_cast<uint32>(fsp_flags));
p.set(dd_space_key_strings[DD_SPACE_SERVER_VERSION],
DD_SPACE_CURRENT_SRV_VERSION);
p.set(dd_space_key_strings[DD_SPACE_VERSION], DD_SPACE_CURRENT_SPACE_VERSION);
p.set(dd_space_key_strings[DD_SPACE_STATE], dd_space_state_values[state]);
}
/** Add fts doc id column and index to new table
when old table has hidden fts doc id without fulltext index
@param[in,out] new_table New dd table
@param[in] old_table Old dd table */
void dd_add_fts_doc_id_index(dd::Table &new_table, const dd::Table &old_table) {
if (new_table.columns()->size() == old_table.columns().size()) {
ut_ad(new_table.indexes()->size() == old_table.indexes().size());
return;
}
ut_ad(new_table.columns()->size() + 1 == old_table.columns().size());
ut_ad(new_table.indexes()->size() + 1 == old_table.indexes().size());
/* Add hidden FTS_DOC_ID column */
dd::Column *col = new_table.add_column();
col->set_hidden(dd::Column::enum_hidden_type::HT_HIDDEN_SE);
col->set_name(FTS_DOC_ID_COL_NAME);
col->set_type(dd::enum_column_types::LONGLONG);
col->set_nullable(false);
col->set_unsigned(true);
col->set_collation_id(1);
/* Add hidden FTS_DOC_ID index */
dd_set_hidden_unique_index(new_table.add_index(), FTS_DOC_ID_INDEX_NAME, col);
return;
}
/** Find the specified dd::Index or dd::Partition_index in an InnoDB table
@tparam Index dd::Index or dd::Partition_index
@param[in] table InnoDB table object
@param[in] dd_index Index to search
@return the dict_index_t object related to the index */
template <typename Index>
const dict_index_t *dd_find_index(const dict_table_t *table, Index *dd_index) {
/* If the name is PRIMARY, return the first index directly,
because the internal index name could be 'GEN_CLUST_INDEX'.
It could be possible that the primary key name is not PRIMARY,
because it's an implicitly upgraded unique index. We have to
search all the indexes */
if (dd_index->name() == "PRIMARY") {
return (table->first_index());
}
/* The order could be different because all unique dd::Index(es)
would be in front of other indexes. */
const dict_index_t *index;
for (index = table->first_index();
(index != nullptr &&
(dd_index->name() != index->name() || !index->is_committed()));
index = index->next()) {
}
ut_ad(index != nullptr);
#ifdef UNIV_DEBUG
/* Never find another index with the same name */
const dict_index_t *next_index = index->next();
for (; (next_index != nullptr && (dd_index->name() != next_index->name() ||
!next_index->is_committed()));
next_index = next_index->next()) {
}
ut_ad(next_index == nullptr);
#endif /* UNIV_DEBUG */
return (index);
}
template const dict_index_t *dd_find_index<dd::Index>(const dict_table_t *,
dd::Index *);
template const dict_index_t *dd_find_index<dd::Partition_index>(
const dict_table_t *, dd::Partition_index *);
/** Create an index.
@param[in] dd_index DD Index
@param[in,out] table InnoDB table
@param[in] strict whether to be strict about the max record size
@param[in] form MySQL table structure
@param[in] key_num key_info[] offset
@return error code
@retval 0 on success
@retval HA_ERR_INDEX_COL_TOO_LONG if a column is too long
@retval HA_ERR_TOO_BIG_ROW if the record is too long */
static MY_ATTRIBUTE((warn_unused_result)) int dd_fill_one_dict_index(
const dd::Index *dd_index, dict_table_t *table, bool strict,
const TABLE_SHARE *form, uint key_num) {
const KEY &key = form->key_info[key_num];
ulint type = 0;
unsigned n_fields = key.user_defined_key_parts;
unsigned n_uniq = n_fields;
ut_ad(!mutex_own(&dict_sys->mutex));
/* This name cannot be used for a non-primary index */
ut_ad(key_num == form->primary_key ||
my_strcasecmp(system_charset_info, key.name, primary_key_name) != 0);
/* PARSER is only valid for FULLTEXT INDEX */
ut_ad((key.flags & (HA_FULLTEXT | HA_USES_PARSER)) != HA_USES_PARSER);
ut_ad(form->fields > 0);
ut_ad(n_fields > 0);
if (key.flags & HA_SPATIAL) {
ut_ad(!table->is_intrinsic());
type = DICT_SPATIAL;
ut_ad(n_fields == 1);
} else if (key.flags & HA_FULLTEXT) {
ut_ad(!table->is_intrinsic());
type = DICT_FTS;
n_uniq = 0;
} else if (key_num == form->primary_key) {
ut_ad(key.flags & HA_NOSAME);
ut_ad(n_uniq > 0);
type = DICT_CLUSTERED | DICT_UNIQUE;
} else {
type = (key.flags & HA_NOSAME) ? DICT_UNIQUE : 0;
}
ut_ad(!!(type & DICT_FTS) == (n_uniq == 0));
dict_index_t *index =
dict_mem_index_create(table->name.m_name, key.name, 0, type, n_fields);
index->n_uniq = n_uniq;
const ulint max_len = DICT_MAX_FIELD_LEN_BY_FORMAT(table);
DBUG_EXECUTE_IF("ib_create_table_fail_at_create_index",
dict_mem_index_free(index);
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), max_len);
return (HA_ERR_TOO_BIG_ROW););
for (unsigned i = 0; i < key.user_defined_key_parts; i++) {
const KEY_PART_INFO *key_part = &key.key_part[i];
unsigned prefix_len = 0;
const Field *field = key_part->field;
ut_ad(field == form->field[key_part->fieldnr - 1]);
ut_ad(field == form->field[field->field_index]);
if (field->is_virtual_gcol()) {
index->type |= DICT_VIRTUAL;
/* Whether it is a multi-value index */
if ((field->gcol_info->expr_item &&
field->gcol_info->expr_item->returns_array()) ||
field->is_array()) {
index->type |= DICT_MULTI_VALUE;
}
}
bool is_asc = true;
if (key_part->key_part_flag & HA_REVERSE_SORT) {
is_asc = false;
}
if (key.flags & HA_SPATIAL) {
prefix_len = 0;
} else if (key.flags & HA_FULLTEXT) {
prefix_len = 0;
} else if (key_part->key_part_flag & HA_PART_KEY_SEG) {
/* SPATIAL and FULLTEXT index always are on
full columns. */
ut_ad(!(key.flags & (HA_SPATIAL | HA_FULLTEXT)));
prefix_len = key_part->length;
ut_ad(prefix_len > 0);
} else {
ut_ad(key.flags & (HA_SPATIAL | HA_FULLTEXT) ||
(!is_blob(field->real_type()) &&
field->real_type() != MYSQL_TYPE_GEOMETRY) ||
key_part->length >= (field->type() == MYSQL_TYPE_VARCHAR
? field->key_length()
: field->pack_length()));
prefix_len = 0;
}
if ((key_part->length > max_len || prefix_len > max_len) &&
!(key.flags & (HA_FULLTEXT))) {
dict_mem_index_free(index);
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), max_len);
return (HA_ERR_INDEX_COL_TOO_LONG);
}
dict_col_t *col = nullptr;
if (innobase_is_v_fld(field)) {
dict_v_col_t *v_col =
dict_table_get_nth_v_col_mysql(table, field->field_index);
col = reinterpret_cast<dict_col_t *>(v_col);
} else {
ulint t_num_v = 0;
for (ulint z = 0; z < field->field_index; z++) {
if (innobase_is_v_fld(form->field[z])) {
t_num_v++;
}
}
col = &table->cols[field->field_index - t_num_v];
}
dict_index_add_col(index, table, col, prefix_len, is_asc);
}
ut_ad(((key.flags & HA_FULLTEXT) == HA_FULLTEXT) ==
!!(index->type & DICT_FTS));
index->n_user_defined_cols = key.user_defined_key_parts;
if (dict_index_add_to_cache(table, index, 0, FALSE) != DB_SUCCESS) {
ut_ad(0);
return (HA_ERR_GENERIC);
}
index = UT_LIST_GET_LAST(table->indexes);
if (index->type & DICT_FTS) {
ut_ad((key.flags & HA_FULLTEXT) == HA_FULLTEXT);
ut_ad(index->n_uniq == 0);
ut_ad(n_uniq == 0);
if (table->fts->cache == nullptr) {
DICT_TF2_FLAG_SET(table, DICT_TF2_FTS);
table->fts->cache = fts_cache_create(table);
rw_lock_x_lock(&table->fts->cache->init_lock);
/* Notify the FTS cache about this index. */
fts_cache_index_cache_create(table, index);
rw_lock_x_unlock(&table->fts->cache->init_lock);
}
}
if (strcmp(index->name, FTS_DOC_ID_INDEX_NAME) == 0) {
ut_ad(table->fts_doc_id_index == nullptr);
table->fts_doc_id_index = index;
}
if (dict_index_is_spatial(index)) {
ut_ad(dd_index->name() == key.name);
size_t geom_col_idx;
for (geom_col_idx = 0; geom_col_idx < dd_index->elements().size();
++geom_col_idx) {
if (!dd_index->elements()[geom_col_idx]->column().is_se_hidden()) break;
}
const dd::Column &col = dd_index->elements()[geom_col_idx]->column();
bool srid_has_value = col.srs_id().has_value();
index->fill_srid_value(srid_has_value ? col.srs_id().value() : 0,
srid_has_value);
}
return (0);
}
/** Parse MERGE_THRESHOLD value from a comment string.
@param[in] thd connection
@param[in] str string which might include 'MERGE_THRESHOLD='
@return value parsed
@retval dict_index_t::MERGE_THRESHOLD_DEFAULT for missing or invalid value. */
static ulint dd_parse_merge_threshold(THD *thd, const char *str) {
static constexpr char label[] = "MERGE_THRESHOLD=";
const char *pos = strstr(str, label);
if (pos != nullptr) {
pos += (sizeof label) - 1;
int ret = atoi(pos);
if (ret > 0 && unsigned(ret) <= DICT_INDEX_MERGE_THRESHOLD_DEFAULT) {
return (static_cast<ulint>(ret));
}
push_warning_printf(thd, Sql_condition::SL_WARNING, WARN_OPTION_IGNORED,
ER_DEFAULT(WARN_OPTION_IGNORED), "MERGE_THRESHOLD");
}
return (DICT_INDEX_MERGE_THRESHOLD_DEFAULT);
}
/** Copy attributes from MySQL TABLE_SHARE into an InnoDB table object.
@param[in,out] thd thread context
@param[in,out] table InnoDB table
@param[in] table_share TABLE_SHARE */
inline void dd_copy_from_table_share(THD *thd, dict_table_t *table,
const TABLE_SHARE *table_share) {
if (table->is_temporary()) {
dict_stats_set_persistent(table, false, true);
} else {
switch (table_share->db_create_options &
(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT)) {
default:
/* If a CREATE or ALTER statement contains
STATS_PERSISTENT=0 STATS_PERSISTENT=1,
it will be interpreted as STATS_PERSISTENT=1. */
case HA_OPTION_STATS_PERSISTENT:
dict_stats_set_persistent(table, true, false);
break;
case HA_OPTION_NO_STATS_PERSISTENT:
dict_stats_set_persistent(table, false, true);
break;
case 0:
break;
}
}
dict_stats_auto_recalc_set(
table, table_share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON,
table_share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF);
table->stats_sample_pages = table_share->stats_sample_pages;
const ulint merge_threshold_table =
table_share->comment.str
? dd_parse_merge_threshold(thd, table_share->comment.str)
: DICT_INDEX_MERGE_THRESHOLD_DEFAULT;
dict_index_t *index = table->first_index();
index->merge_threshold = merge_threshold_table;
if (dict_index_is_auto_gen_clust(index)) {
index = index->next();
}
for (uint i = 0; i < table_share->keys; i++) {
const KEY *key_info = &table_share->key_info[i];
ut_ad(index != nullptr);
if (key_info->flags & HA_USES_COMMENT && key_info->comment.str != nullptr) {
index->merge_threshold =
dd_parse_merge_threshold(thd, key_info->comment.str);
} else {
index->merge_threshold = merge_threshold_table;
}
index = index->next();
/* Skip hidden FTS_DOC_ID index */
if (index != nullptr && index->hidden) {
ut_ad(strcmp(index->name, FTS_DOC_ID_INDEX_NAME) == 0);
index = index->next();
}
}
#ifdef UNIV_DEBUG
if (index != nullptr) {
ut_ad(table_share->keys == 0);
ut_ad(index->hidden);
ut_ad(strcmp(index->name, FTS_DOC_ID_INDEX_NAME) == 0);
}
#endif
}
/** Instantiate index related metadata
@param[in,out] dd_table Global DD table metadata
@param[in] m_form MySQL table definition
@param[in,out] m_table InnoDB table definition
@param[in] create_info create table information
@param[in] zip_allowed if compression is allowed
@param[in] strict if report error in strict mode
@param[in] m_thd THD instance
@return 0 if successful, otherwise error number */
inline int dd_fill_dict_index(const dd::Table &dd_table, const TABLE *m_form,
dict_table_t *m_table,
HA_CREATE_INFO *create_info, bool zip_allowed,
bool strict, THD *m_thd) {
int error = 0;
ut_ad(!mutex_own(&dict_sys->mutex));
/* Create the keys */
if (m_form->s->keys == 0 || m_form->s->primary_key == MAX_KEY) {
/* Create an index which is used as the clustered index;
order the rows by the hidden InnoDB column DB_ROW_ID. */
dict_index_t *index = dict_mem_index_create(
m_table->name.m_name, "GEN_CLUST_INDEX", 0, DICT_CLUSTERED, 0);
index->n_uniq = 0;
dberr_t new_err =
dict_index_add_to_cache(m_table, index, index->page, FALSE);
if (new_err != DB_SUCCESS) {
error = HA_ERR_GENERIC;
goto dd_error;
}
} else {
/* In InnoDB, the clustered index must always be
created first. */
error = dd_fill_one_dict_index(dd_table.indexes()[m_form->s->primary_key],
m_table, strict, m_form->s,
m_form->s->primary_key);
if (error != 0) {
goto dd_error;
}
}
for (uint i = !m_form->s->primary_key; i < m_form->s->keys; i++) {
ulint dd_index_num = i + ((m_form->s->primary_key == MAX_KEY) ? 1 : 0);
error = dd_fill_one_dict_index(dd_table.indexes()[dd_index_num], m_table,
strict, m_form->s, i);
if (error != 0) {
goto dd_error;
}
}
if (dict_table_has_fts_index(m_table)) {
ut_ad(DICT_TF2_FLAG_IS_SET(m_table, DICT_TF2_FTS));
}
/* Create the ancillary tables that are common to all FTS indexes on
this table. */
if (DICT_TF2_FLAG_IS_SET(m_table, DICT_TF2_FTS_HAS_DOC_ID) ||
DICT_TF2_FLAG_IS_SET(m_table, DICT_TF2_FTS)) {
fts_doc_id_index_enum ret;
ut_ad(!m_table->is_intrinsic());
/* Check whether there already exists FTS_DOC_ID_INDEX */
ret = innobase_fts_check_doc_id_index_in_def(m_form->s->keys,
m_form->key_info);
switch (ret) {
case FTS_INCORRECT_DOC_ID_INDEX:
push_warning_printf(m_thd, Sql_condition::SL_WARNING,
ER_WRONG_NAME_FOR_INDEX,
" InnoDB: Index name %s is reserved"
" for the unique index on"
" FTS_DOC_ID column for FTS"
" Document ID indexing"
" on table %s. Please check"
" the index definition to"
" make sure it is of correct"
" type\n",
FTS_DOC_ID_INDEX_NAME, m_table->name.m_name);
if (m_table->fts) {
fts_free(m_table);
}
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), FTS_DOC_ID_INDEX_NAME);
return (HA_ERR_GENERIC);
case FTS_EXIST_DOC_ID_INDEX:
break;
case FTS_NOT_EXIST_DOC_ID_INDEX:
dict_index_t *doc_id_index;
doc_id_index = dict_mem_index_create(
m_table->name.m_name, FTS_DOC_ID_INDEX_NAME, 0, DICT_UNIQUE, 1);
doc_id_index->add_field(FTS_DOC_ID_COL_NAME, 0, true);
dberr_t new_err = dict_index_add_to_cache(m_table, doc_id_index,
doc_id_index->page, FALSE);
if (new_err != DB_SUCCESS) {
error = HA_ERR_GENERIC;
goto dd_error;
}
doc_id_index = UT_LIST_GET_LAST(m_table->indexes);
doc_id_index->hidden = true;
}
/* Cache all the FTS indexes on this table in the FTS
specific structure. They are used for FTS indexed
column update handling. */
if (dict_table_has_fts_index(m_table)) {
fts_t *fts = m_table->fts;
ut_a(fts != nullptr);
dict_table_get_all_fts_indexes(m_table, m_table->fts->indexes);
}
ulint fts_doc_id_col = ULINT_UNDEFINED;
ret = innobase_fts_check_doc_id_index(m_table, nullptr, &fts_doc_id_col);
if (ret != FTS_INCORRECT_DOC_ID_INDEX) {
ut_ad(m_table->fts->doc_col == ULINT_UNDEFINED);
m_table->fts->doc_col = fts_doc_id_col;
ut_ad(m_table->fts->doc_col != ULINT_UNDEFINED);
m_table->fts_doc_id_index =
dict_table_get_index_on_name(m_table, FTS_DOC_ID_INDEX_NAME);
}
}
if (error == 0) {
dd_copy_from_table_share(m_thd, m_table, m_form->s);
ut_ad(!m_table->is_temporary() ||
!dict_table_page_size(m_table).is_compressed());
if (!m_table->is_temporary()) {
dict_table_stats_latch_create(m_table, true);
}
} else {
dd_error:
mutex_enter(&dict_sys->mutex);
for (dict_index_t *f_index = UT_LIST_GET_LAST(m_table->indexes);
f_index != nullptr; f_index = UT_LIST_GET_LAST(m_table->indexes)) {
dict_index_remove_from_cache(m_table, f_index);
}
mutex_exit(&dict_sys->mutex);
dict_mem_table_free(m_table);
}
return (error);
}
/** Determine if a table contains a fulltext index.
@param[in] table dd::Table
@return whether the table contains any fulltext index */
inline bool dd_table_contains_fulltext(const dd::Table &table) {
for (const dd::Index *index : table.indexes()) {
if (index->type() == dd::Index::IT_FULLTEXT) {
return (true);
}
}
return (false);
}
/** Read the metadata of default values for all columns added instantly
@param[in] dd_table dd::Table
@param[in,out] table InnoDB table object */
static void dd_fill_instant_columns(const dd::Table &dd_table,
dict_table_t *table) {
ut_ad(table->has_instant_cols());
ut_ad(dd_table_has_instant_cols(dd_table));
#ifdef UNIV_DEBUG
for (uint16_t i = 0; i < table->get_n_cols(); ++i) {
ut_ad(table->get_col(i)->instant_default == nullptr);
}
#endif /* UNIV_DEBUG */
uint32_t skip = 0;
if (dd_table_is_partitioned(dd_table)) {
uint32_t cols;
dd_table.se_private_data().get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&cols);
ut_ad(cols <= table->get_instant_cols());
/* The dd::Columns should have `cols` default values,
however, this partition table only needs
`table->get_instant_cols()` default values. */
skip = table->get_instant_cols() - cols;
}
/* Assume the order of non-virtual columns are the same */
uint32_t innodb_pos = 0;
for (const auto col : dd_table.columns()) {
if (col->is_virtual() || col->is_se_hidden()) {
continue;
}
dict_col_t *column = table->get_col(innodb_pos++);
ut_ad(!column->is_virtual());
#ifdef UNIV_DEBUG
const char *name = table->col_names;
for (uint32_t i = 0; i < innodb_pos - 1; ++i) {
name += strlen(name) + 1;
}
ut_ad(col->name() == name);
#endif /* UNIV_DEBUG */
const dd::Properties &private_data = col->se_private_data();
if (!private_data.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL]) &&
!private_data.exists(
dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
continue;
}
if (skip > 0) {
--skip;
continue;
}
/* Note that it's before dict_table_add_to_cache(),
don't worry about the dict_sys->size. */
dd_parse_default_value(private_data, column, table->heap);
}
#ifdef UNIV_DEBUG
uint16_t n_default = 0;
for (uint16_t i = 0; i < table->get_n_user_cols(); ++i) {
if (table->get_col(i)->instant_default != nullptr) {
++n_default;
}
}
ut_ad(n_default + table->get_instant_cols() == table->get_n_user_cols());
#endif /* UNIV_DEBUG */
}
/** Instantiate in-memory InnoDB table metadata (dict_table_t),
without any indexes.
@tparam Table dd::Table or dd::Partition
@param[in] dd_tab Global Data Dictionary metadata,
or NULL for internal temporary table
@param[in] m_form MySQL TABLE for current table
@param[in] norm_name normalized table name
@param[in] create_info create info
@param[in] zip_allowed whether ROW_FORMAT=COMPRESSED is OK
@param[in] strict whether to use innodb_strict_mode=ON
@param[in] m_thd thread THD
@param[in] is_implicit if it is an implicit tablespace
@return created dict_table_t on success or nullptr */
template <typename Table>
static inline dict_table_t *dd_fill_dict_table(const Table *dd_tab,
const TABLE *m_form,
const char *norm_name,
HA_CREATE_INFO *create_info,
bool zip_allowed, bool strict,
THD *m_thd, bool is_implicit) {
mem_heap_t *heap;
bool is_encrypted = false;
bool is_discard = false;
ut_ad(dd_tab != nullptr);
ut_ad(m_thd != nullptr);
ut_ad(norm_name != nullptr);
ut_ad(create_info == nullptr || m_form->s->row_type == create_info->row_type);
ut_ad(create_info == nullptr ||
m_form->s->key_block_size == create_info->key_block_size);
ut_ad(dd_tab != nullptr);
if (m_form->s->fields > REC_MAX_N_USER_FIELDS) {
my_error(ER_TOO_MANY_FIELDS, MYF(0));
return (nullptr);
}
/* Set encryption option for file-per-table tablespace. */
dd::String_type encrypt;
if (dd_tab->table().options().exists("encrypt_type")) {
dd_tab->table().options().get("encrypt_type", &encrypt);
if (!Encryption::is_none(encrypt.c_str())) {
ut_ad(innobase_strcasecmp(encrypt.c_str(), "y") == 0);
is_encrypted = true;
}
}
/* Check discard flag. */
const dd::Properties &table_private = dd_tab->table().se_private_data();
if (table_private.exists(dd_table_key_strings[DD_TABLE_DISCARD])) {
table_private.get(dd_table_key_strings[DD_TABLE_DISCARD], &is_discard);
}
const unsigned n_mysql_cols = m_form->s->fields;
bool has_doc_id = false;
/* First check if dd::Table contains the right hidden column
as FTS_DOC_ID */
const dd::Column *doc_col;
doc_col = dd_find_column(&dd_tab->table(), FTS_DOC_ID_COL_NAME);
/* Check weather this is a proper typed FTS_DOC_ID */
if (doc_col && doc_col->type() == dd::enum_column_types::LONGLONG &&
!doc_col->is_nullable()) {
has_doc_id = true;
}
const bool fulltext =
dd_tab != nullptr && dd_table_contains_fulltext(dd_tab->table());
/* If there is a fulltext index, then it must have a FTS_DOC_ID */
if (fulltext) {
ut_ad(has_doc_id);
}
bool add_doc_id = false;
/* Need to add FTS_DOC_ID column if it is not defined by user,
since TABLE_SHARE::fields does not contain it if it is a hidden col */
if (has_doc_id && doc_col->is_se_hidden()) {
#ifdef UNIV_DEBUG
ulint doc_id_col;
ut_ad(!create_table_check_doc_id_col(m_thd, m_form, &doc_id_col));
#endif
add_doc_id = true;
}
const unsigned n_cols = n_mysql_cols + add_doc_id;
bool is_redundant;
bool blob_prefix;
ulint zip_ssize;
row_type real_type = ROW_TYPE_NOT_USED;
if (dd_table_is_partitioned(dd_tab->table())) {
const dd::Properties &part_p = dd_tab->se_private_data();
if (part_p.exists(dd_partition_key_strings[DD_PARTITION_ROW_FORMAT])) {
dd::Table::enum_row_format format;
part_p.get(dd_partition_key_strings[DD_PARTITION_ROW_FORMAT],
reinterpret_cast<uint32 *>(&format));
switch (format) {
case dd::Table::RF_REDUNDANT:
real_type = ROW_TYPE_REDUNDANT;
break;
case dd::Table::RF_COMPACT:
real_type = ROW_TYPE_COMPACT;
break;
case dd::Table::RF_COMPRESSED:
real_type = ROW_TYPE_COMPRESSED;
break;
case dd::Table::RF_DYNAMIC:
real_type = ROW_TYPE_DYNAMIC;
break;
default:
ut_ad(0);
}
}
}
/* Validate the table format options */
if (format_validate(m_thd, m_form, real_type, zip_allowed, strict,
&is_redundant, &blob_prefix, &zip_ssize, is_implicit)) {
return (nullptr);
}
ulint n_v_cols = 0;
ulint n_m_v_cols = 0;
/* Find out the number of virtual columns */
for (ulint i = 0; i < m_form->s->fields; i++) {
Field *field = m_form->field[i];
ut_ad(!(!innobase_is_v_fld(field) && innobase_is_multi_value_fld(field)));
if (innobase_is_v_fld(field)) {
n_v_cols++;
if (innobase_is_multi_value_fld(field)) {
n_m_v_cols++;
}
}
}
ut_ad(n_v_cols <= n_cols);
/* Create the dict_table_t */
dict_table_t *m_table =
dict_mem_table_create(norm_name, 0, n_cols, n_v_cols, n_m_v_cols, 0, 0);
/* Set up the field in the newly allocated dict_table_t */
m_table->id = dd_tab->se_private_id();
if (dd_tab->se_private_data().exists(
dd_table_key_strings[DD_TABLE_DATA_DIRECTORY])) {
m_table->flags |= DICT_TF_MASK_DATA_DIR;
}
/* If the table has instantly added columns, it's necessary to read
the number of instant columns for either normal table(from dd::Table),
or partitioned table(from dd::Partition). One partition may have no
instant columns, which is fine. */
if (dd_table_has_instant_cols(dd_tab->table())) {
uint32_t instant_cols;
if (!dd_table_is_partitioned(dd_tab->table())) {
table_private.get(dd_table_key_strings[DD_TABLE_INSTANT_COLS],
&instant_cols);
m_table->set_instant_cols(instant_cols);
ut_ad(m_table->has_instant_cols());
} else if (dd_part_has_instant_cols(
*reinterpret_cast<const dd::Partition *>(dd_tab))) {
dd_tab->se_private_data().get(
dd_partition_key_strings[DD_PARTITION_INSTANT_COLS], &instant_cols);
m_table->set_instant_cols(instant_cols);
ut_ad(m_table->has_instant_cols());
}
}
/* Check if this table is FTS AUX table, if so, set DICT_TF2_AUX flag */
fts_aux_table_t aux_table;
if (fts_is_aux_table_name(&aux_table, norm_name, strlen(norm_name))) {
DICT_TF2_FLAG_SET(m_table, DICT_TF2_AUX);
m_table->parent_id = aux_table.parent_id;
}
if (is_discard) {
m_table->ibd_file_missing = true;
m_table->flags2 |= DICT_TF2_DISCARDED;
}
if (!is_redundant) {
m_table->flags |= DICT_TF_COMPACT;
}
if (is_implicit) {
m_table->flags2 |= DICT_TF2_USE_FILE_PER_TABLE;
} else {
m_table->flags |= (1 << DICT_TF_POS_SHARED_SPACE);
}
if (!blob_prefix) {
m_table->flags |= (1 << DICT_TF_POS_ATOMIC_BLOBS);
}
if (zip_ssize != 0) {
m_table->flags |= (zip_ssize << DICT_TF_POS_ZIP_SSIZE);
}
m_table->fts = nullptr;
if (has_doc_id) {
if (fulltext) {
DICT_TF2_FLAG_SET(m_table, DICT_TF2_FTS);
}
if (add_doc_id) {
DICT_TF2_FLAG_SET(m_table, DICT_TF2_FTS_HAS_DOC_ID);
}
if (fulltext || add_doc_id) {
m_table->fts = fts_create(m_table);
m_table->fts->cache = fts_cache_create(m_table);
}
}
bool is_temp = (m_form->s->tmp_table_def != nullptr);
if (is_temp) {
m_table->flags2 |= DICT_TF2_TEMPORARY;
}
if (is_encrypted) {
/* We don't support encrypt intrinsic and temporary table. */
ut_ad(!m_table->is_intrinsic() && !m_table->is_temporary());
/* This flag will be used to set file-per-table tablesapce
encryption flag */
DICT_TF2_FLAG_SET(m_table, DICT_TF2_ENCRYPTION_FILE_PER_TABLE);
}
heap = mem_heap_create(1000);
/* Fill out each column info */
for (unsigned i = 0; i < n_mysql_cols; i++) {
const Field *field = m_form->field[i];
ulint prtype = 0;
unsigned col_len = field->pack_length();
/* The MySQL type code has to fit in 8 bits
in the metadata stored in the InnoDB change buffer. */
ut_ad(field->charset() == nullptr ||
field->charset()->number <= MAX_CHAR_COLL_NUM);
ut_ad(field->charset() == nullptr || field->charset()->number > 0);
ulint nulls_allowed;
ulint unsigned_type;
ulint binary_type;
ulint long_true_varchar;
ulint charset_no;
ulint mtype = get_innobase_type_from_mysql_type(&unsigned_type, field);
nulls_allowed = field->real_maybe_null() ? 0 : DATA_NOT_NULL;
/* Convert non nullable fields in FTS AUX tables as nullable.
This is because in 5.7, we created FTS AUX tables clustered
index with nullable field, although NULLS are not inserted.
When fields are nullable, the record layout is dependent on
that. When registering FTS AUX Tables with new DD, we cannot
register nullable fields as part of Primary Key. Hence we register
them as non-nullabe in DD but treat as nullable in InnoDB.
This way the compatibility with 5.7 FTS AUX tables is also
maintained. */
if (m_table->is_fts_aux()) {
const dd::Table &dd_table = dd_tab->table();
const dd::Column *dd_col = dd_find_column(&dd_table, field->field_name);
const dd::Properties &p = dd_col->se_private_data();
if (p.exists("nullable")) {
bool nullable;
p.get("nullable", &nullable);
nulls_allowed = nullable ? 0 : DATA_NOT_NULL;
}
}
binary_type = field->binary() ? DATA_BINARY_TYPE : 0;
charset_no = 0;
if (dtype_is_string_type(mtype)) {
charset_no = static_cast<ulint>(field->charset()->number);
}
long_true_varchar = 0;
if (field->type() == MYSQL_TYPE_VARCHAR) {
col_len -= field->get_length_bytes();
if (field->get_length_bytes() == 2) {
long_true_varchar = DATA_LONG_TRUE_VARCHAR;
}
}
ulint is_virtual = (innobase_is_v_fld(field)) ? DATA_VIRTUAL : 0;
ulint is_multi_val =
innobase_is_multi_value_fld(field) ? DATA_MULTI_VALUE : 0;
bool is_stored = innobase_is_s_fld(field);
if (is_multi_val) {
col_len = field->key_length();
}
if (!is_virtual) {
prtype =
dtype_form_prtype((ulint)field->type() | nulls_allowed |
unsigned_type | binary_type | long_true_varchar,
charset_no);
dict_mem_table_add_col(m_table, heap, field->field_name, mtype, prtype,
col_len);
} else {
prtype = dtype_form_prtype(
(ulint)field->type() | nulls_allowed | unsigned_type | binary_type |
long_true_varchar | is_virtual | is_multi_val,
charset_no);
dict_mem_table_add_v_col(m_table, heap, field->field_name, mtype, prtype,
col_len, i,
field->gcol_info->non_virtual_base_columns());
}
if (is_stored) {
ut_ad(!is_virtual);
/* Added stored column in m_s_cols list. */
dict_mem_table_add_s_col(m_table,
field->gcol_info->non_virtual_base_columns());
}
}
ulint j = 0;
/* For each virtual column, we will need to set up its base column
info */
if (m_table->n_v_cols > 0) {
for (unsigned i = 0; i < n_mysql_cols; i++) {
dict_v_col_t *v_col;
Field *field = m_form->field[i];
if (!innobase_is_v_fld(field)) {
continue;
}
v_col = dict_table_get_nth_v_col(m_table, j);
j++;
innodb_base_col_setup(m_table, field, v_col);
}
}
if (add_doc_id) {
/* Add the hidden FTS_DOC_ID column. */
fts_add_doc_id_column(m_table, heap);
}
/* Add system columns to make adding index work */
dict_table_add_system_columns(m_table, heap);
if (m_table->has_instant_cols()) {
dd_fill_instant_columns(dd_tab->table(), m_table);
}
mem_heap_free(heap);
return (m_table);
}
/** Parse the tablespace file name from filename charset to table name charset
@param[in] file_name tablespace file name
@param[in,out] tablespace_name tablespace name which is in table name
charset. */
void dd_filename_to_spacename(const char *file_name,
std::string *tablespace_name) {
char db_buf[NAME_LEN + 1];
char tbl_buf[NAME_LEN + 1];
char part_buf[NAME_LEN + 1];
char sub_buf[NAME_LEN + 1];
char orig_tablespace[NAME_LEN + 1];
bool is_tmp = false;
db_buf[0] = tbl_buf[0] = part_buf[0] = sub_buf[0] = '\0';
dd_parse_tbl_name(file_name, db_buf, tbl_buf, part_buf, sub_buf, &is_tmp);
if (db_buf[0] == '\0') {
filename_to_tablename((char *)file_name, orig_tablespace, (NAME_LEN + 1));
tablespace_name->append(orig_tablespace);
return;
}
tablespace_name->append(db_buf);
tablespace_name->append("/");
tablespace_name->append(tbl_buf);
if (part_buf[0] != '\0') {
tablespace_name->append(PART_SEPARATOR);
tablespace_name->append(part_buf);
}
if (sub_buf[0] != '\0') {
tablespace_name->append(SUB_PART_SEPARATOR);
tablespace_name->append(sub_buf);
}
if (is_tmp) {
tablespace_name->append(TMP_POSTFIX);
}
/* Name should not exceed schema/table#P#partition#SP#subpartition. */
ut_ad(tablespace_name->size() < MAX_SPACE_NAME_LEN);
}
/* Create metadata for specified tablespace, acquiring exlcusive MDL first
@param[in,out] dd_client data dictionary client
@param[in,out] thd THD
@param[in,out] dd_space_name dd tablespace name
@param[in] space InnoDB tablespace ID
@param[in] flags InnoDB tablespace flags
@param[in] filename filename of this tablespace
@param[in] discarded true if this tablespace was discarded
@param[in,out] dd_space_id dd_space_id
@retval false on success
@retval true on failure */
bool dd_create_tablespace(dd::cache::Dictionary_client *dd_client, THD *thd,
const char *dd_space_name, space_id_t space_id,
uint32_t flags, const char *filename, bool discarded,
dd::Object_id &dd_space_id) {
std::unique_ptr<dd::Tablespace> dd_space(dd::create_object<dd::Tablespace>());
if (dd_space_name != nullptr) {
dd_space->set_name(dd_space_name);
}
if (dd_tablespace_get_mdl(dd_space->name().c_str())) {
return (true);
}
dd_space->set_engine(innobase_hton_name);
dd::Properties &p = dd_space->se_private_data();
p.set(dd_space_key_strings[DD_SPACE_ID], static_cast<uint32>(space_id));
p.set(dd_space_key_strings[DD_SPACE_FLAGS], static_cast<uint32>(flags));
p.set(dd_space_key_strings[DD_SPACE_SERVER_VERSION],
DD_SPACE_CURRENT_SRV_VERSION);
p.set(dd_space_key_strings[DD_SPACE_VERSION], DD_SPACE_CURRENT_SPACE_VERSION);
dd_space_states state =
(fsp_is_undo_tablespace(space_id)
? DD_SPACE_STATE_ACTIVE
: (discarded ? DD_SPACE_STATE_DISCARDED : DD_SPACE_STATE_NORMAL));
p.set(dd_space_key_strings[DD_SPACE_STATE], dd_space_state_values[state]);
dd::Tablespace_file *dd_file = dd_space->add_file();
dd_file->set_filename(filename);
dd_file->se_private_data().set(dd_space_key_strings[DD_SPACE_ID],
static_cast<uint32>(space_id));
dd::Properties &toptions = dd_space->options();
if (!FSP_FLAGS_GET_ENCRYPTION(flags)) {
/* Update DD Option value, for Unencryption */
toptions.set("encryption", "N");
} else {
/* Update DD Option value, for Encryption */
toptions.set("encryption", "Y");
}
if (dd_client->store(dd_space.get())) {
return (true);
}
dd_space_id = dd_space.get()->id();
return (false);
}
/** Create metadata for implicit tablespace
@param[in,out] dd_client data dictionary client
@param[in,out] thd THD
@param[in] space_id InnoDB tablespace ID
@param[in] space_name tablespace name to be set for the
newly created tablespace
@param[in] filename tablespace filename
@param[in] discarded true if this tablespace was discarded
@param[in,out] dd_space_id dd tablespace id
@retval false on success
@retval true on failure */
bool dd_create_implicit_tablespace(dd::cache::Dictionary_client *dd_client,
THD *thd, space_id_t space_id,
const char *space_name, const char *filename,
bool discarded, dd::Object_id &dd_space_id) {
std::string tsn;
fil_space_t *space = fil_space_get(space_id);
uint32_t flags = space->flags;
dd_filename_to_spacename(space_name, &tsn);
bool fail = dd_create_tablespace(dd_client, thd, tsn.c_str(), space_id, flags,
filename, discarded, dd_space_id);
return (fail);
}
/** Drop a tablespace
@param[in,out] dd_client data dictionary client
@param[in,out] thd THD object
@param[in] dd_space_id dd tablespace id
@retval false On success
@retval true On failure */
bool dd_drop_tablespace(dd::cache::Dictionary_client *dd_client, THD *thd,
dd::Object_id dd_space_id) {
dd::Tablespace *dd_space = nullptr;
if (dd_client->acquire_uncached_uncommitted(dd_space_id, &dd_space) ||
dd_space == nullptr) {
my_error(ER_INTERNAL_ERROR, MYF(0),
" InnoDB can't get tablespace object"
" for space ",
dd_space_id);
return (true);
}
ut_a(dd_space != nullptr);
if (dd_tablespace_get_mdl(dd_space->name().c_str())) {
my_error(ER_INTERNAL_ERROR, MYF(0),
" InnoDB can't set exclusive MDL on"
" tablespace ",
dd_space->name().c_str());
return (true);
}
bool error = dd_client->drop(dd_space);
DBUG_EXECUTE_IF("fail_while_dropping_dd_object", error = true;);
if (error) {
my_error(ER_INTERNAL_ERROR, MYF(0), " InnoDB can't drop tablespace object",
dd_space->name().c_str());
}
return (error);
}
/** Determine if a tablespace is implicit.
@param[in,out] client data dictionary client
@param[in] dd_space_id dd tablespace id
@param[out] implicit whether the tablespace is implicit tablespace
@param[out] dd_space DD space object
@retval false on success
@retval true on failure */
bool dd_tablespace_is_implicit(dd::cache::Dictionary_client *client,
dd::Object_id dd_space_id, bool *implicit,
dd::Tablespace **dd_space) {
space_id_t id = 0;
uint32 flags;
const bool fail = client->acquire_uncached_uncommitted<dd::Tablespace>(
dd_space_id, dd_space) ||
(*dd_space) == nullptr ||
(*dd_space)->se_private_data().get(
dd_space_key_strings[DD_SPACE_ID], &id);
if (!fail) {
(*dd_space)->se_private_data().get(dd_space_key_strings[DD_SPACE_FLAGS],
&flags);
*implicit = fsp_is_file_per_table(id, flags);
}
return (fail);
}
/** Load foreign key constraint info for the dd::Table object.
@param[out] m_table InnoDB table handle
@param[in] dd_table Global DD table
@param[in] col_names column names, or NULL
@param[in] ignore_err DICT_ERR_IGNORE_FK_NOKEY or DICT_ERR_IGNORE_NONE
@param[in] dict_locked True if dict_sys->mutex is already held,
otherwise false
@return DB_SUCCESS if successfully load FK constraint */
dberr_t dd_table_load_fk_from_dd(dict_table_t *m_table,
const dd::Table *dd_table,
const char **col_names,
dict_err_ignore_t ignore_err,
bool dict_locked) {
dberr_t err = DB_SUCCESS;
/* Now fill in the foreign key info */
for (const dd::Foreign_key *key : dd_table->foreign_keys()) {
char buf[MAX_FULL_NAME_LEN + 1];
if (*(key->name().c_str()) == '#' && *(key->name().c_str() + 1) == 'f') {
continue;
}
dd::String_type db_name = key->referenced_table_schema_name();
dd::String_type tb_name = key->referenced_table_name();
bool truncated;
build_table_filename(buf, sizeof(buf), db_name.c_str(), tb_name.c_str(),
NULL, 0, &truncated);
ut_ad(!truncated);
char norm_name[FN_REFLEN * 2];
normalize_table_name(norm_name, buf);
dict_foreign_t *foreign = dict_mem_foreign_create();
foreign->foreign_table_name =
mem_heap_strdup(foreign->heap, m_table->name.m_name);
dict_mem_foreign_table_name_lookup_set(foreign, TRUE);
if (innobase_get_lower_case_table_names() == 2) {
innobase_casedn_str(norm_name);
} else {
#ifndef _WIN32
if (innobase_get_lower_case_table_names() == 1) {
innobase_casedn_str(norm_name);
}
#endif /* !_WIN32 */
}
foreign->referenced_table_name = mem_heap_strdup(foreign->heap, norm_name);
dict_mem_referenced_table_name_lookup_set(foreign, TRUE);
ulint db_len = dict_get_db_name_len(m_table->name.m_name);
ut_ad(db_len > 0);
memcpy(buf, m_table->name.m_name, db_len);
buf[db_len] = '\0';
snprintf(norm_name, sizeof norm_name, "%s/%s", buf, key->name().c_str());
foreign->id = mem_heap_strdup(foreign->heap, norm_name);
switch (key->update_rule()) {
case dd::Foreign_key::RULE_NO_ACTION:
/*
Since SET DEFAULT clause is not supported, ignore it by converting
into the value DICT_FOREIGN_ON_UPDATE_NO_ACTION
*/
case dd::Foreign_key::RULE_SET_DEFAULT:
foreign->type = DICT_FOREIGN_ON_UPDATE_NO_ACTION;
break;
case dd::Foreign_key::RULE_RESTRICT:
foreign->type = 0;
break;
case dd::Foreign_key::RULE_CASCADE:
foreign->type = DICT_FOREIGN_ON_UPDATE_CASCADE;
break;
case dd::Foreign_key::RULE_SET_NULL:
foreign->type = DICT_FOREIGN_ON_UPDATE_SET_NULL;
break;
default:
ut_ad(0);
}
switch (key->delete_rule()) {
case dd::Foreign_key::RULE_NO_ACTION:
/*
Since SET DEFAULT clause is not supported, ignore it by converting
into the value DICT_FOREIGN_ON_UPDATE_NO_ACTION
*/
case dd::Foreign_key::RULE_SET_DEFAULT:
foreign->type |= DICT_FOREIGN_ON_DELETE_NO_ACTION;
case dd::Foreign_key::RULE_RESTRICT:
break;
case dd::Foreign_key::RULE_CASCADE:
foreign->type |= DICT_FOREIGN_ON_DELETE_CASCADE;
break;
case dd::Foreign_key::RULE_SET_NULL:
foreign->type |= DICT_FOREIGN_ON_DELETE_SET_NULL;
break;
default:
ut_ad(0);
}
foreign->n_fields = key->elements().size();
foreign->foreign_col_names = static_cast<const char **>(
mem_heap_alloc(foreign->heap, foreign->n_fields * sizeof(void *)));
foreign->referenced_col_names = static_cast<const char **>(
mem_heap_alloc(foreign->heap, foreign->n_fields * sizeof(void *)));
ulint num_ref = 0;
for (const dd::Foreign_key_element *key_e : key->elements()) {
dd::String_type ref_col_name = key_e->referenced_column_name();
foreign->referenced_col_names[num_ref] =
mem_heap_strdup(foreign->heap, ref_col_name.c_str());
ut_ad(ref_col_name.c_str());
const dd::Column *f_col = &key_e->column();
foreign->foreign_col_names[num_ref] =
mem_heap_strdup(foreign->heap, f_col->name().c_str());
num_ref++;
}
if (!dict_locked) {
mutex_enter(&dict_sys->mutex);
}
#ifdef UNIV_DEBUG
dict_table_t *for_table;
for_table =
dict_table_check_if_in_cache_low(foreign->foreign_table_name_lookup);
ut_ad(for_table);
#endif
/* Fill in foreign->foreign_table and index, then add to
dict_table_t */
err =
dict_foreign_add_to_cache(foreign, col_names, FALSE, true, ignore_err);
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
if (err != DB_SUCCESS) {
break;
}
/* Set up the FK virtual column info */
dict_mem_table_free_foreign_vcol_set(m_table);
dict_mem_table_fill_foreign_vcol_set(m_table);
}
return (err);
}
/** Load foreign key constraint for the table. Note, it could also open
the foreign table, if this table is referenced by the foreign table
@param[in,out] client data dictionary client
@param[in] tbl_name Table Name
@param[in] col_names column names, or NULL
@param[out] m_table InnoDB table handle
@param[in] dd_table Global DD table
@param[in] thd thread THD
@param[in] dict_locked True if dict_sys->mutex is already held,
otherwise false
@param[in] check_charsets whether to check charset compatibility
@param[in,out] fk_tables name list for tables that refer to this table
@return DB_SUCCESS if successfully load FK constraint */
dberr_t dd_table_load_fk(dd::cache::Dictionary_client *client,
const char *tbl_name, const char **col_names,
dict_table_t *m_table, const dd::Table *dd_table,
THD *thd, bool dict_locked, bool check_charsets,
dict_names_t *fk_tables) {
dberr_t err = DB_SUCCESS;
dict_err_ignore_t ignore_err = DICT_ERR_IGNORE_NONE;
/* Check whether FOREIGN_KEY_CHECKS is set to 0. If so, the table
can be opened even if some FK indexes are missing. If not, the table
can't be opened in the same situation */
if (thd_test_options(thd, OPTION_NO_FOREIGN_KEY_CHECKS)) {
ignore_err = DICT_ERR_IGNORE_FK_NOKEY;
}
err = dd_table_load_fk_from_dd(m_table, dd_table, col_names, ignore_err,
dict_locked);
if (err != DB_SUCCESS) {
return (err);
}
if (dict_locked) {
mutex_exit(&dict_sys->mutex);
}
DBUG_EXECUTE_IF("enable_stack_overrun_post_alter_commit",
{ DBUG_SET("+d,simulate_stack_overrun"); });
err = dd_table_check_for_child(client, tbl_name, col_names, m_table, dd_table,
thd, check_charsets, ignore_err, fk_tables);
DBUG_EXECUTE_IF("enable_stack_overrun_post_alter_commit",
{ DBUG_SET("-d,simulate_stack_overrun"); });
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
return (err);
}
/** Load foreign key constraint for the table. Note, it could also open
the foreign table, if this table is referenced by the foreign table
@param[in,out] client data dictionary client
@param[in] tbl_name Table Name
@param[in] col_names column names, or NULL
@param[out] m_table InnoDB table handle
@param[in] dd_table Global DD table
@param[in] thd thread THD
@param[in] check_charsets whether to check charset compatibility
@param[in] ignore_err DICT_ERR_IGNORE_FK_NOKEY or DICT_ERR_IGNORE_NONE
@param[in,out] fk_tables name list for tables that refer to this table
@return DB_SUCCESS if successfully load FK constraint */
dberr_t dd_table_check_for_child(dd::cache::Dictionary_client *client,
const char *tbl_name, const char **col_names,
dict_table_t *m_table,
const dd::Table *dd_table, THD *thd,
bool check_charsets,
dict_err_ignore_t ignore_err,
dict_names_t *fk_tables) {
dberr_t err = DB_SUCCESS;
/* TODO: NewDD: Temporary ignore DD system table until
WL#6049 inplace */
if (!dict_sys_t::is_dd_table_id(m_table->id) && fk_tables != nullptr) {
std::vector<dd::String_type> child_schema;
std::vector<dd::String_type> child_name;
char name_buf1[MAX_DATABASE_NAME_LEN + 1];
char name_buf2[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(m_table->name.m_name, name_buf1, name_buf2, nullptr,
nullptr, nullptr);
if (client->fetch_fk_children_uncached(name_buf1, name_buf2, "InnoDB",
false, &child_schema, &child_name)) {
return (DB_ERROR);
}
std::vector<dd::String_type>::iterator it = child_name.begin();
for (auto &db_name : child_schema) {
dd::String_type tb_name = *it;
char buf[2 * NAME_CHAR_LEN * 5 + 2 + 1];
bool truncated;
build_table_filename(buf, sizeof(buf), db_name.c_str(), tb_name.c_str(),
NULL, 0, &truncated);
ut_ad(!truncated);
char full_name[FN_REFLEN];
normalize_table_name(full_name, buf);
if (innobase_get_lower_case_table_names() == 2) {
innobase_casedn_str(full_name);
} else {
#ifndef _WIN32
if (innobase_get_lower_case_table_names() == 1) {
innobase_casedn_str(full_name);
}
#endif /* !_WIN32 */
}
mutex_enter(&dict_sys->mutex);
/* Load the foreign table first */
dict_table_t *foreign_table =
dd_table_open_on_name_in_mem(full_name, true);
if (foreign_table) {
for (auto &fk : foreign_table->foreign_set) {
if (strcmp(fk->referenced_table_name, tbl_name) != 0) {
continue;
}
if (fk->referenced_table) {
ut_ad(fk->referenced_table == m_table);
} else {
err = dict_foreign_add_to_cache(fk, col_names, check_charsets,
false, ignore_err);
if (err != DB_SUCCESS) {
foreign_table->release();
mutex_exit(&dict_sys->mutex);
return (err);
}
}
}
foreign_table->release();
} else {
/* To avoid recursively loading the tables
related through the foreign key constraints,
the child table name is saved here. The child
table will be loaded later, along with its
foreign key constraint. */
lint old_size = mem_heap_get_size(m_table->heap);
fk_tables->push_back(
mem_heap_strdupl(m_table->heap, full_name, strlen(full_name)));
lint new_size = mem_heap_get_size(m_table->heap);
dict_sys->size += new_size - old_size;
}
mutex_exit(&dict_sys->mutex);
ut_ad(it != child_name.end());
++it;
}
}
return (err);
}
/** Get tablespace name of dd::Table
@tparam Table dd::Table or dd::Partition
@param[in] dd_table dd table object
@return the tablespace name or nullptr if failed */
template <typename Table>
const char *dd_table_get_space_name(const Table *dd_table) {
dd::Tablespace *dd_space = nullptr;
THD *thd = current_thd;
const char *space_name;
DBUG_TRACE;
ut_ad(!srv_is_being_shutdown);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
dd::Object_id dd_space_id = (*dd_table->indexes().begin())->tablespace_id();
if (client->acquire_uncached_uncommitted<dd::Tablespace>(dd_space_id,
&dd_space) ||
dd_space == nullptr) {
ut_ad(false);
return nullptr;
}
space_name = dd_space->name().c_str();
return space_name;
}
/** Get the first filepath from mysql.tablespace_datafiles
for a given space_id.
@tparam Table dd::Table or dd::Partition
@param[in,out] heap heap for store file name.
@param[in] table dict table
@param[in] dd_table dd table obj
@return First filepath (caller must invoke ut_free() on it)
@retval nullptr if no mysql.tablespace_datafiles entry was found. */
template <typename Table>
char *dd_get_first_path(mem_heap_t *heap, dict_table_t *table,
Table *dd_table) {
char *filepath = nullptr;
dd::Tablespace *dd_space = nullptr;
THD *thd = current_thd;
MDL_ticket *mdl = nullptr;
dd::Object_id dd_space_id;
ut_ad(!srv_is_being_shutdown);
ut_ad(!mutex_own(&dict_sys->mutex));
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
if (dd_table == nullptr) {
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
const dd::Table *table_def = nullptr;
if (!dd_parse_tbl_name(table->name.m_name, db_buf, tbl_buf, nullptr,
nullptr, nullptr) ||
dd_mdl_acquire(thd, &mdl, db_buf, tbl_buf)) {
return (nullptr);
}
if (client->acquire(db_buf, tbl_buf, &table_def) || table_def == nullptr) {
dd_mdl_release(thd, &mdl);
return (nullptr);
}
dd_space_id = dd_first_index(table_def)->tablespace_id();
dd_mdl_release(thd, &mdl);
} else {
dd_space_id = dd_first_index(dd_table)->tablespace_id();
}
if (client->acquire_uncached_uncommitted<dd::Tablespace>(dd_space_id,
&dd_space)) {
ut_ad(false);
return (nullptr);
}
if (dd_space != nullptr) {
dd::Tablespace_file *dd_file =
const_cast<dd::Tablespace_file *>(*(dd_space->files().begin()));
filepath = mem_heap_strdup(heap, dd_file->filename().c_str());
return (filepath);
}
return (nullptr);
}
/** Make sure the data_dir_path is saved in dict_table_t if this is a
remote single file tablespace. This allows DATA DIRECTORY to be
displayed correctly for SHOW CREATE TABLE. Try to read the filepath
from the fil_system first, then from the DD.
@tparam Table dd::Table or dd::Partition
@param[in,out] table Table object
@param[in] dd_table DD table object
@param[in] dict_mutex_own true if dict_sys->mutex is owned already */
template <typename Table>
void dd_get_and_save_data_dir_path(dict_table_t *table, const Table *dd_table,
bool dict_mutex_own) {
mem_heap_t *heap = NULL;
if (!DICT_TF_HAS_DATA_DIR(table->flags) || table->data_dir_path != nullptr) {
return;
}
char *path = fil_space_get_first_path(table->space);
if (path == nullptr) {
heap = mem_heap_create(1000);
if (dict_mutex_own) {
dict_mutex_exit_for_mysql();
}
path = dd_get_first_path(heap, table, dd_table);
if (dict_mutex_own) {
dict_mutex_enter_for_mysql();
}
}
if (!dict_mutex_own) {
dict_mutex_enter_for_mysql();
}
if (path != nullptr) {
dict_save_data_dir_path(table, path);
}
if (!dict_mutex_own) {
dict_mutex_exit_for_mysql();
}
if (heap != nullptr) {
mem_heap_free(heap);
} else {
ut_free(path);
}
}
template void dd_get_and_save_data_dir_path<dd::Table>(dict_table_t *,
const dd::Table *, bool);
template void dd_get_and_save_data_dir_path<dd::Partition>(
dict_table_t *, const dd::Partition *, bool);
/** Get the meta-data filename from the table name for a
single-table tablespace.
@param[in,out] table table object
@param[in] dd_table DD table object
@param[out] filename filename
@param[in] max_len filename max length */
void dd_get_meta_data_filename(dict_table_t *table, dd::Table *dd_table,
char *filename, ulint max_len) {
/* Make sure the data_dir_path is set. */
dd_get_and_save_data_dir_path(table, dd_table, false);
std::string path = dict_table_get_datadir(table);
auto filepath = Fil_path::make(path, table->name.m_name, CFG, true);
ut_a(max_len >= strlen(filepath) + 1);
strcpy(filename, filepath);
ut_free(filepath);
}
/** Opens a tablespace for dd_load_table_one()
@tparam Table dd::Table or dd::Partition
@param[in,out] dd_table dd table
@param[in,out] table A table that refers to the tablespace to
open
@param[in,out] heap A memory heap
@param[in] expected_fsp_flags expected flags of tablespace to be
loaded
@param[in] ignore_err Whether to ignore an error. */
template <typename Table>
void dd_load_tablespace(const Table *dd_table, dict_table_t *table,
mem_heap_t *heap, dict_err_ignore_t ignore_err,
ulint expected_fsp_flags) {
bool alloc_from_heap = false;
ut_ad(!table->is_temporary());
ut_ad(mutex_own(&dict_sys->mutex));
/* The system and temporary tablespaces are preloaded and
always available. */
if (fsp_is_system_or_temp_tablespace(table->space)) {
return;
}
if (table->flags2 & DICT_TF2_DISCARDED) {
ib::warn(ER_IB_MSG_171)
<< "Tablespace for table " << table->name << " is set as discarded.";
table->ibd_file_missing = TRUE;
return;
}
/* A general tablespace name is not the same as the table name.
Use the general tablespace name if it can be read from the
dictionary, if not use 'innodb_general_##. */
char *shared_space_name = nullptr;
const char *space_name;
std::string tablespace_name;
const char *tbl_name;
if (DICT_TF_HAS_SHARED_SPACE(table->flags)) {
if (table->space == dict_sys_t::s_space_id) {
shared_space_name = mem_strdup(dict_sys_t::s_dd_space_name);
} else if (srv_sys_tablespaces_open) {
/* For avoiding deadlock, we need to exit
dict_sys->mutex. */
mutex_exit(&dict_sys->mutex);
shared_space_name = mem_strdup(dd_table_get_space_name(dd_table));
mutex_enter(&dict_sys->mutex);
} else {
/* Make the temporary tablespace name. */
shared_space_name =
static_cast<char *>(ut_malloc_nokey(strlen(general_space_name) + 20));
sprintf(shared_space_name, "%s_" ULINTPF, general_space_name,
static_cast<ulint>(table->space));
}
space_name = shared_space_name;
tbl_name = space_name;
} else {
tbl_name = table->name.m_name;
dd_filename_to_spacename(tbl_name, &tablespace_name);
space_name = tablespace_name.c_str();
}
/* The tablespace may already be open. */
if (fil_space_exists_in_mem(table->space, space_name, false, true, heap,
table->id)) {
dd_get_and_save_data_dir_path(table, dd_table, true);
ut_free(shared_space_name);
return;
}
if (!(ignore_err & DICT_ERR_IGNORE_RECOVER_LOCK)) {
ib::error(ER_IB_MSG_172)
<< "Failed to find tablespace for table " << table->name
<< " in the cache. Attempting"
" to load the tablespace with space id "
<< table->space;
}
/* Try to get the filepath if this space_id is already open.
If the filepath is not found, fil_ibd_open() will make a default
filepath from the tablespace name */
char *filepath = fil_space_get_first_path(table->space);
if (filepath == nullptr) {
/* boot_tablespaces() made sure that the scanned filepath
is in the DD even if the datafile was moved. So let's use
that path to open this tablespace. */
mutex_exit(&dict_sys->mutex);
filepath = dd_get_first_path(heap, table, dd_table);
mutex_enter(&dict_sys->mutex);
if (filepath == nullptr) {
ib::warn(ER_IB_MSG_173) << "Could not find the filepath"
<< " for table " << table->name << ", space ID "
<< table->space << " in the data dictionary.";
} else {
alloc_from_heap = true;
}
}
/* Try to open the tablespace. We set the 2nd param (fix_dict) to
false because we do not have an x-lock on dict_operation_lock */
dberr_t err =
fil_ibd_open(true, FIL_TYPE_TABLESPACE, table->space, expected_fsp_flags,
space_name, tbl_name, filepath, true, false);
if (err == DB_SUCCESS) {
/* This will set the DATA DIRECTORY for SHOW CREATE TABLE. */
dd_get_and_save_data_dir_path(table, dd_table, true);
} else {
/* We failed to find a sensible tablespace file */
table->ibd_file_missing = TRUE;
}
ut_free(shared_space_name);
if (!alloc_from_heap && filepath) {
ut_free(filepath);
}
}
/** Get the space name from mysql.tablespaces for a given space_id.
@tparam Table dd::Table or dd::Partition
@param[in,out] heap heap for store file name.
@param[in] table dict table
@param[in] dd_table dd table obj
@return First filepath (caller must invoke ut_free() on it)
@retval nullptr if no mysql.tablespace_datafiles entry was found. */
template <typename Table>
char *dd_space_get_name(mem_heap_t *heap, dict_table_t *table,
Table *dd_table) {
dd::Object_id dd_space_id;
THD *thd = current_thd;
dd::Tablespace *dd_space = nullptr;
ut_ad(!srv_is_being_shutdown);
ut_ad(!mutex_own(&dict_sys->mutex));
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
if (dd_table == nullptr) {
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
const dd::Table *table_def = nullptr;
MDL_ticket *mdl = nullptr;
if (!dd_parse_tbl_name(table->name.m_name, db_buf, tbl_buf, nullptr,
nullptr, nullptr) ||
dd_mdl_acquire(thd, &mdl, db_buf, tbl_buf)) {
return (nullptr);
}
if (client->acquire(db_buf, tbl_buf, &table_def) || table_def == nullptr) {
dd_mdl_release(thd, &mdl);
return (nullptr);
}
dd_space_id = dd_first_index(table_def)->tablespace_id();
dd_mdl_release(thd, &mdl);
} else {
dd_space_id = dd_first_index(dd_table)->tablespace_id();
}
if (client->acquire_uncached_uncommitted<dd::Tablespace>(dd_space_id,
&dd_space) ||
dd_space == nullptr) {
ut_ad(false);
return (nullptr);
}
return (mem_heap_strdup(heap, dd_space->name().c_str()));
}
/** Make sure the tablespace name is saved in dict_table_t if the table
uses a general tablespace.
Try to read it from the fil_system_t first, then from DD.
@param[in] table Table object
@param[in] dd_table Global DD table or partition object
@param[in] dict_mutex_own) true if dict_sys->mutex is owned already */
template <typename Table>
void dd_get_and_save_space_name(dict_table_t *table, const Table *dd_table,
bool dict_mutex_own) {
/* Do this only for general tablespaces. */
if (!DICT_TF_HAS_SHARED_SPACE(table->flags)) {
return;
}
bool use_cache = true;
if (table->tablespace != NULL) {
if (srv_sys_tablespaces_open &&
dict_table_has_temp_general_tablespace_name(table->tablespace)) {
/* We previous saved the temporary name,
get the real one now. */
use_cache = false;
} else {
/* Keep and use this name */
return;
}
}
if (use_cache) {
fil_space_t *space = fil_space_acquire_silent(table->space);
if (space != NULL) {
/* Use this name unless it is a temporary general
tablespace name and we can now replace it. */
if (!srv_sys_tablespaces_open ||
!dict_table_has_temp_general_tablespace_name(space->name)) {
/* Use this tablespace name */
table->tablespace = mem_heap_strdup(table->heap, space->name);
fil_space_release(space);
return;
}
fil_space_release(space);
}
}
/* Read it from the dictionary. */
if (srv_sys_tablespaces_open) {
if (dict_mutex_own) {
dict_mutex_exit_for_mysql();
}
table->tablespace = dd_space_get_name(table->heap, table, dd_table);
if (dict_mutex_own) {
dict_mutex_enter_for_mysql();
}
}
}
template void dd_get_and_save_space_name<dd::Table>(dict_table_t *,
const dd::Table *, bool);
template void dd_get_and_save_space_name<dd::Partition>(dict_table_t *,
const dd::Partition *,
bool);
/** Open or load a table definition based on a Global DD object.
@tparam Table dd::Table or dd::Partition
@param[in,out] client data dictionary client
@param[in] table MySQL table definition
@param[in] norm_name Table Name
@param[in] dd_table Global DD table or partition object
@param[in] thd thread THD
@param[in,out] fk_list stack of table names which neet to load
@return ptr to dict_table_t filled, otherwise, nullptr */
template <typename Table>
dict_table_t *dd_open_table_one(dd::cache::Dictionary_client *client,
const TABLE *table, const char *norm_name,
const Table *dd_table, THD *thd,
dict_names_t &fk_list) {
ut_ad(dd_table != nullptr);
bool implicit;
dd::Tablespace *dd_space = nullptr;
if (dd_table->tablespace_id() == dict_sys_t::s_dd_space_id) {
/* DD tables are in shared DD tablespace */
implicit = false;
} else if (dd_tablespace_is_implicit(
client, dd_first_index(dd_table)->tablespace_id(), &implicit,
&dd_space)) {
/* Tablespace no longer exist, it could be already dropped */
return (nullptr);
}
const bool zip_allowed = srv_page_size <= UNIV_ZIP_SIZE_MAX;
const bool strict = false;
bool first_index = true;
/* Create dict_table_t for the table */
dict_table_t *m_table = dd_fill_dict_table(
dd_table, table, norm_name, NULL, zip_allowed, strict, thd, implicit);
if (m_table == nullptr) {
return (nullptr);
}
/* Create dict_index_t for the table */
int ret;
ret = dd_fill_dict_index(dd_table->table(), table, m_table, NULL, zip_allowed,
strict, thd);
if (ret != 0) {
return (nullptr);
}
if (dd_space && !implicit) {
const char *name = dd_space->name().c_str();
if (name) {
m_table->tablespace = mem_heap_strdupl(m_table->heap, name, strlen(name));
}
}
if (Field **autoinc_col = table->s->found_next_number_field) {
const dd::Properties &p = dd_table->table().se_private_data();
dict_table_autoinc_set_col_pos(m_table, (*autoinc_col)->field_index);
uint64 version, autoinc = 0;
if (p.get(dd_table_key_strings[DD_TABLE_VERSION], &version) ||
p.get(dd_table_key_strings[DD_TABLE_AUTOINC], &autoinc)) {
ut_ad(!"problem setting AUTO_INCREMENT");
return (nullptr);
}
m_table->version = version;
dict_table_autoinc_lock(m_table);
dict_table_autoinc_initialize(m_table, autoinc + 1);
dict_table_autoinc_unlock(m_table);
m_table->autoinc_persisted = autoinc;
}
mem_heap_t *heap = mem_heap_create(1000);
bool fail = false;
/* Now fill the space ID and Root page number for each index */
dict_index_t *index = m_table->first_index();
for (const auto dd_index : dd_table->indexes()) {
ut_ad(index != nullptr);
const dd::Properties &se_private_data = dd_index->se_private_data();
uint64 id = 0;
uint32 root = 0;
uint32 sid = 0;
uint64 trx_id = 0;
dd::Object_id index_space_id = dd_index->tablespace_id();
dd::Tablespace *index_space = nullptr;
if (dd_table->tablespace_id() == dict_sys_t::s_dd_space_id) {
sid = dict_sys_t::s_space_id;
} else if (dd_table->tablespace_id() == dict_sys_t::s_dd_temp_space_id) {
sid = dict_sys_t::s_temp_space_id;
} else {
if (client->acquire_uncached_uncommitted<dd::Tablespace>(index_space_id,
&index_space) ||
index_space == nullptr) {
my_error(ER_TABLESPACE_MISSING, MYF(0), m_table->name.m_name);
fail = true;
break;
}
if (index_space->se_private_data().get(dd_space_key_strings[DD_SPACE_ID],
&sid)) {
fail = true;
break;
}
}
if (first_index) {
ut_ad(m_table->space == 0);
m_table->space = sid;
m_table->dd_space_id = index_space_id;
uint32 dd_fsp_flags;
if (dd_table->tablespace_id() == dict_sys_t::s_dd_space_id) {
dd_fsp_flags = dict_tf_to_fsp_flags(m_table->flags);
} else {
ut_ad(dd_space != nullptr);
dd_space->se_private_data().get(dd_space_key_strings[DD_SPACE_FLAGS],
&dd_fsp_flags);
}
mutex_enter(&dict_sys->mutex);
dd_load_tablespace(dd_table, m_table, heap, DICT_ERR_IGNORE_RECOVER_LOCK,
dd_fsp_flags);
mutex_exit(&dict_sys->mutex);
first_index = false;
}
if (se_private_data.get(dd_index_key_strings[DD_INDEX_ID], &id) ||
se_private_data.get(dd_index_key_strings[DD_INDEX_ROOT], &root) ||
se_private_data.get(dd_index_key_strings[DD_INDEX_TRX_ID], &trx_id)) {
fail = true;
break;
}
ut_ad(root > 1);
ut_ad(index->type & DICT_FTS || root != FIL_NULL ||
dict_table_is_discarded(m_table));
ut_ad(id != 0);
index->page = root;
index->space = sid;
index->id = id;
index->trx_id = trx_id;
/** Fill index txn info by from se_private_data */
if (lizard::dd_index_fill_txn_desc(index, se_private_data)) {
fail = true;
break;
}
/** Look up the spatial reference system in the
dictionary. Since this may cause a table open to read the
dictionary tables, it must be done while not holding
&dict_sys->mutex. */
if (dict_index_is_spatial(index))
index->rtr_srs.reset(fetch_srs(index->srid));
index = index->next();
}
if (!implicit) {
dd_get_and_save_space_name(m_table, dd_table, false);
}
mutex_enter(&dict_sys->mutex);
if (fail) {
for (dict_index_t *index = UT_LIST_GET_LAST(m_table->indexes);
index != nullptr; index = UT_LIST_GET_LAST(m_table->indexes)) {
dict_index_remove_from_cache(m_table, index);
}
dict_mem_table_free(m_table);
mutex_exit(&dict_sys->mutex);
mem_heap_free(heap);
return (nullptr);
}
/* Re-check if the table has been opened/added by a concurrent
thread */
dict_table_t *exist = dict_table_check_if_in_cache_low(norm_name);
if (exist != nullptr) {
for (dict_index_t *index = UT_LIST_GET_LAST(m_table->indexes);
index != nullptr; index = UT_LIST_GET_LAST(m_table->indexes)) {
dict_index_remove_from_cache(m_table, index);
}
dict_mem_table_free(m_table);
m_table = exist;
} else {
dict_table_add_to_cache(m_table, TRUE, heap);
if (m_table->fts && dict_table_has_fts_index(m_table)) {
fts_optimize_add_table(m_table);
}
if (dict_sys->dynamic_metadata != nullptr) {
dict_table_load_dynamic_metadata(m_table);
}
}
m_table->acquire();
mutex_exit(&dict_sys->mutex);
/* Check if this is a DD system table */
if (m_table != nullptr) {
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(m_table->name.m_name, db_buf, tbl_buf, nullptr, nullptr,
nullptr);
m_table->is_dd_table =
dd::get_dictionary()->is_dd_table_name(db_buf, tbl_buf);
}
/* Load foreign key info. It could also register child table(s) that
refers to current table */
if (exist == nullptr) {
dberr_t error =
dd_table_load_fk(client, norm_name, nullptr, m_table,
&dd_table->table(), thd, false, true, &fk_list);
if (error != DB_SUCCESS) {
dict_table_close(m_table, FALSE, FALSE);
m_table = nullptr;
}
}
mem_heap_free(heap);
return (m_table);
}
/** Open single table with name
@param[in] name table name
@param[in] dict_locked dict_sys mutex is held or not
@param[in,out] fk_list foreign key name list
@param[in] thd thread THD */
static void dd_open_table_one_on_name(const char *name, bool dict_locked,
dict_names_t &fk_list, THD *thd) {
dict_table_t *table = nullptr;
const dd::Table *dd_table = nullptr;
MDL_ticket *mdl = nullptr;
if (!dict_locked) {
mutex_enter(&dict_sys->mutex);
}
table = dict_table_check_if_in_cache_low(name);
if (table != NULL) {
/* If the table is in cached already, do nothing. */
if (!dict_locked) {
mutex_exit(&dict_sys->mutex);
}
return;
} else {
/* Otherwise, open it by dd obj. */
char db_buf[MAX_DATABASE_NAME_LEN + 1];
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
/* Exit sys mutex to access server info */
mutex_exit(&dict_sys->mutex);
if (!dd_parse_tbl_name(name, db_buf, tbl_buf, nullptr, nullptr, nullptr)) {
goto func_exit;
}
if (dd_mdl_acquire(thd, &mdl, db_buf, tbl_buf)) {
goto func_exit;
}
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
if (client->acquire(db_buf, tbl_buf, &dd_table) || dd_table == nullptr) {
goto func_exit;
}
ut_ad(dd_table->se_private_id() != dd::INVALID_OBJECT_ID);
TABLE_SHARE ts;
init_tmp_table_share(thd, &ts, db_buf, strlen(db_buf),
dd_table->name().c_str(), "" /* file name */, nullptr);
ulint error =
open_table_def_suppress_invalid_meta_data(thd, &ts, *dd_table);
if (error != 0) {
goto func_exit;
}
TABLE td;
error = open_table_from_share(thd, &ts, dd_table->name().c_str(), 0,
SKIP_NEW_HANDLER, 0, &td, false, dd_table);
if (error != 0) {
free_table_share(&ts);
goto func_exit;
}
table = dd_open_table_one(client, &td, name, dd_table, thd, fk_list);
closefrm(&td, false);
free_table_share(&ts);
}
func_exit:
if (table != NULL) {
dd_table_close(table, thd, &mdl, false);
} else {
dd_mdl_release(thd, &mdl);
}
if (dict_locked) {
mutex_enter(&dict_sys->mutex);
}
}
/** Open foreign tables reference a table.
@param[in] fk_list foreign key name list
@param[in] dict_locked dict_sys mutex is locked or not
@param[in] thd thread THD */
void dd_open_fk_tables(dict_names_t &fk_list, bool dict_locked, THD *thd) {
while (!fk_list.empty()) {
char *name = const_cast<char *>(fk_list.front());
if (innobase_get_lower_case_table_names() == 2) {
innobase_casedn_str(name);
} else {
#ifndef _WIN32
if (innobase_get_lower_case_table_names() == 1) {
innobase_casedn_str(name);
}
#endif /* !_WIN32 */
}
dd_open_table_one_on_name(name, dict_locked, fk_list, thd);
fk_list.pop_front();
}
}
/** Open or load a table definition based on a Global DD object.
@tparam Table dd::Table or dd::Partition
@param[in,out] client data dictionary client
@param[in] table MySQL table definition
@param[in] norm_name Table Name
@param[in] dd_table Global DD table or partition object
@param[in] thd thread THD
@return ptr to dict_table_t filled, otherwise, nullptr */
template <typename Table>
dict_table_t *dd_open_table(dd::cache::Dictionary_client *client,
const TABLE *table, const char *norm_name,
const Table *dd_table, THD *thd) {
dict_table_t *m_table = nullptr;
dict_names_t fk_list;
m_table = dd_open_table_one(client, table, norm_name, dd_table, thd, fk_list);
/* If there is foreign table references to this table, we will
try to open them */
if (m_table != nullptr && !fk_list.empty()) {
dd_open_fk_tables(fk_list, false, thd);
}
return (m_table);
}
template dict_table_t *dd_open_table<dd::Table>(dd::cache::Dictionary_client *,
const TABLE *, const char *,
const dd::Table *, THD *);
template dict_table_t *dd_open_table<dd::Partition>(
dd::cache::Dictionary_client *, const TABLE *, const char *,
const dd::Partition *, THD *);
/** Get next record from a new dd system table, like mysql.tables...
@param[in,out] pcur persistent cursor
@param[in] mtr the mini-transaction
@return the next rec of the dd system table */
static const rec_t *dd_getnext_system_low(btr_pcur_t *pcur, mtr_t *mtr) {
rec_t *rec = NULL;
bool is_comp = dict_table_is_comp(pcur->index()->table);
while (!rec || rec_get_deleted_flag(rec, is_comp)) {
btr_pcur_move_to_next_user_rec(pcur, mtr);
if (pcur->index()->is_clustered()) {
assert_lizard_page_attributes(pcur->get_page(), pcur->index());
}
rec = btr_pcur_get_rec(pcur);
if (!btr_pcur_is_on_user_rec(pcur)) {
/* end of index */
btr_pcur_close(pcur);
return (NULL);
}
}
/* Get a record, let's save the position */
btr_pcur_store_position(pcur, mtr);
return (rec);
}
/** Get next record of new DD system tables
@param[in,out] pcur persistent cursor
@param[in] mtr the mini-transaction
@retval next record */
const rec_t *dd_getnext_system_rec(btr_pcur_t *pcur, mtr_t *mtr) {
/* Restore the position */
btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr);
return (dd_getnext_system_low(pcur, mtr));
}
/** Scan a new dd system table, like mysql.tables...
@param[in] thd thd
@param[in,out] mdl mdl lock
@param[in,out] pcur persistent cursor
@param[in,out] mtr the mini-transaction
@param[in] system_table_name which dd system table to open
@param[in,out] table dict_table_t obj of dd system table
@retval the first rec of the dd system table */
const rec_t *dd_startscan_system(THD *thd, MDL_ticket **mdl, btr_pcur_t *pcur,
mtr_t *mtr, const char *system_table_name,
dict_table_t **table) {
dict_index_t *clust_index;
const rec_t *rec = nullptr;
*table = dd_table_open_on_name(thd, mdl, system_table_name, true, false);
mtr_commit(mtr);
clust_index = UT_LIST_GET_FIRST((*table)->indexes);
assert_lizard_dict_index_check(clust_index);
assert_lizard_dict_table_check(*table);
mtr_start(mtr);
btr_pcur_open_at_index_side(true, clust_index, BTR_SEARCH_LEAF, pcur, true, 0,
mtr);
rec = dd_getnext_system_low(pcur, mtr);
return (rec);
}
/**
All DD tables would contain DB_TRX_ID and DB_ROLL_PTR fields
before other fields. This offset indicates the position at
which the first DD column is located.
Lizard: In addition, Lizard also add two columns [DB_SCN_ID, DB_UNDO_PTR]
*/
static const int DD_FIELD_OFFSET = 2 + DATA_N_LIZARD_COLS;
/** Process one mysql.tables record and get the dict_table_t
@param[in] heap temp memory heap
@param[in,out] rec mysql.tables record
@param[in,out] table dict_table_t to fill
@param[in] dd_tables dict_table_t obj of dd system table
@param[in] mdl mdl on the table
@param[in] mtr the mini-transaction
@retval error message, or NULL on success */
const char *dd_process_dd_tables_rec_and_mtr_commit(
mem_heap_t *heap, const rec_t *rec, dict_table_t **table,
dict_table_t *dd_tables, MDL_ticket **mdl, mtr_t *mtr) {
ulint len;
const byte *field;
const char *err_msg = NULL;
ulint table_id;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_tables)));
ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX));
ulint *offsets = rec_get_offsets(rec, dd_tables->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Table>();
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ENGINE") + DD_FIELD_OFFSET, &len);
/* If "engine" field is not "innodb", return. */
if (strncmp((const char *)field, "InnoDB", 6) != 0) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Get the se_private_id field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_ID") + DD_FIELD_OFFSET,
&len);
if (len != 8) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Get the table id */
table_id = mach_read_from_8(field);
/* Skip mysql.* tables. */
if (dict_sys_t::is_dd_table_id(table_id)) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Commit before load the table again */
mtr_commit(mtr);
THD *thd = current_thd;
*table = dd_table_open_on_id(table_id, thd, mdl, true, false);
if (!(*table)) {
err_msg = "Table not found";
}
return (err_msg);
}
/** Process one mysql.table_partitions record and get the dict_table_t
@param[in] heap temp memory heap
@param[in,out] rec mysql.table_partitions record
@param[in,out] table dict_table_t to fill
@param[in] dd_tables dict_table_t obj of dd partition table
@param[in] mdl mdl on the table
@param[in] mtr the mini-transaction
@retval error message, or NULL on success */
const char *dd_process_dd_partitions_rec_and_mtr_commit(
mem_heap_t *heap, const rec_t *rec, dict_table_t **table,
dict_table_t *dd_tables, MDL_ticket **mdl, mtr_t *mtr) {
ulint len;
const byte *field;
const char *err_msg = NULL;
ulint table_id;
ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX));
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_tables)));
ulint *offsets = rec_get_offsets(rec, dd_tables->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Partition>();
/* Get the engine field. */
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ENGINE") + DD_FIELD_OFFSET, &len);
/* If "engine" field is not "innodb", return. */
if (strncmp((const char *)field, "InnoDB", 6) != 0) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Get the se_private_id field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_ID") + DD_FIELD_OFFSET,
&len);
/* When table is partitioned table, the se_private_id is null. */
if (len != 8) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Get the table id */
table_id = mach_read_from_8(field);
/* Skip mysql.* tables. */
if (dict_sys_t::is_dd_table_id(table_id)) {
*table = NULL;
mtr_commit(mtr);
return (err_msg);
}
/* Commit before load the table again */
mtr_commit(mtr);
THD *thd = current_thd;
*table = dd_table_open_on_id(table_id, thd, mdl, true, false);
if (!(*table)) {
err_msg = "Table not found";
}
return (err_msg);
}
/** Process one mysql.columns record and get info to dict_col_t
@param[in,out] heap temp memory heap
@param[in] rec mysql.columns record
@param[in,out] col dict_col_t to fill
@param[in,out] table_id table id
@param[in,out] col_name column name
@param[in,out] nth_v_col nth v column
@param[in] dd_columns dict_table_t obj of mysql.columns
@param[in,out] mtr the mini-transaction
@retval true if column is filled */
bool dd_process_dd_columns_rec(mem_heap_t *heap, const rec_t *rec,
dict_col_t *col, table_id_t *table_id,
char **col_name, ulint *nth_v_col,
const dict_table_t *dd_columns, mtr_t *mtr) {
ulint len;
const byte *field;
dict_col_t *t_col;
ulint pos;
ulint v_pos = 0;
dd::Column::enum_hidden_type hidden;
bool is_virtual;
dict_v_col_t *vcol = nullptr;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_columns)));
ulint *offsets = rec_get_offsets(rec, dd_columns->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Column>();
/* Get the hidden attribute, and skip if it's a hidden column. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_HIDDEN") + DD_FIELD_OFFSET, &len);
hidden = static_cast<dd::Column::enum_hidden_type>(mach_read_from_1(field));
if (hidden == dd::Column::enum_hidden_type::HT_HIDDEN_SE ||
hidden == dd::Column::enum_hidden_type::HT_HIDDEN_SQL) {
mtr_commit(mtr);
return (false);
}
/* Get the column name. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_NAME") + DD_FIELD_OFFSET, &len);
*col_name = mem_heap_strdupl(heap, (const char *)field, len);
/* Get the position. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ORDINAL_POSITION") + DD_FIELD_OFFSET,
&len);
pos = mach_read_from_4(field) - 1;
/* Get the is_virtual attribute. */
field = (const byte *)rec_get_nth_field(rec, offsets,
dd_object_table.field_number("FIELD_IS_VIRTUAL") + DD_FIELD_OFFSET, &len);
is_virtual = mach_read_from_1(field) & 0x01;
/* Get the se_private_data field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_DATA") + DD_FIELD_OFFSET,
&len);
if (len == 0 || len == UNIV_SQL_NULL) {
mtr_commit(mtr);
return (false);
}
char *p_ptr = (char *)mem_heap_strdupl(heap, (const char *)field, len);
dd::String_type prop((char *)p_ptr);
dd::Properties *p = dd::Properties::parse_properties(prop);
/* Load the table and get the col. */
if (!p || !p->exists(dd_index_key_strings[DD_TABLE_ID])) {
if (p) {
delete p;
}
mtr_commit(mtr);
return (false);
}
if (!p->get(dd_index_key_strings[DD_TABLE_ID], (uint64 *)table_id)) {
THD *thd = current_thd;
dict_table_t *table;
MDL_ticket *mdl = NULL;
/* Commit before we try to load the table. */
mtr_commit(mtr);
table = dd_table_open_on_id(*table_id, thd, &mdl, true, true);
if (!table) {
delete p;
return (false);
}
if (is_virtual) {
vcol = dict_table_get_nth_v_col_mysql(table, pos);
if (vcol == nullptr) {
dd_table_close(table, thd, &mdl, true);
delete p;
return (false);
}
/* Copy info. */
col->ind = vcol->m_col.ind;
col->mtype = vcol->m_col.mtype;
col->prtype = vcol->m_col.prtype;
col->len = vcol->m_col.len;
v_pos = dict_create_v_col_pos(vcol->v_pos, vcol->m_col.ind);
} else {
if (table->n_v_cols == 0) {
t_col = table->get_col(pos);
} else {
ulint col_nr;
col_nr = dict_table_has_column(table, *col_name, pos);
t_col = table->get_col(col_nr);
ut_ad(t_col);
}
/* Copy info. */
col->ind = t_col->ind;
col->mtype = t_col->mtype;
col->prtype = t_col->prtype;
col->len = t_col->len;
}
if (p->exists(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT_NULL]) ||
p->exists(dd_column_key_strings[DD_INSTANT_COLUMN_DEFAULT])) {
dd_parse_default_value(*p, col, heap);
}
dd_table_close(table, thd, &mdl, true);
delete p;
} else {
delete p;
mtr_commit(mtr);
return (false);
}
/* Report the virtual column number */
if (col->prtype & DATA_VIRTUAL) {
ut_ad(vcol != nullptr);
ut_ad(v_pos != 0);
ut_ad(is_virtual);
*nth_v_col = dict_get_v_col_pos(v_pos);
} else {
*nth_v_col = ULINT_UNDEFINED;
}
return (true);
}
/** Process one mysql.columns record for virtual columns
@param[in] heap temp memory heap
@param[in,out] rec mysql.columns record
@param[in,out] table_id table id
@param[in,out] pos position
@param[in,out] base_pos base column position
@param[in,out] n_row number of rows
@param[in] dd_columns dict_table_t obj of mysql.columns
@param[in] mtr the mini-transaction
@retval true if virtual info is filled */
bool dd_process_dd_virtual_columns_rec(mem_heap_t *heap, const rec_t *rec,
table_id_t *table_id, ulint **pos,
ulint **base_pos, ulint *n_row,
dict_table_t *dd_columns, mtr_t *mtr) {
ulint len;
const byte *field;
ulint origin_pos;
dd::Column::enum_hidden_type hidden;
bool is_virtual;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_columns)));
ulint *offsets = rec_get_offsets(rec, dd_columns->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Column>();
/* Get the is_virtual attribute, and skip if it's not a virtual column. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_IS_VIRTUAL") + DD_FIELD_OFFSET, &len);
is_virtual = mach_read_from_1(field) & 0x01;
if (!is_virtual) {
mtr_commit(mtr);
return (false);
}
/* Get the hidden attribute, and skip if it's a hidden column. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_HIDDEN") + DD_FIELD_OFFSET, &len);
hidden = static_cast<dd::Column::enum_hidden_type>(mach_read_from_1(field));
if (hidden == dd::Column::enum_hidden_type::HT_HIDDEN_SE) {
mtr_commit(mtr);
return (false);
}
/* Get the position. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ORDINAL_POSITION") + DD_FIELD_OFFSET,
&len);
origin_pos = mach_read_from_4(field) - 1;
/* Get the se_private_data field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_DATA") + DD_FIELD_OFFSET,
&len);
if (len == 0 || len == UNIV_SQL_NULL) {
mtr_commit(mtr);
return (false);
}
char *p_ptr = (char *)mem_heap_strdupl(heap, (const char *)field, len);
dd::String_type prop((char *)p_ptr);
dd::Properties *p = dd::Properties::parse_properties(prop);
/* Load the table and get the col. */
if (!p || !p->exists(dd_index_key_strings[DD_TABLE_ID])) {
if (p) {
delete p;
}
mtr_commit(mtr);
return (false);
}
if (!p->get(dd_index_key_strings[DD_TABLE_ID], (uint64 *)table_id)) {
THD *thd = current_thd;
dict_table_t *table;
MDL_ticket *mdl = NULL;
dict_v_col_t *vcol = NULL;
/* Commit before we try to load the table. */
mtr_commit(mtr);
table = dd_table_open_on_id(*table_id, thd, &mdl, true, true);
if (!table) {
delete p;
return (false);
}
vcol = dict_table_get_nth_v_col_mysql(table, origin_pos);
if (vcol == NULL || vcol->num_base == 0) {
dd_table_close(table, thd, &mdl, true);
delete p;
return (false);
}
*pos = static_cast<ulint *>(
mem_heap_alloc(heap, vcol->num_base * sizeof(ulint)));
*base_pos = static_cast<ulint *>(
mem_heap_alloc(heap, vcol->num_base * sizeof(ulint)));
*n_row = vcol->num_base;
for (ulint i = 0; i < *n_row; i++) {
(*pos)[i] = dict_create_v_col_pos(vcol->v_pos, vcol->m_col.ind);
(*base_pos)[i] = vcol->base_col[i]->ind;
}
dd_table_close(table, thd, &mdl, true);
delete p;
} else {
delete p;
mtr_commit(mtr);
return (false);
}
return (true);
}
/** Process one mysql.indexes record and get dict_index_t
@param[in] heap temp memory heap
@param[in,out] rec mysql.indexes record
@param[in,out] index dict_index_t to fill
@param[in] mdl mdl on index->table
@param[in,out] parent parent table if it's fts aux table.
@param[in,out] parent_mdl mdl on parent if it's fts aux table.
@param[in] dd_indexes dict_table_t obj of mysql.indexes
@param[in] mtr the mini-transaction
@retval true if index is filled */
bool dd_process_dd_indexes_rec(mem_heap_t *heap, const rec_t *rec,
const dict_index_t **index, MDL_ticket **mdl,
dict_table_t **parent, MDL_ticket **parent_mdl,
dict_table_t *dd_indexes, mtr_t *mtr) {
ulint len;
const byte *field;
uint32 index_id;
uint32 space_id;
uint64 table_id;
*index = nullptr;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_indexes)));
ulint *offsets = rec_get_offsets(rec, dd_indexes->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Index>();
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ENGINE") + DD_FIELD_OFFSET, &len);
/* If "engine" field is not "innodb", return. */
if (strncmp((const char *)field, "InnoDB", 6) != 0) {
mtr_commit(mtr);
return (false);
}
/* Get the se_private_data field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_DATA") + DD_FIELD_OFFSET,
&len);
if (len == 0 || len == UNIV_SQL_NULL) {
mtr_commit(mtr);
return (false);
}
/* Get index id. */
dd::String_type prop((char *)field);
dd::Properties *p = dd::Properties::parse_properties(prop);
if (!p || !p->exists(dd_index_key_strings[DD_INDEX_ID]) ||
!p->exists(dd_index_key_strings[DD_INDEX_SPACE_ID])) {
if (p) {
delete p;
}
mtr_commit(mtr);
return (false);
}
if (p->get(dd_index_key_strings[DD_INDEX_ID], &index_id)) {
delete p;
mtr_commit(mtr);
return (false);
}
/* Get the tablespace id. */
if (p->get(dd_index_key_strings[DD_INDEX_SPACE_ID], &space_id)) {
delete p;
mtr_commit(mtr);
return (false);
}
/* Skip mysql.* indexes. */
if (space_id == dict_sys->s_space_id) {
delete p;
mtr_commit(mtr);
return (false);
}
/* Load the table and get the index. */
if (!p->exists(dd_index_key_strings[DD_TABLE_ID])) {
delete p;
mtr_commit(mtr);
return (false);
}
if (!p->get(dd_index_key_strings[DD_TABLE_ID], &table_id)) {
THD *thd = current_thd;
dict_table_t *table;
/* Commit before load the table */
mtr_commit(mtr);
table = dd_table_open_on_id(table_id, thd, mdl, true, true);
if (!table) {
delete p;
return (false);
}
/* For fts aux table, we need to acquire mdl lock on parent. */
if (table->is_fts_aux()) {
fts_aux_table_t fts_table;
/* Find the parent ID. */
ut_d(bool is_fts =) fts_is_aux_table_name(&fts_table, table->name.m_name,
strlen(table->name.m_name));
ut_ad(is_fts);
table_id_t parent_id = fts_table.parent_id;
dd_table_close(table, thd, mdl, true);
*parent = dd_table_open_on_id(parent_id, thd, parent_mdl, true, true);
if (*parent == nullptr) {
delete p;
return (false);
}
table = dd_table_open_on_id(table_id, thd, mdl, true, true);
if (!table) {
dd_table_close(*parent, thd, parent_mdl, true);
delete p;
return (false);
}
}
for (const dict_index_t *t_index = table->first_index(); t_index != NULL;
t_index = t_index->next()) {
if (t_index->space == space_id && t_index->id == index_id) {
*index = t_index;
}
}
if (*index == nullptr) {
dd_table_close(table, thd, mdl, true);
if (table->is_fts_aux() && *parent) {
dd_table_close(*parent, thd, parent_mdl, true);
}
delete p;
return (false);
}
delete p;
} else {
delete p;
mtr_commit(mtr);
return (false);
}
return (true);
}
/** Process one mysql.indexes record and get breif info to dict_index_t
@param[in] heap temp memory heap
@param[in,out] rec mysql.indexes record
@param[in,out] index_id index id
@param[in,out] space_id space id
@param[in] dd_indexes dict_table_t obj of mysql.indexes
@retval true if index is filled */
bool dd_process_dd_indexes_rec_simple(mem_heap_t *heap, const rec_t *rec,
space_index_t *index_id,
space_id_t *space_id,
dict_table_t *dd_indexes) {
ulint len;
const byte *field;
uint32 idx_id;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_indexes)));
ulint *offsets = rec_get_offsets(rec, dd_indexes->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Index>();
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ENGINE") + DD_FIELD_OFFSET, &len);
/* If "engine" field is not "innodb", return. */
if (strncmp((const char *)field, "InnoDB", 6) != 0) {
return (false);
}
/* Get the se_private_data field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_DATA") + DD_FIELD_OFFSET,
&len);
if (len == 0 || len == UNIV_SQL_NULL) {
return (false);
}
/* Get index id. */
dd::String_type prop((char *)field);
dd::Properties *p = dd::Properties::parse_properties(prop);
if (!p || !p->exists(dd_index_key_strings[DD_INDEX_ID]) ||
!p->exists(dd_index_key_strings[DD_INDEX_SPACE_ID])) {
if (p) {
delete p;
}
return (false);
}
if (p->get(dd_index_key_strings[DD_INDEX_ID], &idx_id)) {
delete p;
return (false);
}
*index_id = idx_id;
/* Get the tablespace_id. */
if (p->get(dd_index_key_strings[DD_INDEX_SPACE_ID], space_id)) {
delete p;
return (false);
}
delete p;
return (true);
}
/** Process one mysql.tablespaces record and get info
@param[in] heap temp memory heap
@param[in,out] rec mysql.tablespaces record
@param[in,out] space_id space id
@param[in,out] name space name
@param[in,out] flags space flags
@param[in,out] server_version server version
@param[in,out] space_version space version
@param[in,out] is_encrypted true if tablespace is encrypted
@param[in,out] state space state
@param[in] dd_spaces dict_table_t obj of mysql.tablespaces
@return true if data is retrived */
bool dd_process_dd_tablespaces_rec(mem_heap_t *heap, const rec_t *rec,
space_id_t *space_id, char **name,
uint32_t *flags, uint32 *server_version,
uint32 *space_version, bool *is_encrypted,
dd::String_type *state,
dict_table_t *dd_spaces) {
ulint len;
const byte *field;
char *prop_str;
ut_ad(!rec_get_deleted_flag(rec, dict_table_is_comp(dd_spaces)));
ulint *offsets = rec_get_offsets(rec, dd_spaces->first_index(), NULL,
ULINT_UNDEFINED, &heap);
const dd::Object_table &dd_object_table = dd::get_dd_table<dd::Tablespace>();
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_ENGINE") + DD_FIELD_OFFSET, &len);
/* If "engine" field is not "innodb", return. */
if (strncmp((const char *)field, "InnoDB", 6) != 0) {
return (false);
}
/* Get name field. */
field = rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_NAME") + DD_FIELD_OFFSET, &len);
*name = reinterpret_cast<char *>(mem_heap_zalloc(heap, len + 1));
memcpy(*name, field, len);
/* Get the se_private_data field. */
field = (const byte *)rec_get_nth_field(
rec, offsets,
dd_object_table.field_number("FIELD_SE_PRIVATE_DATA") + DD_FIELD_OFFSET,
&len);
if (len == 0 || len == UNIV_SQL_NULL) {
return (false);
}
prop_str = static_cast<char *>(mem_heap_zalloc(heap, len + 1));
memcpy(prop_str, field, len);
dd::String_type prop(prop_str);
const dd::Properties *p = dd::Properties::parse_properties(prop);
if (!p || !p->exists(dd_space_key_strings[DD_SPACE_ID]) ||
!p->exists(dd_index_key_strings[DD_SPACE_FLAGS])) {
if (p) {
delete p;
}
return (false);
}
/* Get space id. */
if (p->get(dd_space_key_strings[DD_SPACE_ID], space_id)) {
delete p;
return (false);
}
/* Get space flags. */
if (p->get(dd_space_key_strings[DD_SPACE_FLAGS], flags)) {
delete p;
return (false);
}
/* Get server version. */
if (p->get(dd_space_key_strings[DD_SPACE_SERVER_VERSION], server_version)) {
delete p;
return (false);
}
/* Get space version. */
if (p->get(dd_space_key_strings[DD_SPACE_VERSION], space_version)) {
delete p;
return (false);
}
/* Get tablespace state. */
dd_tablespace_get_state(p, state, *space_id);
/* For UNDO tablespaces, encryption is governed by srv_undo_log_encrypt
variable and DD flags are not updated for encryption changes. Following
is a workaround until UNDO tablespace encryption change is done by a DDL. */
if (fsp_is_undo_tablespace(*space_id)) {
*is_encrypted = srv_undo_log_encrypt;
} else if (FSP_FLAGS_GET_ENCRYPTION(*flags)) {
/* Get Encryption. */
*is_encrypted = true;
}
delete p;
return (true);
}
/** Get dd tablespace id for fts table
@param[in] parent_table parent table of fts table
@param[in] table fts table
@param[in,out] dd_space_id dd table space id
@return true on success, false on failure. */
bool dd_get_fts_tablespace_id(const dict_table_t *parent_table,
const dict_table_t *table,
dd::Object_id &dd_space_id) {
char db_name[MAX_DATABASE_NAME_LEN + 1];
char table_name[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(parent_table->name.m_name, db_name, table_name, nullptr,
nullptr, nullptr);
THD *thd = current_thd;
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
dd::Object_id space_id = parent_table->dd_space_id;
dd_space_id = dd::INVALID_OBJECT_ID;
if (dict_table_is_file_per_table(table)) {
/* This means user table and file_per_table */
bool ret;
char *filename = fil_space_get_first_path(table->space);
ret = dd_create_implicit_tablespace(client, thd, table->space,
table->name.m_name, filename, false,
dd_space_id);
ut_free(filename);
if (ret) {
return (false);
}
} else if (table->space != TRX_SYS_SPACE &&
table->space != srv_tmp_space.space_id()) {
/* This is a user table that resides in shared tablespace */
ut_ad(!dict_table_is_file_per_table(table));
ut_ad(DICT_TF_HAS_SHARED_SPACE(table->flags));
/* Currently the tablespace id is hard coded as 0 */
dd_space_id = space_id;
const dd::Tablespace *index_space = NULL;
if (client->acquire<dd::Tablespace>(space_id, &index_space)) {
return (false);
}
uint32 id;
if (index_space == NULL) {
return (false);
} else if (index_space->se_private_data().get(
dd_space_key_strings[DD_SPACE_ID], &id) ||
id != table->space) {
ut_ad(!"missing or incorrect tablespace id");
return (false);
}
} else if (table->space == TRX_SYS_SPACE) {
/* This is a user table that resides in innodb_system
tablespace */
ut_ad(!dict_table_is_file_per_table(table));
dd_space_id = dict_sys_t::s_dd_sys_space_id;
}
return (true);
}
/** Set table options for fts dd tables according to dict table
@param[in,out] dd_table dd table instance
@param[in] table dict table instance */
void dd_set_fts_table_options(dd::Table *dd_table, const dict_table_t *table) {
dd_table->set_engine(innobase_hton_name);
dd_table->set_hidden(dd::Abstract_table::HT_HIDDEN_SE);
dd_table->set_collation_id(my_charset_bin.number);
dd::Table::enum_row_format row_format = dd::Table::RF_DYNAMIC;
switch (dict_tf_get_rec_format(table->flags)) {
case REC_FORMAT_REDUNDANT:
row_format = dd::Table::RF_REDUNDANT;
break;
case REC_FORMAT_COMPACT:
row_format = dd::Table::RF_COMPACT;
break;
case REC_FORMAT_COMPRESSED:
row_format = dd::Table::RF_COMPRESSED;
break;
case REC_FORMAT_DYNAMIC:
row_format = dd::Table::RF_DYNAMIC;
break;
default:
ut_a(0);
}
dd_table->set_row_format(row_format);
/* FTS AUX tables are always not encrypted/compressed
as it is designed now. So both "compress" and "encrypt_type"
option are not set */
dd::Properties *table_options = &dd_table->options();
table_options->set("pack_record", true);
table_options->set("checksum", false);
table_options->set("delay_key_write", false);
table_options->set("avg_row_length", 0);
table_options->set("stats_sample_pages", 0);
table_options->set("stats_auto_recalc", HA_STATS_AUTO_RECALC_DEFAULT);
if (auto zip_ssize = DICT_TF_GET_ZIP_SSIZE(table->flags)) {
table_options->set("key_block_size", 1 << (zip_ssize - 1));
} else {
table_options->set("key_block_size", 0);
}
}
/** Add nullability info to column se_private_data
@param[in,out] dd_col DD table column
@param[in] col InnoDB table column */
static void dd_set_fts_nullability(dd::Column *dd_col, const dict_col_t *col) {
bool is_nullable = !(col->prtype & DATA_NOT_NULL);
dd::Properties &p = dd_col->se_private_data();
p.set("nullable", is_nullable);
}
/** Create dd table for fts aux index table
@param[in] parent_table parent table of fts table
@param[in,out] table fts table
@param[in] charset fts index charset
@return true on success, false on failure */
bool dd_create_fts_index_table(const dict_table_t *parent_table,
dict_table_t *table,
const CHARSET_INFO *charset) {
ut_ad(charset != nullptr);
char db_name[MAX_DATABASE_NAME_LEN + 1];
char table_name[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(table->name.m_name, db_name, table_name, nullptr, nullptr,
nullptr);
/* Create dd::Table object */
THD *thd = current_thd;
dd::Schema_MDL_locker mdl_locker(thd);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
const dd::Schema *schema = nullptr;
if (mdl_locker.ensure_locked(db_name) ||
client->acquire<dd::Schema>(db_name, &schema)) {
return (false);
}
/* Check if schema is nullptr? */
if (schema == nullptr) {
my_error(ER_BAD_DB_ERROR, MYF(0), db_name);
return (false);
}
std::unique_ptr<dd::Table> dd_table_obj(schema->create_table(thd));
dd::Table *dd_table = dd_table_obj.get();
dd_table->set_name(table_name);
dd_table->set_schema_id(schema->id());
dd_set_fts_table_options(dd_table, table);
/* FTS AUX tables are always not encrypted/compressed
as it is designed now. So both "compress" and "encrypt_type"
option are not set */
/* Fill columns */
/* 1st column: word */
const char *col_name = nullptr;
dd::Column *col = dd_table->add_column();
col_name = "word";
col->set_name(col_name);
col->set_type(dd::enum_column_types::VARCHAR);
col->set_char_length(FTS_INDEX_WORD_LEN);
col->set_nullable(false);
col->set_collation_id(charset->number);
ut_ad(strcmp(col_name, table->get_col_name(0)) == 0);
dd_set_fts_nullability(col, table->get_col(0));
dd::Column *key_col1 = col;
/* 2nd column: first_doc_id */
col = dd_table->add_column();
col->set_name("first_doc_id");
col->set_type(dd::enum_column_types::LONGLONG);
col->set_char_length(20);
col->set_numeric_scale(0);
col->set_nullable(false);
col->set_unsigned(true);
col->set_collation_id(charset->number);
dd::Column *key_col2 = col;
/* 3rd column: last_doc_id */
col = dd_table->add_column();
col->set_name("last_doc_id");
col->set_type(dd::enum_column_types::LONGLONG);
col->set_char_length(20);
col->set_numeric_scale(0);
col->set_nullable(false);
col->set_unsigned(true);
col->set_collation_id(charset->number);
/* 4th column: doc_count */
col = dd_table->add_column();
col->set_name("doc_count");
col->set_type(dd::enum_column_types::LONG);
col->set_char_length(4);
col->set_numeric_scale(0);
col->set_nullable(false);
col->set_unsigned(true);
col->set_collation_id(charset->number);
/* 5th column: ilist */
col = dd_table->add_column();
col->set_name("ilist");
col->set_type(dd::enum_column_types::BLOB);
col->set_char_length(8);
col->set_nullable(false);
col->set_collation_id(my_charset_bin.number);
/* Fill index */
dd::Index *index = dd_table->add_index();
index->set_name("FTS_INDEX_TABLE_IND");
index->set_algorithm(dd::Index::IA_BTREE);
index->set_algorithm_explicit(false);
index->set_visible(true);
index->set_type(dd::Index::IT_PRIMARY);
index->set_ordinal_position(1);
index->set_generated(false);
index->set_engine(dd_table->engine());
index->options().set("flags", 32);
dd::Index_element *index_elem;
index_elem = index->add_element(key_col1);
index_elem->set_length(FTS_INDEX_WORD_LEN);
index_elem = index->add_element(key_col2);
index_elem->set_length(FTS_INDEX_FIRST_DOC_ID_LEN);
/* Fill table space info, etc */
dd::Object_id dd_space_id;
if (!dd_get_fts_tablespace_id(parent_table, table, dd_space_id)) {
return (false);
}
table->dd_space_id = dd_space_id;
dd_write_table(dd_space_id, dd_table, table);
MDL_ticket *mdl_ticket = NULL;
if (dd::acquire_exclusive_table_mdl(thd, db_name, table_name, false,
&mdl_ticket)) {
ut_ad(0);
return (false);
}
/* Store table to dd */
bool fail = client->store(dd_table);
if (fail) {
ut_ad(0);
return (false);
}
return (true);
}
/** Create dd table for fts aux common table
@param[in] parent_table parent table of fts table
@param[in,out] table fts table
@param[in] is_config flag whether it's fts aux configure table
@return true on success, false on failure */
bool dd_create_fts_common_table(const dict_table_t *parent_table,
dict_table_t *table, bool is_config) {
char db_name[MAX_DATABASE_NAME_LEN + 1];
char table_name[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(table->name.m_name, db_name, table_name, nullptr, nullptr,
nullptr);
/* Create dd::Table object */
THD *thd = current_thd;
dd::Schema_MDL_locker mdl_locker(thd);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
const dd::Schema *schema = nullptr;
if (mdl_locker.ensure_locked(db_name) ||
client->acquire<dd::Schema>(db_name, &schema)) {
return (false);
}
/* Check if schema is nullptr */
if (schema == nullptr) {
my_error(ER_BAD_DB_ERROR, MYF(0), db_name);
return (false);
}
std::unique_ptr<dd::Table> dd_table_obj(schema->create_table(thd));
dd::Table *dd_table = dd_table_obj.get();
dd_table->set_name(table_name);
dd_table->set_schema_id(schema->id());
dd_set_fts_table_options(dd_table, table);
const char *col_name = nullptr;
/* Fill columns */
if (!is_config) {
/* 1st column: doc_id */
dd::Column *col = dd_table->add_column();
col_name = "doc_id";
col->set_name(col_name);
col->set_type(dd::enum_column_types::LONGLONG);
col->set_char_length(20);
col->set_numeric_scale(0);
col->set_nullable(false);
col->set_unsigned(true);
col->set_collation_id(my_charset_bin.number);
ut_ad(strcmp(col_name, table->get_col_name(0)) == 0);
dd_set_fts_nullability(col, table->get_col(0));
dd::Column *key_col1 = col;
/* Fill index */
dd::Index *index = dd_table->add_index();
index->set_name("FTS_COMMON_TABLE_IND");
index->set_algorithm(dd::Index::IA_BTREE);
index->set_algorithm_explicit(false);
index->set_visible(true);
index->set_type(dd::Index::IT_PRIMARY);
index->set_ordinal_position(1);
index->set_generated(false);
index->set_engine(dd_table->engine());
index->options().set("flags", 32);
dd::Index_element *index_elem;
index_elem = index->add_element(key_col1);
index_elem->set_length(FTS_INDEX_FIRST_DOC_ID_LEN);
} else {
/* Fill columns */
/* 1st column: key */
dd::Column *col = dd_table->add_column();
col_name = "key";
col->set_name(col_name);
col->set_type(dd::enum_column_types::VARCHAR);
col->set_char_length(FTS_CONFIG_TABLE_KEY_COL_LEN);
col->set_nullable(false);
col->set_collation_id(my_charset_latin1.number);
ut_ad(strcmp(col_name, table->get_col_name(0)) == 0);
dd_set_fts_nullability(col, table->get_col(0));
dd::Column *key_col1 = col;
/* 2nd column: value */
col = dd_table->add_column();
col->set_name("value");
col->set_type(dd::enum_column_types::VARCHAR);
col->set_char_length(FTS_CONFIG_TABLE_VALUE_COL_LEN);
col->set_nullable(false);
col->set_collation_id(my_charset_latin1.number);
/* Fill index */
dd::Index *index = dd_table->add_index();
index->set_name("FTS_COMMON_TABLE_IND");
index->set_algorithm(dd::Index::IA_BTREE);
index->set_algorithm_explicit(false);
index->set_visible(true);
index->set_type(dd::Index::IT_PRIMARY);
index->set_ordinal_position(1);
index->set_generated(false);
index->set_engine(dd_table->engine());
index->options().set("flags", 32);
dd::Index_element *index_elem;
index_elem = index->add_element(key_col1);
index_elem->set_length(FTS_CONFIG_TABLE_KEY_COL_LEN);
}
/* Fill table space info, etc */
dd::Object_id dd_space_id;
if (!dd_get_fts_tablespace_id(parent_table, table, dd_space_id)) {
ut_ad(0);
return (false);
}
table->dd_space_id = dd_space_id;
dd_write_table(dd_space_id, dd_table, table);
MDL_ticket *mdl_ticket = NULL;
if (dd::acquire_exclusive_table_mdl(thd, db_name, table_name, false,
&mdl_ticket)) {
return (false);
}
/* Store table to dd */
bool fail = client->store(dd_table);
if (fail) {
ut_ad(0);
return (false);
}
return (true);
}
/** Drop dd table & tablespace for fts aux table
@param[in] name table name
@param[in] file_per_table flag whether use file per table
@return true on success, false on failure. */
bool dd_drop_fts_table(const char *name, bool file_per_table) {
char db_name[MAX_DATABASE_NAME_LEN + 1];
char table_name[MAX_TABLE_NAME_LEN + 1];
dd_parse_tbl_name(name, db_name, table_name, nullptr, nullptr, nullptr);
/* Create dd::Table object */
THD *thd = current_thd;
dd::Schema_MDL_locker mdl_locker(thd);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
MDL_ticket *mdl_ticket = NULL;
if (dd::acquire_exclusive_table_mdl(thd, db_name, table_name, false,
&mdl_ticket)) {
return (false);
}
const dd::Table *dd_table = nullptr;
if (client->acquire<dd::Table>(db_name, table_name, &dd_table)) {
return (false);
}
if (dd_table == nullptr) {
return (false);
}
if (file_per_table) {
dd::Object_id dd_space_id = (*dd_table->indexes().begin())->tablespace_id();
bool error;
error = dd_drop_tablespace(client, thd, dd_space_id);
ut_a(!error);
}
if (client->drop(dd_table)) {
return (false);
}
return (true);
}
/** Rename dd table & tablespace files for fts aux table
@param[in] table dict table
@param[in] old_name old innodb table name
@return true on success, false on failure. */
bool dd_rename_fts_table(const dict_table_t *table, const char *old_name) {
char new_db_name[MAX_DATABASE_NAME_LEN + 1];
char new_table_name[MAX_TABLE_NAME_LEN + 1];
char old_db_name[MAX_DATABASE_NAME_LEN + 1];
char old_table_name[MAX_TABLE_NAME_LEN + 1];
char *new_name = table->name.m_name;
dd_parse_tbl_name(new_name, new_db_name, new_table_name, nullptr, nullptr,
nullptr);
dd_parse_tbl_name(old_name, old_db_name, old_table_name, nullptr, nullptr,
nullptr);
ut_ad(strcmp(new_db_name, old_db_name) != 0);
ut_ad(strcmp(new_table_name, old_table_name) == 0);
/* Create dd::Table object */
THD *thd = current_thd;
dd::Schema_MDL_locker mdl_locker(thd);
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
const dd::Schema *to_sch = nullptr;
if (client->acquire<dd::Schema>(new_db_name, &to_sch)) {
return (false);
}
MDL_ticket *mdl_ticket = nullptr;
if (dd::acquire_exclusive_table_mdl(thd, old_db_name, old_table_name, false,
&mdl_ticket)) {
return (false);
}
MDL_ticket *mdl_ticket2 = nullptr;
if (dd::acquire_exclusive_table_mdl(thd, new_db_name, new_table_name, false,
&mdl_ticket2)) {
return (false);
}
dd::Table *dd_table = nullptr;
if (client->acquire_for_modification<dd::Table>(old_db_name, old_table_name,
&dd_table)) {
return (false);
}
// Set schema id
dd_table->set_schema_id(to_sch->id());
/* Rename dd tablespace file */
if (dict_table_is_file_per_table(table)) {
char *new_path = fil_space_get_first_path(table->space);
if (dd_tablespace_rename(table->dd_space_id, table->name.m_name,
new_path) != DB_SUCCESS) {
ut_a(false);
}
ut_free(new_path);
}
if (client->update(dd_table)) {
ut_ad(0);
return (false);
}
return (true);
}
/** Set the space_id attribute in se_private_data of tablespace
@param[in,out] dd_space dd::Tablespace object
@param[in] space_id tablespace ID */
void dd_tablespace_set_space_id(dd::Tablespace *dd_space, space_id_t space_id) {
dd::Properties &p = dd_space->se_private_data();
p.set(dd_space_key_strings[DD_SPACE_ID], space_id);
}
/** Get the space_id attribute value to se_private_data of tablespace
@param[in] dd_space dd::Tablespace object
@param[in,out] space_id tablespace ID
@retval true on error (DD_FAILURE)
@retval false on success (DD_SUCCESS) */
bool dd_tablespace_get_space_id(const dd::Tablespace *dd_space,
space_id_t *space_id) {
const dd::Properties &p = dd_space->se_private_data();
if (p.exists(dd_space_key_strings[DD_SPACE_ID])) {
p.get(dd_space_key_strings[DD_SPACE_ID], space_id);
return (DD_SUCCESS);
}
return (DD_FAILURE);
}
/** Get state attribute value in dd::Tablespace::se_private_data
@param[in,out] dd_space dd::Tablespace object
@param[in] state value to set for key 'state' */
void dd_tablespace_set_state(dd::Tablespace *dd_space, dd_space_states state) {
dd::Properties &p = dd_space->se_private_data();
p.set(dd_space_key_strings[DD_SPACE_STATE], dd_space_state_values[state]);
}
/** Set Space ID and state attribute in se_private_data of mysql.tablespaces
for the named tablespace.
@param[in] space_name tablespace name
@param[in] space_id tablespace id
@param[in] state value to set for key 'state'
@return DB_SUCCESS or DD_FAILURE. */
bool dd_tablespace_set_id_and_state(const char *space_name, space_id_t space_id,
dd_space_states state) {
THD *thd = current_thd;
dd::Tablespace *dd_space;
dd::cache::Dictionary_client *dc = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser{dc};
dd::String_type tsn{space_name};
bool dd_result = dc->acquire_for_modification(tsn, &dd_space);
if (dd_space == nullptr) {
return (DD_FAILURE);
}
dd_tablespace_set_space_id(dd_space, space_id);
dd_tablespace_set_state(dd_space, state);
return (dd::commit_or_rollback_tablespace_change(thd, dd_space, dd_result));
}
void dd_tablespace_get_state(const dd::Tablespace *dd_space,
dd::String_type *state, space_id_t space_id) {
const dd::Properties &p = dd_space->se_private_data();
dd_tablespace_get_state(&p, state, space_id);
}
void dd_tablespace_get_state(const dd::Properties *p, dd::String_type *state,
space_id_t space_id) {
if (p->exists(dd_space_key_strings[DD_SPACE_STATE])) {
p->get(dd_space_key_strings[DD_SPACE_STATE], state);
} else {
/* If this k/v pair is missing then the database may have been created
by an earlier version. So calculate the state. */
dd_space_states state_enum = dd_tablespace_get_state_enum(p, space_id);
*state = dd_space_state_values[state_enum];
}
}
dd_space_states dd_tablespace_get_state_enum(const dd::Tablespace *dd_space,
space_id_t space_id) {
dd_space_states state_enum = DD_SPACE_STATE__LAST;
const dd::Properties &p = dd_space->se_private_data();
if (p.exists(dd_space_key_strings[DD_SPACE_STATE])) {
dd::String_type state;
p.get(dd_space_key_strings[DD_SPACE_STATE], &state);
/* Convert this string to a number. */
for (int s = DD_SPACE_STATE_NORMAL; s < DD_SPACE_STATE__LAST; s++) {
if (state == dd_space_state_values[s]) {
state_enum = (dd_space_states)s;
break;
}
}
} else {
/* If this k/v pair is missing then the database may have been created
by an earlier version. So calculate the state. */
state_enum = dd_tablespace_get_state_enum(&p, space_id);
}
return (state_enum);
}
dd_space_states dd_tablespace_get_state_enum(const dd::Properties *p,
space_id_t space_id) {
dd_space_states state_enum;
if (space_id == SPACE_UNKNOWN) {
if (p->exists(dd_space_key_strings[DD_SPACE_ID])) {
p->get(dd_space_key_strings[DD_SPACE_ID], &space_id);
} else {
return (DD_SPACE_STATE__LAST);
}
}
ut_ad(space_id != SPACE_UNKNOWN);
if (fsp_is_undo_tablespace(space_id)) {
undo::spaces->s_lock();
undo::Tablespace *undo_space = undo::spaces->find(undo::id2num(space_id));
if (undo_space->is_active()) {
state_enum = DD_SPACE_STATE_ACTIVE;
} else if (undo_space->is_empty()) {
state_enum = DD_SPACE_STATE_EMPTY;
} else {
state_enum = DD_SPACE_STATE_INACTIVE;
}
undo::spaces->s_unlock();
} else {
/* IBD tablespace */
bool is_discarded = false;
if (p->exists(dd_space_key_strings[DD_SPACE_DISCARD])) {
p->get(dd_space_key_strings[DD_SPACE_DISCARD], &is_discarded);
}
state_enum =
is_discarded ? DD_SPACE_STATE_DISCARDED : DD_SPACE_STATE_NORMAL;
}
return (state_enum);
}
/** Get the discarded state from se_private_data of tablespace
@param[in] dd_space dd::Tablespace object */
bool dd_tablespace_is_discarded(const dd::Tablespace *dd_space) {
dd::String_type dd_state;
dd_tablespace_get_state(dd_space, &dd_state);
if (dd_state == dd_space_state_values[DD_SPACE_STATE_DISCARDED]) {
return (true);
}
return (false);
}
bool dd_tablespace_get_mdl(const char *space_name, MDL_ticket **mdl_ticket,
bool foreground) {
THD *thd = current_thd;
/* Safeguard in release mode if background thread doesn't have THD. */
if (thd == nullptr) {
ut_ad(false);
return (true);
}
/* Explicit duration for background threads. */
bool trx_duration = foreground;
/* Background thread should not block on MDL lock. */
ulong timeout = foreground ? thd->variables.lock_wait_timeout : 0;
bool result = acquire_shared_backup_lock(thd, timeout, trx_duration);
if (!result) {
result = dd::acquire_exclusive_tablespace_mdl(thd, space_name, false,
mdl_ticket, trx_duration);
if (result) {
release_backup_lock(thd);
}
}
/* For background thread, clear timeout error. */
if (result && !foreground && thd->is_error()) {
thd->clear_error();
}
return (result);
}
/** Release the MDL held by the given ticket.
@param[in] mdl_ticket tablespace MDL ticket */
void dd_release_mdl(MDL_ticket *mdl_ticket) {
dd::release_mdl(current_thd, mdl_ticket);
release_backup_lock(current_thd);
}
#ifdef UNIV_DEBUG
/** @return total number of indexes of all DD tables */
uint32_t dd_get_total_indexes_num() {
uint32_t indexes_count = 0;
for (uint32_t idx = 0; idx < innodb_dd_table_size; idx++) {
indexes_count += innodb_dd_table[idx].n_indexes;
}
return (indexes_count);
}
#endif /* UNIV_DEBUG */
/** Open a table from its database and table name, this is currently used by
foreign constraint parser to get the referenced table.
@param[in] name foreign key table name
@param[in] database_name table db name
@param[in] database_name_len db name length
@param[in] table_name table db name
@param[in] table_name_len table name length
@param[in,out] table table object or NULL
@param[in,out] mdl mdl on table
@param[in,out] heap heap memory
@return complete table name with database and table name, allocated from
heap memory passed in */
char *dd_get_referenced_table(const char *name, const char *database_name,
ulint database_name_len, const char *table_name,
ulint table_name_len, dict_table_t **table,
MDL_ticket **mdl, mem_heap_t *heap) {
char *ref;
const char *db_name;
bool is_part;
is_part = (strstr(name, PART_SEPARATOR) != nullptr);
*table = nullptr;
if (!database_name) {
/* Use the database name of the foreign key table */
db_name = name;
database_name_len = dict_get_db_name_len(name);
} else {
db_name = database_name;
}
/* Copy database_name, '/', table_name, '\0' */
ref = static_cast<char *>(
mem_heap_alloc(heap, database_name_len + table_name_len + 2));
memcpy(ref, db_name, database_name_len);
ref[database_name_len] = '/';
memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
/* Values; 0 = Store and compare as given; case sensitive
1 = Store and compare in lower; case insensitive
2 = Store as given, compare in lower; case semi-sensitive */
if (innobase_get_lower_case_table_names() == 2) {
innobase_casedn_str(ref);
if (!is_part) {
*table = dd_table_open_on_name(current_thd, mdl, ref, true,
DICT_ERR_IGNORE_NONE);
}
memcpy(ref, db_name, database_name_len);
ref[database_name_len] = '/';
memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
} else {
#ifndef _WIN32
if (innobase_get_lower_case_table_names() == 1) {
innobase_casedn_str(ref);
}
#else
innobase_casedn_str(ref);
#endif /* !_WIN32 */
if (!is_part) {
*table = dd_table_open_on_name(current_thd, mdl, ref, true,
DICT_ERR_IGNORE_NONE);
}
}
return (ref);
}
/** Update all InnoDB tablespace cache objects. This step is done post
dictionary trx rollback, binlog recovery and DDL_LOG apply. So DD is
consistent. Update the cached tablespace objects, if they differ from
the dictionary.
@param[in,out] thd thread handle
@retval true on error
@retval false on success */
bool dd_tablespace_update_cache(THD *thd) {
/* If there are no prepared trxs, then DD reads would have been
already consistent. No need to update cache */
if (!trx_sys->found_prepared_trx) {
return (false);
}
dd::cache::Dictionary_client *dc = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(dc);
std::vector<const dd::Tablespace *> tablespaces;
space_id_t max_id = 0;
if (dc->fetch_global_components(&tablespaces)) {
return (true);
}
bool fail = false;
for (const dd::Tablespace *t : tablespaces) {
ut_ad(!fail);
if (t->engine() != innobase_hton_name) {
continue;
}
const dd::Properties &p = t->se_private_data();
uint32 id;
uint32 flags = 0;
/* There should be exactly one file name associated
with each InnoDB tablespace, except innodb_system */
fail = p.get(dd_space_key_strings[DD_SPACE_ID], &id) ||
p.get(dd_space_key_strings[DD_SPACE_FLAGS], &flags) ||
(t->files().size() != 1 &&
strcmp(t->name().c_str(), dict_sys_t::s_sys_space_name) != 0);
if (fail) {
break;
}
/* Undo tablespaces may be deleted and re-created at
startup and not registered in DD. So exempt undo tablespaces
from verification */
if (fsp_is_undo_tablespace(id)) {
continue;
}
if (!dict_sys_t::is_reserved(id) && id > max_id) {
/* Currently try to find the max one only, it should
be able to reuse the deleted smaller ones later */
max_id = id;
}
const dd::Tablespace_file *f = *t->files().begin();
fail = f == nullptr;
if (fail) {
break;
}
const char *space_name = t->name().c_str();
fil_space_t *space = fil_space_get(id);
if (space != nullptr) {
/* If the tablespace is already in cache, verify that
the tablespace name matches the name in dictionary.
If it doesn't match, use the name from dictionary. */
/* Exclude Encryption flag as (un)encryption operation might be
rolling forward in background thread. */
ut_ad(!((space->flags ^ flags) & ~(FSP_FLAGS_MASK_ENCRYPTION)));
fil_space_update_name(space, space_name);
} else {
fil_type_t purpose = fsp_is_system_temporary(id) ? FIL_TYPE_TEMPORARY
: FIL_TYPE_TABLESPACE;
const char *filename = f->filename().c_str();
/* If the user tablespace is not in cache, load the
tablespace now, with the name from dictionary */
/* It's safe to pass space_name in tablename charset
because filename is already in filename charset. */
dberr_t err = fil_ibd_open(false, purpose, id, flags, space_name, nullptr,
filename, false, false);
switch (err) {
case DB_SUCCESS:
case DB_CANNOT_OPEN_FILE:
break;
default:
ib::info(ER_IB_MSG_174)
<< "Unable to open tablespace " << id << " (flags=" << flags
<< ", filename=" << filename << ")."
<< " Have you deleted/moved the .IBD";
ut_strerr(err);
}
}
}
fil_set_max_space_id_if_bigger(max_id);
return (fail);
}
/* Check if the table belongs to an encrypted tablespace.
@param[in] table table for which check is to be done
@return true if it does. */
bool dd_is_table_in_encrypted_tablespace(const dict_table_t *table) {
fil_space_t *space = fil_space_get(table->space);
if (space != nullptr) {
return (FSP_FLAGS_GET_ENCRYPTION(space->flags));
} else {
/* Its possible that tablespace flag is missing (for ex: after
discard tablespace). In that case get tablespace flags from Data
Dictionary/ */
THD *thd = current_thd;
dd::cache::Dictionary_client *client = dd::get_dd_client(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(client);
dd::Tablespace *dd_space = nullptr;
if (!client->acquire_uncached_uncommitted<dd::Tablespace>(
table->dd_space_id, &dd_space) &&
dd_space != nullptr) {
uint32 flags;
dd_space->se_private_data().get(dd_space_key_strings[DD_SPACE_FLAGS],
&flags);
return (FSP_FLAGS_GET_ENCRYPTION(flags));
}
/* We should not reach here */
ut_ad(0);
return false;
}
}
#endif /* !UNIV_HOTBACKUP */