/** The size of the buffer to use for IO. @paramnphysicalpagesize
@return number of pages */ #define IO_BUFFER_SIZE(n) ((1024 * 1024) / (n))
/** For gathering stats on records during phase I */ struct row_stats_t {
ulint m_n_deleted; /*!< Number of deleted records
found in the index */
ulint m_n_purged; /*!< Number of records purged
optimistically */
ulint m_n_rows; /*!< Number of rows */
ulint m_n_purge_failed; /*!< Number of deleted rows
that could not be purged */
};
/** Index information required by IMPORT. */ struct row_index_t {
index_id_t m_id; /*!< Index id of the table
in the exporting server */
byte* m_name; /*!< Index name */
uint32_t m_space; /*!< Space where it is placed */
uint32_t m_page_no; /*!< Root page number */
ulint m_type; /*!< Index type */
ulint m_trx_id_offset; /*!< Relevant only for clustered indexes,offsetoftransaction
id system column */
ulint m_n_user_defined_cols; /*!< User defined columns */
ulint m_n_uniq; /*!< Number of columns that can
uniquely identify the row */
ulint m_n_nullable; /*!< Number of nullable
columns */
ulint m_n_fields; /*!< Total number of fields */
dict_field_t* m_fields; /*!< Index fields */
const dict_index_t*
m_srv_index; /*!< Index instance in the
importing server */
row_stats_t m_stats; /*!< Statistics gathered during
the import phase */
};
/** Meta data required by IMPORT. */ struct row_import {
row_import() UNIV_NOTHROW
:
m_table(NULL),
m_hostname(NULL),
m_table_name(NULL),
m_autoinc(0),
m_zip_size(0),
m_flags(0),
m_n_cols(0),
m_cols(NULL),
m_col_names(NULL),
m_n_indexes(0),
m_indexes(NULL),
m_missing(true) { }
~row_import() UNIV_NOTHROW;
/** Find the index entry in in the indexes array. @paramnameindexname
@return instance if found else 0. */
row_index_t* get_index(constchar* name) const UNIV_NOTHROW;
/** Get the number of rows in the index. @paramnameindexname
@return number of rows (doesn't include delete marked rows). */
ulint get_n_rows(constchar* name) const UNIV_NOTHROW;
/** Find the ordinal value of the column name in the cfg table columns. @paramnameofcolumntolookfor.
@return ULINT_UNDEFINED if not found. */
ulint find_col(constchar* name) const UNIV_NOTHROW;
/** Get the number of rows for which purge failed during the convertphase. @paramnameindexname
@return number of rows for which purge failed. */
ulint get_n_purge_failed(constchar* name) const UNIV_NOTHROW;
/** Check if the index is clean. ie. no delete-marked records @paramnameindexname
@return true if index needs to be purged. */ bool requires_purge(constchar* name) const UNIV_NOTHROW
{ return(get_n_purge_failed(name) > 0);
}
/** Set the index root <space, pageno> using the index name */ void set_root_by_name() UNIV_NOTHROW;
/** Set the index root <space, pageno> using a heuristic
@return DB_SUCCESS or error code */
dberr_t set_root_by_heuristic() UNIV_NOTHROW;
/** Check if the index schema that was read from the .cfg file matchestheinmemoryindexdefinition. Note:Itwillupdaterow_import_t::m_srv_indextomapthemeta-data readfromthe.cfgfiletotheserverindexinstance.
@return DB_SUCCESS or error code. */
dberr_t match_index_columns(
THD* thd, const dict_index_t* index) UNIV_NOTHROW;
/** Check if the table schema that was read from the .cfg file matchestheinmemorytabledefinition. @paramthdMySQLsessionvariable
@return DB_SUCCESS or error code. */
dberr_t match_table_columns(
THD* thd) UNIV_NOTHROW;
/** Check if the table (and index) schema that was read from the .cfgfilematchestheinmemorytabledefinition. @paramthdMySQLsessionvariable
@return DB_SUCCESS or error code. */
dberr_t match_schema(
THD* thd) UNIV_NOTHROW;
dberr_t match_flags(THD *thd) const ;
ulint find_fts_idx_offset() const
{ for (ulint i= 0; i < m_n_indexes; i++)
{ constchar* index_name= reinterpret_cast<constchar*>(m_indexes[i].m_name); if (!strcmp(index_name, FTS_DOC_ID_INDEX.str)) return i;
} return ULINT_UNDEFINED;
}
const row_index_t *find_index_by_name(constchar *name) const
{ for (ulint i= 0; i < m_n_indexes; i++)
{ constchar* index_name= reinterpret_cast<constchar*>(m_indexes[i].m_name); if (!strcmp(index_name, name)) return &m_indexes[i];
} return nullptr;
}
/** @return whether cfg file has FTS_DOC_ID
& FTS_DOC_ID_INDEX*/ bool has_hidden_fts() const
{ if (m_missing) returnfalse;
ulint col_offset= find_col(FTS_DOC_ID.str); if (col_offset == ULINT_UNDEFINED) returnfalse;
/** Need to check whether the table need to add system generatedftscolumnandsystemgeneratedftsdocumentindex @paramtabletabletobeimported @returnwhetherthetablehastoaddsystemgenerated
fts column and fts index */ bool need_hidden_fts(dict_table_t *table) const
{ return has_hidden_fts() && !table->fts_doc_id_index &&
m_n_cols == static_cast<ulint>(table->n_cols + 1) &&
m_n_indexes == UT_LIST_GET_LEN(table->indexes) + 1;
}
dict_table_t* m_table; /*!< Table instance */
byte* m_hostname; /*!< Hostname where the
tablespace was exported */
byte* m_table_name; /*!< Exporting instance table
name */
ib_uint64_t m_autoinc; /*!< Next autoinc value */
ulint m_zip_size; /*!< ROW_FORMAT=COMPRESSED
page size, or 0 */
ulint m_flags; /*!< Table flags */
ulint m_n_cols; /*!< Number of columns in the
meta-data file */
dict_col_t* m_cols; /*!< Column data */
byte** m_col_names; /*!< Column names, we store the columnnamesseparatelybecause thereisnofieldtostorethe
value in dict_col_t */
ulint m_n_indexes; /*!< Number of indexes,
including clustered index */
row_index_t* m_indexes; /*!< Index meta data */
bool m_missing; /*!< true if a .cfg file was
found and was readable */
};
struct fil_iterator_t {
pfs_os_file_t file; /*!< File handle */ constchar* filepath; /*!< File path name */
os_offset_t start; /*!< From where to start */
os_offset_t end; /*!< Where to stop */
os_offset_t file_size; /*!< File size in bytes */
ulint n_io_buffers; /*!< Number of pages to use
for IO */
byte* io_buffer; /*!< Buffer to use for IO */
fil_space_crypt_t *crypt_data; /*!< Crypt data (if encrypted) */
byte* crypt_io_buffer; /*!< IO buffer when encrypted */
byte* crypt_tmp_buffer; /*!< Temporary buffer for crypt use */
};
/** Use the page cursor to iterate over records in a block. */ class RecIterator { public: /** Default constructor */
RecIterator() noexcept : m_mtr{nullptr}
{
memset(&m_cur, 0x0, sizeof(m_cur)); /* Make page_cur_delete_rec() happy. */
m_mtr.start();
m_mtr.set_log_mode(MTR_LOG_NO_REDO);
}
/** Position the cursor on the first user record. */
rec_t* open(buf_block_t* block, const dict_index_t* index) noexcept
MY_ATTRIBUTE((warn_unused_result))
{
m_cur.index = const_cast<dict_index_t*>(index);
page_cur_set_before_first(block, &m_cur); return next();
}
/** Move to the next record. */
rec_t* next() noexcept MY_ATTRIBUTE((warn_unused_result))
{ return page_cur_move_to_next(&m_cur);
}
/**
@return the current record */
rec_t* current() UNIV_NOTHROW
{
ut_ad(!end()); return(page_cur_get_rec(&m_cur));
}
/**
@return true if cursor is at the end */ bool end() UNIV_NOTHROW
{ return(page_cur_is_after_last(&m_cur) == TRUE);
}
/** Remove the current record
@return true on success */ bool remove(rec_offs* offsets) UNIV_NOTHROW
{ const dict_index_t* const index = m_cur.index;
ut_ad(page_is_leaf(m_cur.block->page.frame)); /* We can't end up with an empty page unless it is root. */ if (page_get_n_recs(m_cur.block->page.frame) <= 1) { return(false);
}
/** Class that purges delete marked records from indexes, both secondary andcluster.Itdoesapessimisticdelete.Thisshouldonlybedoneifwe
couldn't purge the delete marked records during Phase I. */ class IndexPurge { public: /** Constructor @paramtrxtheusertransactioncoveringtheimporttablespace @paramindextobeimported
@param space_id space id of the tablespace */
IndexPurge(
trx_t* trx,
dict_index_t* index) UNIV_NOTHROW
:
m_mtr{trx},
m_index(index),
m_n_rows(0)
{
ib::info() << "Phase II - Purge records from index "
<< index->name;
}
/** Purge delete marked records.
@return DB_SUCCESS or error code. */
dberr_t garbage_collect() UNIV_NOTHROW;
/** The number of records that are not delete marked.
@return total records in the index after purge */
ulint get_n_rows() const UNIV_NOTHROW
{ return(m_n_rows);
}
private: /** Begin import, position the cursor on the first record. */ inlinebool open() noexcept;
/** Close the persistent cursor and commit the mini-transaction. */ void close() noexcept { m_mtr.commit(); btr_pcur_close(&m_pcur); }
/** Position the cursor on the next record.
@return DB_SUCCESS or error code */
dberr_t next() noexcept;
/** Store the persistent cursor position and reopen the B-treecursorinBTR_MODIFY_TREEmode,becausethe
tree structure may be changed during a pessimistic delete. */ inline dberr_t purge_pessimistic_delete() noexcept;
/** Purge a delete-marked record. */
dberr_t purge() noexcept;
private:
mtr_t m_mtr; /*!< Mini-transaction */
btr_pcur_t m_pcur; /*!< Persistent cursor */
dict_index_t* m_index; /*!< Index to be processed */
ulint m_n_rows; /*!< Records in index */
};
/** Functor that is called for each physical page that is read from the
tablespace file. */ class AbstractCallback
{ public: /** Constructor
@param trx covering transaction */
AbstractCallback(trx_t* trx, uint32_t space_id)
:
m_zip_size(0),
m_trx(trx),
m_space(space_id),
m_xdes(),
m_xdes_page_no(UINT32_MAX),
m_space_flags(UINT32_MAX) UNIV_NOTHROW { }
/** Determine the page size to use for traversing the tablespace @paramfile_sizesizeofthetablespacefileinbytes @paramblockcontentsofthefirstpageinthetablespacefile.
@retval DB_SUCCESS or error code. */ virtual dberr_t init(
os_offset_t file_size, const buf_block_t* block) UNIV_NOTHROW;
/** Invoke the functionality for the callback */ virtual dberr_t run(const fil_iterator_t& iter,
buf_block_t* block) UNIV_NOTHROW = 0;
protected: /** Get the physical offset of the extent descriptor within the page. @parampage_nopagenumberoftheextentdescriptor @parampagecontentsofthepagecontainingtheextentdescriptor.
@return the start of the xdes array in a page */ const xdes_t* xdes(
ulint page_no, const page_t* page) const UNIV_NOTHROW
{
ulint offset;
/** Set the current page directory (xdes). If the extent descriptor is markedasfreethenfreethecurrentextentdescriptorandsetitto 0.Thisimpliesthatallpagesthatarecoveredbythisextent descriptorarealsofreed.
/** Check if the page is marked as free in the extent descriptor. @parampage_nopagenumbertocheckintheextentdescriptor.
@return true if the page is marked as free */ bool is_free(uint32_t page_no) const UNIV_NOTHROW
{
ut_a(xdes_calc_descriptor_page(get_zip_size(), page_no)
== m_xdes_page_no);
/** Tryanddeterminetheindexrootpagesbycheckingifthenext/prev
pointers are both FIL_NULL. We need to ensure that skip deleted pages. */ struct FetchIndexRootPages : public AbstractCallback {
/** Index information gathered from the .ibd file. */ struct Index {
/** Fetch the clustered index root page in the tablespace @paramiterTablespaceiterator @paramblockBlocktouseforIO
@retval DB_SUCCESS or error code */
dberr_t run(const fil_iterator_t& iter,
buf_block_t* block) UNIV_NOTHROW override;
/** Check that fsp flags and row formats match. @paramblockblocktoconvert,itisnotfromthebufferpool.
@retval DB_SUCCESS or error code. */
dberr_t operator()(buf_block_t* block) UNIV_NOTHROW override;
/** Get row format from the header and the root index page. */ enum row_type get_row_format(const buf_block_t &block)
{ if (!page_is_comp(block.page.frame)) return ROW_TYPE_REDUNDANT; /* With full_crc32 we cannot tell between dynamic or compact,andreturnnot_used.Wecannotsimplyreturn dynamicorcompact,astheclientofthisfunction willnotbeabletotellwhetheritisdynamicbecause ofthisortheotherbranchbelow.Returningdefault wouldalsoworkifitisimmediatelyhandled,butis stillmoreambiguousthannot_used,whichisnota
row_format at all. */ if (fil_space_t::full_crc32(m_space_flags)) return ROW_TYPE_NOT_USED; if (!(m_space_flags & FSP_FLAGS_MASK_ATOMIC_BLOBS)) return ROW_TYPE_COMPACT; if (FSP_FLAGS_GET_ZIP_SSIZE(m_space_flags)) return ROW_TYPE_COMPRESSED; return ROW_TYPE_DYNAMIC;
}
/** Update the import configuration that will be used to import
the tablespace. */
dberr_t build_row_import(row_import* cfg) const UNIV_NOTHROW;
/** Table definition in server. When the table is being
created, there's no table yet so m_table is nullptr */ const dict_table_t* m_table;
/** Table row format. Only used when a (stub) table is being createdinwhichcasem_tableisnull,forobtainingrow
format from the .ibd for the stub table. */ enum row_type m_row_format;
/** Index information */
Index m_index;
};
/** Called for each block as it is read from the file. Check index pages to determinetheexactrowformat.Wecan'tgetthatfromthetablespace headerflagsalone.
@paramblockblocktoconvert,itisnotfromthebufferpool.
@retval DB_SUCCESS or error code. */
dberr_t FetchIndexRootPages::operator()(buf_block_t* block) UNIV_NOTHROW
{ if (is_interrupted()) return DB_INTERRUPTED;
/** Called for each block as it is read from the file. @paramblockblocktoconvert,itisnotfromthebufferpool.
@retval DB_SUCCESS or error code. */
dberr_t operator()(buf_block_t* block) UNIV_NOTHROW override;
private: /** Update the page, set the space id, max trx id and index id. @paramblockblockreadfromfile @parampage_typetypeofthepage
@retval DB_SUCCESS or error code */
dberr_t update_page(buf_block_t* block, uint16_t& page_type)
UNIV_NOTHROW;
/** Update the space, index id, trx id. @paramblockblocktoconvert
@return DB_SUCCESS or error code */
dberr_t update_index_page(buf_block_t* block) UNIV_NOTHROW;
/** Update the BLOB refrences and write UNDO log entries for rowsthatcan'tbepurgedoptimistically. @paramblockblocktoupdate
@retval DB_SUCCESS or error code */
dberr_t update_records(buf_block_t* block) UNIV_NOTHROW;
/** Validate the space flags and update tablespace header page. @paramblockblockreadfromfile,notfromthebufferpool.
@retval DB_SUCCESS or error code */
dberr_t update_header(buf_block_t* block) UNIV_NOTHROW;
/** Adjust the BLOB reference for a single column that is externally stored @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord @paramicolumnordinalvalue
@return DB_SUCCESS or error code */
dberr_t adjust_cluster_index_blob_column(
rec_t* rec, const rec_offs* offsets,
ulint i) UNIV_NOTHROW;
/** Adjusts the BLOB reference in the clustered index row for all externallystoredcolumns. @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord
@return DB_SUCCESS or error code */
dberr_t adjust_cluster_index_blob_columns(
rec_t* rec, const rec_offs* offsets) UNIV_NOTHROW;
/** In the clustered index, adjust the BLOB pointers as needed. AlsoupdatetheBLOBreference,writethenewspaceid. @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord
@return DB_SUCCESS or error code */
dberr_t adjust_cluster_index_blob_ref(
rec_t* rec, const rec_offs* offsets) UNIV_NOTHROW;
/** Purge delete-marked records, only if it is possible to do sowithoutre-organisingtheB+tree.
@retval true if purged */ bool purge() UNIV_NOTHROW;
/** Adjust the BLOB references and sys fields for the current record. @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord
@return DB_SUCCESS or error code. */
dberr_t adjust_cluster_record(
rec_t* rec, const rec_offs* offsets) UNIV_NOTHROW;
/** Find an index with the matching id.
@return row_index_t* instance or 0 */
row_index_t* find_index(index_id_t id) UNIV_NOTHROW
{
row_index_t* index = &m_cfg->m_indexes[0];
for (ulint i = 0; i < m_cfg->m_n_indexes; ++i, ++index) { if (id == index->m_id) { return(index);
}
}
return(0);
} private: /** Config for table that is being imported. */
row_import* m_cfg;
/** Current index whose pages are being imported */
row_index_t* m_index;
/** Iterator over records in a block */
RecIterator m_rec_iter;
/** Record offset */
rec_offs m_offsets_[REC_OFFS_NORMAL_SIZE];
/** Pointer to m_offsets_ */
rec_offs* m_offsets;
/** Memory heap for the record offsets */
mem_heap_t* m_heap;
/** Cluster index instance */
dict_index_t* m_cluster_index;
};
/**
row_import destructor. */
row_import::~row_import() UNIV_NOTHROW
{ for (ulint i = 0; m_indexes != 0 && i < m_n_indexes; ++i) {
UT_DELETE_ARRAY(m_indexes[i].m_name);
/** Find the index entry in in the indexes array. @paramnameindexname
@return instance if found else 0. */
row_index_t*
row_import::get_index( constchar* name) const UNIV_NOTHROW
{ for (ulint i = 0; i < m_n_indexes; ++i) { constchar* index_name;
row_index_t* index = &m_indexes[i];
/** Get the number of rows in the index. @paramnameindexname
@return number of rows (doesn't include delete marked rows). */
ulint
row_import::get_n_rows( constchar* name) const UNIV_NOTHROW
{ const row_index_t* index = get_index(name);
ut_a(name != 0);
return(index->m_stats.m_n_rows);
}
/** Get the number of rows for which purge failed during the convert phase. @paramnameindexname
@return number of rows for which purge failed. */
ulint
row_import::get_n_purge_failed( constchar* name) const UNIV_NOTHROW
{ const row_index_t* index = get_index(name);
ut_a(name != 0);
return(index->m_stats.m_n_purge_failed);
}
/** Find the ordinal value of the column name in the cfg table columns. @paramnameofcolumntolookfor.
@return ULINT_UNDEFINED if not found. */
ulint
row_import::find_col( constchar* name) const UNIV_NOTHROW
{ for (ulint i = 0; i < m_n_cols; ++i) { constchar* col_name;
if (cfg_index == 0) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index %s not found in tablespace meta-data file.",
index->name());
return(DB_ERROR);
}
if (cfg_index->m_n_fields != index->n_fields) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index field count %u doesn't match" " tablespace metadata file value " ULINTPF,
index->n_fields, cfg_index->m_n_fields);
return(DB_ERROR);
}
cfg_index->m_srv_index = index;
const dict_field_t* field = index->fields; const dict_field_t* cfg_field = cfg_index->m_fields;
for (ulint i = 0; i < index->n_fields; ++i, ++field, ++cfg_field) {
if (field->name() && cfg_field->name()
&& strcmp(field->name(), cfg_field->name()) != 0) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index field name %s doesn't match" " tablespace metadata field name %s" " for field position " ULINTPF,
field->name(), cfg_field->name(), i);
err = DB_ERROR;
}
if (cfg_field->prefix_len != field->prefix_len) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index %s field %s prefix len %u" " doesn't match metadata file value %u",
index->name(), field->name(),
field->prefix_len, cfg_field->prefix_len);
err = DB_ERROR;
}
if (cfg_field->fixed_len != field->fixed_len) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index %s field %s fixed len %u" " doesn't match metadata file value %u",
index->name(), field->name(),
field->fixed_len,
cfg_field->fixed_len);
err = DB_ERROR;
}
if (cfg_field->descending != field->descending) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Index %s field %s is %s which does " "not match with .cfg file",
index->name(), field->name(),
field->descending ? "DESC" : "ASC");
err = DB_ERROR;
}
}
return(err);
}
/** Check if the table schema that was read from the .cfg file matches the inmemorytabledefinition. @paramthdMySQLsessionvariable
@return DB_SUCCESS or error code. */
dberr_t
row_import::match_table_columns(
THD* thd) UNIV_NOTHROW
{
dberr_t err = DB_SUCCESS; const dict_col_t* col = m_table->cols;
for (ulint i = 0; i < m_table->n_cols; ++i, ++col) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s ordinal value mismatch, it's at %u" " in the table and " ULINTPF " in the tablespace meta-data file",
col_name, col->ind, cfg_col_index);
if (cfg_col->prtype != col->prtype) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s precise type mismatch," " it's 0X%X in the table and 0X%X" " in the tablespace meta file",
col_name, col->prtype, cfg_col->prtype);
err = DB_ERROR;
}
if (cfg_col->mtype != col->mtype) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s main type mismatch," " it's 0X%X in the table and 0X%X" " in the tablespace meta file",
col_name, col->mtype, cfg_col->mtype);
err = DB_ERROR;
}
if (cfg_col->len != col->len) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s length mismatch," " it's %u in the table and %u" " in the tablespace meta file",
col_name, col->len, cfg_col->len);
err = DB_ERROR;
}
if (cfg_col->mbminlen != col->mbminlen
|| cfg_col->mbmaxlen != col->mbmaxlen) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s multi-byte len mismatch," " it's %u-%u in the table and %u-%u" " in the tablespace meta file",
col_name, col->mbminlen, col->mbmaxlen,
cfg_col->mbminlen, cfg_col->mbmaxlen);
err = DB_ERROR;
}
if (cfg_col->ind != col->ind) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s position mismatch," " it's %u in the table and %u" " in the tablespace meta file",
col_name, col->ind, cfg_col->ind);
err = DB_ERROR;
}
if (cfg_col->ord_part != col->ord_part) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s ordering mismatch," " it's %u in the table and %u" " in the tablespace meta file",
col_name, col->ord_part,
cfg_col->ord_part);
err = DB_ERROR;
}
if (cfg_col->max_prefix != col->max_prefix) {
ib_errf(thd,
IB_LOG_LEVEL_ERROR,
ER_TABLE_SCHEMA_MISMATCH, "Column %s max prefix mismatch" " it's %u in the table and %u" " in the tablespace meta file",
col_name, col->max_prefix,
cfg_col->max_prefix);
err = DB_ERROR;
}
}
}
ib_errf(thd, IB_LOG_LEVEL_ERROR, ER_TABLE_SCHEMA_MISMATCH, "Table flags don't match, server table has 0x%x and the meta-data " "file has 0x%zx; .cfg file uses %s",
m_table->flags, m_flags, msg);
return DB_ERROR;
}
/** Check if the table (and index) schema that was read from the .cfg file matchestheinmemorytabledefinition. @paramthdMySQLsessionvariable
@return DB_SUCCESS or error code. */
dberr_t
row_import::match_schema(
THD* thd) UNIV_NOTHROW
{ /* Do some simple checks. */
if (UT_LIST_GET_LEN(m_table->indexes) != m_n_indexes) {
/* If the number of indexes don't match then it is better toaborttheIMPORT.Itiseasyfortheusertocreatea
table matching the IMPORT definition. */
ib_errf(thd, IB_LOG_LEVEL_ERROR, ER_TABLE_SCHEMA_MISMATCH, "Number of indexes don't match, table has " ULINTPF " indexes but the tablespace meta-data file has "
ULINTPF " indexes",
UT_LIST_GET_LEN(m_table->indexes), m_n_indexes);
return(DB_ERROR);
}
dberr_t err = match_table_columns(thd);
if (err != DB_SUCCESS) { return(err);
}
/* Check if the index definitions match. */
const dict_index_t* index;
for (index = UT_LIST_GET_FIRST(m_table->indexes);
index != 0;
index = UT_LIST_GET_NEXT(indexes, index)) {
dberr_t index_err;
index_err = match_index_columns(thd, index);
if (index_err != DB_SUCCESS) {
err = index_err;
}
}
return(err);
}
/**
Set the index root <space, pageno>, using index name. */ void
row_import::set_root_by_name() UNIV_NOTHROW
{
row_index_t* cfg_index = m_indexes;
for (ulint i = 0; i < m_n_indexes; ++i, ++cfg_index) {
dict_index_t* index;
/**
Begin import, position the cursor on the first record. */ inlinebool IndexPurge::open() noexcept
{
m_mtr.start();
m_mtr.set_log_mode(MTR_LOG_NO_REDO);
btr_pcur_init(&m_pcur);
if (m_pcur.open_leaf(true, m_index, BTR_MODIFY_LEAF, &m_mtr) != DB_SUCCESS) returnfalse;
rec_t *rec= page_rec_get_next(btr_pcur_get_rec(&m_pcur)); if (!rec) returnfalse; if (rec_is_metadata(rec, *m_index)) /* Skip the metadata pseudo-record. */
btr_pcur_get_page_cur(&m_pcur)->rec= rec; returntrue;
}
/** Positionthecursoronthenextrecord.
@return DB_SUCCESS or error code */
dberr_t IndexPurge::next() noexcept
{ if (UNIV_UNLIKELY(!btr_pcur_move_to_next_on_page(&m_pcur))) { return DB_CORRUPTION;
}
/* When switching pages, commit the mini-transaction
in order to release the latch on the old page. */
if (!btr_pcur_is_after_last_on_page(&m_pcur)) { return(DB_SUCCESS);
} elseif (trx_is_interrupted(m_mtr.trx)) { /* Check after every page because the check
is expensive. */ return(DB_INTERRUPTED);
}
btr_pcur_store_position(&m_pcur, &m_mtr);
mtr_commit(&m_mtr);
mtr_start(&m_mtr);
mtr_set_log_mode(&m_mtr, MTR_LOG_NO_REDO);
if (m_pcur.restore_position(BTR_MODIFY_LEAF, &m_mtr)
== btr_pcur_t::CORRUPTED) { return DB_CORRUPTION;
} /* The following is based on btr_pcur_move_to_next_user_rec(). */
m_pcur.old_rec = nullptr;
ut_ad(m_pcur.latch_mode == BTR_MODIFY_LEAF); do { if (btr_pcur_is_after_last_on_page(&m_pcur)) { if (btr_pcur_is_after_last_in_tree(&m_pcur)) { return DB_END_OF_INDEX;
}
if (dberr_t err = btr_pcur_move_to_next_page(&m_pcur,
&m_mtr)) { return err;
}
} elseif (!btr_pcur_move_to_next_on_page(&m_pcur)) { return DB_CORRUPTION;
}
} while (!btr_pcur_is_on_user_rec(&m_pcur));
return DB_SUCCESS;
}
/** Storethepersistentcursorpositionandreopenthe B-treecursorinBTR_MODIFY_TREEmode,becausethe
tree structure may be changed during a pessimistic delete. */ inline dberr_t IndexPurge::purge_pessimistic_delete() noexcept
{
dberr_t err; if (m_pcur.restore_position(BTR_PURGE_TREE, &m_mtr) != btr_pcur_t::CORRUPTED)
{
ut_ad(rec_get_deleted_flag(btr_pcur_get_rec(&m_pcur),
m_index->table->not_redundant()));
btr_cur_pessimistic_delete(&err, FALSE, btr_pcur_get_btr_cur(&m_pcur), 0, false, &m_mtr);
} else
err= DB_CORRUPTION;
/** Adjust the BLOB reference for a single column that is externally stored @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord @paramicolumnordinalvalue
@return DB_SUCCESS or error code */ inline
dberr_t
PageConverter::adjust_cluster_index_blob_column(
rec_t* rec, const rec_offs* offsets,
ulint i) UNIV_NOTHROW
{
ulint len;
byte* field;
field = rec_get_nth_field(rec, offsets, i, &len);
DBUG_EXECUTE_IF("ib_import_trigger_corruption_2",
len = BTR_EXTERN_FIELD_REF_SIZE - 1;);
if (len < BTR_EXTERN_FIELD_REF_SIZE) {
ib_errf(m_trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_INNODB_INDEX_CORRUPT, "Externally stored column(" ULINTPF ") has a reference length of " ULINTPF " in the cluster index %s",
i, len, m_cluster_index->name());
return(DB_CORRUPTION);
}
field += len - (BTR_EXTERN_FIELD_REF_SIZE - BTR_EXTERN_SPACE_ID);
mach_write_to_4(field, get_space_id());
if (UNIV_LIKELY_NULL(m_rec_iter.current_block()->page.zip.data)) {
page_zip_write_blob_ptr(
m_rec_iter.current_block(), rec, m_cluster_index,
offsets, i, &m_rec_iter.m_mtr);
}
return(DB_SUCCESS);
}
/** Adjusts the BLOB reference in the clustered index row for all externally storedcolumns. @paramrecrecordtoupdate @paramoffsetscolumnoffsetsfortherecord
@return DB_SUCCESS or error code */ inline
dberr_t
PageConverter::adjust_cluster_index_blob_columns(
rec_t* rec, const rec_offs* offsets) UNIV_NOTHROW
{
ut_ad(rec_offs_any_extern(offsets));
/* Adjust the space_id in the BLOB pointers. */
for (ulint i = 0; i < rec_offs_n_fields(offsets); ++i) {
/* Only if the column is stored "externally". */
if (rec_offs_nth_extern(offsets, i)) {
dberr_t err;
/** Purge delete-marked records, only if it is possible to do so without re-organisingtheB+tree.
@return true if purge succeeded */ inlinebool PageConverter::purge() UNIV_NOTHROW
{ /* We can't have a page that is empty and not root. */ if (m_rec_iter.remove(m_offsets)) {
/** Update the BLOB refrences and write UNDO log entries for rowsthatcan'tbepurgedoptimistically. @paramblockblocktoupdate
@retval DB_SUCCESS or error code */ inline
dberr_t
PageConverter::update_records(
buf_block_t* block) UNIV_NOTHROW
{
ibool comp = dict_table_is_comp(m_cfg->m_table); bool clust_index = m_index->m_srv_index == m_cluster_index;
/* This will also position the cursor on the first user record. */
rec_t* rec = m_rec_iter.open(block, m_index->m_srv_index);
if (!rec) { return DB_CORRUPTION;
}
ulint deleted;
if (!page_has_prev(block->page.frame)
&& m_index->m_srv_index->is_instant()) { /* Expect to find the hidden metadata record */ if (page_rec_is_supremum(rec)) { return DB_CORRUPTION;
}
if (!(info_bits & REC_INFO_MIN_REC_FLAG)) { return DB_CORRUPTION;
}
if (!(info_bits & REC_INFO_DELETED_FLAG)
!= !m_index->m_srv_index->table->instant) { return DB_CORRUPTION;
}
deleted = 0; goto first;
}
while (!m_rec_iter.end()) {
rec = m_rec_iter.current();
deleted = rec_get_deleted_flag(rec, comp);
/* For the clustered index we have to adjust the BLOB referenceandthesystemfieldsirrespectiveofthe deletemarkedflag.Theadjustmentofdeletemarked
cluster records is required for purge to work later. */
/* If it is a delete marked record then try an
optimistic delete. */
if (deleted) {
++m_index->m_stats.m_n_deleted; /* A successful purge will move the cursor to the
next record. */
if (purge()) { continue;
}
} else {
++m_index->m_stats.m_n_rows;
}
if (!m_rec_iter.next()) { return DB_CORRUPTION;
}
}
return(DB_SUCCESS);
}
/** Update the space, index id, trx id.
@return DB_SUCCESS or error code */ inline
dberr_t
PageConverter::update_index_page(
buf_block_t* block) UNIV_NOTHROW
{ const page_id_t page_id(block->page.id());
if (is_free(page_id.page_no())) { return(DB_SUCCESS);
}
buf_frame_t* page = block->page.frame; const index_id_t id = btr_page_get_index_id(page);
if (id != m_index->m_id) {
row_index_t* index = find_index(id);
if (UNIV_UNLIKELY(!index)) { if (!m_cfg->m_missing) {
ib::warn() << "Unknown index id " << id
<< " on page " << page_id.page_no();
} return DB_SUCCESS;
}
m_index = index;
}
/* If the .cfg file is missing and there is an index mismatch
then ignore the error. */ if (m_cfg->m_missing && !m_index->m_srv_index) { return(DB_SUCCESS);
}
/* This has to be written to uncompressed index header. Set it to
the current index id. */
mach_write_to_8(page + (PAGE_HEADER + PAGE_INDEX_ID),
m_index->m_srv_index->id); if (UNIV_LIKELY_NULL(block->page.zip.data)) {
memcpy(&block->page.zip.data[PAGE_HEADER + PAGE_INDEX_ID],
&block->page.frame[PAGE_HEADER + PAGE_INDEX_ID], 8);
}
if (m_index->m_srv_index->is_clust()) { if (page_id.page_no() != m_index->m_srv_index->page) { goto clear_page_max_trx_id;
}
} elseif (page_is_leaf(page)) { /* Set PAGE_MAX_TRX_ID on secondary index leaf pages. */
mach_write_to_8(&block->page.frame
[PAGE_HEADER + PAGE_MAX_TRX_ID], m_trx->id); if (UNIV_LIKELY_NULL(block->page.zip.data)) {
memcpy_aligned<8>(&block->page.zip.data
[PAGE_HEADER + PAGE_MAX_TRX_ID],
&block->page.frame
[PAGE_HEADER + PAGE_MAX_TRX_ID], 8);
}
} else {
clear_page_max_trx_id: /* Clear PAGE_MAX_TRX_ID so that it can be usedforotherpurposesinthefuture.IMPORT inMySQL5.6,5.7andMariaDB10.0and10.1 wouldsetthefieldtothetransactionIDeven
on clustered index pages. */
memset_aligned<8>(&block->page.frame
[PAGE_HEADER + PAGE_MAX_TRX_ID], 0, 8); if (UNIV_LIKELY_NULL(block->page.zip.data)) {
memset_aligned<8>(&block->page.zip.data
[PAGE_HEADER + PAGE_MAX_TRX_ID], 0, 8);
}
}
if (page_is_empty(page)) {
/* Only a root page can be empty. */ if (page_has_siblings(page)) { // TODO: We should relax this and skip secondary // indexes. Mark them as corrupt because they can // always be rebuilt. return(DB_CORRUPTION);
}
/* Write space_id to the tablespace header, page 0. */
mach_write_to_4(FIL_PAGE_SPACE_ID + frame, get_space_id());
memcpy_aligned<2>(FSP_HEADER_OFFSET + FSP_SPACE_ID + frame,
FIL_PAGE_SPACE_ID + frame, 4); /* Write back the adjusted flags. */
mach_write_to_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + frame, m_space_flags);
return DB_SUCCESS;
}
/** Update the page, set the space id, max trx id and index id. @paramblockblockreadfromfile
@retval DB_SUCCESS or error code */ inline
dberr_t
PageConverter::update_page(buf_block_t* block, uint16_t& page_type)
UNIV_NOTHROW
{
dberr_t err = DB_SUCCESS;
switch (page_type = fil_page_get_type(get_frame(block))) { case FIL_PAGE_TYPE_FSP_HDR:
ut_a(block->page.id().page_no() == 0); /* Work directly on the uncompressed page headers. */ return(update_header(block));
case FIL_PAGE_INDEX: case FIL_PAGE_RTREE: /* We need to decompress the contents
before we can do anything. */
if (is_compressed_table() && !buf_zip_decompress(block, TRUE)) { return(DB_CORRUPTION);
}
/* fall through */ case FIL_PAGE_TYPE_INSTANT: /* This is on every page in the tablespace. */
mach_write_to_4(
get_frame(block)
+ FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, get_space_id());
/* Only update the Btree nodes. */ return(update_index_page(block));
case FIL_PAGE_TYPE_SYS: /* This is page 0 in the system tablespace. */ return(DB_CORRUPTION);
case FIL_PAGE_TYPE_XDES:
err = set_current_xdes(
block->page.id().page_no(), get_frame(block)); /* fall through */ case FIL_PAGE_INODE: case FIL_PAGE_TYPE_TRX_SYS: case FIL_PAGE_IBUF_FREE_LIST: case FIL_PAGE_TYPE_ALLOCATED: case FIL_PAGE_IBUF_BITMAP: case FIL_PAGE_TYPE_BLOB: case FIL_PAGE_TYPE_ZBLOB: case FIL_PAGE_TYPE_ZBLOB2:
/* Work directly on the uncompressed page headers. */ /* This is on every page in the tablespace. */
mach_write_to_4(
get_frame(block)
+ FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, get_space_id());
return(err);
}
ib::warn() << "Unknown page type (" << page_type << ")";
return(DB_CORRUPTION);
}
/** Called for every page in the tablespace. If the page was not updatedthenitsstatemustbesettoBUF_PAGE_NOT_USED. @paramblockblockreadfromfile,noteitisnotfromthebufferpool
@retval DB_SUCCESS or error code. */
dberr_t PageConverter::operator()(buf_block_t* block) UNIV_NOTHROW
{ /* If we already had an old page with matching number in the buffer pool,evictitnow,becausewenolongerevictthepageson
DISCARD TABLESPACE. */ if (buf_block_t *b= buf_pool.page_fix(block->page.id(), nullptr, nullptr,
buf_pool_t::FIX_ALSO_FREED))
{
ut_ad(!b->page.oldest_modification());
mysql_mutex_lock(&buf_pool.mutex);
b->unfix();
if (!buf_LRU_free_page(&b->page, true))
ut_ad(0);
mysql_mutex_unlock(&buf_pool.mutex);
}
uint16_t page_type;
if (dberr_t err= update_page(block, page_type)) return err;
/*****************************************************************//**
Adjust the root page index node and leaf node segment headers, update
with the new space id. For all the table's secondary indexes.
@return error code */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_import_adjust_root_pages_of_secondary_indexes( /*==============================================*/
trx_t* trx, /*!< in: transaction used for
the import */
dict_table_t* table, /*!< in: table the indexes
belong to */ const row_import& cfg) /*!< Import context */
{
dict_index_t* index;
ulint n_rows_in_table;
dberr_t err = DB_SUCCESS;
/* Skip the clustered index. */
index = dict_table_get_first_index(table);
/* Adjust the root pages of the secondary indexes only. */ while ((index = dict_table_get_next_index(index)) != NULL) {
ut_a(!dict_index_is_clust(index));
if (!(index->type & DICT_CORRUPT)
&& index->page != FIL_NULL) {
/* Update the Btree segment headers for index node and
leaf nodes in the root page. Set the new space id. */
ib_errf(trx->mysql_thd,
IB_LOG_LEVEL_WARN,
ER_INNODB_INDEX_CORRUPT, "Index %s not found or corrupt," " you should recreate this index.",
index->name());
/* Do not bail out, so that the data
can be recovered. */
ib_errf(trx->mysql_thd,
IB_LOG_LEVEL_WARN,
ER_INNODB_INDEX_CORRUPT, "Index '%s' contains " ULINTPF " entries, " "should be " ULINTPF ", you should recreate " "this index.", index->name(),
purge.get_n_rows(), n_rows_in_table);
index->type |= DICT_CORRUPT;
/* Do not bail out, so that the data
can be recovered. */
err = DB_SUCCESS;
}
}
return(err);
}
/*****************************************************************//**
Read the a string from the meta data file.
@return DB_SUCCESS or error code. */ static
dberr_t
row_import_cfg_read_string( /*=======================*/
FILE* file, /*!< in/out: File to read from */
byte* ptr, /*!< out: string to read */
ulint max_len) /*!< in: maximum length of the output
buffer in bytes */
{
DBUG_EXECUTE_IF("ib_import_string_read_error",
errno = EINVAL; return(DB_IO_ERROR););
/*****************************************************************//**
Read the index names and root page numbers of the indexes and set the values.
Row format [root_page_no, len of str, str ... ]
@return DB_SUCCESS or error code. */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_import_read_index_data( /*=======================*/
FILE* file, /*!< in: File to read from */
THD* thd, /*!< in: session */
row_import* cfg) /*!< in/out: meta-data read */
{
byte* ptr;
row_index_t* cfg_index;
byte row[sizeof(index_id_t) + sizeof(ib_uint32_t) * 9];
/* FIXME: What is the max value? */
ut_a(cfg->m_n_indexes > 0);
ut_a(cfg->m_n_indexes < 1024);
cfg_index->m_trx_id_offset = mach_read_from_4(ptr); if (cfg_index->m_trx_id_offset != mach_read_from_4(ptr)) {
ut_ad(0); /* Overflow. Pretend that the clustered index
has a variable-length PRIMARY KEY. */
cfg_index->m_trx_id_offset = 0;
}
ptr += sizeof(ib_uint32_t);
/* The NUL byte is included in the name length. */
ulint len = mach_read_from_4(ptr);
if (len == 0 || len > NAME_LEN + 1) {
ib_errf(thd, IB_LOG_LEVEL_ERROR,
ER_INNODB_INDEX_CORRUPT, "Index name length (" ULINTPF ") is too long, " "the meta-data is corrupt", len);
/* Read the number of indexes. */ if (fread(row, 1, sizeof(row), file) != sizeof(row)) {
ib_senderrf(
thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
(ulong) errno, strerror(errno), "while reading number of indexes.");
return(DB_IO_ERROR);
}
cfg->m_n_indexes = mach_read_from_4(row);
if (cfg->m_n_indexes == 0) {
ib_errf(thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR, "Number of indexes in meta-data file is 0");
return(DB_CORRUPTION);
} elseif (cfg->m_n_indexes > 1024) { // FIXME: What is the upper limit? */
ib_errf(thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR, "Number of indexes in meta-data file is too high: "
ULINTPF, cfg->m_n_indexes);
cfg->m_n_indexes = 0;
/* Read the table name of tablespace that was exported. */ if (fread(value, 1, sizeof(value), file) != sizeof(value)) {
ib_senderrf(
thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
(ulong) errno, strerror(errno), "while reading meta-data table name length.");
return(DB_IO_ERROR);
}
len = mach_read_from_4(value);
if (len == 0 || len > MAX_FULL_NAME_LEN + 1) {
ib_senderrf(
thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR, 0UL, strerror(EINVAL), "while reading .cfg table name length.");
return(DB_CORRUPTION);
}
/* NUL byte is part of name length. */
cfg->m_table_name = UT_NEW_ARRAY_NOKEY(byte, len);
ib_errf(thd, IB_LOG_LEVEL_ERROR, ER_TABLE_SCHEMA_MISMATCH, "Tablespace to be imported has a different" " page size than this server. Server page size" " is %lu, whereas tablespace page size" " is " ULINTPF,
srv_page_size,
logical_page_size);
/* Check the version number. */ switch (mach_read_from_4(row)) { case IB_EXPORT_CFG_VERSION_V1: return(row_import_read_v1(file, thd, &cfg)); default:
ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_NOT_SUPPORTED_YET, "meta-data version");
}
return(DB_ERROR);
}
#define BTR_BLOB_HDR_PART_LEN 0/*!< BLOB part len on this page */ #define BTR_BLOB_HDR_NEXT_PAGE_NO 4/*!< next BLOB part page no,
FIL_NULL if none */ #define BTR_BLOB_HDR_SIZE 8/*!< Size of a BLOB part header, in bytes */
/* decrypt and decompress page if needed */ static dberr_t decrypt_decompress(fil_space_crypt_t *space_crypt,
uint32_t space_flags, span<byte> page,
uint32_t space_id, byte *page_compress_buf,
byte *tmp_frame)
{ auto *data= page.data();
if (space_crypt && space_crypt->should_encrypt())
{
uint page_size= static_cast<uint>(page.size());
if (!buf_page_verify_crypt_checksum(data, space_flags)) return DB_CORRUPTION;
/** Add fts index to the table
@param table fts index to be added on the table */ staticvoid add_fts_index(dict_table_t *table)
{
dict_index_t *fts_index= dict_mem_index_create(
table, FTS_DOC_ID_INDEX.str, DICT_UNIQUE, 2);
fts_index->lock.SRW_LOCK_INIT(index_tree_rw_lock_key);
fts_index->page= FIL_NULL;
fts_index->cached= 1;
fts_index->n_uniq= 1; /* Add fields for FTS_DOC_ID_INDEX */
dict_index_add_col(
fts_index, table,
&table->cols[table->n_cols - (DATA_N_SYS_COLS + 1)], 0);
dict_index_t *clust_index= UT_LIST_GET_FIRST(table->indexes); for (ulint i= 0; i < clust_index->n_uniq; i++)
dict_index_add_col(fts_index, table, clust_index->fields[i].col,
clust_index->fields[i].prefix_len);
UT_LIST_ADD_LAST(fts_index->table->indexes, fts_index);
}
/** Append the hidden fts column and fts doc index to the existingtable @paramtabletabletobeimported @paramthdthread @paramcfgmetadatarequiredbyimport
@return table which has fts doc id and fts doc id index */ static dict_table_t *build_fts_hidden_table(
dict_table_t *table, const row_import &cfg)
{
dict_table_t *new_table= dict_table_t::create(
{table->name.m_name, strlen(table->name.m_name)},
table->space, table->n_t_cols - (DATA_N_SYS_COLS - 1),
table->n_v_cols, table->flags,
table->flags2);
new_table->id= table->id;
new_table->space_id= table->space_id; constchar* col_name= &table->col_names[0]; /* Copy columns from old table to new fts table */ for (ulint new_i= 0;
new_i < ulint(new_table->n_cols - (DATA_N_SYS_COLS + 1));
new_i++)
{
dict_mem_table_add_col(new_table, new_table->heap, col_name,
table->cols[new_i].mtype,
table->cols[new_i].prtype,
table->cols[new_i].len);
col_name+= strlen(col_name) + 1;
}
bool is_clustered= true; /* Copy indexes from old table to new table */ for (dict_index_t *old_index= UT_LIST_GET_FIRST(table->indexes);
old_index; is_clustered= false)
{
dict_index_t *new_index= dict_mem_index_create(
new_table, old_index->name, old_index->type,
old_index->n_fields + is_clustered);
new_index->lock.SRW_LOCK_INIT(index_tree_rw_lock_key);
new_index->id= old_index->id;
new_index->n_uniq= old_index->n_uniq;
new_index->type= old_index->type;
new_index->cached= 1;
new_index->n_user_defined_cols= old_index->n_user_defined_cols;
new_index->n_core_null_bytes= old_index->n_core_null_bytes; /* Copy all fields from old index to new index */ for (ulint i= 0; i < old_index->n_fields; i++)
{
dict_field_t *field= dict_index_get_nth_field(old_index, i);
dict_col_t *col= field->col; if (col->is_virtual())
{
dict_v_col_t *v_col= reinterpret_cast<dict_v_col_t*>(col);
col= &new_table->v_cols[v_col->v_pos].m_col;
} else
{ unsigned ind= field->col->ind; if (ind >= fts_col_ind) ind++;
col= &new_table->cols[ind];
}
dict_index_add_col(new_index, new_table, col,
field->prefix_len); if (i < old_index->n_uniq) col->ord_part= 1;
}
if (is_clustered)
{ /* Add fts doc id in clustered index */
dict_index_add_col(
new_index, new_table, &table->cols[fts_col_ind], 0);
new_index->fields[old_index->n_fields].fixed_len= sizeof(doc_id_t);
}
auto *space_crypt= fil_space_read_crypt_data(zip_size, first_page.get());
SCOPE_EXIT([&space_crypt]() { if (space_crypt)
fil_space_destroy_crypt_data(&space_crypt);
});
/* The unused part of the BLOB page should be zero-filled. */ for (const byte *
b= second_page.get() + (FIL_PAGE_DATA + BTR_BLOB_HDR_SIZE) +
len,
*const end= second_page.get() + srv_page_size - BTR_EXTERN_LEN;
b < end;)
{ if (*b++) goto incompatible;
}
if (index->table->deserialise_columns(
&second_page[FIL_PAGE_DATA + BTR_BLOB_HDR_SIZE], len)) goto incompatible;
}
/* In fact, because we only ever append fields to the metadata record,itisalsoOKtoperformREADUNCOMMITTEDand thenignoreanyextrafields,providedthat
trx_sys.is_registered(DB_TRX_ID). */ if (rec_offs_n_fields(offsets) >
ulint(index->n_fields) + !!index->table->instant &&
!trx_sys.is_registered(current_trx(),
row_get_rec_trx_id(rec, index, offsets))) goto inconsistent;
/** Convert the InnoDB ROW_FORMAT from rec_format_enum to row_type. @param[in]fromROW_FORMATasarec_format_enum
@return the row_type representation of ROW_FORMAT. */ staticenum row_type from_rec_format(const rec_format_enum from)
{ switch (from) { case REC_FORMAT_COMPACT: return ROW_TYPE_COMPACT; case REC_FORMAT_DYNAMIC: return ROW_TYPE_DYNAMIC; case REC_FORMAT_REDUNDANT: return ROW_TYPE_REDUNDANT; case REC_FORMAT_COMPRESSED: return ROW_TYPE_COMPRESSED;
}
/* If we set the corrupt bit during the IMPORT phase then
we need to update the system tables. */
pars_info_bind_int4_literal(info, "type", &type);
pars_info_bind_int4_literal(info, "space", &space);
pars_info_bind_int4_literal(info, "page", &page);
pars_info_bind_ull_literal(info, "index_id", &index_id);
pars_info_bind_ull_literal(info, "table_id", &table_id);
if (err != DB_SUCCESS) {
ib_errf(trx->mysql_thd, IB_LOG_LEVEL_ERROR,
ER_INTERNAL_ERROR, "While updating the <space, root page" " number> of index %s - %s",
index->name(), ut_strerr(err));
break;
}
}
que_graph_free(graph);
return(err);
}
/** Callback arg for row_import_set_discarded. */ struct discard_t {
ib_uint32_t flags2; /*!< Value read from column */ bool state; /*!< New state of the flag */
ulint n_recs; /*!< Number of recs processed */
};
/******************************************************************//**
Fetch callback that sets or unsets the DISCARDED tablespace flag in
SYS_TABLES. The flags is stored in MIX_LEN column.
@returnFALSEif all OK */ static
ibool
row_import_set_discarded( /*=====================*/ void* row, /*!< in: sel_node_t* */ void* user_arg) /*!< in: bool set/unset flag */
{
sel_node_t* node = static_cast<sel_node_t*>(row);
discard_t* discard = static_cast<discard_t*>(user_arg);
dfield_t* dfield = que_node_get_val(node->select_list);
dtype_t* type = dfield_get_type(dfield);
ulint len = dfield_get_len(dfield);
/* There should be at most one matching record. */
ut_a(discard->n_recs == 1);
return(FALSE);
}
/** Update the DICT_TF2_DISCARDED flag in SYS_TABLES.MIX_LEN. @param[in,out]trxdictionarytransaction @param[in]table_idtableidentifier @param[in]discardedwhethertosetorcleartheflag
@return DB_SUCCESS or error code */
dberr_t row_import_update_discarded_flag(trx_t* trx, table_id_t table_id, bool discarded)
{
pars_info_t* info;
discard_t discard;
staticconstchar sql[] = "PROCEDURE UPDATE_DISCARDED_FLAG() IS\n" "DECLARE FUNCTION my_func;\n" "DECLARE CURSOR c IS\n" " SELECT MIX_LEN" " FROM SYS_TABLES" " WHERE ID = :table_id FOR UPDATE;" "\n" "BEGIN\n" "OPEN c;\n" "WHILE 1 = 1 LOOP\n" " FETCH c INTO my_func();\n" " IF c % NOTFOUND THEN\n" " EXIT;\n" " END IF;\n" "END LOOP;\n" "UPDATE SYS_TABLES" " SET MIX_LEN = :flags2" " WHERE ID = :table_id;\n" "CLOSE c;\n" "END;\n";
/* m_table is null iff we are trying to create a (stub) table, in
which case we want to get row format for the table creation. */ if (m_table)
err= this->operator()(block); else
m_row_format= get_row_format(*block);
func_exit:
free(page_compress_buf); return err;
}
/* TODO: For ROW_FORMAT=COMPRESSED tables we do a lot of useless copyingfornon-indexpages.Unfortunately,itis
required by buf_zip_decompress() */
dberr_t err = DB_SUCCESS; bool page_compressed = false; bool punch_hole = !my_test_if_thinly_provisioned(iter.file);
for (offset = iter.start; offset < iter.end; offset += n_bytes) { if (callback.is_interrupted()) {
err = DB_INTERRUPTED; goto func_exit;
}
bool updated = false;
os_offset_t page_off = offset;
ulint n_pages_read = n_bytes / size; /* This block is not attached to buf_pool */
block->page.id_.set_page_no(uint32_t(page_off / size));
for (ulint i = 0; i < n_pages_read;
++block->page.id_,
++i, page_off += size, block->page.frame += size) {
byte* src = readptr + i * size; const ulint page_no = page_get_page_no(src); if (!page_no && block->page.id().page_no()) { if (!buf_is_zeroes(span<const byte>(src,
size))) { goto page_corrupted;
} /* Proceed to the next page,
because this one is all zero. */ continue;
}
/* When tablespace is encrypted or compressed its firstpage(i.e.page0)isnotencryptedor
compressed and there is no need to copy frame. */ if (encrypted && block->page.id().page_no() != 0) {
byte *local_frame = callback.get_frame(block);
ut_ad((writeptr + (i * size)) != local_frame);
memcpy((writeptr + (i * size)), local_frame, size);
}
if (frame_changed) { if (block->page.zip.data) {
block->page.zip.data = dst;
} else {
block->page.frame = dst;
}
}
if (!success) { /* The following call prints an error message */
os_file_get_last_error(true);
sql_print_error("InnoDB: could not open the " "tablespace file %s.\n",
filepath);
ut_free(filepath); return DB_TABLESPACE_NOT_FOUND;
} else {
err = DB_SUCCESS;
}
}
/* Allocate a page to read in the tablespace header, so that we candeterminethepagesizeandzip_size(ifitiscompressed).
We allocate an extra page in case it is a compressed table. */
if (err == DB_SUCCESS) {
err = callback.init(file_size, block);
}
if (err == DB_SUCCESS) {
block->page.id_ = page_id_t(callback.get_space_id(), 0); if (ulint zip_size = callback.get_zip_size()) {
page_zip_set_size(&block->page.zip, zip_size); /* ROW_FORMAT=COMPRESSED is not optimised for block IO
for now. We do the IMPORT page by page. */
n_io_buffers = 1;
}
/* If tablespace is encrypted, it needs extra buffers */ if (iter.crypt_data && n_io_buffers > 1) { /* decrease io buffers so that memory
consumption will not double */
n_io_buffers /= 2;
}
return que_eval_sql(info, "PROCEDURE ADD_FTS_COL () IS\n" "BEGIN\n" "INSERT INTO SYS_COLUMNS VALUES" "(:id,:pos,'FTS_DOC_ID',6, 1795, 8, 0);\n" "UPDATE SYS_TABLES SET N_COLS = N_COLS + 1" " WHERE ID = :id;\n" "INSERT INTO SYS_INDEXES VALUES" "(:id, :idx_id, 'FTS_DOC_ID_INDEX', 1," " 2, :space, :page_no, 50);\n" "INSERT INTO SYS_FIELDS VALUES" "(:idx_id, 1, 'FTS_DOC_ID');\n" "END;\n", trx);
}
PRAGMA_DISABLE_CHECK_STACK_FRAME
/*****************************************************************//**
Imports a tablespace. The space id in the .ibd file must match the space id
of the table in the data dictionary.
@return error code or DB_SUCCESS */
dberr_t
row_import_for_mysql( /*=================*/
dict_table_t* table, /*!< in/out: table */
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct in MySQL */
{
dberr_t err;
ib_uint64_t autoinc = 0; char* filepath = NULL;
trx_t* trx = prebuilt->trx;
/* The caller assured that this is not read_only_mode and that no
temporary tablespace is being imported. */
ut_ad(!srv_read_only_mode);
ut_ad(!recv_sys.rpo);
ut_ad(!table->is_temporary());
#ifdef BTR_CUR_HASH_ADAPT /* On DISCARD TABLESPACE, we did not drop any adaptive hash indexentries.Ifwereplacedthediscardedtablespacewitha smalleronehere,therecouldstillbesomeadaptivehash indexentriesthatpointtocachedgarbagepagesinthebuffer pool,becausePageConverter::operator()onlyevictedthose pagesthatwerereplacedbytheimportedpages.Wemust detachanyremainingadaptivehashindexentries,becausethe adaptivehashindexmustbeasubsetofthetablecontents;
false positives are not tolerated. */ for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes); index;
index = UT_LIST_GET_NEXT(indexes, index)) {
index = index->clone_if_needed();
} #endif/* BTR_CUR_HASH_ADAPT */
UT_LIST_GET_FIRST(table->indexes)->clear_instant_alter();
/* Assign an undo segment for the transaction, so that the
transaction will be recovered after a crash. */
/* TODO: Do not write any undo log for the IMPORT cleanup. */
{
mtr_t mtr{trx};
mtr.start();
trx_undo_assign(&mtr, &err);
mtr.commit();
}
/* If the table is stored in a remote tablespace, we need to determinethatfilepathfromthelinkfileandsystemtables.
Find the space ID in SYS_TABLES since this is an ALTER TABLE. */
dict_get_and_save_data_dir_path(table);
if (filepath == NULL) { return row_import_cleanup(prebuilt, DB_OUT_OF_MEMORY);
}
/* Open the tablespace so that we can access via the buffer pool. Thetablespaceisinitiallyopenedasatemporaryone,because wewillnotbewritinganyredologforitbeforewehaveinvoked
fil_space_t::set_imported() to declare it a persistent tablespace. */
ib::info() << "Phase III - Flush changes to disk";
/* Ensure that all pages dirtied during the IMPORT make it to disk. Theonlydirtypagesgeneratedshouldbefromthepessimisticpurge
of delete marked records that couldn't be purged in Phase I. */ while (buf_flush_list_space(table->space));
for (ulint count = 0; table->space->referenced(); count++) { /* Issue a warning every 10.24 seconds, starting after
2.56 seconds */ if ((count & 511) == 128) {
ib::warn() << "Waiting for flush to complete on "
<< prebuilt->table->name;
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
ib::info() << "Phase IV - Flush complete";
table->space->set_imported();
err = lock_sys_tables(trx); if (err != DB_SUCCESS) { goto import_error;
} /* The dictionary latches will be released in in row_import_cleanup()
after the transaction commit, for both success and error. */
row_mysql_lock_data_dictionary(trx);
if (prebuilt->table != table) { /* Add fts_doc_id and fts_doc_idx in data dictionary */
err = innodb_insert_hidden_fts_col(
table, cfg.find_col(FTS_DOC_ID.str), trx);
DBUG_EXECUTE_IF("ib_import_fts_error",
err= DB_DUPLICATE_KEY;); if (err != DB_SUCCESS) { goto import_error;
}
} /* Update the root pages of the table's indexes. */
err = row_import_update_index_root(trx, table, false);
/* Set autoinc value read from .cfg file, if one was specified.
Otherwise, read the PAGE_ROOT_AUTO_INC and set it to table autoinc. */
row_import_autoinc(table, prebuilt, autoinc);
/** Prepare the create info to create a new stub table for import. @paramthdConnection @paramnameTablename,format:"db/table_name". @paramcreate_infoThecreateinfoforcreatingastub. @returnER_errorcode
@retval 0 on success */ int prepare_create_stub_for_import(THD *thd, constchar *name,
HA_CREATE_INFO& create_info)
{
DBUG_ENTER("prepare_create_stub_for_import");
FetchIndexRootPages fetchIndexRootPages; if (fil_tablespace_iterate(name, IO_BUFFER_SIZE(srv_page_size),
fetchIndexRootPages, fil_path_to_mysql_datadir)
!= DB_SUCCESS)
{ constchar *ibd_path= fil_make_filepath(
fil_path_to_mysql_datadir, table_name_t(const_cast<char*>(name)), IBD, true); if (!ibd_path) return(ER_ENGINE_OUT_OF_MEMORY);
sql_print_error("InnoDB: failed to get row format from %s.\n",
ibd_path);
DBUG_RETURN(ER_INNODB_IMPORT_ERROR);
}
create_info.init(); /* get the row format from ibd. */
create_info.row_type= fetchIndexRootPages.m_row_format; /* if .cfg exists, get the row format from cfg, and compare with ibd,reporterrorifdifferent,exceptwhencfgreports compact/dynamicandibdreportsnot_used(indicatingeithercompact
or dynamic but not sure) */ constenum row_type row_type_from_cfg=
get_row_type_from_cfg(fil_path_to_mysql_datadir, name, thd); if (row_type_from_cfg != ROW_TYPE_NOT_USED)
{ /* if ibd reports not_used but cfg reports compact or dynamic, go
with cfg. */ if (create_info.row_type != row_type_from_cfg &&
!((row_type_from_cfg == ROW_TYPE_COMPACT ||
row_type_from_cfg == ROW_TYPE_DYNAMIC) &&
create_info.row_type == ROW_TYPE_NOT_USED))
{
sql_print_error( "InnoDB: cfg and ibd disagree on row format for table %s.\n",
name);
DBUG_RETURN(ER_INNODB_IMPORT_ERROR);
} else
create_info.row_type= row_type_from_cfg;
} elseif (create_info.row_type == ROW_TYPE_NOT_USED)
create_info.row_type= ROW_TYPE_DYNAMIC;
DBUG_RETURN(0);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.129 Sekunden
(vorverarbeitet am 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.