mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			5123 lines
		
	
	
	
		
			139 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5123 lines
		
	
	
	
		
			139 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 | 
						|
Copyright (c) 2012, 2016, Oracle and/or its affiliates. All Rights Reserved.
 | 
						|
Copyright (c) 2015, 2023, MariaDB Corporation.
 | 
						|
 | 
						|
This program is free software; you can redistribute it and/or modify it under
 | 
						|
the terms of the GNU General Public License as published by the Free Software
 | 
						|
Foundation; version 2 of the License.
 | 
						|
 | 
						|
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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA
 | 
						|
 | 
						|
*****************************************************************************/
 | 
						|
 | 
						|
/**************************************************//**
 | 
						|
@file row/row0import.cc
 | 
						|
Import a tablespace to a running instance.
 | 
						|
 | 
						|
Created 2012-02-08 by Sunny Bains.
 | 
						|
*******************************************************/
 | 
						|
 | 
						|
#include "row0import.h"
 | 
						|
#include "btr0pcur.h"
 | 
						|
#ifdef BTR_CUR_HASH_ADAPT
 | 
						|
# include "btr0sea.h"
 | 
						|
#endif
 | 
						|
#include "buf0flu.h"
 | 
						|
#include "que0que.h"
 | 
						|
#include "dict0boot.h"
 | 
						|
#include "dict0load.h"
 | 
						|
#include "pars0pars.h"
 | 
						|
#include "row0row.h"
 | 
						|
#include "row0sel.h"
 | 
						|
#include "row0mysql.h"
 | 
						|
#include "srv0start.h"
 | 
						|
#include "row0quiesce.h"
 | 
						|
#include "fil0pagecompress.h"
 | 
						|
#include "trx0undo.h"
 | 
						|
#include "lock0lock.h"
 | 
						|
#include "lzo/lzo1x.h"
 | 
						|
#include "snappy-c.h"
 | 
						|
#include "log.h"
 | 
						|
#include "table.h"
 | 
						|
#include "ha_innodb.h"
 | 
						|
 | 
						|
#include "scope.h"
 | 
						|
#include "dict0crea.h"
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#ifdef HAVE_MY_AES_H
 | 
						|
#include <my_aes.h>
 | 
						|
#endif
 | 
						|
 | 
						|
using st_::span;
 | 
						|
 | 
						|
/** The size of the buffer to use for IO.
 | 
						|
@param n physical page size
 | 
						|
@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, offset of transaction
 | 
						|
						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.
 | 
						|
	@param name index name
 | 
						|
	@return instance if found else 0. */
 | 
						|
	row_index_t* get_index(const char* name) const UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** Get the number of rows in the index.
 | 
						|
	@param name index name
 | 
						|
	@return number of rows (doesn't include delete marked rows). */
 | 
						|
	ulint	get_n_rows(const char* name) const UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** Find the ordinal value of the column name in the cfg table columns.
 | 
						|
	@param name of column to look for.
 | 
						|
	@return ULINT_UNDEFINED if not found. */
 | 
						|
	ulint find_col(const char* name) const UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** Get the number of rows for which purge failed during the
 | 
						|
	convert phase.
 | 
						|
	@param name index name
 | 
						|
	@return number of rows for which purge failed. */
 | 
						|
	ulint get_n_purge_failed(const char* name) const UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** Check if the index is clean. ie. no delete-marked records
 | 
						|
	@param name index name
 | 
						|
	@return true if index needs to be purged. */
 | 
						|
	bool requires_purge(const char* 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
 | 
						|
	matches the in memory index definition.
 | 
						|
	Note: It will update row_import_t::m_srv_index to map the meta-data
 | 
						|
	read from the .cfg file to the server index instance.
 | 
						|
	@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
 | 
						|
	matches the in memory table definition.
 | 
						|
	@param thd MySQL session variable
 | 
						|
	@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
 | 
						|
	.cfg file matches the in memory table definition.
 | 
						|
	@param thd MySQL session variable
 | 
						|
	@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++)
 | 
						|
	  {
 | 
						|
            const char* index_name=
 | 
						|
              reinterpret_cast<const char*>(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(const char *name) const
 | 
						|
	{
 | 
						|
	  for (ulint i= 0; i < m_n_indexes; i++)
 | 
						|
	  {
 | 
						|
            const char* index_name=
 | 
						|
              reinterpret_cast<const char*>(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) return false;
 | 
						|
          ulint col_offset= find_col(FTS_DOC_ID.str);
 | 
						|
	  if (col_offset == ULINT_UNDEFINED) return false;
 | 
						|
 | 
						|
          const dict_col_t *col= &m_cols[col_offset];
 | 
						|
	  if (col->mtype != DATA_INT
 | 
						|
              || (col->prtype & ~(DATA_NOT_NULL
 | 
						|
		                  | DATA_UNSIGNED | DATA_BINARY_TYPE
 | 
						|
				  | DATA_FTS_DOC_ID))
 | 
						|
	      || col->len != sizeof(doc_id_t))
 | 
						|
            return false;
 | 
						|
 | 
						|
	  return find_index_by_name(FTS_DOC_ID_INDEX.str) != nullptr;
 | 
						|
	}
 | 
						|
 | 
						|
        /** Need to check whether the table need to add system
 | 
						|
        generated fts column and system generated fts document index
 | 
						|
        @param table table to be imported
 | 
						|
        @return whether the table has to add system generated
 | 
						|
        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
 | 
						|
						column names separately because
 | 
						|
						there is no field to store the
 | 
						|
						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 */
 | 
						|
	const char*	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() UNIV_NOTHROW
 | 
						|
	{
 | 
						|
		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));
 | 
						|
	}
 | 
						|
 | 
						|
	buf_block_t* current_block() const { return m_cur.block; }
 | 
						|
 | 
						|
	/**
 | 
						|
	@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);
 | 
						|
		}
 | 
						|
 | 
						|
		if (!rec_offs_any_extern(offsets)
 | 
						|
		    && m_cur.block->page.id().page_no() != index->page
 | 
						|
		    && ((page_get_data_size(m_cur.block->page.frame)
 | 
						|
			 - rec_offs_size(offsets)
 | 
						|
			 < BTR_CUR_PAGE_COMPRESS_LIMIT(index))
 | 
						|
			|| !page_has_siblings(m_cur.block->page.frame)
 | 
						|
			|| (page_get_n_recs(m_cur.block->page.frame) < 2))) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
#ifdef UNIV_ZIP_DEBUG
 | 
						|
		page_zip_des_t* page_zip = buf_block_get_page_zip(m_cur.block);
 | 
						|
		ut_a(!page_zip || page_zip_validate(
 | 
						|
			     page_zip, m_cur.block->page.frame, index));
 | 
						|
#endif /* UNIV_ZIP_DEBUG */
 | 
						|
 | 
						|
		page_cur_delete_rec(&m_cur, offsets, &m_mtr);
 | 
						|
 | 
						|
#ifdef UNIV_ZIP_DEBUG
 | 
						|
		ut_a(!page_zip || page_zip_validate(
 | 
						|
			     page_zip, m_cur.block->page.frame, index));
 | 
						|
#endif /* UNIV_ZIP_DEBUG */
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	page_cur_t	m_cur;
 | 
						|
public:
 | 
						|
	mtr_t		m_mtr;
 | 
						|
};
 | 
						|
 | 
						|
/** Class that purges delete marked records from indexes, both secondary
 | 
						|
and cluster. It does a pessimistic delete. This should only be done if we
 | 
						|
couldn't purge the delete marked records during Phase I. */
 | 
						|
class IndexPurge {
 | 
						|
public:
 | 
						|
	/** Constructor
 | 
						|
	@param trx the user transaction covering the import tablespace
 | 
						|
	@param index to be imported
 | 
						|
	@param space_id space id of the tablespace */
 | 
						|
	IndexPurge(
 | 
						|
		trx_t*		trx,
 | 
						|
		dict_index_t*	index) UNIV_NOTHROW
 | 
						|
		:
 | 
						|
		m_trx(trx),
 | 
						|
		m_index(index),
 | 
						|
		m_n_rows(0)
 | 
						|
	{
 | 
						|
		ib::info() << "Phase II - Purge records from index "
 | 
						|
			<< index->name;
 | 
						|
	}
 | 
						|
 | 
						|
	/** Destructor */
 | 
						|
	~IndexPurge() UNIV_NOTHROW = default;
 | 
						|
 | 
						|
	/** 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. */
 | 
						|
  inline bool 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-tree cursor in BTR_MODIFY_TREE mode, because the
 | 
						|
  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;
 | 
						|
 | 
						|
protected:
 | 
						|
	// Disable copying
 | 
						|
	IndexPurge();
 | 
						|
	IndexPurge(const IndexPurge&);
 | 
						|
	IndexPurge &operator=(const IndexPurge&);
 | 
						|
 | 
						|
private:
 | 
						|
	trx_t*			m_trx;		/*!< User transaction */
 | 
						|
	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 { }
 | 
						|
 | 
						|
	/** Free any extent descriptor instance */
 | 
						|
	virtual ~AbstractCallback()
 | 
						|
	{
 | 
						|
		UT_DELETE_ARRAY(m_xdes);
 | 
						|
	}
 | 
						|
 | 
						|
	/** Determine the page size to use for traversing the tablespace
 | 
						|
	@param file_size size of the tablespace file in bytes
 | 
						|
	@param block contents of the first page in the tablespace file.
 | 
						|
	@retval DB_SUCCESS or error code. */
 | 
						|
	virtual dberr_t init(
 | 
						|
		os_offset_t		file_size,
 | 
						|
		const buf_block_t*	block) UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** @return true if compressed table. */
 | 
						|
	bool is_compressed_table() const UNIV_NOTHROW
 | 
						|
	{
 | 
						|
		return get_zip_size();
 | 
						|
	}
 | 
						|
 | 
						|
	/** @return the tablespace flags */
 | 
						|
	uint32_t get_space_flags() const { return m_space_flags; }
 | 
						|
 | 
						|
	/**
 | 
						|
	Set the name of the physical file and the file handle that is used
 | 
						|
	to open it for the file that is being iterated over.
 | 
						|
	@param filename the physical name of the tablespace file
 | 
						|
	@param file OS file handle */
 | 
						|
	void set_file(const char* filename, pfs_os_file_t file) UNIV_NOTHROW
 | 
						|
	{
 | 
						|
		m_file = file;
 | 
						|
		m_filepath = filename;
 | 
						|
	}
 | 
						|
 | 
						|
	ulint get_zip_size() const { return m_zip_size; }
 | 
						|
	ulint physical_size() const
 | 
						|
	{
 | 
						|
		return m_zip_size ? m_zip_size : srv_page_size;
 | 
						|
	}
 | 
						|
 | 
						|
	const char* filename() const { return m_filepath; }
 | 
						|
 | 
						|
	/**
 | 
						|
	Called for every page in the tablespace. If the page was not
 | 
						|
	updated then its state must be set to BUF_PAGE_NOT_USED. For
 | 
						|
	compressed tables the page descriptor memory will be at offset:
 | 
						|
		block->page.frame + srv_page_size;
 | 
						|
	@param block block read from file, note it is not from the buffer pool
 | 
						|
	@retval DB_SUCCESS or error code. */
 | 
						|
	virtual dberr_t operator()(buf_block_t* block) UNIV_NOTHROW = 0;
 | 
						|
 | 
						|
	/** @return the tablespace identifier */
 | 
						|
	uint32_t get_space_id() const { return m_space; }
 | 
						|
 | 
						|
	bool is_interrupted() const { return trx_is_interrupted(m_trx); }
 | 
						|
 | 
						|
	/**
 | 
						|
	Get the data page depending on the table type, compressed or not.
 | 
						|
	@param block - block read from disk
 | 
						|
	@retval the buffer frame */
 | 
						|
	static byte* get_frame(const buf_block_t* block)
 | 
						|
	{
 | 
						|
		return block->page.zip.data
 | 
						|
			? block->page.zip.data : block->page.frame;
 | 
						|
	}
 | 
						|
 | 
						|
	/** 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.
 | 
						|
	@param page_no page number of the extent descriptor
 | 
						|
	@param page contents of the page containing the extent descriptor.
 | 
						|
	@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;
 | 
						|
 | 
						|
		offset = xdes_calc_descriptor_index(get_zip_size(), page_no);
 | 
						|
 | 
						|
		return(page + XDES_ARR_OFFSET + XDES_SIZE * offset);
 | 
						|
	}
 | 
						|
 | 
						|
	/** Set the current page directory (xdes). If the extent descriptor is
 | 
						|
	marked as free then free the current extent descriptor and set it to
 | 
						|
	0. This implies that all pages that are covered by this extent
 | 
						|
	descriptor are also freed.
 | 
						|
 | 
						|
	@param page_no offset of page within the file
 | 
						|
	@param page page contents
 | 
						|
	@return DB_SUCCESS or error code. */
 | 
						|
	dberr_t	set_current_xdes(
 | 
						|
		uint32_t	page_no,
 | 
						|
		const page_t*	page) UNIV_NOTHROW
 | 
						|
	{
 | 
						|
		m_xdes_page_no = page_no;
 | 
						|
 | 
						|
		UT_DELETE_ARRAY(m_xdes);
 | 
						|
		m_xdes = NULL;
 | 
						|
 | 
						|
		if (mach_read_from_4(XDES_ARR_OFFSET + XDES_STATE + page)
 | 
						|
		    != XDES_FREE) {
 | 
						|
			const ulint physical_size = m_zip_size
 | 
						|
				? m_zip_size : srv_page_size;
 | 
						|
 | 
						|
			m_xdes = UT_NEW_ARRAY_NOKEY(xdes_t, physical_size);
 | 
						|
 | 
						|
			/* Trigger OOM */
 | 
						|
			DBUG_EXECUTE_IF(
 | 
						|
				"ib_import_OOM_13",
 | 
						|
				UT_DELETE_ARRAY(m_xdes);
 | 
						|
				m_xdes = NULL;
 | 
						|
			);
 | 
						|
 | 
						|
			if (m_xdes == NULL) {
 | 
						|
				return(DB_OUT_OF_MEMORY);
 | 
						|
			}
 | 
						|
 | 
						|
			memcpy(m_xdes, page, physical_size);
 | 
						|
		}
 | 
						|
 | 
						|
		return(DB_SUCCESS);
 | 
						|
	}
 | 
						|
 | 
						|
	/** Check if the page is marked as free in the extent descriptor.
 | 
						|
	@param page_no page number to check in the extent descriptor.
 | 
						|
	@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);
 | 
						|
 | 
						|
		if (m_xdes != 0) {
 | 
						|
			const xdes_t*	xdesc = xdes(page_no, m_xdes);
 | 
						|
			uint32_t	pos = page_no % FSP_EXTENT_SIZE;
 | 
						|
 | 
						|
			return xdes_is_free(xdesc, pos);
 | 
						|
		}
 | 
						|
 | 
						|
		/* If the current xdes was free, the page must be free. */
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
protected:
 | 
						|
	/** The ROW_FORMAT=COMPRESSED page size, or 0. */
 | 
						|
	ulint			m_zip_size;
 | 
						|
 | 
						|
	/** File handle to the tablespace */
 | 
						|
	pfs_os_file_t		m_file;
 | 
						|
 | 
						|
	/** Physical file path. */
 | 
						|
	const char*		m_filepath;
 | 
						|
 | 
						|
	/** Covering transaction. */
 | 
						|
	trx_t*			m_trx;
 | 
						|
 | 
						|
	/** Space id of the file being iterated over. */
 | 
						|
	uint32_t		m_space;
 | 
						|
 | 
						|
	/** Current extent descriptor page */
 | 
						|
	xdes_t*			m_xdes;
 | 
						|
 | 
						|
	/** Physical page offset in the file of the extent descriptor */
 | 
						|
	uint32_t		m_xdes_page_no;
 | 
						|
 | 
						|
	/** Flags value read from the header page */
 | 
						|
	uint32_t		m_space_flags;
 | 
						|
};
 | 
						|
 | 
						|
ATTRIBUTE_COLD static dberr_t invalid_space_flags(uint32_t flags)
 | 
						|
{
 | 
						|
  if (fsp_flags_is_incompatible_mysql(flags))
 | 
						|
  {
 | 
						|
    sql_print_error("InnoDB: unsupported MySQL tablespace");
 | 
						|
    return DB_UNSUPPORTED;
 | 
						|
  }
 | 
						|
 | 
						|
  sql_print_error("InnoDB: Invalid FSP_SPACE_FLAGS=0x%" PRIx32, flags);
 | 
						|
  return DB_CORRUPTION;
 | 
						|
}
 | 
						|
 | 
						|
/** Determine the page size to use for traversing the tablespace
 | 
						|
@param file_size size of the tablespace file in bytes
 | 
						|
@param block contents of the first page in the tablespace file.
 | 
						|
@retval DB_SUCCESS or error code. */
 | 
						|
dberr_t
 | 
						|
AbstractCallback::init(
 | 
						|
	os_offset_t		file_size,
 | 
						|
	const buf_block_t*	block) UNIV_NOTHROW
 | 
						|
{
 | 
						|
	const page_t*		page = block->page.frame;
 | 
						|
 | 
						|
	m_space_flags = fsp_header_get_flags(page);
 | 
						|
	if (!fil_space_t::is_valid_flags(m_space_flags, true)) {
 | 
						|
		uint32_t cflags = fsp_flags_convert_from_101(m_space_flags);
 | 
						|
		if (cflags == UINT32_MAX) {
 | 
						|
			return DB_CORRUPTION;
 | 
						|
		}
 | 
						|
		m_space_flags = cflags;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Clear the DATA_DIR flag, which is basically garbage. */
 | 
						|
	m_space_flags &= ~(1U << FSP_FLAGS_POS_RESERVED);
 | 
						|
	m_zip_size = fil_space_t::zip_size(m_space_flags);
 | 
						|
	const ulint logical_size = fil_space_t::logical_size(m_space_flags);
 | 
						|
	const ulint physical_size = fil_space_t::physical_size(m_space_flags);
 | 
						|
 | 
						|
	if (logical_size != srv_page_size) {
 | 
						|
 | 
						|
		ib::error() << "Page size " << logical_size
 | 
						|
			<< " of ibd file is not the same as the server page"
 | 
						|
			" size " << srv_page_size;
 | 
						|
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
 | 
						|
	} else if (file_size & (physical_size - 1)) {
 | 
						|
 | 
						|
		ib::error() << "File size " << file_size << " is not a"
 | 
						|
			" multiple of the page size "
 | 
						|
			<< physical_size;
 | 
						|
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
	}
 | 
						|
 | 
						|
	if (m_space == UINT32_MAX) {
 | 
						|
		m_space = mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_ID
 | 
						|
					   + page);
 | 
						|
	}
 | 
						|
 | 
						|
	return set_current_xdes(0, page);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
TODO: This can be made parallel trivially by chunking up the file
 | 
						|
and creating a callback per thread.. Main benefit will be to use
 | 
						|
multiple CPUs for checksums and compressed tables. We have to do
 | 
						|
compressed tables block by block right now. Secondly we need to
 | 
						|
decompress/compress and copy too much of data. These are
 | 
						|
CPU intensive.
 | 
						|
 | 
						|
Iterate over all the pages in the tablespace.
 | 
						|
@param iter - Tablespace iterator
 | 
						|
@param block - block to use for IO
 | 
						|
@param callback - Callback to inspect and update page contents
 | 
						|
@retval DB_SUCCESS or error code */
 | 
						|
static dberr_t fil_iterate(
 | 
						|
	const fil_iterator_t&	iter,
 | 
						|
	buf_block_t*		block,
 | 
						|
	AbstractCallback&	callback);
 | 
						|
 | 
						|
/**
 | 
						|
Try and determine the index root pages by checking if the next/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 {
 | 
						|
 | 
						|
		Index(index_id_t id, uint32_t page_no)
 | 
						|
			:
 | 
						|
			m_id(id),
 | 
						|
			m_page_no(page_no) { }
 | 
						|
 | 
						|
		index_id_t	m_id;		/*!< Index id */
 | 
						|
		uint32_t	m_page_no;	/*!< Root page number */
 | 
						|
	};
 | 
						|
 | 
						|
	/** Constructor
 | 
						|
	@param trx covering (user) transaction
 | 
						|
	@param table table definition in server .*/
 | 
						|
	FetchIndexRootPages(const dict_table_t* table = nullptr,
 | 
						|
			    trx_t* trx = nullptr)
 | 
						|
		:
 | 
						|
		AbstractCallback(trx, UINT32_MAX),
 | 
						|
		m_table(table), m_index(0, 0) UNIV_NOTHROW { }
 | 
						|
 | 
						|
	/** Destructor */
 | 
						|
	~FetchIndexRootPages() UNIV_NOTHROW override = default;
 | 
						|
 | 
						|
	/** Fetch the clustered index root page in the tablespace
 | 
						|
	@param iter	Tablespace iterator
 | 
						|
	@param block	Block to use for IO
 | 
						|
	@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.
 | 
						|
	@param block block to convert, it is not from the buffer pool.
 | 
						|
	@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, and return not_used. We cannot simply return
 | 
						|
		dynamic or compact, as the client of this function
 | 
						|
		will not be able to tell whether it is dynamic because
 | 
						|
		of this or the other branch below. Returning default
 | 
						|
		would also work if it is immediately handled, but is
 | 
						|
		still more ambiguous than not_used, which is not a
 | 
						|
		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
 | 
						|
	created in which case m_table is null, for obtaining row
 | 
						|
	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
 | 
						|
determine the exact row format. We can't get that from the tablespace
 | 
						|
header flags alone.
 | 
						|
 | 
						|
@param block block to convert, it is not from the buffer pool.
 | 
						|
@retval DB_SUCCESS or error code. */
 | 
						|
dberr_t FetchIndexRootPages::operator()(buf_block_t* block) UNIV_NOTHROW
 | 
						|
{
 | 
						|
	if (is_interrupted()) return DB_INTERRUPTED;
 | 
						|
 | 
						|
	const page_t*	page = get_frame(block);
 | 
						|
 | 
						|
	m_index.m_id = btr_page_get_index_id(page);
 | 
						|
	m_index.m_page_no = block->page.id().page_no();
 | 
						|
 | 
						|
	/* Check that the tablespace flags match the table flags. */
 | 
						|
	const uint32_t expected = dict_tf_to_fsp_flags(m_table->flags);
 | 
						|
	if (!fsp_flags_match(expected, m_space_flags)) {
 | 
						|
		ib_errf(m_trx->mysql_thd, IB_LOG_LEVEL_ERROR,
 | 
						|
			ER_TABLE_SCHEMA_MISMATCH,
 | 
						|
			"Expected FSP_SPACE_FLAGS=0x%x, .ibd "
 | 
						|
			"file contains 0x%x.",
 | 
						|
			unsigned(expected),
 | 
						|
			unsigned(m_space_flags));
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!page_is_comp(block->page.frame) !=
 | 
						|
	    !dict_table_is_comp(m_table)) {
 | 
						|
		ib_errf(m_trx->mysql_thd, IB_LOG_LEVEL_ERROR,
 | 
						|
			ER_TABLE_SCHEMA_MISMATCH,
 | 
						|
			"ROW_FORMAT mismatch");
 | 
						|
		return DB_CORRUPTION;
 | 
						|
	}
 | 
						|
 | 
						|
	return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Update the import configuration that will be used to import the tablespace.
 | 
						|
@return error code or DB_SUCCESS */
 | 
						|
dberr_t
 | 
						|
FetchIndexRootPages::build_row_import(row_import* cfg) const UNIV_NOTHROW
 | 
						|
{
 | 
						|
	ut_a(cfg->m_table == m_table);
 | 
						|
	cfg->m_zip_size = m_zip_size;
 | 
						|
	cfg->m_n_indexes = 1;
 | 
						|
 | 
						|
	if (cfg->m_n_indexes == 0) {
 | 
						|
 | 
						|
		ib::error() << "No B+Tree found in tablespace";
 | 
						|
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
	}
 | 
						|
 | 
						|
	cfg->m_indexes = UT_NEW_ARRAY_NOKEY(row_index_t, cfg->m_n_indexes);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_11",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_indexes);
 | 
						|
		cfg->m_indexes = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_indexes == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	memset(cfg->m_indexes, 0x0, sizeof(*cfg->m_indexes) * cfg->m_n_indexes);
 | 
						|
 | 
						|
	row_index_t*	cfg_index = cfg->m_indexes;
 | 
						|
 | 
						|
	char	name[BUFSIZ];
 | 
						|
 | 
						|
	snprintf(name, sizeof(name), "index" IB_ID_FMT, m_index.m_id);
 | 
						|
 | 
						|
	ulint	len = strlen(name) + 1;
 | 
						|
 | 
						|
	cfg_index->m_name = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_12",
 | 
						|
		UT_DELETE_ARRAY(cfg_index->m_name);
 | 
						|
		cfg_index->m_name = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg_index->m_name == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	memcpy(cfg_index->m_name, name, len);
 | 
						|
 | 
						|
	cfg_index->m_id = m_index.m_id;
 | 
						|
 | 
						|
	cfg_index->m_space = m_space;
 | 
						|
 | 
						|
	cfg_index->m_page_no = m_index.m_page_no;
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/* Functor that is called for each physical page that is read from the
 | 
						|
tablespace file.
 | 
						|
 | 
						|
  1. Check each page for corruption.
 | 
						|
 | 
						|
  2. Update the space id and LSN on every page
 | 
						|
     * For the header page
 | 
						|
       - Validate the flags
 | 
						|
       - Update the LSN
 | 
						|
 | 
						|
  3. On Btree pages
 | 
						|
     * Set the index id
 | 
						|
     * Update the max trx id
 | 
						|
     * In a cluster index, update the system columns
 | 
						|
     * In a cluster index, update the BLOB ptr, set the space id
 | 
						|
     * Purge delete marked records, but only if they can be easily
 | 
						|
       removed from the page
 | 
						|
     * Keep a counter of number of rows, ie. non-delete-marked rows
 | 
						|
     * Keep a counter of number of delete marked rows
 | 
						|
     * Keep a counter of number of purge failure
 | 
						|
     * If a page is stamped with an index id that isn't in the .cfg file
 | 
						|
       we assume it is deleted and the page can be ignored.
 | 
						|
 | 
						|
   4. Set the page state to dirty so that it will be written to disk.
 | 
						|
*/
 | 
						|
class PageConverter : public AbstractCallback {
 | 
						|
public:
 | 
						|
	/** Constructor
 | 
						|
	@param cfg config of table being imported.
 | 
						|
	@param space_id tablespace identifier
 | 
						|
	@param trx transaction covering the import */
 | 
						|
	PageConverter(row_import* cfg, uint32_t space_id, trx_t* trx)
 | 
						|
		:
 | 
						|
		AbstractCallback(trx, space_id),
 | 
						|
		m_cfg(cfg),
 | 
						|
		m_index(cfg->m_indexes),
 | 
						|
		m_rec_iter(),
 | 
						|
		m_offsets_(), m_offsets(m_offsets_),
 | 
						|
		m_heap(0),
 | 
						|
		m_cluster_index(dict_table_get_first_index(cfg->m_table))
 | 
						|
	{
 | 
						|
		rec_offs_init(m_offsets_);
 | 
						|
	}
 | 
						|
 | 
						|
	~PageConverter() UNIV_NOTHROW override
 | 
						|
	{
 | 
						|
		if (m_heap != 0) {
 | 
						|
			mem_heap_free(m_heap);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	dberr_t run(const fil_iterator_t& iter,
 | 
						|
		    buf_block_t* block) UNIV_NOTHROW override
 | 
						|
	{
 | 
						|
		return fil_iterate(iter, block, *this);
 | 
						|
	}
 | 
						|
 | 
						|
	/** Called for each block as it is read from the file.
 | 
						|
	@param block block to convert, it is not from the buffer pool.
 | 
						|
	@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.
 | 
						|
	@param block block read from file
 | 
						|
	@param page_type type of the page
 | 
						|
	@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.
 | 
						|
	@param block block to convert
 | 
						|
	@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
 | 
						|
	rows that can't be purged optimistically.
 | 
						|
	@param block block to update
 | 
						|
	@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.
 | 
						|
	@param block block read from file, not from the buffer pool.
 | 
						|
	@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
 | 
						|
	@param rec record to update
 | 
						|
	@param offsets column offsets for the record
 | 
						|
	@param i column ordinal value
 | 
						|
	@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
 | 
						|
	externally stored columns.
 | 
						|
	@param rec record to update
 | 
						|
	@param offsets column offsets for the record
 | 
						|
	@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.
 | 
						|
	Also update the BLOB reference, write the new space id.
 | 
						|
	@param rec record to update
 | 
						|
	@param offsets column offsets for the record
 | 
						|
	@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
 | 
						|
	so without re-organising the B+tree.
 | 
						|
	@retval true if purged */
 | 
						|
	bool purge() UNIV_NOTHROW;
 | 
						|
 | 
						|
	/** Adjust the BLOB references and sys fields for the current record.
 | 
						|
	@param rec record to update
 | 
						|
	@param offsets column offsets for the record
 | 
						|
	@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);
 | 
						|
 | 
						|
		if (m_indexes[i].m_fields == NULL) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		dict_field_t*	fields = m_indexes[i].m_fields;
 | 
						|
		ulint		n_fields = m_indexes[i].m_n_fields;
 | 
						|
 | 
						|
		for (ulint j = 0; j < n_fields; ++j) {
 | 
						|
			UT_DELETE_ARRAY(const_cast<char*>(fields[j].name()));
 | 
						|
		}
 | 
						|
 | 
						|
		UT_DELETE_ARRAY(fields);
 | 
						|
	}
 | 
						|
 | 
						|
	for (ulint i = 0; m_col_names != 0 && i < m_n_cols; ++i) {
 | 
						|
		UT_DELETE_ARRAY(m_col_names[i]);
 | 
						|
	}
 | 
						|
 | 
						|
	UT_DELETE_ARRAY(m_cols);
 | 
						|
	UT_DELETE_ARRAY(m_indexes);
 | 
						|
	UT_DELETE_ARRAY(m_col_names);
 | 
						|
	UT_DELETE_ARRAY(m_table_name);
 | 
						|
	UT_DELETE_ARRAY(m_hostname);
 | 
						|
}
 | 
						|
 | 
						|
/** Find the index entry in in the indexes array.
 | 
						|
@param name index name
 | 
						|
@return instance if found else 0. */
 | 
						|
row_index_t*
 | 
						|
row_import::get_index(
 | 
						|
	const char*	name) const UNIV_NOTHROW
 | 
						|
{
 | 
						|
	for (ulint i = 0; i < m_n_indexes; ++i) {
 | 
						|
		const char*	index_name;
 | 
						|
		row_index_t*	index = &m_indexes[i];
 | 
						|
 | 
						|
		index_name = reinterpret_cast<const char*>(index->m_name);
 | 
						|
 | 
						|
		if (strcmp(index_name, name) == 0) {
 | 
						|
 | 
						|
			return(index);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(0);
 | 
						|
}
 | 
						|
 | 
						|
/** Get the number of rows in the index.
 | 
						|
@param name index name
 | 
						|
@return number of rows (doesn't include delete marked rows). */
 | 
						|
ulint
 | 
						|
row_import::get_n_rows(
 | 
						|
	const char*	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.
 | 
						|
@param name index name
 | 
						|
@return number of rows for which purge failed. */
 | 
						|
ulint
 | 
						|
row_import::get_n_purge_failed(
 | 
						|
	const char*	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.
 | 
						|
@param name of column to look for.
 | 
						|
@return ULINT_UNDEFINED if not found. */
 | 
						|
ulint
 | 
						|
row_import::find_col(
 | 
						|
	const char*	name) const UNIV_NOTHROW
 | 
						|
{
 | 
						|
	for (ulint i = 0; i < m_n_cols; ++i) {
 | 
						|
		const char*	col_name;
 | 
						|
 | 
						|
		col_name = reinterpret_cast<const char*>(m_col_names[i]);
 | 
						|
 | 
						|
		if (strcmp(col_name, name) == 0) {
 | 
						|
			return(i);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return(ULINT_UNDEFINED);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Check if the index schema that was read from the .cfg file matches the
 | 
						|
in memory index definition.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
dberr_t
 | 
						|
row_import::match_index_columns(
 | 
						|
	THD*			thd,
 | 
						|
	const dict_index_t*	index) UNIV_NOTHROW
 | 
						|
{
 | 
						|
	row_index_t*		cfg_index;
 | 
						|
	dberr_t			err = DB_SUCCESS;
 | 
						|
 | 
						|
	cfg_index = get_index(index->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
 | 
						|
in memory table definition.
 | 
						|
@param thd MySQL session variable
 | 
						|
@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) {
 | 
						|
 | 
						|
		const char*	col_name;
 | 
						|
		ulint		cfg_col_index;
 | 
						|
 | 
						|
		col_name = dict_table_get_col_name(
 | 
						|
			m_table, dict_col_get_no(col)).str;
 | 
						|
 | 
						|
		cfg_col_index = find_col(col_name);
 | 
						|
 | 
						|
		if (cfg_col_index == ULINT_UNDEFINED) {
 | 
						|
 | 
						|
			ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
				 ER_TABLE_SCHEMA_MISMATCH,
 | 
						|
				 "Column %s not found in tablespace.",
 | 
						|
				 col_name);
 | 
						|
 | 
						|
			err = DB_ERROR;
 | 
						|
		} else if (cfg_col_index != col->ind) {
 | 
						|
 | 
						|
			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);
 | 
						|
 | 
						|
			err = DB_ERROR;
 | 
						|
		} else {
 | 
						|
			const dict_col_t*	cfg_col;
 | 
						|
 | 
						|
			cfg_col = &m_cols[cfg_col_index];
 | 
						|
			ut_a(cfg_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;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
dberr_t row_import::match_flags(THD *thd) const
 | 
						|
{
 | 
						|
  ulint mismatch= (m_table->flags ^ m_flags) & ~DICT_TF_MASK_DATA_DIR;
 | 
						|
  if (!mismatch)
 | 
						|
    return DB_SUCCESS;
 | 
						|
 | 
						|
  const char *msg;
 | 
						|
  if (mismatch & DICT_TF_MASK_ZIP_SSIZE)
 | 
						|
  {
 | 
						|
    if ((m_table->flags & DICT_TF_MASK_ZIP_SSIZE) &&
 | 
						|
        (m_flags & DICT_TF_MASK_ZIP_SSIZE))
 | 
						|
    {
 | 
						|
      switch (m_flags & DICT_TF_MASK_ZIP_SSIZE) {
 | 
						|
      case 0U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        goto uncompressed;
 | 
						|
      case 1U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        msg= "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=1";
 | 
						|
        break;
 | 
						|
      case 2U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        msg= "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=2";
 | 
						|
        break;
 | 
						|
      case 3U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        msg= "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4";
 | 
						|
        break;
 | 
						|
      case 4U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        msg= "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8";
 | 
						|
        break;
 | 
						|
      case 5U << DICT_TF_POS_ZIP_SSIZE:
 | 
						|
        msg= "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=16";
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        msg= "strange KEY_BLOCK_SIZE";
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else if (m_flags & DICT_TF_MASK_ZIP_SSIZE)
 | 
						|
      msg= "ROW_FORMAT=COMPRESSED";
 | 
						|
    else
 | 
						|
      goto uncompressed;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
  uncompressed:
 | 
						|
    msg= (m_flags & DICT_TF_MASK_ATOMIC_BLOBS) ? "ROW_FORMAT=DYNAMIC"
 | 
						|
         : (m_flags & DICT_TF_MASK_COMPACT)    ? "ROW_FORMAT=COMPACT"
 | 
						|
                                               : "ROW_FORMAT=REDUNDANT";
 | 
						|
  }
 | 
						|
 | 
						|
  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
 | 
						|
matches the in memory table definition.
 | 
						|
@param thd MySQL session variable
 | 
						|
@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
 | 
						|
		to abort the IMPORT. It is easy for the user to create a
 | 
						|
		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;
 | 
						|
 | 
						|
		const char*	index_name;
 | 
						|
 | 
						|
		index_name = reinterpret_cast<const char*>(cfg_index->m_name);
 | 
						|
 | 
						|
		index = dict_table_get_index_on_name(m_table, index_name);
 | 
						|
 | 
						|
		/* We've already checked that it exists. */
 | 
						|
		ut_a(index != 0);
 | 
						|
 | 
						|
		index->page = cfg_index->m_page_no;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Set the index root <space, pageno>, using a heuristic.
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
dberr_t
 | 
						|
row_import::set_root_by_heuristic() UNIV_NOTHROW
 | 
						|
{
 | 
						|
	row_index_t*	cfg_index = m_indexes;
 | 
						|
 | 
						|
	ut_a(m_n_indexes > 0);
 | 
						|
 | 
						|
	// TODO: For now use brute force, based on ordinality
 | 
						|
 | 
						|
	if (UT_LIST_GET_LEN(m_table->indexes) != m_n_indexes) {
 | 
						|
 | 
						|
		ib::warn() << "Table " << m_table->name << " should have "
 | 
						|
			<< UT_LIST_GET_LEN(m_table->indexes) << " indexes but"
 | 
						|
			" the tablespace has " << m_n_indexes << " indexes";
 | 
						|
	}
 | 
						|
 | 
						|
	ulint	i = 0;
 | 
						|
	dberr_t	err = DB_SUCCESS;
 | 
						|
 | 
						|
	for (dict_index_t* index = UT_LIST_GET_FIRST(m_table->indexes);
 | 
						|
	     index != 0;
 | 
						|
	     index = UT_LIST_GET_NEXT(indexes, index)) {
 | 
						|
 | 
						|
		if (index->type & DICT_FTS) {
 | 
						|
			index->type |= DICT_CORRUPT;
 | 
						|
			ib::warn() << "Skipping FTS index: " << index->name;
 | 
						|
		} else if (i < m_n_indexes) {
 | 
						|
 | 
						|
			UT_DELETE_ARRAY(cfg_index[i].m_name);
 | 
						|
 | 
						|
			ulint	len = strlen(index->name) + 1;
 | 
						|
 | 
						|
			cfg_index[i].m_name = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
			/* Trigger OOM */
 | 
						|
			DBUG_EXECUTE_IF(
 | 
						|
				"ib_import_OOM_14",
 | 
						|
				UT_DELETE_ARRAY(cfg_index[i].m_name);
 | 
						|
				cfg_index[i].m_name = NULL;
 | 
						|
			);
 | 
						|
 | 
						|
			if (cfg_index[i].m_name == NULL) {
 | 
						|
				err = DB_OUT_OF_MEMORY;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			memcpy(cfg_index[i].m_name, index->name, len);
 | 
						|
 | 
						|
			cfg_index[i].m_srv_index = index;
 | 
						|
 | 
						|
			index->page = cfg_index[i++].m_page_no;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Purge delete marked records.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
dberr_t
 | 
						|
IndexPurge::garbage_collect() UNIV_NOTHROW
 | 
						|
{
 | 
						|
	ibool	comp = dict_table_is_comp(m_index->table);
 | 
						|
 | 
						|
	/* Open the persistent cursor and start the mini-transaction. */
 | 
						|
 | 
						|
	dberr_t err = open() ? next() : DB_CORRUPTION;
 | 
						|
 | 
						|
	for (; err == DB_SUCCESS; err = next()) {
 | 
						|
 | 
						|
		rec_t*	rec = btr_pcur_get_rec(&m_pcur);
 | 
						|
		ibool	deleted = rec_get_deleted_flag(rec, comp);
 | 
						|
 | 
						|
		if (!deleted) {
 | 
						|
			++m_n_rows;
 | 
						|
		} else {
 | 
						|
			err = purge();
 | 
						|
			if (err != DB_SUCCESS) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Close the persistent cursor and commit the mini-transaction. */
 | 
						|
 | 
						|
	close();
 | 
						|
 | 
						|
	return(err == DB_END_OF_INDEX ? DB_SUCCESS : err);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Begin import, position the cursor on the first record. */
 | 
						|
inline bool 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)
 | 
						|
    return false;
 | 
						|
 | 
						|
  rec_t *rec= page_rec_get_next(btr_pcur_get_rec(&m_pcur));
 | 
						|
  if (!rec)
 | 
						|
    return false;
 | 
						|
  if (rec_is_metadata(rec, *m_index))
 | 
						|
    /* Skip the metadata pseudo-record. */
 | 
						|
    btr_pcur_get_page_cur(&m_pcur)->rec= rec;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Position the cursor on the next record.
 | 
						|
@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);
 | 
						|
	} else if (trx_is_interrupted(m_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;
 | 
						|
			}
 | 
						|
		} else if (!btr_pcur_move_to_next_on_page(&m_pcur)) {
 | 
						|
			return DB_CORRUPTION;
 | 
						|
		}
 | 
						|
	} while (!btr_pcur_is_on_user_rec(&m_pcur));
 | 
						|
 | 
						|
	return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Store the persistent cursor position and reopen the
 | 
						|
B-tree cursor in BTR_MODIFY_TREE mode, because the
 | 
						|
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;
 | 
						|
 | 
						|
  m_mtr.commit();
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
dberr_t IndexPurge::purge() noexcept
 | 
						|
{
 | 
						|
  btr_pcur_store_position(&m_pcur, &m_mtr);
 | 
						|
  m_mtr.commit();
 | 
						|
  m_mtr.start();
 | 
						|
  m_mtr.set_log_mode(MTR_LOG_NO_REDO);
 | 
						|
  dberr_t err= purge_pessimistic_delete();
 | 
						|
 | 
						|
  m_mtr.start();
 | 
						|
  m_mtr.set_log_mode(MTR_LOG_NO_REDO);
 | 
						|
  if (err == DB_SUCCESS)
 | 
						|
    err= (m_pcur.restore_position(BTR_MODIFY_LEAF, &m_mtr) ==
 | 
						|
          btr_pcur_t::CORRUPTED)
 | 
						|
      ? DB_CORRUPTION : DB_SUCCESS;
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
/** Adjust the BLOB reference for a single column that is externally stored
 | 
						|
@param rec record to update
 | 
						|
@param offsets column offsets for the record
 | 
						|
@param i column ordinal value
 | 
						|
@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
 | 
						|
stored columns.
 | 
						|
@param rec record to update
 | 
						|
@param offsets column offsets for the record
 | 
						|
@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;
 | 
						|
 | 
						|
			err = adjust_cluster_index_blob_column(rec, offsets, i);
 | 
						|
 | 
						|
			if (err != DB_SUCCESS) {
 | 
						|
				return(err);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/** In the clustered index, adjust BLOB pointers as needed. Also update the
 | 
						|
BLOB reference, write the new space id.
 | 
						|
@param rec record to update
 | 
						|
@param offsets column offsets for the record
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
inline
 | 
						|
dberr_t
 | 
						|
PageConverter::adjust_cluster_index_blob_ref(
 | 
						|
	rec_t*		rec,
 | 
						|
	const rec_offs*	offsets) UNIV_NOTHROW
 | 
						|
{
 | 
						|
	if (rec_offs_any_extern(offsets)) {
 | 
						|
		dberr_t	err;
 | 
						|
 | 
						|
		err = adjust_cluster_index_blob_columns(rec, offsets);
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
			return(err);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/** Purge delete-marked records, only if it is possible to do so without
 | 
						|
re-organising the B+tree.
 | 
						|
@return true if purge succeeded */
 | 
						|
inline bool PageConverter::purge() UNIV_NOTHROW
 | 
						|
{
 | 
						|
	/* We can't have a page that is empty and not root. */
 | 
						|
	if (m_rec_iter.remove(m_offsets)) {
 | 
						|
 | 
						|
		++m_index->m_stats.m_n_purged;
 | 
						|
 | 
						|
		return(true);
 | 
						|
	} else {
 | 
						|
		++m_index->m_stats.m_n_purge_failed;
 | 
						|
	}
 | 
						|
 | 
						|
	return(false);
 | 
						|
}
 | 
						|
 | 
						|
/** Adjust the BLOB references and sys fields for the current record.
 | 
						|
@param rec record to update
 | 
						|
@param offsets column offsets for the record
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
inline
 | 
						|
dberr_t
 | 
						|
PageConverter::adjust_cluster_record(
 | 
						|
	rec_t*			rec,
 | 
						|
	const rec_offs*		offsets) UNIV_NOTHROW
 | 
						|
{
 | 
						|
	dberr_t	err;
 | 
						|
 | 
						|
	if ((err = adjust_cluster_index_blob_ref(rec, offsets)) == DB_SUCCESS) {
 | 
						|
 | 
						|
		/* Reset DB_TRX_ID and DB_ROLL_PTR.  Normally, these fields
 | 
						|
		are only written in conjunction with other changes to the
 | 
						|
		record. */
 | 
						|
		ulint	trx_id_pos = m_cluster_index->n_uniq
 | 
						|
			? m_cluster_index->n_uniq : 1;
 | 
						|
		if (UNIV_LIKELY_NULL(m_rec_iter.current_block()
 | 
						|
				     ->page.zip.data)) {
 | 
						|
			page_zip_write_trx_id_and_roll_ptr(
 | 
						|
				m_rec_iter.current_block(),
 | 
						|
				rec, m_offsets, trx_id_pos,
 | 
						|
				0, roll_ptr_t(1) << ROLL_PTR_INSERT_FLAG_POS,
 | 
						|
				&m_rec_iter.m_mtr);
 | 
						|
		} else {
 | 
						|
			ulint	len;
 | 
						|
			byte*	ptr = rec_get_nth_field(
 | 
						|
				rec, m_offsets, trx_id_pos, &len);
 | 
						|
			ut_ad(len == DATA_TRX_ID_LEN);
 | 
						|
			memcpy(ptr, reset_trx_id, sizeof reset_trx_id);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
/** Update the BLOB refrences and write UNDO log entries for
 | 
						|
rows that can't be purged optimistically.
 | 
						|
@param block block to update
 | 
						|
@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;
 | 
						|
		}
 | 
						|
 | 
						|
		const ulint info_bits = rec_get_info_bits(rec, comp);
 | 
						|
 | 
						|
		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
 | 
						|
		reference and the system fields irrespective of the
 | 
						|
		delete marked flag. The adjustment of delete marked
 | 
						|
		cluster records is required for purge to work later. */
 | 
						|
 | 
						|
		if (deleted || clust_index) {
 | 
						|
first:
 | 
						|
			m_offsets = rec_get_offsets(
 | 
						|
				rec, m_index->m_srv_index, m_offsets,
 | 
						|
				m_index->m_srv_index->n_core_fields,
 | 
						|
				ULINT_UNDEFINED, &m_heap);
 | 
						|
		}
 | 
						|
 | 
						|
		if (clust_index) {
 | 
						|
 | 
						|
			dberr_t err = adjust_cluster_record(rec, m_offsets);
 | 
						|
 | 
						|
			if (err != DB_SUCCESS) {
 | 
						|
				return(err);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* 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);
 | 
						|
	}
 | 
						|
 | 
						|
	if (m_index && page_id.page_no() == m_index->m_page_no) {
 | 
						|
		byte *b = FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF + FSEG_HDR_SPACE
 | 
						|
			+ page;
 | 
						|
		mach_write_to_4(b, page_id.space());
 | 
						|
 | 
						|
		memcpy(FIL_PAGE_DATA + PAGE_BTR_SEG_TOP + FSEG_HDR_SPACE
 | 
						|
		       + page, b, 4);
 | 
						|
		if (UNIV_LIKELY_NULL(block->page.zip.data)) {
 | 
						|
			memcpy(&block->page.zip.data[FIL_PAGE_DATA
 | 
						|
						     + PAGE_BTR_SEG_TOP
 | 
						|
						     + FSEG_HDR_SPACE], b, 4);
 | 
						|
			memcpy(&block->page.zip.data[FIL_PAGE_DATA
 | 
						|
						     + PAGE_BTR_SEG_LEAF
 | 
						|
						     + FSEG_HDR_SPACE], b, 4);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
#ifdef UNIV_ZIP_DEBUG
 | 
						|
	ut_a(!block->page.zip.data || page_zip_validate(&block->page.zip, page,
 | 
						|
							m_index->m_srv_index));
 | 
						|
#endif /* UNIV_ZIP_DEBUG */
 | 
						|
 | 
						|
	/* 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;
 | 
						|
		}
 | 
						|
	} else if (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
 | 
						|
		used for other purposes in the future. IMPORT
 | 
						|
		in MySQL 5.6, 5.7 and MariaDB 10.0 and 10.1
 | 
						|
		would set the field to the transaction ID even
 | 
						|
		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);
 | 
						|
		}
 | 
						|
 | 
						|
		return(DB_SUCCESS);
 | 
						|
	}
 | 
						|
 | 
						|
	return page_is_leaf(block->page.frame)
 | 
						|
		? update_records(block)
 | 
						|
		: DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
/** Validate the space flags and update tablespace header page.
 | 
						|
@param block block read from file, not from the buffer pool.
 | 
						|
@retval DB_SUCCESS or error code */
 | 
						|
inline dberr_t PageConverter::update_header(buf_block_t* block) UNIV_NOTHROW
 | 
						|
{
 | 
						|
  byte *frame= get_frame(block);
 | 
						|
  if (memcmp_aligned<2>(FIL_PAGE_SPACE_ID + frame,
 | 
						|
                        FSP_HEADER_OFFSET + FSP_SPACE_ID + frame, 4))
 | 
						|
    ib::warn() << "Space id check in the header failed: ignored";
 | 
						|
  else if (!mach_read_from_4(FIL_PAGE_SPACE_ID + frame))
 | 
						|
    return DB_CORRUPTION;
 | 
						|
 | 
						|
  memset(frame + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION, 0, 8);
 | 
						|
 | 
						|
  /* 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.
 | 
						|
@param block block read from file
 | 
						|
@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;
 | 
						|
 | 
						|
	ut_ad(!block->page.zip.data == !is_compressed_table());
 | 
						|
 | 
						|
	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
 | 
						|
updated then its state must be set to BUF_PAGE_NOT_USED.
 | 
						|
@param block block read from file, note it is not from the buffer pool
 | 
						|
@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, evict it now, because we no longer evict the pages on
 | 
						|
  DISCARD TABLESPACE. */
 | 
						|
  if (buf_block_t *b= buf_pool.page_fix(block->page.id(), 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;
 | 
						|
 | 
						|
  const bool full_crc32= fil_space_t::full_crc32(get_space_flags());
 | 
						|
  byte *frame= get_frame(block);
 | 
						|
  memset_aligned<8>(frame + FIL_PAGE_LSN, 0, 8);
 | 
						|
 | 
						|
  if (!block->page.zip.data)
 | 
						|
    buf_flush_init_for_writing(nullptr, block->page.frame, nullptr,
 | 
						|
                               full_crc32);
 | 
						|
  else if (fil_page_type_is_index(page_type))
 | 
						|
    buf_flush_init_for_writing(nullptr, block->page.zip.data, &block->page.zip,
 | 
						|
                               full_crc32);
 | 
						|
  else
 | 
						|
    /* Calculate and update the checksum of non-index
 | 
						|
    pages for ROW_FORMAT=COMPRESSED tables. */
 | 
						|
    buf_flush_update_zip_checksum(block->page.zip.data, block->zip_size());
 | 
						|
 | 
						|
  return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static void reload_fts_table(row_prebuilt_t *prebuilt,
 | 
						|
                             dict_table_t* table)
 | 
						|
{
 | 
						|
  ut_ad(prebuilt->table != table);
 | 
						|
  /* Reload the table in case of hidden fts column */
 | 
						|
  const table_id_t id= prebuilt->table->id;
 | 
						|
  prebuilt->table->release();
 | 
						|
  dict_sys.remove(prebuilt->table);
 | 
						|
  prebuilt->table=
 | 
						|
    dict_table_open_on_id(id, true, DICT_TABLE_OP_NORMAL);
 | 
						|
  prebuilt->table->space= table->space;
 | 
						|
}
 | 
						|
 | 
						|
/** Clean up after import tablespace.
 | 
						|
@param  prebuilt  prebuilt from handler
 | 
						|
@param  err       error code
 | 
						|
@param  fts_table constructed table which has system generated
 | 
						|
                  fulltext document id
 | 
						|
@return error code or DB_SUCCESS */
 | 
						|
static
 | 
						|
dberr_t
 | 
						|
row_import_cleanup(row_prebuilt_t* prebuilt,
 | 
						|
                   dberr_t         err,
 | 
						|
                   dict_table_t*   fts_table = nullptr)
 | 
						|
{
 | 
						|
	dict_table_t* table = prebuilt->table;
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		table->file_unreadable = true;
 | 
						|
		if (table->space) {
 | 
						|
			fil_close_tablespace(table->space_id);
 | 
						|
			table->space = NULL;
 | 
						|
		}
 | 
						|
 | 
						|
		prebuilt->trx->error_info = NULL;
 | 
						|
 | 
						|
		ib::info() << "Discarding tablespace of table "
 | 
						|
			   << table->name << ": " << err;
 | 
						|
 | 
						|
		for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
 | 
						|
		     index;
 | 
						|
		     index = UT_LIST_GET_NEXT(indexes, index)) {
 | 
						|
			index->page = FIL_NULL;
 | 
						|
		}
 | 
						|
 | 
						|
		prebuilt->trx->rollback();
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		DBUG_EXECUTE_IF("ib_import_before_commit_crash", DBUG_SUICIDE(););
 | 
						|
		prebuilt->trx->commit();
 | 
						|
	}
 | 
						|
 | 
						|
	if (fts_table && fts_table != prebuilt->table) {
 | 
						|
 | 
						|
		if (err == DB_SUCCESS) {
 | 
						|
			reload_fts_table(prebuilt, fts_table);
 | 
						|
			table= prebuilt->table;
 | 
						|
			ib::warn() << "Added system generated FTS_DOC_ID "
 | 
						|
				   "and FTS_DOC_ID_INDEX while importing "
 | 
						|
				   "the tablespace " << prebuilt->table->name;
 | 
						|
		} else if (fts_table->space) {
 | 
						|
			fil_close_tablespace(fts_table->space_id);
 | 
						|
			fts_table->space = NULL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (!prebuilt->trx->dict_operation_lock_mode) {
 | 
						|
			dict_sys.lock(SRW_LOCK_CALL);
 | 
						|
		}
 | 
						|
 | 
						|
		dict_index_t* index = UT_LIST_GET_FIRST(
 | 
						|
					fts_table->indexes);
 | 
						|
		while (index) {
 | 
						|
			dict_index_t* next_index =
 | 
						|
				UT_LIST_GET_NEXT(indexes, index);
 | 
						|
			dict_index_remove_from_cache(fts_table, index);
 | 
						|
			index = next_index;
 | 
						|
		}
 | 
						|
		dict_mem_table_free(fts_table);
 | 
						|
 | 
						|
		if (!prebuilt->trx->dict_operation_lock_mode) {
 | 
						|
			dict_sys.unlock();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (prebuilt->trx->dict_operation_lock_mode) {
 | 
						|
		row_mysql_unlock_data_dictionary(prebuilt->trx);
 | 
						|
	}
 | 
						|
 | 
						|
	prebuilt->trx->op_info = "";
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_before_checkpoint_crash", DBUG_SUICIDE(););
 | 
						|
 | 
						|
	if (err != DB_SUCCESS
 | 
						|
	    || !dict_table_get_first_index(table)->is_gen_clust()) {
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	btr_cur_t cur;
 | 
						|
	mtr_t mtr;
 | 
						|
	mtr.start();
 | 
						|
	err = cur.open_leaf(false, dict_table_get_first_index(table),
 | 
						|
			    BTR_SEARCH_LEAF, &mtr);
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
	} else if (const rec_t *rec =
 | 
						|
		   page_rec_get_prev(btr_cur_get_rec(&cur))) {
 | 
						|
		if (page_rec_is_user_rec(rec))
 | 
						|
			table->row_id= mach_read_from_6(rec);
 | 
						|
	}
 | 
						|
	mtr.commit();
 | 
						|
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/** Report error during tablespace import.
 | 
						|
@param  prebuilt  prebuilt from the handler
 | 
						|
@param  err       error code
 | 
						|
@param  fts_table table definition containing hidden FTS_DOC_ID column
 | 
						|
@return error code or DB_SUCCESS */
 | 
						|
static
 | 
						|
dberr_t
 | 
						|
row_import_error(
 | 
						|
	row_prebuilt_t*	prebuilt,
 | 
						|
	dberr_t		err,
 | 
						|
	dict_table_t*	fts_table=nullptr)
 | 
						|
{
 | 
						|
	if (!trx_is_interrupted(prebuilt->trx)) {
 | 
						|
		char	table_name[MAX_FULL_NAME_LEN + 1];
 | 
						|
 | 
						|
		innobase_format_name(
 | 
						|
			table_name, sizeof(table_name),
 | 
						|
			prebuilt->table->name.m_name);
 | 
						|
 | 
						|
		ib_senderrf(
 | 
						|
			prebuilt->trx->mysql_thd, IB_LOG_LEVEL_WARN,
 | 
						|
			ER_INNODB_IMPORT_ERROR,
 | 
						|
			table_name, (ulong) err, ut_strerr(err));
 | 
						|
	}
 | 
						|
 | 
						|
	return row_import_cleanup(prebuilt, err, fts_table);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
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);
 | 
						|
 | 
						|
	n_rows_in_table = cfg.get_n_rows(index->name);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_sec_rec_count_mismatch_failure",
 | 
						|
			n_rows_in_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. */
 | 
						|
 | 
						|
			err = btr_root_adjust_on_import(index);
 | 
						|
		} else {
 | 
						|
			ib::warn() << "Skip adjustment of root pages for"
 | 
						|
				" index " << index->name << ".";
 | 
						|
 | 
						|
			err = DB_CORRUPTION;
 | 
						|
		}
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
 | 
						|
			if (index->type & DICT_CLUSTERED) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			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. */
 | 
						|
 | 
						|
			err = DB_SUCCESS;
 | 
						|
			index->type |= DICT_CORRUPT;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		/* If we failed to purge any records in the index then
 | 
						|
		do it the hard way.
 | 
						|
 | 
						|
		TODO: We can do this in the first pass by generating UNDO log
 | 
						|
		records for the failed rows. */
 | 
						|
 | 
						|
		if (!cfg.requires_purge(index->name)) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		IndexPurge   purge(trx, index);
 | 
						|
 | 
						|
		trx->op_info = "secondary: purge delete marked records";
 | 
						|
 | 
						|
		err = purge.garbage_collect();
 | 
						|
 | 
						|
		trx->op_info = "";
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
			break;
 | 
						|
		} else if (purge.get_n_rows() != n_rows_in_table) {
 | 
						|
 | 
						|
			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););
 | 
						|
 | 
						|
	ulint		len = 0;
 | 
						|
 | 
						|
	while (!feof(file)) {
 | 
						|
		int	ch = fgetc(file);
 | 
						|
 | 
						|
		if (ch == EOF) {
 | 
						|
			break;
 | 
						|
		} else if (ch != 0) {
 | 
						|
			if (len < max_len) {
 | 
						|
				ptr[len++] = static_cast<byte>(ch);
 | 
						|
			} else {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		/* max_len includes the NUL byte */
 | 
						|
		} else if (len != max_len - 1) {
 | 
						|
			break;
 | 
						|
		} else {
 | 
						|
			ptr[len] = 0;
 | 
						|
			return(DB_SUCCESS);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	errno = EINVAL;
 | 
						|
 | 
						|
	return(DB_IO_ERROR);
 | 
						|
}
 | 
						|
 | 
						|
/*********************************************************************//**
 | 
						|
Write the meta data (index user fields) config file.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static	MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
dberr_t
 | 
						|
row_import_cfg_read_index_fields(
 | 
						|
/*=============================*/
 | 
						|
	FILE*			file,	/*!< in: file to write to */
 | 
						|
	THD*			thd,	/*!< in/out: session */
 | 
						|
	row_index_t*		index)	/*!< Index being read in */
 | 
						|
{
 | 
						|
	byte			row[sizeof(ib_uint32_t) * 3];
 | 
						|
	ulint			n_fields = index->m_n_fields;
 | 
						|
 | 
						|
	index->m_fields = UT_NEW_ARRAY_NOKEY(dict_field_t, n_fields);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_4",
 | 
						|
		UT_DELETE_ARRAY(index->m_fields);
 | 
						|
		index->m_fields = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (index->m_fields == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	dict_field_t*	field = index->m_fields;
 | 
						|
 | 
						|
	for (ulint i = 0; i < n_fields; ++i, ++field) {
 | 
						|
		byte*		ptr = row;
 | 
						|
 | 
						|
		/* Trigger EOF */
 | 
						|
		DBUG_EXECUTE_IF("ib_import_io_read_error_1",
 | 
						|
				(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
		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 index fields.");
 | 
						|
 | 
						|
			return(DB_IO_ERROR);
 | 
						|
		}
 | 
						|
 | 
						|
		new (field) dict_field_t();
 | 
						|
 | 
						|
		field->prefix_len = mach_read_from_4(ptr) & ((1U << 12) - 1);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		uint32_t fixed_len = mach_read_from_4(ptr);
 | 
						|
 | 
						|
		field->descending = bool(fixed_len >> 31);
 | 
						|
 | 
						|
		field->fixed_len = fixed_len & ((1U << 10) - 1);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		/* Include the NUL byte in the length. */
 | 
						|
		ulint	len = mach_read_from_4(ptr);
 | 
						|
 | 
						|
		byte*	name = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
		/* Trigger OOM */
 | 
						|
		DBUG_EXECUTE_IF(
 | 
						|
			"ib_import_OOM_5",
 | 
						|
			UT_DELETE_ARRAY(name);
 | 
						|
			name = NULL;
 | 
						|
		);
 | 
						|
 | 
						|
		if (name == NULL) {
 | 
						|
			return(DB_OUT_OF_MEMORY);
 | 
						|
		}
 | 
						|
 | 
						|
		field->name = reinterpret_cast<const char*>(name);
 | 
						|
 | 
						|
		dberr_t	err = row_import_cfg_read_string(file, name, len);
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
 | 
						|
			ib_senderrf(
 | 
						|
				thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
				(ulong) errno, strerror(errno),
 | 
						|
				"while parsing table name.");
 | 
						|
 | 
						|
			return(err);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
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->m_indexes = UT_NEW_ARRAY_NOKEY(row_index_t, cfg->m_n_indexes);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_6",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_indexes);
 | 
						|
		cfg->m_indexes = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_indexes == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	memset(cfg->m_indexes, 0x0, sizeof(*cfg->m_indexes) * cfg->m_n_indexes);
 | 
						|
 | 
						|
	cfg_index = cfg->m_indexes;
 | 
						|
 | 
						|
	for (ulint i = 0; i < cfg->m_n_indexes; ++i, ++cfg_index) {
 | 
						|
		/* Trigger EOF */
 | 
						|
		DBUG_EXECUTE_IF("ib_import_io_read_error_2",
 | 
						|
				(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
		/* Read the index data. */
 | 
						|
		size_t	n_bytes = fread(row, 1, sizeof(row), file);
 | 
						|
 | 
						|
		/* Trigger EOF */
 | 
						|
		DBUG_EXECUTE_IF("ib_import_io_read_error",
 | 
						|
				(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
		if (n_bytes != sizeof(row)) {
 | 
						|
			char	msg[BUFSIZ];
 | 
						|
 | 
						|
			snprintf(msg, sizeof(msg),
 | 
						|
				 "while reading index meta-data, expected "
 | 
						|
				 "to read " ULINTPF
 | 
						|
				 " bytes but read only " ULINTPF " bytes",
 | 
						|
				 sizeof(row), n_bytes);
 | 
						|
 | 
						|
			ib_senderrf(
 | 
						|
				thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
				(ulong) errno, strerror(errno), msg);
 | 
						|
 | 
						|
			ib::error() << "IO Error: " << msg;
 | 
						|
 | 
						|
			return(DB_IO_ERROR);
 | 
						|
		}
 | 
						|
 | 
						|
		ptr = row;
 | 
						|
 | 
						|
		cfg_index->m_id = mach_read_from_8(ptr);
 | 
						|
		ptr += sizeof(index_id_t);
 | 
						|
 | 
						|
		cfg_index->m_space = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		cfg_index->m_page_no = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		cfg_index->m_type = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		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);
 | 
						|
 | 
						|
		cfg_index->m_n_user_defined_cols = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		cfg_index->m_n_uniq = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		cfg_index->m_n_nullable = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		cfg_index->m_n_fields = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		/* The NUL byte is included in the name length. */
 | 
						|
		ulint	len = mach_read_from_4(ptr);
 | 
						|
 | 
						|
		if (len > OS_FILE_MAX_PATH) {
 | 
						|
			ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
				ER_INNODB_INDEX_CORRUPT,
 | 
						|
				"Index name length (" ULINTPF ") is too long, "
 | 
						|
				"the meta-data is corrupt", len);
 | 
						|
 | 
						|
			return(DB_CORRUPTION);
 | 
						|
		}
 | 
						|
 | 
						|
		cfg_index->m_name = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
		/* Trigger OOM */
 | 
						|
		DBUG_EXECUTE_IF(
 | 
						|
			"ib_import_OOM_7",
 | 
						|
			UT_DELETE_ARRAY(cfg_index->m_name);
 | 
						|
			cfg_index->m_name = NULL;
 | 
						|
		);
 | 
						|
 | 
						|
		if (cfg_index->m_name == NULL) {
 | 
						|
			return(DB_OUT_OF_MEMORY);
 | 
						|
		}
 | 
						|
 | 
						|
		dberr_t	err;
 | 
						|
 | 
						|
		err = row_import_cfg_read_string(file, cfg_index->m_name, len);
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
 | 
						|
			ib_senderrf(
 | 
						|
				thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
				(ulong) errno, strerror(errno),
 | 
						|
				"while parsing index name.");
 | 
						|
 | 
						|
			return(err);
 | 
						|
		}
 | 
						|
 | 
						|
		err = row_import_cfg_read_index_fields(file, thd, cfg_index);
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
			return(err);
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Set the index root page number for v1 format.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static
 | 
						|
dberr_t
 | 
						|
row_import_read_indexes(
 | 
						|
/*====================*/
 | 
						|
	FILE*		file,		/*!< in: File to read from */
 | 
						|
	THD*		thd,		/*!< in: session */
 | 
						|
	row_import*	cfg)		/*!< in/out: meta-data read */
 | 
						|
{
 | 
						|
	byte		row[sizeof(ib_uint32_t)];
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_3",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	/* 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);
 | 
						|
 | 
						|
	} else if (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;
 | 
						|
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
	}
 | 
						|
 | 
						|
	return(row_import_read_index_data(file, thd, cfg));
 | 
						|
}
 | 
						|
 | 
						|
/*********************************************************************//**
 | 
						|
Read the meta data (table columns) config file. Deserialise the contents of
 | 
						|
dict_col_t structure, along with the column name. */
 | 
						|
static	MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
dberr_t
 | 
						|
row_import_read_columns(
 | 
						|
/*====================*/
 | 
						|
	FILE*			file,	/*!< in: file to write to */
 | 
						|
	THD*			thd,	/*!< in/out: session */
 | 
						|
	row_import*		cfg)	/*!< in/out: meta-data read */
 | 
						|
{
 | 
						|
	dict_col_t*		col;
 | 
						|
	byte			row[sizeof(ib_uint32_t) * 8];
 | 
						|
 | 
						|
	/* FIXME: What should the upper limit be? */
 | 
						|
	ut_a(cfg->m_n_cols > 0);
 | 
						|
	ut_a(cfg->m_n_cols < 1024);
 | 
						|
 | 
						|
	cfg->m_cols = UT_NEW_ARRAY_NOKEY(dict_col_t, cfg->m_n_cols);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_8",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_cols);
 | 
						|
		cfg->m_cols = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_cols == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	cfg->m_col_names = UT_NEW_ARRAY_NOKEY(byte*, cfg->m_n_cols);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_9",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_col_names);
 | 
						|
		cfg->m_col_names = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_col_names == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	memset(cfg->m_cols, 0x0, sizeof(cfg->m_cols) * cfg->m_n_cols);
 | 
						|
	memset(cfg->m_col_names, 0x0, sizeof(cfg->m_col_names) * cfg->m_n_cols);
 | 
						|
 | 
						|
	col = cfg->m_cols;
 | 
						|
 | 
						|
	for (ulint i = 0; i < cfg->m_n_cols; ++i, ++col) {
 | 
						|
		byte*		ptr = row;
 | 
						|
 | 
						|
		/* Trigger EOF */
 | 
						|
		DBUG_EXECUTE_IF("ib_import_io_read_error_4",
 | 
						|
				(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
		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 table column meta-data.");
 | 
						|
 | 
						|
			return(DB_IO_ERROR);
 | 
						|
		}
 | 
						|
 | 
						|
		col->prtype = mach_read_from_4(ptr);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		col->mtype = static_cast<byte>(mach_read_from_4(ptr));
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		col->len = static_cast<uint16_t>(mach_read_from_4(ptr));
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		uint32_t mbminmaxlen = mach_read_from_4(ptr);
 | 
						|
		col->mbmaxlen = (mbminmaxlen / 5) & 7;
 | 
						|
		col->mbminlen = (mbminmaxlen % 5) & 7;
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		col->ind = mach_read_from_4(ptr) & dict_index_t::MAX_N_FIELDS;
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		col->ord_part = mach_read_from_4(ptr) & 1;
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		col->max_prefix = mach_read_from_4(ptr) & ((1U << 12) - 1);
 | 
						|
		ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
		/* Read in the column name as [len, byte array]. The len
 | 
						|
		includes the NUL byte. */
 | 
						|
 | 
						|
		ulint		len = mach_read_from_4(ptr);
 | 
						|
 | 
						|
		/* FIXME: What is the maximum column name length? */
 | 
						|
		if (len == 0 || len > 128) {
 | 
						|
			ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
				ER_IO_READ_ERROR,
 | 
						|
				"Column name length " ULINTPF ", is invalid",
 | 
						|
				len);
 | 
						|
 | 
						|
			return(DB_CORRUPTION);
 | 
						|
		}
 | 
						|
 | 
						|
		cfg->m_col_names[i] = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
		/* Trigger OOM */
 | 
						|
		DBUG_EXECUTE_IF(
 | 
						|
			"ib_import_OOM_10",
 | 
						|
			UT_DELETE_ARRAY(cfg->m_col_names[i]);
 | 
						|
			cfg->m_col_names[i] = NULL;
 | 
						|
		);
 | 
						|
 | 
						|
		if (cfg->m_col_names[i] == NULL) {
 | 
						|
			return(DB_OUT_OF_MEMORY);
 | 
						|
		}
 | 
						|
 | 
						|
		dberr_t	err;
 | 
						|
 | 
						|
		err = row_import_cfg_read_string(
 | 
						|
			file, cfg->m_col_names[i], len);
 | 
						|
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
 | 
						|
			ib_senderrf(
 | 
						|
				thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
				(ulong) errno, strerror(errno),
 | 
						|
				"while parsing table column name.");
 | 
						|
 | 
						|
			return(err);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(DB_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Read the contents of the <tablespace>.cfg file.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static	MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
dberr_t
 | 
						|
row_import_read_v1(
 | 
						|
/*===============*/
 | 
						|
	FILE*		file,		/*!< in: File to read from */
 | 
						|
	THD*		thd,		/*!< in: session */
 | 
						|
	row_import*	cfg)		/*!< out: meta data */
 | 
						|
{
 | 
						|
	byte		value[sizeof(ib_uint32_t)];
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_5",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	/* Read the hostname where the tablespace 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 export hostname length.");
 | 
						|
 | 
						|
		return(DB_IO_ERROR);
 | 
						|
	}
 | 
						|
 | 
						|
	ulint	len = mach_read_from_4(value);
 | 
						|
 | 
						|
	/* NUL byte is part of name length. */
 | 
						|
	cfg->m_hostname = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_1",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_hostname);
 | 
						|
		cfg->m_hostname = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_hostname == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	dberr_t	err = row_import_cfg_read_string(file, cfg->m_hostname, len);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
 | 
						|
		ib_senderrf(
 | 
						|
			thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
			(ulong) errno, strerror(errno),
 | 
						|
			"while parsing export hostname.");
 | 
						|
 | 
						|
		return(err);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_6",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	/* 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);
 | 
						|
 | 
						|
	/* NUL byte is part of name length. */
 | 
						|
	cfg->m_table_name = UT_NEW_ARRAY_NOKEY(byte, len);
 | 
						|
 | 
						|
	/* Trigger OOM */
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_2",
 | 
						|
		UT_DELETE_ARRAY(cfg->m_table_name);
 | 
						|
		cfg->m_table_name = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (cfg->m_table_name == NULL) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	err = row_import_cfg_read_string(file, cfg->m_table_name, len);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		ib_senderrf(
 | 
						|
			thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
			(ulong) errno, strerror(errno),
 | 
						|
			"while parsing table name.");
 | 
						|
 | 
						|
		return(err);
 | 
						|
	}
 | 
						|
 | 
						|
	ib::info() << "Importing tablespace for table '" << cfg->m_table_name
 | 
						|
		<< "' that was exported from host '" << cfg->m_hostname << "'";
 | 
						|
 | 
						|
	byte		row[sizeof(ib_uint32_t) * 3];
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_7",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	/* Read the autoinc value. */
 | 
						|
	if (fread(row, 1, sizeof(ib_uint64_t), file) != sizeof(ib_uint64_t)) {
 | 
						|
		ib_senderrf(
 | 
						|
			thd, IB_LOG_LEVEL_ERROR, ER_IO_READ_ERROR,
 | 
						|
			(ulong) errno, strerror(errno),
 | 
						|
			"while reading autoinc value.");
 | 
						|
 | 
						|
		return(DB_IO_ERROR);
 | 
						|
	}
 | 
						|
 | 
						|
	cfg->m_autoinc = mach_read_from_8(row);
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_8",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	/* Read the tablespace page size. */
 | 
						|
	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 meta-data header.");
 | 
						|
 | 
						|
		return(DB_IO_ERROR);
 | 
						|
	}
 | 
						|
 | 
						|
	byte*		ptr = row;
 | 
						|
 | 
						|
	const ulint	logical_page_size = mach_read_from_4(ptr);
 | 
						|
	ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
	if (logical_page_size != srv_page_size) {
 | 
						|
 | 
						|
		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);
 | 
						|
 | 
						|
		return(DB_ERROR);
 | 
						|
	}
 | 
						|
 | 
						|
	cfg->m_flags = mach_read_from_4(ptr);
 | 
						|
	ptr += sizeof(ib_uint32_t);
 | 
						|
 | 
						|
	cfg->m_zip_size = dict_tf_get_zip_size(cfg->m_flags);
 | 
						|
	cfg->m_n_cols = mach_read_from_4(ptr);
 | 
						|
 | 
						|
	if (!dict_tf_is_valid(cfg->m_flags)) {
 | 
						|
		ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
			ER_TABLE_SCHEMA_MISMATCH,
 | 
						|
			"Invalid table flags: " ULINTPF, cfg->m_flags);
 | 
						|
 | 
						|
		return(DB_CORRUPTION);
 | 
						|
	}
 | 
						|
 | 
						|
	err = row_import_read_columns(file, thd, cfg);
 | 
						|
 | 
						|
	if (err == DB_SUCCESS) {
 | 
						|
		err = row_import_read_indexes(file, thd, cfg);
 | 
						|
	}
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Read the contents of the <tablespace>.cfg file.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static	MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
dberr_t
 | 
						|
row_import_read_meta_data(
 | 
						|
/*======================*/
 | 
						|
	FILE*		file,		/*!< in: File to read from */
 | 
						|
	THD*		thd,		/*!< in: session */
 | 
						|
	row_import&	cfg)		/*!< out: contents of the .cfg file */
 | 
						|
{
 | 
						|
	byte		row[sizeof(ib_uint32_t)];
 | 
						|
 | 
						|
	/* Trigger EOF */
 | 
						|
	DBUG_EXECUTE_IF("ib_import_io_read_error_9",
 | 
						|
			(void) fseek(file, 0L, SEEK_END););
 | 
						|
 | 
						|
	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 meta-data version.");
 | 
						|
 | 
						|
		return(DB_IO_ERROR);
 | 
						|
	}
 | 
						|
 | 
						|
	/* 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;
 | 
						|
 | 
						|
    dberr_t err=
 | 
						|
      fil_space_decrypt(space_id, space_flags, space_crypt,
 | 
						|
                        tmp_frame, page_size, data);
 | 
						|
 | 
						|
    memcpy(data, tmp_frame, page_size);
 | 
						|
 | 
						|
    if (err)
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
 | 
						|
  bool page_compressed= false;
 | 
						|
 | 
						|
  if (fil_space_t::full_crc32(space_flags) &&
 | 
						|
      fil_space_t::is_compressed(space_flags))
 | 
						|
    page_compressed= buf_page_is_compressed(data, space_flags);
 | 
						|
  else
 | 
						|
  {
 | 
						|
    switch (fil_page_get_type(data)) {
 | 
						|
    case FIL_PAGE_PAGE_COMPRESSED:
 | 
						|
    case FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED:
 | 
						|
      page_compressed= true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (page_compressed)
 | 
						|
  {
 | 
						|
    auto compress_length=
 | 
						|
      fil_page_decompress(page_compress_buf, data, space_flags);
 | 
						|
    ut_ad(compress_length != srv_page_size);
 | 
						|
 | 
						|
    if (compress_length == 0)
 | 
						|
      return DB_CORRUPTION;
 | 
						|
  }
 | 
						|
 | 
						|
  return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static size_t get_buf_size()
 | 
						|
{
 | 
						|
  return srv_page_size + (
 | 
						|
           provider_service_lzo->is_loaded ? LZO1X_1_15_MEM_COMPRESS :
 | 
						|
           provider_service_snappy->is_loaded ? snappy_max_compressed_length(srv_page_size) :
 | 
						|
           0
 | 
						|
         );
 | 
						|
}
 | 
						|
 | 
						|
/** Add fts index to the table
 | 
						|
@param table fts index to be added on the table */
 | 
						|
static void 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
 | 
						|
existing table
 | 
						|
@param  table  table to be imported
 | 
						|
@param  thd    thread
 | 
						|
@param  cfg    metadata required by import
 | 
						|
@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;
 | 
						|
  const char* 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;
 | 
						|
  }
 | 
						|
 | 
						|
  unsigned fts_col_ind= unsigned(table->n_cols - DATA_N_SYS_COLS);
 | 
						|
  fts_add_doc_id_column(new_table, new_table->heap);
 | 
						|
  new_table->cols[fts_col_ind].ind=
 | 
						|
    fts_col_ind & dict_index_t::MAX_N_FIELDS;
 | 
						|
  new_table->cols[fts_col_ind].ord_part= 1;
 | 
						|
  dict_table_add_system_columns(new_table, new_table->heap);
 | 
						|
 | 
						|
  col_name= &table->v_col_names[0];
 | 
						|
  for (ulint new_i= 0; new_i < new_table->n_v_cols; new_i++)
 | 
						|
  {
 | 
						|
    dict_col_t old_vcol= table->v_cols[new_i].m_col;
 | 
						|
    dict_mem_table_add_v_col(new_table, new_table->heap, col_name,
 | 
						|
                             old_vcol.mtype, old_vcol.prtype,
 | 
						|
                             old_vcol.len, old_vcol.ind + 1,
 | 
						|
                             table->v_cols[new_i].num_base);
 | 
						|
    for (ulint i= 0; i < table->v_cols[new_i].num_base; i++)
 | 
						|
    {
 | 
						|
      dict_col_t *base_col= dict_table_get_nth_col(
 | 
						|
        new_table, table->v_cols[new_i].base_col[i]->ind);
 | 
						|
      new_table->v_cols[new_i].base_col[i]= base_col;
 | 
						|
    }
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    UT_LIST_ADD_LAST(new_index->table->indexes, new_index);
 | 
						|
    old_index= UT_LIST_GET_NEXT(indexes, old_index);
 | 
						|
    if (UT_LIST_GET_LEN(new_table->indexes)
 | 
						|
        == cfg.find_fts_idx_offset())
 | 
						|
      add_fts_index(new_table);
 | 
						|
  }
 | 
						|
  return new_table;
 | 
						|
}
 | 
						|
 | 
						|
/* find, parse instant metadata, performing various checks,
 | 
						|
and apply it to dict_table_t
 | 
						|
@return DB_SUCCESS or some error */
 | 
						|
static dberr_t handle_instant_metadata(dict_table_t *table,
 | 
						|
                                       const row_import &cfg)
 | 
						|
{
 | 
						|
  dict_get_and_save_data_dir_path(table);
 | 
						|
 | 
						|
  char *filepath;
 | 
						|
  if (DICT_TF_HAS_DATA_DIR(table->flags))
 | 
						|
  {
 | 
						|
    ut_a(table->data_dir_path);
 | 
						|
    filepath= fil_make_filepath(table->data_dir_path, table->name, IBD, true);
 | 
						|
  }
 | 
						|
  else
 | 
						|
    filepath= fil_make_filepath(nullptr, table->name, IBD, false);
 | 
						|
 | 
						|
  if (!filepath)
 | 
						|
    return DB_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  SCOPE_EXIT([filepath]() { ut_free(filepath); });
 | 
						|
 | 
						|
  bool success;
 | 
						|
  auto file= os_file_create_simple_no_error_handling(
 | 
						|
      innodb_data_file_key, filepath, OS_FILE_OPEN, OS_FILE_READ_WRITE, false,
 | 
						|
      &success);
 | 
						|
  if (!success)
 | 
						|
    return DB_IO_ERROR;
 | 
						|
 | 
						|
  if (os_file_get_size(file) < srv_page_size)
 | 
						|
    return DB_CORRUPTION;
 | 
						|
 | 
						|
  SCOPE_EXIT([&file]() { os_file_close(file); });
 | 
						|
 | 
						|
  std::unique_ptr<byte[], decltype(&aligned_free)> first_page(
 | 
						|
      static_cast<byte *>(aligned_malloc(srv_page_size, srv_page_size)),
 | 
						|
      &aligned_free);
 | 
						|
 | 
						|
  if (dberr_t err= os_file_read(IORequestReadPartial, file, first_page.get(),
 | 
						|
                                0, srv_page_size, nullptr))
 | 
						|
    return err;
 | 
						|
 | 
						|
  auto space_flags= fsp_header_get_flags(first_page.get());
 | 
						|
 | 
						|
  if (!fil_space_t::is_valid_flags(space_flags, true))
 | 
						|
  {
 | 
						|
    auto cflags= fsp_flags_convert_from_101(space_flags);
 | 
						|
    if (cflags == UINT32_MAX)
 | 
						|
      return invalid_space_flags(space_flags);
 | 
						|
    space_flags= static_cast<decltype(space_flags)>(cflags);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!cfg.m_missing)
 | 
						|
  {
 | 
						|
    if (dberr_t err= cfg.match_flags(current_thd))
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
 | 
						|
  const unsigned zip_size= fil_space_t::zip_size(space_flags);
 | 
						|
  const unsigned physical_size= zip_size ? zip_size : unsigned(srv_page_size);
 | 
						|
  ut_ad(physical_size <= UNIV_PAGE_SIZE_MAX);
 | 
						|
  const uint32_t space_id= page_get_space_id(first_page.get());
 | 
						|
 | 
						|
  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);
 | 
						|
  });
 | 
						|
 | 
						|
  std::unique_ptr<byte[], decltype(&aligned_free)> page(
 | 
						|
      static_cast<byte *>(
 | 
						|
          aligned_malloc(UNIV_PAGE_SIZE_MAX, UNIV_PAGE_SIZE_MAX)),
 | 
						|
      &aligned_free);
 | 
						|
 | 
						|
  if (dberr_t err= os_file_read(
 | 
						|
          IORequestReadPartial, file, page.get(), 3 * physical_size,
 | 
						|
          physical_size, nullptr))
 | 
						|
    return err;
 | 
						|
 | 
						|
  std::unique_ptr<byte[]> page_compress_buf(new byte[get_buf_size()]);
 | 
						|
  std::unique_ptr<byte[], decltype(&aligned_free)> crypt_tmp_frame(
 | 
						|
      static_cast<byte *>(
 | 
						|
          aligned_malloc(physical_size, CPU_LEVEL1_DCACHE_LINESIZE)),
 | 
						|
      &aligned_free);
 | 
						|
 | 
						|
  if (dberr_t err= decrypt_decompress(space_crypt, space_flags,
 | 
						|
                                      {page.get(), static_cast<size_t>
 | 
						|
                                       (physical_size)},
 | 
						|
                                      space_id, page_compress_buf.get(),
 | 
						|
                                      crypt_tmp_frame.get()))
 | 
						|
    return err;
 | 
						|
 | 
						|
  if (table->supports_instant())
 | 
						|
  {
 | 
						|
    dict_index_t *index= dict_table_get_first_index(table);
 | 
						|
 | 
						|
    if (!page_is_comp(page.get()) != !dict_table_is_comp(table))
 | 
						|
    {
 | 
						|
      ib_errf(current_thd, IB_LOG_LEVEL_ERROR, ER_TABLE_SCHEMA_MISMATCH,
 | 
						|
              "ROW_FORMAT mismatch");
 | 
						|
      return DB_CORRUPTION;
 | 
						|
    }
 | 
						|
 | 
						|
    if (btr_cur_instant_root_init(index, page.get()))
 | 
						|
      return DB_CORRUPTION;
 | 
						|
 | 
						|
    ut_ad(index->n_core_null_bytes != dict_index_t::NO_CORE_NULL_BYTES);
 | 
						|
 | 
						|
    if (fil_page_get_type(page.get()) == FIL_PAGE_INDEX)
 | 
						|
    {
 | 
						|
      ut_ad(!index->is_instant());
 | 
						|
      return DB_SUCCESS;
 | 
						|
    }
 | 
						|
 | 
						|
    mem_heap_t *heap= NULL;
 | 
						|
    SCOPE_EXIT([&heap]() {
 | 
						|
      if (heap)
 | 
						|
        mem_heap_free(heap);
 | 
						|
    });
 | 
						|
 | 
						|
    while (btr_page_get_level(page.get()) != 0)
 | 
						|
    {
 | 
						|
      const rec_t *rec= page_rec_get_next(page_get_infimum_rec(page.get()));
 | 
						|
      if (!rec)
 | 
						|
        return DB_CORRUPTION;
 | 
						|
 | 
						|
      /* Relax the assertion in rec_init_offsets(). */
 | 
						|
      ut_ad(!index->in_instant_init);
 | 
						|
      ut_d(index->in_instant_init= true);
 | 
						|
      rec_offs *offsets=
 | 
						|
          rec_get_offsets(rec, index, nullptr, 0, ULINT_UNDEFINED, &heap);
 | 
						|
      ut_d(index->in_instant_init= false);
 | 
						|
 | 
						|
      uint64_t child_page_no= btr_node_ptr_get_child_page_no(rec, offsets);
 | 
						|
 | 
						|
      if (dberr_t err=
 | 
						|
          os_file_read(IORequestReadPartial, file, page.get(),
 | 
						|
                       child_page_no * physical_size, physical_size, nullptr))
 | 
						|
        return err;
 | 
						|
 | 
						|
      if (dberr_t err= decrypt_decompress(space_crypt, space_flags,
 | 
						|
                                          {page.get(), static_cast<size_t>
 | 
						|
                                           (physical_size)}, space_id,
 | 
						|
                                          page_compress_buf.get(),
 | 
						|
                                          crypt_tmp_frame.get()))
 | 
						|
        return err;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto *rec= page_rec_get_next_const(page_get_infimum_rec(page.get()));
 | 
						|
    const auto comp= dict_table_is_comp(index->table);
 | 
						|
 | 
						|
    if (!rec || page_rec_is_supremum(rec))
 | 
						|
    {
 | 
						|
    corrupted_metadata:
 | 
						|
      ib::error() << "Table " << index->table->name
 | 
						|
                  << " is missing instant ALTER metadata";
 | 
						|
      index->table->corrupted= true;
 | 
						|
      return DB_CORRUPTION;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto info_bits= rec_get_info_bits(rec, comp);
 | 
						|
    if (!(info_bits & REC_INFO_MIN_REC_FLAG))
 | 
						|
      goto corrupted_metadata;
 | 
						|
 | 
						|
    if ((info_bits & ~REC_INFO_DELETED_FLAG) != REC_INFO_MIN_REC_FLAG ||
 | 
						|
        (comp && rec_get_status(rec) != REC_STATUS_INSTANT))
 | 
						|
    {
 | 
						|
    incompatible:
 | 
						|
      ib::error() << "Table " << index->table->name
 | 
						|
                  << " contains unrecognizable instant ALTER metadata";
 | 
						|
      index->table->corrupted= true;
 | 
						|
      return DB_CORRUPTION;
 | 
						|
    }
 | 
						|
 | 
						|
    if (info_bits & REC_INFO_DELETED_FLAG)
 | 
						|
    {
 | 
						|
      ulint trx_id_offset= index->trx_id_offset;
 | 
						|
      ut_ad(index->n_uniq);
 | 
						|
 | 
						|
      if (trx_id_offset)
 | 
						|
      {
 | 
						|
      }
 | 
						|
      else if (index->table->not_redundant())
 | 
						|
      {
 | 
						|
 | 
						|
        for (uint i= index->n_uniq; i--;)
 | 
						|
          trx_id_offset+= index->fields[i].fixed_len;
 | 
						|
      }
 | 
						|
      else if (rec_get_1byte_offs_flag(rec))
 | 
						|
      {
 | 
						|
        trx_id_offset= rec_1_get_field_end_info(rec, index->n_uniq - 1);
 | 
						|
        ut_ad(!(trx_id_offset & REC_1BYTE_SQL_NULL_MASK));
 | 
						|
        trx_id_offset&= ~REC_1BYTE_SQL_NULL_MASK;
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
        trx_id_offset= rec_2_get_field_end_info(rec, index->n_uniq - 1);
 | 
						|
        ut_ad(!(trx_id_offset & REC_2BYTE_SQL_NULL_MASK));
 | 
						|
        trx_id_offset&= ~REC_2BYTE_SQL_NULL_MASK;
 | 
						|
      }
 | 
						|
 | 
						|
      const byte *ptr=
 | 
						|
          rec + trx_id_offset + (DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN);
 | 
						|
 | 
						|
      if (mach_read_from_4(ptr + BTR_EXTERN_LEN))
 | 
						|
        goto incompatible;
 | 
						|
 | 
						|
      uint len= mach_read_from_4(ptr + BTR_EXTERN_LEN + 4);
 | 
						|
      if (!len || mach_read_from_4(ptr + BTR_EXTERN_OFFSET) != FIL_PAGE_DATA)
 | 
						|
        goto incompatible;
 | 
						|
 | 
						|
      std::unique_ptr<byte[], decltype(&aligned_free)>
 | 
						|
        second_page(static_cast<byte*>(aligned_malloc(physical_size,
 | 
						|
                                                      physical_size)),
 | 
						|
                    &aligned_free);
 | 
						|
 | 
						|
      if (dberr_t err=
 | 
						|
          os_file_read(IORequestReadPartial, file, second_page.get(),
 | 
						|
                       physical_size *
 | 
						|
                       mach_read_from_4(ptr + BTR_EXTERN_PAGE_NO),
 | 
						|
                       physical_size, nullptr))
 | 
						|
        return err;
 | 
						|
 | 
						|
      if (dberr_t err= decrypt_decompress(space_crypt, space_flags,
 | 
						|
                                          {second_page.get(),
 | 
						|
                                           static_cast<size_t>(physical_size)},
 | 
						|
                                          space_id, page_compress_buf.get(),
 | 
						|
                                          crypt_tmp_frame.get()))
 | 
						|
        return err;
 | 
						|
 | 
						|
      if (fil_page_get_type(second_page.get()) != FIL_PAGE_TYPE_BLOB ||
 | 
						|
          mach_read_from_4(
 | 
						|
              &second_page[FIL_PAGE_DATA + BTR_BLOB_HDR_NEXT_PAGE_NO]) !=
 | 
						|
              FIL_NULL ||
 | 
						|
          mach_read_from_4(
 | 
						|
              &second_page[FIL_PAGE_DATA + BTR_BLOB_HDR_PART_LEN]) != len)
 | 
						|
        goto incompatible;
 | 
						|
 | 
						|
      /* 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;
 | 
						|
    }
 | 
						|
 | 
						|
    rec_offs *offsets= rec_get_offsets(
 | 
						|
        rec, index, nullptr, index->n_core_fields, ULINT_UNDEFINED, &heap);
 | 
						|
    if (rec_offs_any_default(offsets))
 | 
						|
    {
 | 
						|
    inconsistent:
 | 
						|
      goto incompatible;
 | 
						|
    }
 | 
						|
 | 
						|
    /* In fact, because we only ever append fields to the metadata
 | 
						|
    record, it is also OK to perform READ UNCOMMITTED and
 | 
						|
    then ignore any extra fields, provided that
 | 
						|
    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;
 | 
						|
 | 
						|
    for (unsigned i= index->n_core_fields; i < index->n_fields; i++)
 | 
						|
    {
 | 
						|
      dict_col_t *col= index->fields[i].col;
 | 
						|
      const unsigned o= i + !!index->table->instant;
 | 
						|
      ulint len;
 | 
						|
      const byte *data= rec_get_nth_field(rec, offsets, o, &len);
 | 
						|
      ut_ad(!col->is_added());
 | 
						|
      ut_ad(!col->def_val.data);
 | 
						|
      col->def_val.len= len;
 | 
						|
      switch (len) {
 | 
						|
      case UNIV_SQL_NULL:
 | 
						|
        continue;
 | 
						|
      case 0:
 | 
						|
        col->def_val.data= field_ref_zero;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      ut_ad(len != UNIV_SQL_DEFAULT);
 | 
						|
      if (!rec_offs_nth_extern(offsets, o))
 | 
						|
        col->def_val.data= mem_heap_dup(index->table->heap, data, len);
 | 
						|
      else if (len < BTR_EXTERN_FIELD_REF_SIZE ||
 | 
						|
               !memcmp(data + len - BTR_EXTERN_FIELD_REF_SIZE, field_ref_zero,
 | 
						|
                       BTR_EXTERN_FIELD_REF_SIZE))
 | 
						|
      {
 | 
						|
        col->def_val.len= UNIV_SQL_DEFAULT;
 | 
						|
        goto inconsistent;
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
        col->def_val.data= btr_copy_externally_stored_field(
 | 
						|
            &col->def_val.len, data, srv_page_size, len, index->table->heap);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Read the contents of a .cfg file.
 | 
						|
@param[in]  filename  Path to the cfg file
 | 
						|
@param[in]  thd       Connection
 | 
						|
@param[out] cfg   Contents of the .cfg file.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static dberr_t row_import_read_cfg_internal(const char *filename, THD *thd,
 | 
						|
                                            row_import &cfg)
 | 
						|
{
 | 
						|
  FILE *file= fopen(filename, "rb");
 | 
						|
 | 
						|
  cfg.m_missing= !file;
 | 
						|
 | 
						|
  if (!file)
 | 
						|
  {
 | 
						|
    char msg[BUFSIZ];
 | 
						|
    snprintf(msg, sizeof(msg),
 | 
						|
             "Error opening '%s', will attempt to import"
 | 
						|
             " without schema verification", filename);
 | 
						|
    ib_senderrf(thd, IB_LOG_LEVEL_WARN, ER_IO_READ_ERROR,
 | 
						|
                (ulong) errno, strerror(errno), msg);
 | 
						|
    return DB_FAIL;
 | 
						|
  }
 | 
						|
 | 
						|
  dberr_t err= row_import_read_meta_data(file, thd, cfg);
 | 
						|
  fclose(file);
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Read the contents of the <tablename>.cfg file.
 | 
						|
@return DB_SUCCESS or error code. */
 | 
						|
static	MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
dberr_t
 | 
						|
row_import_read_cfg(
 | 
						|
/*================*/
 | 
						|
	dict_table_t*	table,	/*!< in: table */
 | 
						|
	THD*		thd,	/*!< in: session */
 | 
						|
	row_import&	cfg)	/*!< out: contents of the .cfg file */
 | 
						|
{
 | 
						|
	char		name[OS_FILE_MAX_PATH];
 | 
						|
 | 
						|
	cfg.m_table = table;
 | 
						|
 | 
						|
	srv_get_meta_data_filename(table, name, sizeof(name));
 | 
						|
 | 
						|
	return row_import_read_cfg_internal(name, thd, cfg);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/** Convert the InnoDB ROW_FORMAT from rec_format_enum to row_type.
 | 
						|
@param[in]  from  ROW_FORMAT as a rec_format_enum
 | 
						|
@return the row_type representation of ROW_FORMAT. */
 | 
						|
static enum 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;
 | 
						|
  }
 | 
						|
 | 
						|
  ut_ad("invalid format" == 0);
 | 
						|
  return ROW_TYPE_NOT_USED;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Read the row type from a .cfg file.
 | 
						|
@param  dir_path  Path to the data directory containing the .cfg file
 | 
						|
@param  name      Name of the table
 | 
						|
@param  thd       Connection
 | 
						|
@retval ROW_TYPE_COMPACT    for ROW_FORMAT=COMPACT
 | 
						|
@retval ROW_TYPE_DYNAMIC    for ROW_FORMAT=DYNAMIC
 | 
						|
@retval ROW_TYPE_REDUNDANT  for ROW_FORMAT=REDUNDANT
 | 
						|
@retval ROW_TYPE_COMPRESSED for ROW_FORMAT=COMPRESSED
 | 
						|
@retval ROW_TYPE_NOT_USED to signal error */
 | 
						|
static enum row_type get_row_type_from_cfg(const char* dir_path,
 | 
						|
                                           const char* name, THD* thd)
 | 
						|
{
 | 
						|
  char* filename= fil_make_filepath(dir_path,
 | 
						|
                                    table_name_t(const_cast<char*>(name)),
 | 
						|
                                    CFG, dir_path != nullptr);
 | 
						|
  if (!filename)
 | 
						|
    return ROW_TYPE_NOT_USED;
 | 
						|
  row_import cfg;
 | 
						|
  dberr_t err= row_import_read_cfg_internal(filename, thd, cfg);
 | 
						|
  ut_free(filename);
 | 
						|
  if (err == DB_SUCCESS)
 | 
						|
    return from_rec_format(dict_tf_get_rec_format(cfg.m_flags));
 | 
						|
  return ROW_TYPE_NOT_USED;
 | 
						|
}
 | 
						|
 | 
						|
/** Update the root page numbers and tablespace ID of a table.
 | 
						|
@param[in,out]	trx	dictionary transaction
 | 
						|
@param[in,out]	table	persistent table
 | 
						|
@param[in]	reset	whether to reset the fields to FIL_NULL
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
dberr_t
 | 
						|
row_import_update_index_root(trx_t* trx, dict_table_t* table, bool reset)
 | 
						|
{
 | 
						|
	const dict_index_t*	index;
 | 
						|
	que_t*			graph = 0;
 | 
						|
	dberr_t			err = DB_SUCCESS;
 | 
						|
 | 
						|
	ut_ad(reset || table->space->id == table->space_id);
 | 
						|
 | 
						|
	static const char	sql[] = {
 | 
						|
		"PROCEDURE UPDATE_INDEX_ROOT() IS\n"
 | 
						|
		"BEGIN\n"
 | 
						|
		"UPDATE SYS_INDEXES\n"
 | 
						|
		"SET SPACE = :space,\n"
 | 
						|
		"    PAGE_NO = :page,\n"
 | 
						|
		"    TYPE = :type\n"
 | 
						|
		"WHERE TABLE_ID = :table_id AND ID = :index_id;\n"
 | 
						|
		"END;\n"};
 | 
						|
 | 
						|
	table->def_trx_id = trx->id;
 | 
						|
 | 
						|
	for (index = dict_table_get_first_index(table);
 | 
						|
	     index != 0;
 | 
						|
	     index = dict_table_get_next_index(index)) {
 | 
						|
 | 
						|
		pars_info_t*	info;
 | 
						|
		ib_uint32_t	page;
 | 
						|
		ib_uint32_t	space;
 | 
						|
		ib_uint32_t	type;
 | 
						|
		index_id_t	index_id;
 | 
						|
		table_id_t	table_id;
 | 
						|
 | 
						|
		info = (graph != 0) ? graph->info : pars_info_create();
 | 
						|
 | 
						|
		mach_write_to_4(
 | 
						|
			reinterpret_cast<byte*>(&type),
 | 
						|
			index->type);
 | 
						|
 | 
						|
		mach_write_to_4(
 | 
						|
			reinterpret_cast<byte*>(&page),
 | 
						|
			reset ? FIL_NULL : index->page);
 | 
						|
 | 
						|
		mach_write_to_4(
 | 
						|
			reinterpret_cast<byte*>(&space),
 | 
						|
			reset ? FIL_NULL : index->table->space_id);
 | 
						|
 | 
						|
		mach_write_to_8(
 | 
						|
			reinterpret_cast<byte*>(&index_id),
 | 
						|
			index->id);
 | 
						|
 | 
						|
		mach_write_to_8(
 | 
						|
			reinterpret_cast<byte*>(&table_id),
 | 
						|
			table->id);
 | 
						|
 | 
						|
		/* 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 (graph == 0) {
 | 
						|
			graph = pars_sql(info, sql);
 | 
						|
			ut_a(graph);
 | 
						|
			graph->trx = trx;
 | 
						|
		}
 | 
						|
 | 
						|
		que_thr_t*	thr;
 | 
						|
 | 
						|
		ut_a(thr = que_fork_start_command(graph));
 | 
						|
 | 
						|
		que_run_threads(thr);
 | 
						|
 | 
						|
		DBUG_EXECUTE_IF("ib_import_internal_error",
 | 
						|
				trx->error_state = DB_ERROR;);
 | 
						|
 | 
						|
		err = trx->error_state;
 | 
						|
 | 
						|
		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.
 | 
						|
@return FALSE if 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);
 | 
						|
 | 
						|
	ut_a(dtype_get_mtype(type) == DATA_INT);
 | 
						|
	ut_a(len == sizeof(ib_uint32_t));
 | 
						|
 | 
						|
	ulint	flags2 = mach_read_from_4(
 | 
						|
		static_cast<byte*>(dfield_get_data(dfield)));
 | 
						|
 | 
						|
#if defined __GNUC__ && !defined __clang__
 | 
						|
# pragma GCC diagnostic push
 | 
						|
# if __GNUC__ < 12 || defined WITH_UBSAN
 | 
						|
#  pragma GCC diagnostic ignored "-Wconversion"
 | 
						|
# endif
 | 
						|
#endif
 | 
						|
	if (discard->state) {
 | 
						|
		flags2 |= DICT_TF2_DISCARDED;
 | 
						|
	} else {
 | 
						|
		flags2 &= ~DICT_TF2_DISCARDED;
 | 
						|
	}
 | 
						|
#if defined __GNUC__ && !defined __clang__
 | 
						|
# pragma GCC diagnostic pop
 | 
						|
#endif
 | 
						|
 | 
						|
	mach_write_to_4(reinterpret_cast<byte*>(&discard->flags2), flags2);
 | 
						|
 | 
						|
	++discard->n_recs;
 | 
						|
 | 
						|
	/* 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]	trx		dictionary transaction
 | 
						|
@param[in]	table_id	table identifier
 | 
						|
@param[in]	discarded	whether to set or clear the flag
 | 
						|
@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;
 | 
						|
 | 
						|
	static const char	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";
 | 
						|
 | 
						|
	discard.n_recs = 0;
 | 
						|
	discard.state = discarded;
 | 
						|
	discard.flags2 = ULINT32_UNDEFINED;
 | 
						|
 | 
						|
	info = pars_info_create();
 | 
						|
 | 
						|
	pars_info_add_ull_literal(info, "table_id", table_id);
 | 
						|
	pars_info_bind_int4_literal(info, "flags2", &discard.flags2);
 | 
						|
 | 
						|
	pars_info_bind_function(
 | 
						|
		info, "my_func", row_import_set_discarded, &discard);
 | 
						|
 | 
						|
	dberr_t	err = que_eval_sql(info, sql, trx);
 | 
						|
 | 
						|
	ut_a(discard.n_recs == 1);
 | 
						|
	ut_a(discard.flags2 != ULINT32_UNDEFINED);
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
/** InnoDB writes page by page when there is page compressed
 | 
						|
tablespace involved. It does help to save the disk space when
 | 
						|
punch hole is enabled
 | 
						|
@param iter     Tablespace iterator
 | 
						|
@param full_crc32    whether the file is in the full_crc32 format
 | 
						|
@param offset   offset of the file to be written
 | 
						|
@param writeptr buffer to be written
 | 
						|
@param n_bytes  number of bytes to be written
 | 
						|
@param try_punch_only   Try the range punch only because the
 | 
						|
                        current range is full of empty pages
 | 
						|
@return DB_SUCCESS */
 | 
						|
static
 | 
						|
dberr_t fil_import_compress_fwrite(const fil_iterator_t &iter,
 | 
						|
                                   bool full_crc32,
 | 
						|
                                   os_offset_t offset,
 | 
						|
                                   const byte *writeptr,
 | 
						|
                                   ulint n_bytes,
 | 
						|
                                   bool try_punch_only= false)
 | 
						|
{
 | 
						|
  if (dberr_t err= os_file_punch_hole(iter.file, offset, n_bytes))
 | 
						|
    return err;
 | 
						|
 | 
						|
  if (try_punch_only)
 | 
						|
    return DB_SUCCESS;
 | 
						|
 | 
						|
  for (ulint j= 0; j < n_bytes; j+= srv_page_size)
 | 
						|
  {
 | 
						|
    /* Read the original data length from block and
 | 
						|
    safer to read FIL_PAGE_COMPRESSED_SIZE because it
 | 
						|
    is not encrypted*/
 | 
						|
    ulint n_write_bytes= srv_page_size;
 | 
						|
    if (j || offset)
 | 
						|
    {
 | 
						|
      n_write_bytes= mach_read_from_2(writeptr + j + FIL_PAGE_DATA);
 | 
						|
      const unsigned ptype= mach_read_from_2(writeptr + j + FIL_PAGE_TYPE);
 | 
						|
      /* Ignore the empty page */
 | 
						|
      if (ptype == 0 && n_write_bytes == 0)
 | 
						|
        continue;
 | 
						|
      if (full_crc32)
 | 
						|
        n_write_bytes= buf_page_full_crc32_size(writeptr + j,
 | 
						|
                                                nullptr, nullptr);
 | 
						|
      else
 | 
						|
      {
 | 
						|
        n_write_bytes+= ptype == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
 | 
						|
          ? FIL_PAGE_DATA + FIL_PAGE_ENCRYPT_COMP_METADATA_LEN
 | 
						|
          : FIL_PAGE_DATA + FIL_PAGE_COMP_METADATA_LEN;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (dberr_t err= os_file_write(IORequestWrite, iter.filepath, iter.file,
 | 
						|
                                   writeptr + j, offset + j, n_write_bytes))
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
 | 
						|
  return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
dberr_t FetchIndexRootPages::run(const fil_iterator_t& iter,
 | 
						|
                                 buf_block_t* block) UNIV_NOTHROW
 | 
						|
{
 | 
						|
  const unsigned zip_size= fil_space_t::zip_size(m_space_flags);
 | 
						|
  const unsigned size= zip_size ? zip_size : unsigned(srv_page_size);
 | 
						|
  byte* page_compress_buf= static_cast<byte*>(malloc(get_buf_size()));
 | 
						|
  const bool full_crc32 = fil_space_t::full_crc32(m_space_flags);
 | 
						|
  bool skip_checksum_check = false;
 | 
						|
  ut_ad(!srv_read_only_mode);
 | 
						|
 | 
						|
  if (!page_compress_buf)
 | 
						|
    return DB_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  const bool encrypted= iter.crypt_data != NULL &&
 | 
						|
    iter.crypt_data->should_encrypt();
 | 
						|
  byte* const readptr= iter.io_buffer;
 | 
						|
  block->page.frame= readptr;
 | 
						|
 | 
						|
  if (block->page.zip.data)
 | 
						|
    block->page.zip.data= readptr;
 | 
						|
 | 
						|
  bool page_compressed= false;
 | 
						|
 | 
						|
  dberr_t err= os_file_read(IORequestReadPartial, iter.file, readptr,
 | 
						|
                            3 * size, size, nullptr);
 | 
						|
  if (err != DB_SUCCESS)
 | 
						|
  {
 | 
						|
    ib::error() << iter.filepath << ": os_file_read() failed";
 | 
						|
    goto func_exit;
 | 
						|
  }
 | 
						|
 | 
						|
  if (page_get_page_no(readptr) != 3)
 | 
						|
  {
 | 
						|
page_corrupted:
 | 
						|
    ib::warn() << filename() << ": Page 3 at offset "
 | 
						|
               << 3 * size << " looks corrupted.";
 | 
						|
    err= DB_CORRUPTION;
 | 
						|
    goto func_exit;
 | 
						|
  }
 | 
						|
 | 
						|
  block->page.id_.set_page_no(3);
 | 
						|
  if (full_crc32 && fil_space_t::is_compressed(m_space_flags))
 | 
						|
    page_compressed= buf_page_is_compressed(readptr, m_space_flags);
 | 
						|
  else
 | 
						|
  {
 | 
						|
    switch (fil_page_get_type(readptr)) {
 | 
						|
    case FIL_PAGE_PAGE_COMPRESSED:
 | 
						|
    case FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED:
 | 
						|
      if (block->page.zip.data)
 | 
						|
        goto page_corrupted;
 | 
						|
      page_compressed= true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (encrypted)
 | 
						|
  {
 | 
						|
    if (!buf_page_verify_crypt_checksum(readptr, m_space_flags))
 | 
						|
      goto page_corrupted;
 | 
						|
 | 
						|
    dberr_t err= fil_space_decrypt(get_space_id(), m_space_flags,
 | 
						|
                                   iter.crypt_data, iter.crypt_tmp_buffer,
 | 
						|
                                   size, readptr);
 | 
						|
 | 
						|
    memcpy_aligned<CPU_LEVEL1_DCACHE_LINESIZE>(readptr, iter.crypt_tmp_buffer,
 | 
						|
                                               size);
 | 
						|
 | 
						|
    if (err)
 | 
						|
      goto func_exit;
 | 
						|
  }
 | 
						|
 | 
						|
  /* For full_crc32 format, skip checksum check
 | 
						|
  after decryption. */
 | 
						|
  skip_checksum_check= full_crc32 && encrypted;
 | 
						|
 | 
						|
  if (page_compressed)
 | 
						|
  {
 | 
						|
    ulint compress_length= fil_page_decompress(page_compress_buf,
 | 
						|
                                               readptr,
 | 
						|
                                               m_space_flags);
 | 
						|
    ut_ad(compress_length != srv_page_size);
 | 
						|
    if (compress_length == 0)
 | 
						|
      goto page_corrupted;
 | 
						|
  }
 | 
						|
  else if (!skip_checksum_check
 | 
						|
           && buf_page_is_corrupted(false, readptr, m_space_flags))
 | 
						|
    goto page_corrupted;
 | 
						|
 | 
						|
  /* 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;
 | 
						|
}
 | 
						|
 | 
						|
static dberr_t fil_iterate(
 | 
						|
	const fil_iterator_t&	iter,
 | 
						|
	buf_block_t*		block,
 | 
						|
	AbstractCallback&	callback)
 | 
						|
{
 | 
						|
	os_offset_t		offset;
 | 
						|
	const ulint		size = callback.physical_size();
 | 
						|
	ulint			n_bytes = iter.n_io_buffers * size;
 | 
						|
 | 
						|
	byte* page_compress_buf= static_cast<byte*>(malloc(get_buf_size()));
 | 
						|
	ut_ad(!srv_read_only_mode);
 | 
						|
 | 
						|
	if (!page_compress_buf) {
 | 
						|
		return DB_OUT_OF_MEMORY;
 | 
						|
	}
 | 
						|
 | 
						|
	uint32_t actual_space_id = 0;
 | 
						|
	const bool full_crc32 = fil_space_t::full_crc32(
 | 
						|
		callback.get_space_flags());
 | 
						|
 | 
						|
	/* TODO: For ROW_FORMAT=COMPRESSED tables we do a lot of useless
 | 
						|
	copying for non-index pages. Unfortunately, it is
 | 
						|
	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;
 | 
						|
		}
 | 
						|
 | 
						|
		byte*		io_buffer = iter.io_buffer;
 | 
						|
		block->page.frame = io_buffer;
 | 
						|
 | 
						|
		if (block->page.zip.data) {
 | 
						|
			/* Zip IO is done in the compressed page buffer. */
 | 
						|
			io_buffer = block->page.zip.data;
 | 
						|
		}
 | 
						|
 | 
						|
		/* We have to read the exact number of bytes. Otherwise the
 | 
						|
		InnoDB IO functions croak on failed reads. */
 | 
						|
 | 
						|
		n_bytes = ulint(ut_min(os_offset_t(n_bytes),
 | 
						|
				       iter.end - offset));
 | 
						|
 | 
						|
		ut_ad(n_bytes > 0);
 | 
						|
		ut_ad(!(n_bytes % size));
 | 
						|
 | 
						|
		const bool encrypted = iter.crypt_data != NULL
 | 
						|
			&& iter.crypt_data->should_encrypt();
 | 
						|
		/* Use additional crypt io buffer if tablespace is encrypted */
 | 
						|
		byte* const readptr = encrypted
 | 
						|
			? iter.crypt_io_buffer : io_buffer;
 | 
						|
		byte* const writeptr = readptr;
 | 
						|
 | 
						|
		err = os_file_read(IORequestReadPartial, iter.file, readptr,
 | 
						|
				   offset, n_bytes, nullptr);
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
			ib::error() << iter.filepath
 | 
						|
				    << ": os_file_read() failed";
 | 
						|
			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;
 | 
						|
			}
 | 
						|
 | 
						|
			if (page_no != block->page.id().page_no()) {
 | 
						|
page_corrupted:
 | 
						|
				ib::warn() << callback.filename()
 | 
						|
					   << ": Page " << (offset / size)
 | 
						|
					   << " at offset " << offset
 | 
						|
					   << " looks corrupted.";
 | 
						|
				err = DB_CORRUPTION;
 | 
						|
				goto func_exit;
 | 
						|
			}
 | 
						|
 | 
						|
			if (block->page.id().page_no() == 0) {
 | 
						|
				actual_space_id = mach_read_from_4(
 | 
						|
					src + FIL_PAGE_SPACE_ID);
 | 
						|
			}
 | 
						|
 | 
						|
			const uint16_t type = fil_page_get_type(src);
 | 
						|
			page_compressed =
 | 
						|
				(full_crc32
 | 
						|
				 && fil_space_t::is_compressed(
 | 
						|
					callback.get_space_flags())
 | 
						|
				 && buf_page_is_compressed(
 | 
						|
					src, callback.get_space_flags()))
 | 
						|
				|| type == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
 | 
						|
				|| type == FIL_PAGE_PAGE_COMPRESSED;
 | 
						|
 | 
						|
			if (page_compressed && block->page.zip.data) {
 | 
						|
				goto page_corrupted;
 | 
						|
			}
 | 
						|
 | 
						|
			bool decrypted = false;
 | 
						|
			byte* dst = io_buffer + i * size;
 | 
						|
			bool frame_changed = false;
 | 
						|
			uint key_version = buf_page_get_key_version(
 | 
						|
				src, callback.get_space_flags());
 | 
						|
 | 
						|
			if (!encrypted) {
 | 
						|
			} else if (!key_version) {
 | 
						|
				if (block->page.id().page_no() == 0
 | 
						|
				    && block->page.zip.data) {
 | 
						|
					block->page.zip.data = src;
 | 
						|
					frame_changed = true;
 | 
						|
				} else if (!page_compressed
 | 
						|
					   && type != FIL_PAGE_TYPE_XDES
 | 
						|
					   && !block->page.zip.data) {
 | 
						|
					block->page.frame = src;
 | 
						|
					frame_changed = true;
 | 
						|
				} else {
 | 
						|
					ut_ad(dst != src);
 | 
						|
					memcpy(dst, src, size);
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if (!buf_page_verify_crypt_checksum(
 | 
						|
					src, callback.get_space_flags())) {
 | 
						|
					goto page_corrupted;
 | 
						|
				}
 | 
						|
 | 
						|
				if ((err = fil_space_decrypt(
 | 
						|
					actual_space_id,
 | 
						|
					callback.get_space_flags(),
 | 
						|
					iter.crypt_data, dst,
 | 
						|
					callback.physical_size(),
 | 
						|
					src))) {
 | 
						|
					goto func_exit;
 | 
						|
				}
 | 
						|
 | 
						|
				decrypted = true;
 | 
						|
				updated = true;
 | 
						|
			}
 | 
						|
 | 
						|
			/* For full_crc32 format, skip checksum check
 | 
						|
			after decryption. */
 | 
						|
			bool skip_checksum_check = full_crc32 && encrypted;
 | 
						|
 | 
						|
			/* If the original page is page_compressed, we need
 | 
						|
			to decompress it before adjusting further. */
 | 
						|
			if (page_compressed) {
 | 
						|
				ulint compress_length = fil_page_decompress(
 | 
						|
					page_compress_buf, dst,
 | 
						|
					callback.get_space_flags());
 | 
						|
				ut_ad(compress_length != srv_page_size);
 | 
						|
				if (compress_length == 0) {
 | 
						|
					goto page_corrupted;
 | 
						|
				}
 | 
						|
				updated = true;
 | 
						|
			} else if (!skip_checksum_check
 | 
						|
				   && buf_page_is_corrupted(
 | 
						|
					   false,
 | 
						|
					   encrypted && !frame_changed
 | 
						|
					   ? dst : src,
 | 
						|
					   callback.get_space_flags())) {
 | 
						|
				goto page_corrupted;
 | 
						|
			}
 | 
						|
 | 
						|
			if ((err = callback(block)) != DB_SUCCESS) {
 | 
						|
				goto func_exit;
 | 
						|
			} else if (!updated) {
 | 
						|
				updated = !!block->page.frame;
 | 
						|
			}
 | 
						|
 | 
						|
			/* If tablespace is encrypted we use additional
 | 
						|
			temporary scratch area where pages are read
 | 
						|
			for decrypting readptr == crypt_io_buffer != io_buffer.
 | 
						|
 | 
						|
			Destination for decryption is a buffer pool block
 | 
						|
			block->page.frame == dst == io_buffer that is updated.
 | 
						|
			Pages that did not require decryption even when
 | 
						|
			tablespace is marked as encrypted are not copied
 | 
						|
			instead block->page.frame is set to src == readptr.
 | 
						|
 | 
						|
			For encryption we again use temporary scratch area
 | 
						|
			writeptr != io_buffer == dst
 | 
						|
			that is then written to the tablespace
 | 
						|
 | 
						|
			(1) For normal tables io_buffer == dst == writeptr
 | 
						|
			(2) For only page compressed tables
 | 
						|
			io_buffer == dst == writeptr
 | 
						|
			(3) For encrypted (and page compressed)
 | 
						|
			readptr != io_buffer == dst != writeptr
 | 
						|
			*/
 | 
						|
 | 
						|
			ut_ad(!encrypted && !page_compressed ?
 | 
						|
			      src == dst && dst == writeptr + (i * size):1);
 | 
						|
			ut_ad(page_compressed && !encrypted ?
 | 
						|
			      src == dst && dst == writeptr + (i * size):1);
 | 
						|
			ut_ad(encrypted ?
 | 
						|
			      src != dst && dst != writeptr + (i * size):1);
 | 
						|
 | 
						|
			/* When tablespace is encrypted or compressed its
 | 
						|
			first page (i.e. page 0) is not encrypted or
 | 
						|
			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;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			src =  io_buffer + (i * size);
 | 
						|
 | 
						|
			if (page_compressed) {
 | 
						|
				updated = true;
 | 
						|
				if (ulint len = fil_page_compress(
 | 
						|
					    src,
 | 
						|
					    page_compress_buf,
 | 
						|
					    callback.get_space_flags(),
 | 
						|
					    512,/* FIXME: proper block size */
 | 
						|
					    encrypted)) {
 | 
						|
					/* FIXME: remove memcpy() */
 | 
						|
					memcpy(src, page_compress_buf, len);
 | 
						|
					memset(src + len, 0,
 | 
						|
					       srv_page_size - len);
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			/* Encrypt the page if encryption was used. */
 | 
						|
			if (encrypted && decrypted) {
 | 
						|
				byte *dest = writeptr + i * size;
 | 
						|
 | 
						|
				byte* tmp = fil_encrypt_buf(
 | 
						|
					iter.crypt_data,
 | 
						|
					block->page.id().space(),
 | 
						|
					block->page.id().page_no(),
 | 
						|
					src, block->zip_size(), dest,
 | 
						|
					full_crc32);
 | 
						|
 | 
						|
				if (tmp == src) {
 | 
						|
					/* TODO: remove unnecessary memcpy's */
 | 
						|
					ut_ad(dest != src);
 | 
						|
					memcpy(dest, src, size);
 | 
						|
				}
 | 
						|
 | 
						|
				updated = true;
 | 
						|
			}
 | 
						|
 | 
						|
			/* Write checksum for the compressed full crc32 page.*/
 | 
						|
			if (full_crc32 && page_compressed) {
 | 
						|
				ut_ad(updated);
 | 
						|
				byte* dest = writeptr + i * size;
 | 
						|
				ut_d(bool comp = false);
 | 
						|
				ut_d(bool corrupt = false);
 | 
						|
				ulint size = buf_page_full_crc32_size(
 | 
						|
					dest,
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
					&comp, &corrupt
 | 
						|
#else
 | 
						|
					NULL, NULL
 | 
						|
#endif
 | 
						|
				);
 | 
						|
				ut_ad(!comp == (size == srv_page_size));
 | 
						|
				ut_ad(!corrupt);
 | 
						|
				mach_write_to_4(dest + (size - 4),
 | 
						|
						my_crc32c(0, dest, size - 4));
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (page_compressed && punch_hole) {
 | 
						|
			err = fil_import_compress_fwrite(
 | 
						|
				iter, full_crc32, offset, writeptr, n_bytes,
 | 
						|
				!updated);
 | 
						|
 | 
						|
			if (err != DB_SUCCESS) {
 | 
						|
				punch_hole = false;
 | 
						|
				if (updated) {
 | 
						|
					goto normal_write;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else if (updated) {
 | 
						|
normal_write:
 | 
						|
			/* A page was updated in the set, write it back. */
 | 
						|
			err = os_file_write(IORequestWrite,
 | 
						|
					    iter.filepath, iter.file,
 | 
						|
					    writeptr, offset, n_bytes);
 | 
						|
 | 
						|
			if (err != DB_SUCCESS) {
 | 
						|
				goto func_exit;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
func_exit:
 | 
						|
	free(page_compress_buf);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Iterate over all or some pages in the tablespace.
 | 
						|
@param dir_path      the path to data dir storing the tablespace
 | 
						|
@param name          the table name
 | 
						|
@param n_io_buffers  number of blocks to read and write together
 | 
						|
@param callback      functor that will do the page queries or updates
 | 
						|
@return	DB_SUCCESS or error code */
 | 
						|
static
 | 
						|
dberr_t
 | 
						|
fil_tablespace_iterate(
 | 
						|
/*===================*/
 | 
						|
	const char *name,
 | 
						|
	ulint n_io_buffers,
 | 
						|
	AbstractCallback &callback,
 | 
						|
	const char *dir_path)
 | 
						|
{
 | 
						|
	dberr_t		err;
 | 
						|
	pfs_os_file_t	file;
 | 
						|
	char*		filepath;
 | 
						|
 | 
						|
	ut_a(n_io_buffers > 0);
 | 
						|
	ut_ad(!srv_read_only_mode);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_trigger_corruption_1",
 | 
						|
			return(DB_CORRUPTION););
 | 
						|
 | 
						|
	table_name_t table_name(const_cast<char*>(name));
 | 
						|
	filepath= fil_make_filepath(dir_path, table_name, IBD,
 | 
						|
				    dir_path != nullptr);
 | 
						|
	if (!filepath) {
 | 
						|
		return(DB_OUT_OF_MEMORY);
 | 
						|
	} else {
 | 
						|
		bool	success;
 | 
						|
 | 
						|
		file = os_file_create_simple_no_error_handling(
 | 
						|
			innodb_data_file_key, filepath,
 | 
						|
			OS_FILE_OPEN, OS_FILE_READ_WRITE, false, &success);
 | 
						|
 | 
						|
		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;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	callback.set_file(filepath, file);
 | 
						|
 | 
						|
	os_offset_t	file_size = os_file_get_size(file);
 | 
						|
	ut_a(file_size != (os_offset_t) -1);
 | 
						|
 | 
						|
	/* Allocate a page to read in the tablespace header, so that we
 | 
						|
	can determine the page size and zip_size (if it is compressed).
 | 
						|
	We allocate an extra page in case it is a compressed table. */
 | 
						|
 | 
						|
	byte*	page = static_cast<byte*>(aligned_malloc(2 * srv_page_size,
 | 
						|
							 srv_page_size));
 | 
						|
 | 
						|
	buf_block_t* block = reinterpret_cast<buf_block_t*>
 | 
						|
		(ut_zalloc_nokey(sizeof *block));
 | 
						|
	block->page.frame = page;
 | 
						|
	block->page.init(buf_page_t::UNFIXED + 1, page_id_t{~0ULL});
 | 
						|
 | 
						|
	/* Read the first page and determine the page size. */
 | 
						|
 | 
						|
	err = os_file_read(IORequestReadPartial, file, page, 0, srv_page_size,
 | 
						|
			   nullptr);
 | 
						|
 | 
						|
	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;
 | 
						|
		}
 | 
						|
 | 
						|
		fil_iterator_t	iter;
 | 
						|
 | 
						|
		/* read (optional) crypt data */
 | 
						|
		iter.crypt_data = fil_space_read_crypt_data(
 | 
						|
			callback.get_zip_size(), page);
 | 
						|
 | 
						|
		/* 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;
 | 
						|
		}
 | 
						|
 | 
						|
		iter.file = file;
 | 
						|
		iter.start = 0;
 | 
						|
		iter.end = file_size;
 | 
						|
		iter.filepath = filepath;
 | 
						|
		iter.file_size = file_size;
 | 
						|
		iter.n_io_buffers = n_io_buffers;
 | 
						|
 | 
						|
		size_t buf_size = (1 + iter.n_io_buffers) * srv_page_size;
 | 
						|
 | 
						|
		/* Add an extra page for compressed page scratch area. */
 | 
						|
		iter.io_buffer = static_cast<byte*>(
 | 
						|
			aligned_malloc(buf_size, srv_page_size));
 | 
						|
 | 
						|
		if (iter.crypt_data) {
 | 
						|
			iter.crypt_io_buffer = static_cast<byte *>(
 | 
						|
				aligned_malloc(buf_size, srv_page_size));
 | 
						|
			iter.crypt_tmp_buffer = static_cast<byte *>(
 | 
						|
				aligned_malloc(buf_size, CPU_LEVEL1_DCACHE_LINESIZE));
 | 
						|
		} else {
 | 
						|
			iter.crypt_io_buffer = NULL;
 | 
						|
			iter.crypt_tmp_buffer = NULL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (block->page.zip.ssize) {
 | 
						|
			ut_ad(iter.n_io_buffers == 1);
 | 
						|
			block->page.frame = iter.io_buffer;
 | 
						|
			block->page.zip.data = block->page.frame
 | 
						|
				+ srv_page_size;
 | 
						|
		}
 | 
						|
 | 
						|
		err = callback.run(iter, block);
 | 
						|
 | 
						|
		if (iter.crypt_data) {
 | 
						|
			fil_space_destroy_crypt_data(&iter.crypt_data);
 | 
						|
		}
 | 
						|
 | 
						|
		aligned_free(iter.crypt_tmp_buffer);
 | 
						|
		aligned_free(iter.crypt_io_buffer);
 | 
						|
		aligned_free(iter.io_buffer);
 | 
						|
	}
 | 
						|
 | 
						|
	if (err == DB_SUCCESS) {
 | 
						|
		ib::info() << "Sync to disk";
 | 
						|
 | 
						|
		if (!os_file_flush(file)) {
 | 
						|
			ib::info() << "os_file_flush() failed!";
 | 
						|
			err = DB_IO_ERROR;
 | 
						|
		} else {
 | 
						|
			ib::info() << "Sync to disk - done!";
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	os_file_close(file);
 | 
						|
 | 
						|
	aligned_free(page);
 | 
						|
	ut_free(filepath);
 | 
						|
	ut_free(block);
 | 
						|
 | 
						|
	return(err);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Iterate over all or some pages in the tablespace.
 | 
						|
@param table         the table definiton in the server
 | 
						|
@param n_io_buffers  number of blocks to read and write together
 | 
						|
@param callback      functor that will do the page queries or updates
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
static dberr_t fil_tablespace_iterate(dict_table_t *table, ulint n_io_buffers,
 | 
						|
                                      AbstractCallback &callback)
 | 
						|
{
 | 
						|
  /* Make sure the data_dir_path is set. */
 | 
						|
  dict_get_and_save_data_dir_path(table);
 | 
						|
  ut_ad(!DICT_TF_HAS_DATA_DIR(table->flags) || table->data_dir_path);
 | 
						|
  const char *data_dir_path= DICT_TF_HAS_DATA_DIR(table->flags)
 | 
						|
    ? table->data_dir_path : nullptr;
 | 
						|
  return fil_tablespace_iterate(table->name.m_name, n_io_buffers, callback,
 | 
						|
                                data_dir_path);
 | 
						|
}
 | 
						|
 | 
						|
static void row_import_autoinc(dict_table_t *table, row_prebuilt_t *prebuilt,
 | 
						|
                               uint64_t autoinc)
 | 
						|
{
 | 
						|
  if (!table->persistent_autoinc)
 | 
						|
  {
 | 
						|
    ut_ad(!autoinc);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (autoinc)
 | 
						|
  {
 | 
						|
    btr_write_autoinc(dict_table_get_first_index(table), autoinc - 1);
 | 
						|
  autoinc_set:
 | 
						|
    table->autoinc= autoinc;
 | 
						|
    sql_print_information("InnoDB: %.*sQ.%sQ autoinc value set to " UINT64PF,
 | 
						|
                          int(table->name.dblen()), table->name.m_name,
 | 
						|
                          table->name.basename(), autoinc);
 | 
						|
  }
 | 
						|
  else if (TABLE *t= prebuilt->m_mysql_table)
 | 
						|
  {
 | 
						|
    if (const Field *ai= t->found_next_number_field)
 | 
						|
    {
 | 
						|
      autoinc= 1 +
 | 
						|
        btr_read_autoinc_with_fallback(table, innodb_col_no(ai),
 | 
						|
                                       t->s->mysql_version,
 | 
						|
                                       innobase_get_int_col_max_value(ai));
 | 
						|
      goto autoinc_set;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/** Update the virtual column position in SYS_COLUMNS and SYS_VIRTUAL
 | 
						|
@param  table_id   table identifier
 | 
						|
@param  new_pos    position value
 | 
						|
@param  trx        transaction
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
dberr_t update_vcol_pos(table_id_t table_id, ulint new_pos, trx_t *trx)
 | 
						|
{
 | 
						|
  pars_info_t *info= pars_info_create();
 | 
						|
  pars_info_add_ull_literal(info, "id", table_id);
 | 
						|
  pars_info_add_int4_literal(info, "old_pos", new_pos - 1);
 | 
						|
  DBUG_EXECUTE_IF("ib_import_vcol_update_fail",
 | 
						|
                  return DB_DUPLICATE_KEY;);
 | 
						|
  return que_eval_sql(info,
 | 
						|
                      "PROCEDURE UPDATE_VCOL () IS\n"
 | 
						|
                      "BEGIN\n"
 | 
						|
		      "UPDATE SYS_COLUMNS SET POS = POS + 1 "
 | 
						|
		      "WHERE TABLE_ID= :id AND POS = :old_pos;\n"
 | 
						|
		      "UPDATE SYS_VIRTUAL SET POS = POS + 1 "
 | 
						|
		      "WHERE TABLE_ID= :id AND POS = :old_pos;\n"
 | 
						|
		      "END\n;", trx);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
1) Update the position of the columns and
 | 
						|
2) Insert the hidden fts doc id in the sys columns table
 | 
						|
3) Insert the hidden fts doc id in the sys indexes and
 | 
						|
sys_fields table
 | 
						|
@param  table   table to be imported
 | 
						|
@param  fts_pos position of fts doc id column
 | 
						|
@param  trx     transaction
 | 
						|
@return DB_SUCCESS or error code */
 | 
						|
static
 | 
						|
dberr_t innodb_insert_hidden_fts_col(dict_table_t* table,
 | 
						|
                                     ulint  fts_pos,
 | 
						|
                                     trx_t* trx)
 | 
						|
{
 | 
						|
  dict_index_t* fts_idx=
 | 
						|
    dict_table_get_index_on_name(table, FTS_DOC_ID_INDEX.str);
 | 
						|
  if (!fts_idx) return DB_ERROR;
 | 
						|
  for (ulint new_i= 0; new_i < table->n_v_cols; new_i++)
 | 
						|
  {
 | 
						|
    ulint pos= dict_create_v_col_pos(
 | 
						|
                 table->v_cols[new_i].v_pos,
 | 
						|
		 table->v_cols[new_i].m_col.ind);
 | 
						|
    if (dberr_t err= update_vcol_pos(table->id, pos, trx))
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
  pars_info_t *info= pars_info_create();
 | 
						|
  pars_info_add_ull_literal(info, "id", table->id);
 | 
						|
  dict_hdr_get_new_id(NULL, &fts_idx->id, NULL);
 | 
						|
  pars_info_add_ull_literal(info, "idx_id", fts_idx->id);
 | 
						|
  pars_info_add_int4_literal(info, "pos", fts_pos);
 | 
						|
  pars_info_add_int4_literal(info, "space", fts_idx->table->space_id);
 | 
						|
  pars_info_add_int4_literal(info, "page_no", fts_idx->page);
 | 
						|
 | 
						|
  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);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
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(!table->is_temporary());
 | 
						|
 | 
						|
	ut_ad(table->space_id);
 | 
						|
	ut_ad(table->space_id < SRV_SPACE_ID_UPPER_BOUND);
 | 
						|
	ut_ad(trx);
 | 
						|
	ut_ad(trx->state == TRX_STATE_ACTIVE);
 | 
						|
	ut_ad(!table->is_readable());
 | 
						|
	ut_ad(prebuilt->table == table);
 | 
						|
 | 
						|
#ifdef BTR_CUR_HASH_ADAPT
 | 
						|
	/* On DISCARD TABLESPACE, we did not drop any adaptive hash
 | 
						|
	index entries. If we replaced the discarded tablespace with a
 | 
						|
	smaller one here, there could still be some adaptive hash
 | 
						|
	index entries that point to cached garbage pages in the buffer
 | 
						|
	pool, because PageConverter::operator() only evicted those
 | 
						|
	pages that were replaced by the imported pages. We must
 | 
						|
	detach any remaining adaptive hash index entries, because the
 | 
						|
	adaptive hash index must be a subset of the table contents;
 | 
						|
	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;
 | 
						|
		mtr.start();
 | 
						|
		trx_undo_assign(trx, &err, &mtr);
 | 
						|
		mtr.commit();
 | 
						|
	}
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_undo_assign_failure",
 | 
						|
			err = DB_TOO_MANY_CONCURRENT_TRXS;);
 | 
						|
 | 
						|
	if (err == DB_SUCCESS && !trx->has_logged_persistent()) {
 | 
						|
		err = DB_TOO_MANY_CONCURRENT_TRXS;
 | 
						|
	}
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		return row_import_cleanup(prebuilt, err);
 | 
						|
	}
 | 
						|
 | 
						|
	trx->op_info = "read meta-data file";
 | 
						|
 | 
						|
	row_import	cfg;
 | 
						|
	THD* thd = trx->mysql_thd;
 | 
						|
	err = row_import_read_cfg(table, thd, cfg);
 | 
						|
 | 
						|
	/* Check if the table column definitions match the contents
 | 
						|
	of the config file. */
 | 
						|
 | 
						|
	if (err == DB_SUCCESS) {
 | 
						|
 | 
						|
		if (cfg.need_hidden_fts(table)) {
 | 
						|
			cfg.m_table = table = build_fts_hidden_table(
 | 
						|
						table, cfg);
 | 
						|
		}
 | 
						|
 | 
						|
		err = handle_instant_metadata(table, cfg);
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
import_error:
 | 
						|
			return row_import_error(
 | 
						|
					prebuilt, err, table);
 | 
						|
		}
 | 
						|
 | 
						|
		/* We have a schema file, try and match it with our
 | 
						|
		data dictionary. */
 | 
						|
 | 
						|
		err = cfg.match_schema(thd);
 | 
						|
 | 
						|
		/* Update index->page and SYS_INDEXES.PAGE_NO to match the
 | 
						|
		B-tree root page numbers in the tablespace. Use the index
 | 
						|
		name from the .cfg file to find match. */
 | 
						|
 | 
						|
		if (err == DB_SUCCESS) {
 | 
						|
			cfg.set_root_by_name();
 | 
						|
			autoinc = cfg.m_autoinc;
 | 
						|
		}
 | 
						|
 | 
						|
		DBUG_EXECUTE_IF("ib_import_set_index_root_failure",
 | 
						|
				err = DB_TOO_MANY_CONCURRENT_TRXS;);
 | 
						|
 | 
						|
	} else if (cfg.m_missing) {
 | 
						|
		/* We don't have a schema file, we will have to discover
 | 
						|
		the index root pages from the .ibd file and skip the schema
 | 
						|
		matching step. */
 | 
						|
 | 
						|
		ut_a(err == DB_FAIL);
 | 
						|
 | 
						|
		cfg.m_zip_size = 0;
 | 
						|
 | 
						|
		if (UT_LIST_GET_LEN(table->indexes) > 1) {
 | 
						|
			ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
				ER_INTERNAL_ERROR,
 | 
						|
				"Drop all secondary indexes before importing "
 | 
						|
				"table %s when .cfg file is missing.",
 | 
						|
				table->name.m_name);
 | 
						|
			err = DB_ERROR;
 | 
						|
			goto import_error;
 | 
						|
		}
 | 
						|
 | 
						|
		FetchIndexRootPages	fetchIndexRootPages(table, trx);
 | 
						|
 | 
						|
		err = fil_tablespace_iterate(
 | 
						|
			table, IO_BUFFER_SIZE(srv_page_size),
 | 
						|
			fetchIndexRootPages);
 | 
						|
 | 
						|
		if (err == DB_SUCCESS) {
 | 
						|
 | 
						|
			err = fetchIndexRootPages.build_row_import(&cfg);
 | 
						|
 | 
						|
			/* Update index->page and SYS_INDEXES.PAGE_NO
 | 
						|
			to match the B-tree root page numbers in the
 | 
						|
			tablespace. */
 | 
						|
 | 
						|
			if (err == DB_SUCCESS) {
 | 
						|
				err = cfg.set_root_by_heuristic();
 | 
						|
 | 
						|
				if (err == DB_SUCCESS) {
 | 
						|
					err = handle_instant_metadata(table,
 | 
						|
								      cfg);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	trx->op_info = "importing tablespace";
 | 
						|
 | 
						|
	ib::info() << "Phase I - Update all pages";
 | 
						|
 | 
						|
	/* Iterate over all the pages and do the sanity checking and
 | 
						|
	the conversion required to import the tablespace. */
 | 
						|
 | 
						|
	PageConverter	converter(&cfg, table->space_id, trx);
 | 
						|
 | 
						|
	/* Set the IO buffer size in pages. */
 | 
						|
 | 
						|
	err = fil_tablespace_iterate(
 | 
						|
		table, IO_BUFFER_SIZE(cfg.m_zip_size ? cfg.m_zip_size
 | 
						|
				      : srv_page_size), converter);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_reset_space_and_lsn_failure",
 | 
						|
			err = DB_TOO_MANY_CONCURRENT_TRXS;);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		char	table_name[MAX_FULL_NAME_LEN + 1];
 | 
						|
 | 
						|
		innobase_format_name(
 | 
						|
			table_name, sizeof(table_name),
 | 
						|
			table->name.m_name);
 | 
						|
 | 
						|
		if (err != DB_DECRYPTION_FAILED) {
 | 
						|
 | 
						|
			ib_errf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
				ER_INTERNAL_ERROR,
 | 
						|
			"Error importing tablespace for table %s : %s",
 | 
						|
				table_name, ut_strerr(err));
 | 
						|
		}
 | 
						|
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If the table is stored in a remote tablespace, we need to
 | 
						|
	determine that filepath from the link file and system tables.
 | 
						|
	Find the space ID in SYS_TABLES since this is an ALTER TABLE. */
 | 
						|
	dict_get_and_save_data_dir_path(table);
 | 
						|
 | 
						|
	ut_ad(!DICT_TF_HAS_DATA_DIR(table->flags) || table->data_dir_path);
 | 
						|
	const char *data_dir_path = DICT_TF_HAS_DATA_DIR(table->flags)
 | 
						|
		? table->data_dir_path : nullptr;
 | 
						|
	fil_space_t::name_type name{
 | 
						|
		table->name.m_name, strlen(table->name.m_name)};
 | 
						|
 | 
						|
	filepath = fil_make_filepath(data_dir_path, name, IBD,
 | 
						|
				     data_dir_path != nullptr);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_import_OOM_15",
 | 
						|
		ut_free(filepath);
 | 
						|
		filepath = NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	if (filepath == NULL) {
 | 
						|
		return row_import_cleanup(prebuilt, DB_OUT_OF_MEMORY);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Open the tablespace so that we can access via the buffer pool.
 | 
						|
	The tablespace is initially opened as a temporary one, because
 | 
						|
	we will not be writing any redo log for it before we have invoked
 | 
						|
	fil_space_t::set_imported() to declare it a persistent tablespace. */
 | 
						|
 | 
						|
	table->space = fil_ibd_open(table->space_id,
 | 
						|
				    dict_tf_to_fsp_flags(table->flags),
 | 
						|
				    fil_space_t::VALIDATE_IMPORT,
 | 
						|
				    name, filepath, &err);
 | 
						|
 | 
						|
	ut_ad((table->space == NULL) == (err != DB_SUCCESS));
 | 
						|
	DBUG_EXECUTE_IF("ib_import_open_tablespace_failure",
 | 
						|
			err = DB_TABLESPACE_NOT_FOUND; table->space = NULL;);
 | 
						|
 | 
						|
	if (!table->space) {
 | 
						|
		ib_senderrf(thd, IB_LOG_LEVEL_ERROR,
 | 
						|
			ER_GET_ERRMSG,
 | 
						|
			err, ut_strerr(err), filepath);
 | 
						|
	}
 | 
						|
 | 
						|
	ut_free(filepath);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		return row_import_cleanup(prebuilt, err);
 | 
						|
	}
 | 
						|
 | 
						|
	/* The first index must always be the clustered index. */
 | 
						|
 | 
						|
	dict_index_t*	index = dict_table_get_first_index(table);
 | 
						|
 | 
						|
	if (!dict_index_is_clust(index)) {
 | 
						|
		err = DB_CORRUPTION;
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Update the Btree segment headers for index node and
 | 
						|
	leaf nodes in the root page. Set the new space id. */
 | 
						|
 | 
						|
	err = btr_root_adjust_on_import(index);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_cluster_root_adjust_failure",
 | 
						|
			err = DB_CORRUPTION;);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	} else if (cfg.requires_purge(index->name)) {
 | 
						|
 | 
						|
		/* Purge any delete-marked records that couldn't be
 | 
						|
		purged during the page conversion phase from the
 | 
						|
		cluster index. */
 | 
						|
 | 
						|
		IndexPurge	purge(trx, index);
 | 
						|
 | 
						|
		trx->op_info = "cluster: purging delete marked records";
 | 
						|
 | 
						|
		err = purge.garbage_collect();
 | 
						|
 | 
						|
		trx->op_info = "";
 | 
						|
	}
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_cluster_failure", err = DB_CORRUPTION;);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* For secondary indexes, purge any records that couldn't be purged
 | 
						|
	during the page conversion phase. */
 | 
						|
 | 
						|
	err = row_import_adjust_root_pages_of_secondary_indexes(
 | 
						|
		trx, table, cfg);
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF("ib_import_sec_root_adjust_failure",
 | 
						|
			err = DB_CORRUPTION;);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	ib::info() << "Phase III - Flush changes to disk";
 | 
						|
 | 
						|
	/* Ensure that all pages dirtied during the IMPORT make it to disk.
 | 
						|
	The only dirty pages generated should be from the pessimistic purge
 | 
						|
	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);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	err = row_import_update_discarded_flag(trx, table->id, false);
 | 
						|
 | 
						|
	if (err != DB_SUCCESS) {
 | 
						|
		goto import_error;
 | 
						|
	}
 | 
						|
 | 
						|
	table->file_unreadable = false;
 | 
						|
	table->flags2 &= ~DICT_TF2_DISCARDED & ((1U << DICT_TF2_BITS) - 1);
 | 
						|
 | 
						|
	/* 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);
 | 
						|
 | 
						|
	return row_import_cleanup(prebuilt, err, table);
 | 
						|
}
 | 
						|
 | 
						|
/** Prepare the create info to create a new stub table for import.
 | 
						|
@param thd          Connection
 | 
						|
@param name         Table name, format: "db/table_name".
 | 
						|
@param create_info  The create info for creating a stub.
 | 
						|
@return ER_ error code
 | 
						|
@retval 0 on success */
 | 
						|
int prepare_create_stub_for_import(THD *thd, const char *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)
 | 
						|
  {
 | 
						|
    const char *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, report error if different, except when cfg reports
 | 
						|
  compact/dynamic and ibd reports not_used (indicating either compact
 | 
						|
  or dynamic but not sure) */
 | 
						|
  const enum 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;
 | 
						|
  }
 | 
						|
  else if (create_info.row_type == ROW_TYPE_NOT_USED)
 | 
						|
    create_info.row_type= ROW_TYPE_DYNAMIC;
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 |