summaryrefslogtreecommitdiff
path: root/storage/innobase/dict/dict0mem.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/dict/dict0mem.cc')
-rw-r--r--storage/innobase/dict/dict0mem.cc485
1 files changed, 405 insertions, 80 deletions
diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc
index 8327318e46c..f380feff7ef 100644
--- a/storage/innobase/dict/dict0mem.cc
+++ b/storage/innobase/dict/dict0mem.cc
@@ -34,9 +34,9 @@ Created 1/8/1996 Heikki Tuuri
#include "mach0data.h"
#include "dict0dict.h"
#include "fts0priv.h"
-#include "ut0crc32.h"
#include "lock0lock.h"
#include "sync0sync.h"
+#include "row0row.h"
#include <iostream>
#define DICT_HEAP_SIZE 100 /*!< initial memory heap size when
@@ -50,6 +50,29 @@ static const char* innobase_system_databases[] = {
NullS
};
+/** Determine if a table belongs to innobase_system_databases[]
+@param[in] name database_name/table_name
+@return whether the database_name is in innobase_system_databases[] */
+static bool dict_mem_table_is_system(const char *name)
+{
+ /* table has the following format: database/table
+ and some system table are of the form SYS_* */
+ if (!strchr(name, '/')) {
+ return true;
+ }
+ size_t table_len = strlen(name);
+ const char *system_db;
+ int i = 0;
+ while ((system_db = innobase_system_databases[i++])
+ && (system_db != NullS)) {
+ size_t len = strlen(system_db);
+ if (table_len > len && !strncmp(name, system_db, len)) {
+ return true;
+ }
+ }
+ return false;
+}
+
/** The start of the table basename suffix for partitioned tables */
const char table_name_t::part_suffix[4]
#ifdef _WIN32
@@ -58,10 +81,6 @@ const char table_name_t::part_suffix[4]
= "#P#";
#endif
-/** An interger randomly initialized at startup used to make a temporary
-table name as unuique as possible. */
-static ib_uint32_t dict_temp_file_num;
-
/** Display an identifier.
@param[in,out] s output stream
@param[in] id_name SQL identifier (other than table name)
@@ -103,8 +122,7 @@ dict_table_t*
dict_mem_table_create(
/*==================*/
const char* name, /*!< in: table name */
- ulint space, /*!< in: space where the clustered index of
- the table is placed */
+ fil_space_t* space, /*!< in: tablespace */
ulint n_cols, /*!< in: total number of columns including
virtual and non-virtual columns */
ulint n_v_cols,/*!< in: number of virtual columns */
@@ -115,6 +133,10 @@ dict_mem_table_create(
mem_heap_t* heap;
ut_ad(name);
+ ut_ad(!space
+ || space->purpose == FIL_TYPE_TABLESPACE
+ || space->purpose == FIL_TYPE_TEMPORARY
+ || space->purpose == FIL_TYPE_IMPORT);
ut_a(dict_tf2_is_valid(flags, flags2));
ut_a(!(flags2 & DICT_TF2_UNUSED_BIT_MASK));
@@ -138,10 +160,11 @@ dict_mem_table_create(
table->flags2 = (unsigned int) flags2;
table->name.m_name = mem_strdup(name);
table->is_system_db = dict_mem_table_is_system(table->name.m_name);
- table->space = (unsigned int) space;
+ table->space = space;
+ table->space_id = space ? space->id : ULINT_UNDEFINED;
table->n_t_cols = unsigned(n_cols + DATA_N_SYS_COLS);
table->n_v_cols = (unsigned int) (n_v_cols);
- table->n_cols = table->n_t_cols - table->n_v_cols;
+ table->n_cols = unsigned(table->n_t_cols - table->n_v_cols);
table->cols = static_cast<dict_col_t*>(
mem_heap_alloc(heap, table->n_cols * sizeof(dict_col_t)));
@@ -255,7 +278,7 @@ dict_add_col_name(
s += strlen(s) + 1;
}
- old_len = s - col_names;
+ old_len = unsigned(s - col_names);
} else {
old_len = 0;
}
@@ -317,6 +340,16 @@ dict_mem_table_add_col(
col = dict_table_get_nth_col(table, i);
dict_mem_fill_column_struct(col, i, mtype, prtype, len);
+
+ switch (prtype & DATA_VERSIONED) {
+ case DATA_VERS_START:
+ ut_ad(!table->vers_start);
+ table->vers_start = i;
+ break;
+ case DATA_VERS_END:
+ ut_ad(!table->vers_end);
+ table->vers_end = i;
+ }
}
/** Adds a virtual column definition to a table.
@@ -374,7 +407,7 @@ dict_mem_table_add_v_col(
i, name, heap);
}
- v_col = dict_table_get_nth_v_col(table, i);
+ v_col = &table->v_cols[i];
dict_mem_fill_column_struct(&v_col->m_col, pos, mtype, prtype, len);
v_col->v_pos = i;
@@ -403,7 +436,7 @@ dict_mem_table_add_s_col(
dict_table_t* table,
ulint num_base)
{
- ulint i = table->n_def - 1;
+ unsigned i = unsigned(table->n_def) - 1;
dict_col_t* col = dict_table_get_nth_col(table, i);
dict_s_col_t s_col;
@@ -464,13 +497,13 @@ dict_mem_table_col_rename_low(
/* We need to adjust all affected index->field
pointers, as in dict_index_add_col(). First, copy
table->col_names. */
- ulint prefix_len = s - t_col_names;
+ ulint prefix_len = ulint(s - t_col_names);
for (; i < n_col; i++) {
s += strlen(s) + 1;
}
- ulint full_len = s - t_col_names;
+ ulint full_len = ulint(s - t_col_names);
char* col_names;
if (to_len > from_len) {
@@ -503,12 +536,12 @@ dict_mem_table_col_rename_low(
/* if is_virtual and that in field->col does
not match, continue */
if ((!is_virtual) !=
- (!dict_col_is_virtual(field->col))) {
+ (!field->col->is_virtual())) {
continue;
}
ulint name_ofs
- = field->name - t_col_names;
+ = ulint(field->name - t_col_names);
if (name_ofs <= prefix_len) {
field->name = col_names + name_ofs;
} else {
@@ -712,9 +745,11 @@ dict_mem_fill_column_struct(
column->mtype = (unsigned int) mtype;
column->prtype = (unsigned int) prtype;
column->len = (unsigned int) col_len;
- dtype_get_mblen(mtype, prtype, &mbminlen, &mbmaxlen);
+ dtype_get_mblen(mtype, prtype, &mbminlen, &mbmaxlen);
column->mbminlen = mbminlen;
column->mbmaxlen = mbmaxlen;
+ column->def_val.data = NULL;
+ column->def_val.len = UNIV_SQL_DEFAULT;
}
/**********************************************************************//**
@@ -723,11 +758,8 @@ Creates an index memory object.
dict_index_t*
dict_mem_index_create(
/*==================*/
- const char* table_name, /*!< in: table name */
+ dict_table_t* table, /*!< in: table */
const char* index_name, /*!< in: index name */
- ulint space, /*!< in: space where the index tree is
- placed, ignored if the index is of
- the clustered type */
ulint type, /*!< in: DICT_UNIQUE,
DICT_CLUSTERED, ... ORed */
ulint n_fields) /*!< in: number of fields */
@@ -735,15 +767,16 @@ dict_mem_index_create(
dict_index_t* index;
mem_heap_t* heap;
- ut_ad(table_name && index_name);
+ ut_ad(!table || table->magic_n == DICT_TABLE_MAGIC_N);
+ ut_ad(index_name);
heap = mem_heap_create(DICT_HEAP_SIZE);
index = static_cast<dict_index_t*>(
mem_heap_zalloc(heap, sizeof(*index)));
+ index->table = table;
- dict_mem_fill_index_struct(index, heap, table_name, index_name,
- space, type, n_fields);
+ dict_mem_fill_index_struct(index, heap, index_name, type, n_fields);
dict_index_zip_pad_mutex_create_lazy(index);
@@ -1044,7 +1077,7 @@ dict_mem_index_add_field(
index->n_def++;
- field = dict_index_get_nth_field(index, index->n_def - 1);
+ field = dict_index_get_nth_field(index, unsigned(index->n_def) - 1);
field->name = name;
field->prefix_len = (unsigned int) prefix_len;
@@ -1098,46 +1131,15 @@ dict_mem_create_temporary_tablename(
ut_ad(dbend);
size_t dblen = size_t(dbend - dbtab) + 1;
- if (srv_safe_truncate) {
- /* InnoDB will drop all #sql tables at startup.
- Therefore, the id alone should generate a unique
- and previously non-existent name. */
- size = dblen + (sizeof(TEMP_FILE_PREFIX) + 3 + 20);
- name = static_cast<char*>(mem_heap_alloc(heap, size));
- memcpy(name, dbtab, dblen);
- snprintf(name + dblen, size - dblen,
- TEMP_FILE_PREFIX_INNODB UINT64PF, id);
- return name;
- }
- /* Increment a randomly initialized number for each temp file. */
- my_atomic_add32((int32*) &dict_temp_file_num, 1);
-
- size = dblen + (sizeof(TEMP_FILE_PREFIX) + 3 + 20 + 1 + 10);
+ size = dblen + (sizeof(TEMP_FILE_PREFIX) + 3 + 20);
name = static_cast<char*>(mem_heap_alloc(heap, size));
memcpy(name, dbtab, dblen);
snprintf(name + dblen, size - dblen,
- TEMP_FILE_PREFIX_INNODB UINT64PF "-" UINT32PF,
- id, dict_temp_file_num);
+ TEMP_FILE_PREFIX_INNODB UINT64PF, id);
return(name);
}
-/** Initialize dict memory variables */
-void
-dict_mem_init(void)
-{
- /* Initialize a randomly distributed temporary file number */
- ib_uint32_t now = static_cast<ib_uint32_t>(time(NULL));
-
- const byte* buf = reinterpret_cast<const byte*>(&now);
-
- dict_temp_file_num = ut_crc32(buf, sizeof(now));
-
- DBUG_PRINT("dict_mem_init",
- ("Starting Temporary file number is " UINT32PF,
- dict_temp_file_num));
-}
-
/** Validate the search order in the foreign key set.
@param[in] fk_set the foreign key set to be validated
@return true if search order is fine in the set, false otherwise. */
@@ -1195,31 +1197,354 @@ operator<< (std::ostream& out, const dict_foreign_set& fk_set)
return(out);
}
-/****************************************************************//**
-Determines if a table belongs to a system database
-@return */
-bool
-dict_mem_table_is_system(
-/*================*/
- char *name) /*!< in: table name */
+/** Adjust clustered index metadata for instant ADD COLUMN.
+@param[in] clustered index definition after instant ADD COLUMN */
+inline void dict_index_t::instant_add_field(const dict_index_t& instant)
{
- ut_ad(name);
+ DBUG_ASSERT(is_primary());
+ DBUG_ASSERT(instant.is_primary());
+ DBUG_ASSERT(!instant.is_instant());
+ DBUG_ASSERT(n_def == n_fields);
+ DBUG_ASSERT(instant.n_def == instant.n_fields);
+
+ DBUG_ASSERT(type == instant.type);
+ DBUG_ASSERT(trx_id_offset == instant.trx_id_offset);
+ DBUG_ASSERT(n_user_defined_cols == instant.n_user_defined_cols);
+ DBUG_ASSERT(n_uniq == instant.n_uniq);
+ DBUG_ASSERT(instant.n_fields > n_fields);
+ DBUG_ASSERT(instant.n_def > n_def);
+ DBUG_ASSERT(instant.n_nullable >= n_nullable);
+ DBUG_ASSERT(instant.n_core_fields >= n_core_fields);
+ DBUG_ASSERT(instant.n_core_null_bytes >= n_core_null_bytes);
+
+ n_fields = instant.n_fields;
+ n_def = instant.n_def;
+ n_nullable = instant.n_nullable;
+ fields = static_cast<dict_field_t*>(
+ mem_heap_dup(heap, instant.fields, n_fields * sizeof *fields));
+
+ ut_d(unsigned n_null = 0);
+
+ for (unsigned i = 0; i < n_fields; i++) {
+ DBUG_ASSERT(fields[i].same(instant.fields[i]));
+ const dict_col_t* icol = instant.fields[i].col;
+ DBUG_ASSERT(!icol->is_virtual());
+ dict_col_t* col = fields[i].col = &table->cols[
+ icol - instant.table->cols];
+ fields[i].name = col->name(*table);
+ ut_d(n_null += col->is_nullable());
+ }
- /* table has the following format: database/table
- and some system table are of the form SYS_* */
- if (strchr(name, '/')) {
- size_t table_len = strlen(name);
- const char *system_db;
- int i = 0;
- while ((system_db = innobase_system_databases[i++])
- && (system_db != NullS)) {
- size_t len = strlen(system_db);
- if (table_len > len && !strncmp(name, system_db, len)) {
- return true;
+ ut_ad(n_null == n_nullable);
+}
+
+/** Adjust metadata for instant ADD COLUMN.
+@param[in] table table definition after instant ADD COLUMN */
+void dict_table_t::instant_add_column(const dict_table_t& table)
+{
+ DBUG_ASSERT(!table.cached);
+ DBUG_ASSERT(table.n_def == table.n_cols);
+ DBUG_ASSERT(table.n_t_def == table.n_t_cols);
+ DBUG_ASSERT(n_def == n_cols);
+ DBUG_ASSERT(n_t_def == n_t_cols);
+ DBUG_ASSERT(table.n_cols > n_cols);
+ ut_ad(mutex_own(&dict_sys->mutex));
+
+ const char* end = table.col_names;
+ for (unsigned i = table.n_cols; i--; ) end += strlen(end) + 1;
+
+ col_names = static_cast<char*>(
+ mem_heap_dup(heap, table.col_names,
+ ulint(end - table.col_names)));
+ const dict_col_t* const old_cols = cols;
+ const dict_col_t* const old_cols_end = cols + n_cols;
+ cols = static_cast<dict_col_t*>(mem_heap_dup(heap, table.cols,
+ table.n_cols
+ * sizeof *cols));
+
+ /* Preserve the default values of previously instantly
+ added columns. */
+ for (unsigned i = unsigned(n_cols) - DATA_N_SYS_COLS; i--; ) {
+ cols[i].def_val = old_cols[i].def_val;
+ }
+
+ /* Copy the new default values to this->heap. */
+ for (unsigned i = n_cols; i < table.n_cols; i++) {
+ dict_col_t& c = cols[i - DATA_N_SYS_COLS];
+ DBUG_ASSERT(c.is_instant());
+ if (c.def_val.len == 0) {
+ c.def_val.data = field_ref_zero;
+ } else if (const void*& d = c.def_val.data) {
+ d = mem_heap_dup(heap, d, c.def_val.len);
+ } else {
+ DBUG_ASSERT(c.def_val.len == UNIV_SQL_NULL);
+ }
+ }
+
+ const unsigned old_n_cols = n_cols;
+ const unsigned n_add = unsigned(table.n_cols - n_cols);
+
+ n_t_def += n_add;
+ n_t_cols += n_add;
+ n_cols = table.n_cols;
+ n_def = n_cols;
+
+ for (unsigned i = n_v_def; i--; ) {
+ const dict_v_col_t& v = v_cols[i];
+ for (ulint n = v.num_base; n--; ) {
+ dict_col_t*& base = v.base_col[n];
+ if (!base->is_virtual()) {
+ DBUG_ASSERT(base >= old_cols);
+ size_t n = size_t(base - old_cols);
+ DBUG_ASSERT(n + DATA_N_SYS_COLS < old_n_cols);
+ base = &cols[n];
}
}
- return false;
- } else {
- return true;
}
+
+ dict_index_t* index = dict_table_get_first_index(this);
+
+ index->instant_add_field(*dict_table_get_first_index(&table));
+
+ while ((index = dict_table_get_next_index(index)) != NULL) {
+ for (unsigned i = 0; i < index->n_fields; i++) {
+ dict_field_t& field = index->fields[i];
+ if (field.col < old_cols
+ || field.col >= old_cols_end) {
+ DBUG_ASSERT(field.col->is_virtual());
+ } else {
+ /* Secondary indexes may contain user
+ columns and DB_ROW_ID (if there is
+ GEN_CLUST_INDEX instead of PRIMARY KEY),
+ but not DB_TRX_ID,DB_ROLL_PTR. */
+ DBUG_ASSERT(field.col >= old_cols);
+ size_t n = size_t(field.col - old_cols);
+ DBUG_ASSERT(n + DATA_N_SYS_COLS <= old_n_cols);
+ if (n + DATA_N_SYS_COLS >= old_n_cols) {
+ /* Replace DB_ROW_ID */
+ n += n_add;
+ }
+ field.col = &cols[n];
+ DBUG_ASSERT(!field.col->is_virtual());
+ field.name = field.col->name(*this);
+ }
+ }
+ }
+}
+
+/** Roll back instant_add_column().
+@param[in] old_n_cols original n_cols
+@param[in] old_cols original cols
+@param[in] old_col_names original col_names */
+void
+dict_table_t::rollback_instant(
+ unsigned old_n_cols,
+ dict_col_t* old_cols,
+ const char* old_col_names)
+{
+ ut_ad(mutex_own(&dict_sys->mutex));
+ dict_index_t* index = indexes.start;
+ /* index->is_instant() does not necessarily hold here, because
+ the table may have been emptied */
+ DBUG_ASSERT(old_n_cols >= DATA_N_SYS_COLS);
+ DBUG_ASSERT(n_cols >= old_n_cols);
+ DBUG_ASSERT(n_cols == n_def);
+ DBUG_ASSERT(index->n_def == index->n_fields);
+
+ const unsigned n_remove = n_cols - old_n_cols;
+
+ for (unsigned i = index->n_fields - n_remove; i < index->n_fields;
+ i++) {
+ if (index->fields[i].col->is_nullable()) {
+ index->n_nullable--;
+ }
+ }
+
+ index->n_fields -= n_remove;
+ index->n_def = index->n_fields;
+ if (index->n_core_fields > index->n_fields) {
+ index->n_core_fields = index->n_fields;
+ index->n_core_null_bytes
+ = UT_BITS_IN_BYTES(unsigned(index->n_nullable));
+ }
+
+ const dict_col_t* const new_cols = cols;
+ const dict_col_t* const new_cols_end = cols + n_cols;
+
+ cols = old_cols;
+ col_names = old_col_names;
+ n_cols = old_n_cols;
+ n_def = old_n_cols;
+ n_t_def -= n_remove;
+ n_t_cols -= n_remove;
+
+ for (unsigned i = n_v_def; i--; ) {
+ const dict_v_col_t& v = v_cols[i];
+ for (ulint n = v.num_base; n--; ) {
+ dict_col_t*& base = v.base_col[n];
+ if (!base->is_virtual()) {
+ base = &cols[base - new_cols];
+ }
+ }
+ }
+
+ do {
+ for (unsigned i = 0; i < index->n_fields; i++) {
+ dict_field_t& field = index->fields[i];
+ if (field.col < new_cols
+ || field.col >= new_cols_end) {
+ DBUG_ASSERT(field.col->is_virtual());
+ } else {
+ DBUG_ASSERT(field.col >= new_cols);
+ size_t n = size_t(field.col - new_cols);
+ DBUG_ASSERT(n <= n_cols);
+ if (n + DATA_N_SYS_COLS >= n_cols) {
+ n -= n_remove;
+ }
+ field.col = &cols[n];
+ DBUG_ASSERT(!field.col->is_virtual());
+ field.name = field.col->name(*this);
+ }
+ }
+ } while ((index = dict_table_get_next_index(index)) != NULL);
+}
+
+/** Trim the instantly added columns when an insert into SYS_COLUMNS
+is rolled back during ALTER TABLE or recovery.
+@param[in] n number of surviving non-system columns */
+void dict_table_t::rollback_instant(unsigned n)
+{
+ ut_ad(mutex_own(&dict_sys->mutex));
+ dict_index_t* index = indexes.start;
+ DBUG_ASSERT(index->is_instant());
+ DBUG_ASSERT(index->n_def == index->n_fields);
+ DBUG_ASSERT(n_cols == n_def);
+ DBUG_ASSERT(n >= index->n_uniq);
+ DBUG_ASSERT(n_cols > n + DATA_N_SYS_COLS);
+ const unsigned n_remove = n_cols - n - DATA_N_SYS_COLS;
+
+ char* names = const_cast<char*>(dict_table_get_col_name(this, n));
+ const char* sys = names;
+ for (unsigned i = n_remove; i--; ) {
+ sys += strlen(sys) + 1;
+ }
+ static const char system[] = "DB_ROW_ID\0DB_TRX_ID\0DB_ROLL_PTR";
+ DBUG_ASSERT(!memcmp(sys, system, sizeof system));
+ for (unsigned i = index->n_fields - n_remove; i < index->n_fields;
+ i++) {
+ if (index->fields[i].col->is_nullable()) {
+ index->n_nullable--;
+ }
+ }
+ index->n_fields -= n_remove;
+ index->n_def = index->n_fields;
+ memmove(names, sys, sizeof system);
+ memmove(cols + n, cols + n_cols - DATA_N_SYS_COLS,
+ DATA_N_SYS_COLS * sizeof *cols);
+ n_cols -= n_remove;
+ n_def = n_cols;
+ n_t_cols -= n_remove;
+ n_t_def -= n_remove;
+
+ for (unsigned i = DATA_N_SYS_COLS; i--; ) {
+ cols[n_cols - i].ind--;
+ }
+
+ if (dict_index_is_auto_gen_clust(index)) {
+ DBUG_ASSERT(index->n_uniq == 1);
+ dict_field_t* field = index->fields;
+ field->name = sys;
+ field->col = dict_table_get_sys_col(this, DATA_ROW_ID);
+ field++;
+ field->name = sys + sizeof "DB_ROW_ID";
+ field->col = dict_table_get_sys_col(this, DATA_TRX_ID);
+ field++;
+ field->name = sys + sizeof "DB_ROW_ID\0DB_TRX_ID";
+ field->col = dict_table_get_sys_col(this, DATA_ROLL_PTR);
+
+ /* Replace the DB_ROW_ID column in secondary indexes. */
+ while ((index = dict_table_get_next_index(index)) != NULL) {
+ field = &index->fields[index->n_fields - 1];
+ DBUG_ASSERT(field->col->mtype == DATA_SYS);
+ DBUG_ASSERT(field->col->prtype
+ == DATA_NOT_NULL + DATA_TRX_ID);
+ field->col--;
+ field->name = sys;
+ }
+
+ return;
+ }
+
+ dict_field_t* field = &index->fields[index->n_uniq];
+ field->name = sys + sizeof "DB_ROW_ID";
+ field->col = dict_table_get_sys_col(this, DATA_TRX_ID);
+ field++;
+ field->name = sys + sizeof "DB_ROW_ID\0DB_TRX_ID";
+ field->col = dict_table_get_sys_col(this, DATA_ROLL_PTR);
+}
+
+
+/** Check if record in clustered index is historical row.
+@param[in] rec clustered row
+@param[in] offsets offsets
+@return true if row is historical */
+bool
+dict_index_t::vers_history_row(
+ const rec_t* rec,
+ const rec_offs* offsets)
+{
+ ut_ad(is_primary());
+
+ ulint len;
+ dict_col_t& col= table->cols[table->vers_end];
+ ut_ad(col.vers_sys_end());
+ ulint nfield = dict_col_get_clust_pos(&col, this);
+ const byte *data = rec_get_nth_field(rec, offsets, nfield, &len);
+ if (col.vers_native()) {
+ ut_ad(len == sizeof trx_id_max_bytes);
+ return 0 != memcmp(data, trx_id_max_bytes, len);
+ }
+ ut_ad(len == sizeof timestamp_max_bytes);
+ return 0 != memcmp(data, timestamp_max_bytes, len);
+}
+
+/** Check if record in secondary index is historical row.
+@param[in] rec record in a secondary index
+@param[out] history_row true if row is historical
+@return true on error */
+bool
+dict_index_t::vers_history_row(
+ const rec_t* rec,
+ bool &history_row)
+{
+ ut_ad(!is_primary());
+
+ bool error = false;
+ mem_heap_t* heap = NULL;
+ dict_index_t* clust_index = NULL;
+ rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
+ rec_offs* offsets = offsets_;
+ rec_offs_init(offsets_);
+
+ mtr_t mtr;
+ mtr.start();
+
+ rec_t* clust_rec =
+ row_get_clust_rec(BTR_SEARCH_LEAF, rec, this, &clust_index, &mtr);
+ if (clust_rec) {
+ offsets = rec_get_offsets(clust_rec, clust_index, offsets, true,
+ ULINT_UNDEFINED, &heap);
+
+ history_row = clust_index->vers_history_row(clust_rec, offsets);
+ } else {
+ ib::error() << "foreign constraints: secondary index is out of "
+ "sync";
+ ut_ad(!"secondary index is out of sync");
+ error = true;
+ }
+ mtr.commit();
+ if (heap) {
+ mem_heap_free(heap);
+ }
+ return(error);
}