/**************************************************************//**
Checks if the page in the cursor can be merged with given page. If necessary, re-organize the merge_page.
@returntrueif possible to merge. */ static bool
btr_can_merge_with_page( /*====================*/
btr_cur_t* cursor, /*!< in: cursor on the page to merge */
uint32_t page_no, /*!< in: a sibling page */
buf_block_t** merge_block, /*!< out: the merge block */
mtr_t* mtr); /*!< in: mini-transaction */
/** Check a file segment header within a B-tree root page. @paramoffsetfilesegmentheaderoffset @paramblockB-treerootpage @paramspacetablespace
@return whether the segment header is valid */ bool btr_root_fseg_validate(ulint offset, const buf_block_t &block, const fil_space_t &space)
{
ut_ad(block.page.id().space() == space.id); const uint16_t hdr= mach_read_from_2(offset + FSEG_HDR_OFFSET +
block.page.frame); if (FIL_PAGE_DATA <= hdr && hdr <= srv_page_size - FIL_PAGE_DATA_END &&
mach_read_from_4(block.page.frame + offset + FSEG_HDR_SPACE) == space.id) returntrue;
sql_print_error("InnoDB: Index root page " UINT32PF " in %s is corrupted " "at " ULINTPF,
block.page.id().page_no(),
UT_LIST_GET_FIRST(space.chain)->name, offset); returnfalse;
}
/** Report a read failure if it is a decryption failure. @paramerrerrorcode
@param index the index that is being accessed */
ATTRIBUTE_COLD void btr_read_failed(dberr_t err, const dict_index_t &index)
{ if (err == DB_DECRYPTION_FAILED)
innodb_decryption_failed(nullptr, index.table);
}
/**************************************************************//**
Gets the root node of a tree and x- or s-latches it.
@return root page, x- or s-latched */
buf_block_t*
btr_root_block_get( /*===============*/
dict_index_t* index, /*!< in: index tree */
rw_lock_type_t mode, /*!< in: either RW_S_LATCH
or RW_X_LATCH */
mtr_t* mtr, /*!< in: mtr */
dberr_t* err) /*!< out: error code */
{
ut_ad(mode != RW_NO_LATCH);
if (!index->table || !index->table->space)
{
*err= DB_TABLESPACE_NOT_FOUND; return nullptr;
}
/**************************************************************//**
Gets the root node of a tree and sx-latches it for segment access.
@return root page, sx-latched */ static
page_t*
btr_root_get( /*=========*/
dict_index_t* index, /*!< in: index tree */
mtr_t* mtr, /*!< in: mtr */
dberr_t* err) /*!< out: error code */
{ /* Intended to be used for accessing file segment lists.
Concurrent read of other data is allowed. */ if (buf_block_t *root= btr_root_block_get(index, RW_SX_LATCH, mtr, err)) return root->page.frame; return nullptr;
}
/**************************************************************//**
Checks a file segment header within a B-tree root page and updates
the segment header space id.
@returnTRUEif valid */ static bool
btr_root_fseg_adjust_on_import( /*===========================*/
fseg_header_t* seg_header, /*!< in/out: segment header */
page_zip_des_t* page_zip, /*!< in/out: compressed page,
or NULL */
ulint space) /*!< in: tablespace identifier */
{
ulint offset = mach_read_from_2(seg_header + FSEG_HDR_OFFSET);
/**************************************************************//**
Checks and adjusts the root node of a tree during IMPORT TABLESPACE.
@param trx transaction
@param index index tree
@return error code */
dberr_t btr_root_adjust_on_import(trx_t *trx, const dict_index_t *index)
{
dberr_t err;
mtr_t mtr{trx};
page_t* page;
page_zip_des_t* page_zip;
dict_table_t* table = index->table;
/* Check if the page format and table format agree. */ if (page_is_compact_format != dict_table_is_comp(table)) {
err = DB_CORRUPTION;
} else { /* Check that the table flags and the tablespace
flags match. */
uint32_t tf = dict_tf_to_fsp_flags(table->flags);
uint32_t sf = table->space->flags;
sf &= ~FSP_FLAGS_MEM_MASK;
tf &= ~FSP_FLAGS_MEM_MASK; if (fil_space_t::is_flags_equal(tf, sf)
|| fil_space_t::is_flags_equal(sf, tf)) {
mysql_mutex_lock(&fil_system.mutex);
table->space->flags = (table->space->flags
& ~FSP_FLAGS_MEM_MASK)
| (tf & FSP_FLAGS_MEM_MASK);
mysql_mutex_unlock(&fil_system.mutex);
err = DB_SUCCESS;
} else {
err = DB_CORRUPTION;
}
}
} else {
err = DB_SUCCESS;
}
/* Check and adjust the file segment headers, if all OK so far. */ if (err == DB_SUCCESS
&& (!btr_root_fseg_adjust_on_import(
FIL_PAGE_DATA + PAGE_BTR_SEG_LEAF
+ page, page_zip, table->space_id)
|| !btr_root_fseg_adjust_on_import(
FIL_PAGE_DATA + PAGE_BTR_SEG_TOP
+ page, page_zip, table->space_id))) {
err = DB_CORRUPTION;
}
func_exit:
mtr_commit(&mtr);
return(err);
}
/**************************************************************//**
Creates a new index page (not the root, and also not
used in page reorganization). @see btr_page_empty(). */ void
btr_page_create( /*============*/
buf_block_t* block, /*!< in/out: page to be created */
page_zip_des_t* page_zip,/*!< in/out: compressed page, or NULL */
dict_index_t* index, /*!< in: index */
ulint level, /*!< in: the B-tree level of the page */
mtr_t* mtr) /*!< in: mtr */
{
ut_ad(mtr->memo_contains_flagged(block, MTR_MEMO_PAGE_X_FIX));
byte *index_id= my_assume_aligned<2>(PAGE_HEADER + PAGE_INDEX_ID +
block->page.frame);
if (UNIV_LIKELY_NULL(page_zip))
{
mach_write_to_8(index_id, index->id);
page_create_zip(block, index, level, 0, mtr);
} else
{
page_create(block, mtr, dict_table_is_comp(index->table)); if (index->is_spatial())
{
static_assert(((FIL_PAGE_INDEX & 0xff00) | byte(FIL_PAGE_RTREE)) ==
FIL_PAGE_RTREE, "compatibility");
mtr->write<1>(*block, FIL_PAGE_TYPE + 1 + block->page.frame,
byte(FIL_PAGE_RTREE)); if (mach_read_from_8(block->page.frame + FIL_RTREE_SPLIT_SEQ_NUM))
mtr->memset(block, FIL_RTREE_SPLIT_SEQ_NUM, 8, 0);
} /* Set the level of the new index page */
mtr->write<2,mtr_t::MAYBE_NOP>(*block,
my_assume_aligned<2>(PAGE_HEADER +
PAGE_LEVEL +
block->page.frame),
level);
mtr->write<8,mtr_t::MAYBE_NOP>(*block, index_id, index->id);
}
}
/** Fetch an index root page that was already latched in the
mini-transaction. */ static buf_block_t *btr_get_latched_root(const dict_index_t &index, mtr_t *mtr)
{ return mtr->get_already_latched(page_id_t{index.table->space_id, index.page},
MTR_MEMO_PAGE_SX_FIX);
}
/** Fetch an index page that should have been already latched in the
mini-transaction. */ static buf_block_t *
btr_block_reget(mtr_t *mtr, const dict_index_t &index, const page_id_t id, dberr_t *err)
{ if (buf_block_t *block= mtr->get_already_latched(id, MTR_MEMO_PAGE_X_FIX))
{
*err= DB_SUCCESS; return block;
}
static MY_ATTRIBUTE((nonnull, warn_unused_result)) /** Acquire a latch on the index root page for allocating or freeing pages. @paramindexindextree @parammtrmini-transaction @paramerrerrorcode @returnrootpage
@retval nullptr if an error occurred */
buf_block_t *btr_root_block_sx(dict_index_t *index, mtr_t *mtr, dberr_t *err)
{
buf_block_t *root=
mtr->get_already_latched(page_id_t{index->table->space_id, index->page},
MTR_MEMO_PAGE_SX_FIX); if (!root)
{
root= btr_root_block_get(index, RW_SX_LATCH, mtr, err); if (UNIV_UNLIKELY(!root)) return root;
} #ifdef BTR_CUR_HASH_ADAPT
ut_d(elseif (dict_index_t *index= root->index))
ut_ad(!index->freed()); #endif return root;
}
/**************************************************************//**
Allocates a new file page to be used in an index tree. NOTE: we assume
that the caller has made the reservation for free extents!
@retval NULL if no page could be allocated */
MY_ATTRIBUTE((nonnull, warn_unused_result))
buf_block_t*
btr_page_alloc(
dict_index_t* index, /*!< in: index */
uint32_t hint_page_no, /*!< in: hint of a good page */
byte file_direction, /*!< in: direction where a possible
page split is made */
ulint level, /*!< in: level where the page is placed
in the tree */
mtr_t* mtr, /*!< in/out: mini-transaction
for the allocation */
mtr_t* init_mtr, /*!< in/out: mtr or another mini-transactioninwhichthe
page should be initialized. */
dberr_t* err) /*!< out: error code */
{
ut_ad(level < BTR_MAX_NODE_LEVEL);
/* The page was marked free in the allocation bitmap, but it shouldremainexclusivelylatcheduntilmtr_t::commit()oruntilit
is explicitly freed from the mini-transaction. */
ut_ad(mtr->memo_contains_flagged(block, MTR_MEMO_PAGE_X_FIX)); return err;
}
/** Set the child page number in a node pointer record. @param[in,out]blocknon-leafindexpage @param[in,out]recnodepointerrecordinthepage @param[in]offsetsrec_get_offsets(rec) @param[in]page_nochildpagenumber @param[in,out]mtrmini-transaction
Sets the child node file address in a node pointer. */ inlinevoid btr_node_ptr_set_child_page_no(buf_block_t *block,
rec_t *rec, const rec_offs *offsets,
ulint page_no, mtr_t *mtr)
{
ut_ad(rec_offs_validate(rec, NULL, offsets));
ut_ad(!page_rec_is_leaf(rec));
ut_ad(!rec_offs_comp(offsets) || rec_get_node_ptr_flag(rec));
MY_ATTRIBUTE((nonnull(2,3,4), warn_unused_result)) /************************************************************//**
Returns the upper level node pointer to a page. It is assumed that mtr holds
an sx-latch on the tree.
@return rec_get_offsets() of the node pointer record */ static
rec_offs*
btr_page_get_father_node_ptr_for_validate(
rec_offs* offsets,/*!< in: work area for the return value */
mem_heap_t* heap, /*!< in: memory heap to use */
btr_cur_t* cursor, /*!< in: cursor pointing to user record, out:cursoronnodepointerrecord,
its page x-latched */
mtr_t* mtr) /*!< in: mtr */
{ const uint32_t page_no = btr_cur_get_block(cursor)->page.id().page_no();
dict_index_t* index = btr_cur_get_index(cursor);
ut_ad(!dict_index_is_spatial(index));
ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_X_LOCK));
ut_ad(dict_index_get_page(index) != page_no);
ulint i; for (i= 0; i < mtr->get_savepoint(); i++) if (buf_block_t *block= mtr->block_at_savepoint(i)) if (block->page.id().page_no() == p)
{
ut_ad(block->page.lock.have_u_or_x() ||
(!block->page.lock.have_s() && index->lock.have_x()));
uint16_t up_match= 0, low_match= 0;
cursor->page_cur.block= block; if (page_cur_search_with_match(tuple, PAGE_CUR_LE, &up_match,
&low_match, &cursor->page_cur,
nullptr)) return nullptr;
offsets= rec_get_offsets(cursor->page_cur.rec, index, offsets, 0,
ULINT_UNDEFINED, &heap);
p= btr_node_ptr_get_child_page_no(cursor->page_cur.rec, offsets); if (p != page_no)
{ if (btr_page_get_level(block->page.frame) == level) return nullptr;
i= 0; // MDEV-29835 FIXME: require all pages to be latched in order! continue;
}
ut_ad(block->page.lock.have_u_or_x()); if (block->page.lock.have_u_not_x())
{ /* btr_cur_t::search_leaf(BTR_MODIFY_TREE) only U-latches the
root page initially. */
ut_ad(block->page.id().page_no() == index->page);
block->page.lock.u_x_upgrade();
mtr->page_lock_upgrade(*block);
} return offsets;
}
return nullptr;
}
/************************************************************//**
Returns the upper level node pointer to a page. It is assumed that mtr holds
an x-latch on the tree.
@return rec_get_offsets() of the node pointer record
@retval nullptr on corruption */ static
rec_offs*
btr_page_get_father_block( /*======================*/
rec_offs* offsets,/*!< in: work area for the return value */
mem_heap_t* heap, /*!< in: memory heap to use */
mtr_t* mtr, /*!< in: mtr */
btr_cur_t* cursor) /*!< out: cursor on node pointer record,
its page x-latched */
noexcept
{ const page_t *page= btr_cur_get_page(cursor); const rec_t *rec= page_is_comp(page)
? page_rec_next_get<true>(page, page + PAGE_NEW_INFIMUM)
: page_rec_next_get<false>(page, page + PAGE_OLD_INFIMUM); if (UNIV_UNLIKELY(!rec)) return nullptr;
cursor->page_cur.rec= const_cast<rec_t*>(rec); return btr_page_get_parent(offsets, heap, cursor, mtr);
}
/** Seek to the parent page of a B-tree page. @parammtrmini-transaction @paramcursorcursorpointingtothex-latchedparentpage
@return whether the cursor was successfully positioned */ bool btr_page_get_father(mtr_t *mtr, btr_cur_t *cursor) noexcept
{
page_t *page= btr_cur_get_page(cursor); const rec_t *rec= page_is_comp(page)
? page_rec_next_get<true>(page, page + PAGE_NEW_INFIMUM)
: page_rec_next_get<false>(page, page + PAGE_OLD_INFIMUM); if (UNIV_UNLIKELY(!rec)) returnfalse;
cursor->page_cur.rec= const_cast<rec_t*>(rec);
mem_heap_t *heap= mem_heap_create(100); constbool got= btr_page_get_parent(nullptr, heap, cursor, mtr);
mem_heap_free(heap); return got;
}
#ifdef UNIV_DEBUG /** PAGE_INDEX_ID value for freed index B-trees */
constexpr index_id_t BTR_FREED_INDEX_ID = 0; #endif
if (btr_root_fseg_validate(PAGE_HEADER + PAGE_BTR_SEG_TOP, *block, space))
{ /* Free the entire segment in small steps. */
ut_d(mtr->freeing_tree()); while (!fseg_free_step(block, PAGE_HEADER + PAGE_BTR_SEG_TOP, mtr));
}
}
MY_ATTRIBUTE((warn_unused_result)) /** Prepare to free a B-tree. @param[in]page_idpageid @param[in]zip_sizeROW_FORMAT=COMPRESSEDpagesize,or0 @param[in]index_idPAGE_INDEX_IDcontents @param[in,out]mtrmini-transaction @returnrootblock,toinvokebtr_free_but_not_root()andbtr_free_root()
@retval NULL if the page is no longer a matching B-tree page */ static
buf_block_t *btr_free_root_check(const page_id_t page_id, ulint zip_size,
index_id_t index_id, mtr_t *mtr)
{
ut_ad(page_id.space() != SRV_TMP_SPACE_ID);
ut_ad(index_id != BTR_FREED_INDEX_ID);
if (block)
{
btr_search_drop_page_hash_index(block,reinterpret_cast<dict_index_t*>(-1)); if (fil_page_index_page_check(block->page.frame) &&
index_id == btr_page_get_index_id(block->page.frame)) /* This should be a root page. It should not be possible to reassignthesameindex_idforsomeotherindexinthe
tablespace. */
ut_ad(!page_has_siblings(block->page.frame)); else
block= nullptr;
}
if (!fseg_create(space, PAGE_HEADER + PAGE_BTR_SEG_LEAF, mtr,
err, false, block)) { /* Not enough space for new segment, free root
segment before return. */
btr_free_root(block, *space, mtr); return FIL_NULL;
}
ut_ad(!page_has_siblings(block->page.frame));
btr_root_page_init(block, index_id, index, mtr);
/* In the following assertion we test that two records of maximum allowedsizefitontherootpage:thisfactisneededtoensure
correctness of split algorithms */
/** Clear the index tree and reinitialize the root page, in the rollbackofTRX_UNDO_EMPTY.TheBTR_SEG_LEAFisfreedandreinitialized. @paramthrquerythread
@return error code */
dberr_t dict_index_t::clear(que_thr_t *thr)
{
mtr_t mtr{thr_get_trx(thr)};
mtr.start(); if (table->is_temporary())
mtr.set_log_mode(MTR_LOG_NO_REDO); else
set_modified(mtr);
mtr_sx_lock_index(this, &mtr);
/** Read the last used AUTO_INCREMENT value from PAGE_ROOT_AUTO_INC. @param[in,out]indexclusteredindex @returnthelastusedAUTO_INCREMENTvalue
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
ib_uint64_t
btr_read_autoinc(dict_index_t* index)
{
ut_ad(index->is_primary());
ut_ad(index->table->persistent_autoinc);
ut_ad(!index->table->is_temporary());
mtr_t mtr{nullptr};
mtr.start();
dberr_t err;
uint64_t autoinc; if (buf_block_t *root= btr_root_block_get(index, RW_S_LATCH, &mtr, &err))
autoinc= page_get_autoinc(root->page.frame); else
autoinc= 0;
mtr.commit(); return autoinc;
}
while (index && (index->fields[0].col != &col || index->is_corrupted()))
index= dict_table_get_next_index(index);
return index;
}
/** Read the last used AUTO_INCREMENT value from PAGE_ROOT_AUTO_INC, orfallbacktoMAX(auto_increment_column). @paramtabletablecontaininganAUTO_INCREMENTcolumn @paramcol_noindexoftheAUTO_INCREMENTcolumn @parammysql_versionTABLE_SHARE::mysql_version @parammaxthemaximumvalueoftheAUTO_INCREMENTcolumn @returntheAUTO_INCREMENTvalue
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
uint64_t btr_read_autoinc_with_fallback(const dict_table_t *table, unsigned col_no, ulong mysql_version,
uint64_t max)
{
ut_ad(table->persistent_autoinc);
ut_ad(!table->is_temporary());
if (autoinc > 0 && autoinc <= max && mysql_version >= 100210); elseif (dict_index_t *index=
table->get_index(*dict_table_get_nth_col(table, col_no)))
{ /* Read MAX(autoinc_col), in case this table had originally been createdbeforeMariaDB10.2.4introducedpersistentAUTO_INCREMENT andMariaDB10.2.10fixedMDEV-12123,andtherecouldbeagarbage
value in the PAGE_ROOT_AUTO_INC field. */ const uint64_t max_autoinc= row_search_max_autoinc(index); constbool need_adjust{autoinc > max || autoinc < max_autoinc};
ut_ad(max_autoinc <= max);
if (UNIV_UNLIKELY(need_adjust) && !high_level_read_only &&
!recv_sys.rpo && !opt_readonly)
{
sql_print_information("InnoDB: Resetting PAGE_ROOT_AUTO_INC from "
UINT64PF " to " UINT64PF " on table %.*sQ.%sQ (created with version %lu)",
autoinc, max_autoinc, int(table->name.dblen()), table->name.m_name,
table->name.basename(), mysql_version);
autoinc= max_autoinc;
index->set_modified(mtr);
page_set_autoinc(block, max_autoinc, &mtr, true);
}
}
}
mtr.commit(); return autoinc;
}
/** Write the next available AUTO_INCREMENT value to PAGE_ROOT_AUTO_INC. @param[in,out]trxtransaction @param[in,out]indexclusteredindex @param[in]autoinctheAUTO_INCREMENTvalue @param[in]resetwhethertoresettheAUTO_INCREMENT toapossiblysmallervaluethancurrently
exists in the page */ void btr_write_autoinc(trx_t *trx, dict_index_t *index, uint64_t autoinc, bool reset)
{
ut_ad(index->is_primary());
ut_ad(index->table->persistent_autoinc);
ut_ad(!index->table->is_temporary());
/* Save the cursor position. */ const ulint pos= page_rec_get_n_recs_before(cursor->rec);
if (UNIV_UNLIKELY(pos == ULINT_UNDEFINED)) return DB_CORRUPTION;
btr_search_drop_page_hash_index(block, nullptr);
buf_block_t *old= buf_block_alloc(); /* Copy the old page to temporary space */
memcpy_aligned<UNIV_PAGE_SIZE_MIN>(old->page.frame, block->page.frame,
srv_page_size);
/* Copy the PAGE_MAX_TRX_ID or PAGE_ROOT_AUTO_INC. */
ut_ad(!page_get_max_trx_id(block->page.frame));
memcpy_aligned<8>(PAGE_MAX_TRX_ID + PAGE_HEADER + block->page.frame,
PAGE_MAX_TRX_ID + PAGE_HEADER + old->page.frame, 8); #ifdef UNIV_DEBUG if (page_get_max_trx_id(block->page.frame)) /* PAGE_MAX_TRX_ID must be zero on non-leaf pages other than
clustered index root pages. */
ut_ad(!cursor->index->is_primary()
? page_is_leaf(block->page.frame)
: block->page.id().page_no() == cursor->index->page); else /* PAGE_MAX_TRX_ID is unused in clustered index pages (other than therootwhereitisrepurposedasPAGE_ROOT_AUTO_INC),non-leaf pages,andintemporarytables.Itwasalwayszero-initializedin page_create().PAGE_MAX_TRX_IDmustbenonzeroonsecondaryindex
leaf pages. */
ut_ad(cursor->index->table->is_temporary() ||
!page_is_leaf(block->page.frame) ||
cursor->index->is_primary()); #endif
if (UNIV_UNLIKELY(data_size1 != data_size2 || max1 != max2))
{
sql_print_error("InnoDB: Page old data size %u new data size %u" ", page old max ins size %zu new max ins size %zu",
data_size1, data_size2, max1, max2); return DB_CORRUPTION;
}
/* Restore the cursor position. */ if (!pos)
ut_ad(cursor->rec == page_get_infimum_rec(block->page.frame)); elseif (!(cursor->rec= page_rec_get_nth(block->page.frame, pos))) return DB_CORRUPTION;
if (!cursor->index->has_locking()); elseif (cursor->index->page == FIL_NULL)
ut_ad(cursor->index->is_dummy); else
lock_move_reorganize_page(block, old);
/* Write log for the changes, if needed. */ if (log_mode == MTR_LOG_ALL)
{ /* Check and log the changes in the page header. */
ulint a, e; for (a= PAGE_HEADER, e= PAGE_MAX_TRX_ID + PAGE_HEADER; a < e; a++)
{ if (old->page.frame[a] == block->page.frame[a]) continue; while (--e, old->page.frame[e] == block->page.frame[e]);
e++;
ut_ad(a < e); /* Write log for the changed page header fields. */
mtr->memcpy(*block, a, e - a); break;
}
if (page_is_comp(block->page.frame))
{ /* info_bits=0, n_owned=1, heap_no=0, status */
ut_ad(!memcmp(PAGE_NEW_INFIMUM - REC_N_NEW_EXTRA_BYTES +
block->page.frame,
PAGE_NEW_INFIMUM - REC_N_NEW_EXTRA_BYTES +
old->page.frame, 3)); /* If the 'next' pointer of the infimum record has changed, log it. */
a= PAGE_NEW_INFIMUM - 2;
e= a + 2; if (block->page.frame[a] == old->page.frame[a])
a++; if (--e, block->page.frame[e] != old->page.frame[e])
e++; if (ulint len= e - a)
mtr->memcpy(*block, a, len); /* The infimum record itself must not change. */
ut_ad(!memcmp(PAGE_NEW_INFIMUM + block->page.frame,
PAGE_NEW_INFIMUM + old->page.frame, 8)); /* Log any change of the n_owned of the supremum record. */
a= PAGE_NEW_SUPREMUM - REC_N_NEW_EXTRA_BYTES; if (block->page.frame[a] != old->page.frame[a])
mtr->memcpy(*block, a, 1); /* The rest of the supremum record must not change. */
ut_ad(!memcmp(&block->page.frame[a + 1], &old->page.frame[a + 1],
PAGE_NEW_SUPREMUM_END - PAGE_NEW_SUPREMUM +
REC_N_NEW_EXTRA_BYTES - 1));
/* Log the differences in the payload. */ for (a= PAGE_NEW_SUPREMUM_END, e= top; a < e; a++)
{ if (old->page.frame[a] == block->page.frame[a]) continue; while (--e, old->page.frame[e] == block->page.frame[e]);
e++;
ut_ad(a < e); /* TODO: write MEMMOVE records to minimize this further! */
mtr->memcpy(*block, a, e - a); break;
}
} else
{ /* info_bits=0, n_owned=1, heap_no=0, number of fields, 1-byte format */
ut_ad(!memcmp(PAGE_OLD_INFIMUM - REC_N_OLD_EXTRA_BYTES +
block->page.frame,
PAGE_OLD_INFIMUM - REC_N_OLD_EXTRA_BYTES +
old->page.frame, 4)); /* If the 'next' pointer of the infimum record has changed, log it. */
a= PAGE_OLD_INFIMUM - 2;
e= a + 2; if (block->page.frame[a] == old->page.frame[a])
a++; if (--e, block->page.frame[e] != old->page.frame[e])
e++; if (ulint len= e - a)
mtr->memcpy(*block, a, len); /* The infimum record itself must not change. */
ut_ad(!memcmp(PAGE_OLD_INFIMUM + block->page.frame,
PAGE_OLD_INFIMUM + old->page.frame, 8)); /* Log any change of the n_owned of the supremum record. */
a= PAGE_OLD_SUPREMUM - REC_N_OLD_EXTRA_BYTES; if (block->page.frame[a] != old->page.frame[a])
mtr->memcpy(*block, a, 1);
ut_ad(!memcmp(&block->page.frame[a + 1], &old->page.frame[a + 1],
PAGE_OLD_SUPREMUM_END - PAGE_OLD_SUPREMUM +
REC_N_OLD_EXTRA_BYTES - 1));
/* Log the differences in the payload. */ for (a= PAGE_OLD_SUPREMUM_END, e= top; a < e; a++)
{ if (old->page.frame[a] == block->page.frame[a]) continue; while (--e, old->page.frame[e] == block->page.frame[e]);
e++;
ut_ad(a < e); /* TODO: write MEMMOVE records to minimize this further! */
mtr->memcpy(*block, a, e - a); break;
}
}
e= srv_page_size - PAGE_DIR;
a= e - PAGE_DIR_SLOT_SIZE * page_dir_get_n_slots(block->page.frame);
/* Zero out the payload area. */
mtr->memset(*block, top, a - top, 0);
/* Log changes to the page directory. */ for (; a < e; a++)
{ if (old->page.frame[a] == block->page.frame[a]) continue; while (--e, old->page.frame[e] == block->page.frame[e]);
e++;
ut_ad(a < e); /* Write log for the changed page directory slots. */
mtr->memcpy(*block, a, e - a); break;
}
}
/** Reorganize an index page. @returnerrorcode
@retval DB_FAIL if reorganizing a ROW_FORMAT=COMPRESSED page failed */
dberr_t
btr_page_reorganize_block(
ulint z_level,/*!< in: compression level to be used
if dealing with compressed page */
buf_block_t* block, /*!< in/out: B-tree page */
dict_index_t* index, /*!< in: the index tree of the page */
mtr_t* mtr) /*!< in/out: mini-transaction */
{ if (buf_block_get_page_zip(block)) return page_zip_reorganize(block, index, z_level, mtr, true);
page_cur_t cur;
page_cur_set_before_first(block, &cur);
cur.index= index; return btr_page_reorganize_low(&cur, mtr);
}
/** Reorganize an index page. @paramcursorpagecursor @parammtrmini-transaction @returnerrorcode
@retval DB_FAIL if reorganizing a ROW_FORMAT=COMPRESSED page failed */
dberr_t btr_page_reorganize(page_cur_t *cursor, mtr_t *mtr)
{ if (!buf_block_get_page_zip(cursor->block)) return btr_page_reorganize_low(cursor, mtr);
ulint pos= page_rec_get_n_recs_before(cursor->rec); if (UNIV_UNLIKELY(pos == ULINT_UNDEFINED)) return DB_CORRUPTION;
/*************************************************************//**
Makes tree one level higher by splitting the root, and inserts
the tuple. It is assumed that mtr contains an x-latch on the tree.
NOTE that the operation of this function must always succeed,
we cannot reverse it: therefore enough free disk space must be
guaranteed to be available before this function is called.
@return inserted record */
rec_t*
btr_root_raise_and_insert( /*======================*/
ulint flags, /*!< in: undo logging and locking flags */
btr_cur_t* cursor, /*!< in: cursor at which to insert: must be ontherootpage;whenthefunctionreturns, thecursorispositionedonthepredecessor
of the inserted record */
rec_offs** offsets,/*!< out: offsets on inserted record */
mem_heap_t** heap, /*!< in/out: pointer to memory heap, or NULL */ const dtuple_t* tuple, /*!< in: tuple to insert */
ulint n_ext, /*!< in: number of externally stored columns */
mtr_t* mtr, /*!< in: mtr */
dberr_t* err) /*!< out: error code */
{
dict_index_t* index;
dtuple_t* node_ptr;
ulint level;
rec_t* node_ptr_rec;
page_cur_t* page_cursor;
page_zip_des_t* root_page_zip;
page_zip_des_t* new_page_zip;
buf_block_t* root;
buf_block_t* new_block;
/* Allocate a new page to the tree. Root splitting is done by first movingtherootrecordstothenewpage,emptyingtheroot,putting
a node pointer to the new page, and then splitting the new page. */
/* Copy the records from root to the new page one by one. */ if (0 #ifdef UNIV_ZIP_COPY
|| new_page_zip #endif/* UNIV_ZIP_COPY */
|| !page_copy_rec_list_end(new_block, root,
page_get_infimum_rec(root->page.frame),
index, mtr, err)) { switch (*err) { case DB_SUCCESS: break; case DB_FAIL:
*err = DB_SUCCESS; break; default: return nullptr;
}
ut_a(new_page_zip);
/* Copy the page byte for byte. */
page_zip_copy_recs(new_block, root_page_zip,
root->page.frame, index, mtr);
/* Update the lock table and possible hash index. */ if (index->has_locking()) {
lock_move_rec_list_end(
new_block, root,
page_get_infimum_rec(root->page.frame));
}
constexpr uint16_t max_trx_id = PAGE_HEADER + PAGE_MAX_TRX_ID; if (!index->is_primary()) { /* In secondary indexes, PAGE_MAX_TRX_IDcanberesetontherootpage,because thefieldonlymattersonleafpages,andtherootno longerisaleafpage.(OlderversionsofInnoDBdid
set PAGE_MAX_TRX_ID on all secondary index pages.) */
byte* p = my_assume_aligned<8>(
PAGE_HEADER + PAGE_MAX_TRX_ID + root->page.frame); if (mach_read_from_8(p)) {
mtr->memset(root, max_trx_id, 8, 0); if (UNIV_LIKELY_NULL(root->page.zip.data)) {
memset_aligned<8>(max_trx_id
+ root->page.zip.data, 0, 8);
}
}
} else { /* PAGE_ROOT_AUTO_INC is only present in the clustered index rootpage;onotherclusteredindexpages,wewanttoreserve
the field PAGE_MAX_TRX_ID for future use. */
byte* p = my_assume_aligned<8>(
PAGE_HEADER + PAGE_MAX_TRX_ID + new_block->page.frame); if (mach_read_from_8(p)) {
mtr->memset(new_block, max_trx_id, 8, 0); if (UNIV_LIKELY_NULL(new_block->page.zip.data)) {
memset_aligned<8>(max_trx_id
+ new_block->page.zip.data, 0, 8);
}
}
}
/* If this is a pessimistic insert which is actually done to performapessimisticupdatethenwehavestoredthelock informationoftherecordtobeinsertedontheinfimumofthe
root page: we cannot discard the lock structs on the root page */
if (index->has_locking()) {
lock_update_root_raise(*new_block, root_id);
}
/* Create a memory heap where the node pointer is stored */ if (!*heap) {
*heap = mem_heap_create(1000);
}
const uint32_t new_page_no = new_block->page.id().page_no(); const rec_t* rec= page_is_comp(new_block->page.frame)
? page_rec_next_get<true>(new_block->page.frame,
new_block->page.frame
+ PAGE_NEW_INFIMUM)
: page_rec_next_get<false>(new_block->page.frame,
new_block->page.frame
+ PAGE_OLD_INFIMUM);
ut_ad(rec); /* We just created the page. */
/* Build the node pointer (= node key and page address) for the
child */
node_ptr = dict_index_build_node_ptr(index, rec, new_page_no, *heap,
level); /* The node pointer must be marked as the predefined minimum record, asthereisnoloweralphabeticallimittorecordsintheleftmost
node of a level: */
dtuple_set_info_bits(node_ptr,
dtuple_get_info_bits(node_ptr)
| REC_INFO_MIN_REC_FLAG);
/* Rebuild the root page to get free space */
btr_page_empty(root, root_page_zip, index, level + 1, mtr); /* btr_page_empty() is supposed to zero-initialize the field. */
ut_ad(!page_get_instant(root->page.frame));
if (index->is_instant()) {
ut_ad(!root_page_zip);
btr_set_instant(root, *index, mtr);
}
/* Split the child and insert tuple */ return btr_page_split_and_insert(flags, cursor, offsets, heap,
tuple, n_ext, mtr, err);
}
/** Decide if the page should be split at the convergence point of inserts convergingtotheleft. @paramcursorinsertposition @returnthefirstrecordtobemovedtotherighthalfpage
@retval nullptr if no split is recommended */
rec_t *btr_page_get_split_rec_to_left(const btr_cur_t *cursor) noexcept
{ const rec_t *split_rec= btr_cur_get_rec(cursor); const page_t *page= btr_cur_get_page(cursor); const rec_t *const last= page + page_header_get_offs(page, PAGE_LAST_INSERT);
if (page_is_comp(page))
{ if (last != page_rec_next_get<true>(page, split_rec)) return nullptr; /* The metadata record must be present in the leftmost leaf page oftheclusteredindex,ifandonlyifindex->is_instant(). However,duringinnobase_instant_try(),index->is_instant()would alreadyholdwhenrow_ins_clust_index_entry_low()isbeinginvoked toinsertthethemetadatarecord.So,wecanonlyassertthat
when the metadata record exists, index->is_instant() must hold. */ const rec_t *const infimum= page + PAGE_NEW_INFIMUM;
ut_ad(!page_is_leaf(page) || page_has_prev(page) ||
cursor->index()->is_instant() ||
!(rec_get_info_bits(page_rec_next_get<true>(page, infimum), true) &
REC_INFO_MIN_REC_FLAG)); /* If the convergence is in the middle of a page, include also the recordimmediatelybeforethenewinserttotheupperpage. Otherwise,wecouldrepeatedlymovefrompagetopagelotsof
records smaller than the convergence point. */ if (split_rec == infimum ||
split_rec == page_rec_next_get<true>(page, infimum))
split_rec= page_rec_next_get<true>(page, split_rec);
} else
{ if (last != page_rec_next_get<false>(page, split_rec)) return nullptr; const rec_t *const infimum= page + PAGE_OLD_INFIMUM;
ut_ad(!page_is_leaf(page) || page_has_prev(page) ||
cursor->index()->is_instant() ||
!(rec_get_info_bits(page_rec_next_get<false>(page, infimum), false) &
REC_INFO_MIN_REC_FLAG)); if (split_rec == infimum ||
split_rec == page_rec_next_get<false>(page, infimum))
split_rec= page_rec_next_get<false>(page, split_rec);
}
returnconst_cast<rec_t*>(split_rec);
}
/** Decide if the page should be split at the convergence point of inserts convergingtotheright. @paramcursorinsertposition @paramsplit_recifsplitrecommended,thefirstrecordontheright halfpage,ornullptriftheto-be-insertedrecordshouldbefirst
@return whether split is recommended */ bool
btr_page_get_split_rec_to_right(const btr_cur_t *cursor, rec_t **split_rec)
noexcept
{ const rec_t *insert_point= btr_cur_get_rec(cursor); const page_t *page= btr_cur_get_page(cursor);
/* We use eager heuristics: if the new insert would be right after thepreviousinsertonthesamepage,weassumethatthereisa
pattern of sequential inserts here. */ if (page + page_header_get_offs(page, PAGE_LAST_INSERT) != insert_point) returnfalse;
if (page_is_comp(page))
{ const rec_t *const supremum= page + PAGE_NEW_SUPREMUM;
insert_point= page_rec_next_get<true>(page, insert_point); if (!insert_point); elseif (insert_point == supremum)
insert_point= nullptr; else
{
insert_point= page_rec_next_get<true>(page, insert_point); if (insert_point == supremum)
insert_point= nullptr; /* If there are >= 2 user records up from the insert point, splitallbut1off.Wewanttokeeponebecausethensequential insertscandothenecessarychecksoftherightsearchposition
just by looking at the records on this page. */
}
} else
{ const rec_t *const supremum= page + PAGE_OLD_SUPREMUM;
insert_point= page_rec_next_get<false>(page, insert_point); if (!insert_point); elseif (insert_point == supremum)
insert_point= nullptr; else
{
insert_point= page_rec_next_get<false>(page, insert_point); if (insert_point == supremum)
insert_point= nullptr;
}
}
/*************************************************************//**
Calculates a split record such that the tuple will certainly fit on
its half-page when the split is performed. We assume in this function
only that the cursor page has at least one user record.
@return split record, or NULL if tuple will be the first record on
the lower or upper half-page (determined by btr_page_tuple_smaller()) */ static
rec_t*
btr_page_get_split_rec( /*===================*/
btr_cur_t* cursor, /*!< in: cursor at which insert should be made */ const dtuple_t* tuple, /*!< in: tuple to insert */
ulint n_ext) /*!< in: number of externally stored columns */
{
page_t* page;
page_zip_des_t* page_zip;
ulint insert_size;
ulint free_space;
ulint total_data;
ulint total_n_recs;
ulint total_space;
ulint incl_data;
rec_t* ins_rec;
rec_t* rec;
rec_t* next_rec;
ulint n;
mem_heap_t* heap;
rec_offs* offsets;
page_zip = btr_cur_get_page_zip(cursor); if (page_zip) { /* Estimate the free space of an empty compressed page. */
ulint free_space_zip = page_zip_empty_size(
cursor->index()->n_fields,
page_zip_get_size(page_zip));
/* We start to include records to the left half, and when the spacereservedbythemexceedshalfoftotal_space,thenif theincludedrecordsfitontheleftpage,theywillbeputthere ifsomethingwasleftoveralsofortherightpage, otherwisethelastincludedrecordwillbethefirstontheright
half page */
do { /* Decide the next record to include */ if (rec == ins_rec) {
rec = NULL; /* NULL denotes that tuple is
now included */
} elseif (rec == NULL) {
rec = page_rec_get_next(ins_rec);
} else {
rec = page_rec_get_next(rec);
}
n++;
} while (incl_data + page_dir_calc_reserved_space(n)
< total_space / 2);
if (incl_data + page_dir_calc_reserved_space(n) <= free_space) { /* The next record will be the first on therighthalfpageifitisnotthe
supremum record of page */
if (total_data + page_dir_calc_reserved_space(total_n_recs)
<= free_space) {
/* Ok, there will be enough available space on the
half page where the tuple is inserted */
return(true);
}
if (!(rec = page_rec_get_next_const(rec))) { break;
}
}
return(false);
} #endif
/*******************************************************//**
Inserts a data tuple to a tree on a non-leaf level. It is assumed
that mtr holds an x-latch on the tree. */
dberr_t
btr_insert_on_non_leaf_level(
ulint flags, /*!< in: undo logging and locking flags */
dict_index_t* index, /*!< in: index */
ulint level, /*!< in: level, must be > 0 */
dtuple_t* tuple, /*!< in: the record to be inserted */
mtr_t* mtr) /*!< in: mtr */
{
big_rec_t* dummy_big_rec;
btr_cur_t cursor;
rec_t* rec;
mem_heap_t* heap = NULL;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs* offsets = offsets_;
rec_offs_init(offsets_);
rtr_info_t rtr_info;
if (index->is_spatial()) { /* For spatial index, initialize structures to track
its parents etc. */
rtr_init_rtr_info(&rtr_info, false, &cursor, index, false);
MY_ATTRIBUTE((nonnull,warn_unused_result)) /**************************************************************//**
Attaches the halves of an index page on the appropriate level in an
index tree. */ static
dberr_t
btr_attach_half_pages( /*==================*/
ulint flags, /*!< in: undo logging and
locking flags */
dict_index_t* index, /*!< in: the index tree */
buf_block_t* block, /*!< in/out: page to be split */ const rec_t* split_rec, /*!< in: first record on upper
half page */
buf_block_t* new_block, /*!< in/out: the new half page */
ulint direction, /*!< in: FSP_UP or FSP_DOWN */
mtr_t* mtr) /*!< in: mtr */
{
dtuple_t* node_ptr_upper;
mem_heap_t* heap;
buf_block_t* prev_block = nullptr;
buf_block_t* next_block = nullptr;
buf_block_t* lower_block;
buf_block_t* upper_block;
/* Get the level of the split pages */ const ulint level = btr_page_get_level(block->page.frame);
ut_ad(level == btr_page_get_level(new_block->page.frame));
page_id_t id{block->page.id()};
/* Get the previous and next pages of page */ const uint32_t prev_page_no = btr_page_get_prev(block->page.frame); const uint32_t next_page_no = btr_page_get_next(block->page.frame);
/* for consistency, both blocks should be locked, before change */ if (prev_page_no != FIL_NULL && direction == FSP_DOWN) {
id.set_page_no(prev_page_no);
prev_block = mtr->get_already_latched(id, MTR_MEMO_PAGE_X_FIX); #if1/* MDEV-29835 FIXME: acquire page latches upfront */ if (!prev_block) {
ut_ad(mtr->memo_contains(index->lock,
MTR_MEMO_X_LOCK));
prev_block = btr_block_get(*index, prev_page_no,
RW_X_LATCH, mtr);
} #endif
} if (next_page_no != FIL_NULL && direction != FSP_DOWN) {
id.set_page_no(next_page_no);
next_block = mtr->get_already_latched(id, MTR_MEMO_PAGE_X_FIX); #if1/* MDEV-29835 FIXME: acquire page latches upfront */ if (!next_block) {
ut_ad(mtr->memo_contains(index->lock,
MTR_MEMO_X_LOCK));
next_block = btr_block_get(*index, next_page_no,
RW_X_LATCH, mtr);
} #endif
}
/* Build the node pointer (= node key and page address) for the upper
half */
/*************************************************************//**
Determine if a tuple is smaller than any record on the page.
@returnTRUEif smaller */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool
btr_page_tuple_smaller( /*===================*/
btr_cur_t* cursor, /*!< in: b-tree cursor */ const dtuple_t* tuple, /*!< in: tuple to consider */
rec_offs** offsets,/*!< in/out: temporary storage */
ulint n_uniq, /*!< in: number of unique fields
in the index page records */
mem_heap_t** heap) /*!< in/out: heap for offsets */
{
buf_block_t* block; const rec_t* first_rec;
page_cur_t pcur;
/* Read the first user record in the page. */
block = btr_cur_get_block(cursor);
page_cur_set_before_first(block, &pcur); if (UNIV_UNLIKELY(!(first_rec = page_cur_move_to_next(&pcur)))) {
ut_ad("corrupted page" == 0); returnfalse;
}
/** Insert the tuple into the right sibling page, if the cursor is at the end ofapage. @param[in]flagsundologgingandlockingflags @param[in,out]cursorcursoratwhichtoinsert;whenthefunctionsucceeds, thecursorispositionedbeforetheinsertpoint. @param[out]offsetsoffsetsoninsertedrecord @param[in,out]heapmemoryheapforallocatingoffsets @param[in]tupletupletoinsert @param[in]n_extnumberofexternallystoredcolumns @param[in,out]mtrmini-transaction @returninsertedrecord(firstrecordontherightsiblingpage); thecursorwillbepositionedonthepageinfimum
@retval NULL if the operation was not performed */ static
rec_t*
btr_insert_into_right_sibling(
ulint flags,
btr_cur_t* cursor,
rec_offs** offsets,
mem_heap_t* heap, const dtuple_t* tuple,
ulint n_ext,
mtr_t* mtr)
{
buf_block_t* block = btr_cur_get_block(cursor);
page_t* page = buf_block_get_frame(block); const uint32_t next_page_no = btr_page_get_next(page);
/*************************************************************//**
Moves record list end to another page. Moved records include
split_rec.
@return error code */ static
dberr_t
page_move_rec_list_end( /*===================*/
buf_block_t* new_block, /*!< in/out: index page where to move */
buf_block_t* block, /*!< in: index page from where to move */
rec_t* split_rec, /*!< in: first record to move */
dict_index_t* index, /*!< in: record descriptor */
mtr_t* mtr) /*!< in: mtr */
{
page_t* new_page = buf_block_get_frame(new_block);
ulint old_data_size;
ulint new_data_size;
ulint old_n_recs;
ulint new_n_recs;
/*************************************************************//**
Moves record list start to another page. Moved records donot include
split_rec.
@return error code */ static
dberr_t
page_move_rec_list_start( /*=====================*/
buf_block_t* new_block, /*!< in/out: index page where to move */
buf_block_t* block, /*!< in/out: page containing split_rec */
rec_t* split_rec, /*!< in: first record not to move */
dict_index_t* index, /*!< in: record descriptor */
mtr_t* mtr) /*!< in: mtr */
{
dberr_t err; if (page_copy_rec_list_start(new_block, block, split_rec, index, mtr, &err))
page_delete_rec_list_start(split_rec, block, index, mtr); return err;
}
/*************************************************************//**
Splits an index page to halves and inserts the tuple. It is assumed
that mtr holds an x-latch to the index tree. NOTE: the tree x-latch is
released within this function! NOTE that the operation of this
function must always succeed, we cannot reverse it: therefore enough
free disk space (2 pages) must be guaranteed to be available before this function is called.
@return inserted record or NULL if run out of space */
rec_t*
btr_page_split_and_insert( /*======================*/
ulint flags, /*!< in: undo logging and locking flags */
btr_cur_t* cursor, /*!< in: cursor at which to insert; when the functionreturns,thecursorispositioned
on the predecessor of the inserted record */
rec_offs** offsets,/*!< out: offsets on inserted record */
mem_heap_t** heap, /*!< in/out: pointer to memory heap, or NULL */ const dtuple_t* tuple, /*!< in: tuple to insert */
ulint n_ext, /*!< in: number of externally stored columns */
mtr_t* mtr, /*!< in: mtr */
dberr_t* err) /*!< out: error code */
{
buf_block_t* block;
page_t* page;
page_zip_des_t* page_zip;
buf_block_t* new_block;
page_t* new_page;
page_zip_des_t* new_page_zip;
rec_t* split_rec;
buf_block_t* left_block;
buf_block_t* right_block;
page_cur_t* page_cursor;
rec_t* first_rec;
byte* buf = 0; /* remove warning */
rec_t* move_limit;
ulint n_iterations = 0;
ulint n_uniq;
/* try to insert to the next page if possible before split */ if (rec_t* rec = btr_insert_into_right_sibling(
flags, cursor, offsets, *heap, tuple, n_ext, mtr)) { return(rec);
}
/* 1. Decide the split record; split_rec == NULL means that the tupletobeinsertedshouldbethefirstrecordontheupper
half-page */ bool insert_left = false;
uint32_t hint_page_no = block->page.id().page_no() + 1;
byte direction = FSP_UP;
if (n_iterations > 0) {
split_rec = btr_page_get_split_rec(cursor, tuple, n_ext);
if (split_rec == NULL) {
insert_left = btr_page_tuple_smaller(
cursor, tuple, offsets, n_uniq, heap);
}
} elseif (btr_page_get_split_rec_to_right(cursor, &split_rec)) {
} elseif ((split_rec = btr_page_get_split_rec_to_left(cursor))) {
direction = FSP_DOWN;
hint_page_no -= 2;
} else { /* If there is only one record in the index page, we can'tsplitthenodeinthemiddlebydefault.Weneed todeterminewhetherthenewrecordwillbeinserted
to the left or right. */
if (UNIV_UNLIKELY(!split_rec)) {
*err = DB_CORRUPTION; return nullptr;
}
}
got_split_rec: /* 2. Allocate a new page to the index */ const uint16_t page_level = btr_page_get_level(page);
new_block = btr_page_alloc(cursor->index(), hint_page_no, direction,
page_level, mtr, mtr, err);
if (page_level && UNIV_LIKELY_NULL(new_page_zip)) { /* ROW_FORMAT=COMPRESSED non-leaf pages are not expected
to contain FIL_NULL in FIL_PAGE_PREV at this stage. */
memset_aligned<4>(new_page + FIL_PAGE_PREV, 0, 4);
}
btr_page_create(new_block, new_page_zip, cursor->index(),
page_level, mtr);
/* 3. Calculate the first record on the upper half-page, and the firstrecord(move_limit)onoriginalpagewhichendsuponthe
upper half */
if (split_rec) {
first_rec = move_limit = split_rec;
if (UNIV_UNLIKELY(*err != DB_SUCCESS)) { return nullptr;
}
#ifdef UNIV_DEBUG /* If the split is made on the leaf level and the insert will fit ontheappropriatehalf-page,wemayreleasethetreex-latch. Wecanthenmovetherecordsafterreleasingthetreelatch,
thus reducing the tree latch contention. */ constbool insert_will_fit = !new_page_zip
&& btr_page_insert_fits(cursor, split_rec, offsets, tuple,
n_ext, heap); #endif if (!split_rec && !insert_left) {
UT_DELETE_ARRAY(buf);
buf = NULL;
}
#if0// FIXME: this used to be a no-op, and may cause trouble if enabled if (insert_will_fit
&& page_is_leaf(page)
&& !dict_index_is_online_ddl(cursor->index())) {
mtr->release(cursor->index()->lock); /* NOTE: We cannot release root block latch here, because it
has segment header and already modified in most of cases.*/
} #endif
/* 5. Move then the records to the new page */ if (direction == FSP_DOWN) { /* fputs("Split left\n", stderr); */
/* For some reason, compressing new_block failed, eventhoughitshouldcontainfewerrecordsthan theoriginalpage.Copythepagebyteforbyte andthendeletetherecordsfrombothpages
as appropriate. Deleting will always succeed. */
ut_a(new_page_zip);
/* For some reason, compressing new_page failed, eventhoughitshouldcontainfewerrecordsthan theoriginalpage.Copythepagebyteforbyte andthendeletetherecordsfrombothpages
as appropriate. Deleting will always succeed. */
ut_a(new_page_zip);
/* At this point, split_rec, move_limit and first_rec may point
to garbage on the old page. */
/* 6. The split and the tree modification is now completed. Decide the
page where the tuple should be inserted */
rec_t* rec;
buf_block_t* const insert_block = insert_left
? left_block : right_block;
/* 7. Reposition the cursor for insert and try insertion */
page_cursor = btr_cur_get_page_cur(cursor);
page_cursor->block = insert_block;
if (rec == NULL) { /* The insert did not fit on the page: loop back to the
start of the function for a new split */
insert_failed:
n_iterations++;
ut_ad(n_iterations < 2
|| buf_block_get_page_zip(insert_block));
ut_ad(!insert_will_fit);
if (prev)
btr_page_set_next(prev, next_page_no, mtr);
return DB_SUCCESS;
}
/*************************************************************//** If page is the only on its level, this function moves its records to the
father page, thus reducing the tree height.
@return father block */
buf_block_t*
btr_lift_page_up(
dict_index_t* index, /*!< in: index tree */
buf_block_t* block, /*!< in: page which is the only on its level; mustnotbeempty:use btr_discard_only_page_on_levelifthelast
record from the page should be removed */
que_thr_t* thr, /*!< in/out: query thread */
mtr_t* mtr, /*!< in/out: mini-transaction */
dberr_t* err) /*!< out: error code */
{
buf_block_t* father_block;
ulint page_level;
page_zip_des_t* father_page_zip;
page_t* page = buf_block_get_frame(block);
ulint root_page_no;
buf_block_t* blocks[BTR_MAX_LEVELS];
ulint n_blocks; /*!< last used index in blocks[] */
ulint i; bool lift_father_up;
buf_block_t* block_orig = block;
/* Store all ancestor pages so we can reset their levelslateron.Wehavetodoallthesearcheson thetreenowbecauselateron,afterwe'vereplaced thefirstlevel,thetreeisinaninconsistentstate
and can not be searched. */ for (b = father_block;
b->page.id().page_no() != root_page_no; ) {
ut_a(n_blocks < BTR_MAX_LEVELS);
if (UNIV_UNLIKELY(!offsets)) { goto parent_corrupted;
}
blocks[n_blocks++] = b = btr_cur_get_block(&cursor);
}
lift_father_up = (n_blocks && page_level == 0); if (lift_father_up) { /* The father page also should be the only on its level (not root).Weshouldliftupthefatherpageatfirst. Becausetheleafpageshouldbelifteduponlyforrootpage. Thefreeingpageisbasedonpage_level(==0or!=0) tochoosesegment.Ifthepage_levelischanged==0from!=0, laterfreeingofthepagedoesn'tfindthepageallocation
to be freed.*/
/* Make the father empty */
btr_page_empty(father_block, father_page_zip, index, page_level, mtr); /* btr_page_empty() is supposed to zero-initialize the field. */
ut_ad(!page_get_instant(father_block->page.frame));
if (index->is_instant()
&& father_block->page.id().page_no() == root_page_no) {
ut_ad(!father_page_zip);
/* Copy the records to the father page one by one. */ if (0 #ifdef UNIV_ZIP_COPY
|| father_page_zip #endif/* UNIV_ZIP_COPY */
|| !page_copy_rec_list_end(father_block, block,
page_get_infimum_rec(page),
index, mtr, err)) { switch (*err) { case DB_SUCCESS: break; case DB_FAIL:
*err = DB_SUCCESS; break; default: return nullptr;
}
/*************************************************************//**
Tries to merge the page first to the left immediate brother if such a
brother exists, and the node pointers to the current page and to the brother
reside on the same page. If the left brother does not satisfy these
conditions, looks at the right brother. If the page is the only one on that
level lifts the records of the page to the father page, thus reducing the
tree height. It is assumed that mtr holds an x-latch on the tree and on the
page. If cursor is on the leaf level, mtr must also hold x-latches to the
brothers, if they exist.
@return error code */
dberr_t
btr_compress( /*=========*/
btr_cur_t* cursor, /*!< in/out: cursor on the page to merge orlift;thepagemustnotbeempty: whendeletingrecords,usebtr_discard_page()
if the page would become empty */ bool adjust, /*!< in: whether the cursor position should be
adjusted even when compression occurs */
mtr_t* mtr) /*!< in/out: mini-transaction */
{
dict_index_t* index;
buf_block_t* merge_block = nullptr;
page_t* merge_page = nullptr;
page_zip_des_t* merge_page_zip;
ibool is_left;
buf_block_t* block;
page_t* page;
btr_cur_t father_cursor;
mem_heap_t* heap;
rec_offs* offsets;
ulint nth_rec = 0; /* remove bogus warning */ bool mbr_changed = false; #ifdef UNIV_DEBUG bool leftmost_child; #endif
DBUG_ENTER("btr_compress");
block = btr_cur_get_block(cursor);
page = btr_cur_get_page(cursor);
index = btr_cur_get_index(cursor);
/* Move records to the merge page */ if (is_left) {
rtr_mbr_t new_mbr;
rec_offs* offsets2 = NULL;
/* For rtree, we need to update father's mbr. */ if (index->is_spatial()) { /* We only support merge pages with the same parent
page */ if (!rtr_check_same_block(
index, &cursor2,
btr_cur_get_block(&father_cursor), heap)) {
is_left = false; goto retry;
}
/* Set rtr_info for cursor2, since it is
necessary in recursive page merge. */
cursor2.rtr_info = cursor->rtr_info;
cursor2.tree_height = cursor->tree_height;
/* No GAP lock needs to be worrying about */
lock_sys.prdt_page_free_from_discard(id);
} else {
err = btr_cur_node_ptr_delete(&father_cursor, mtr); if (UNIV_UNLIKELY(err != DB_SUCCESS)) { goto err_exit;
} if (index->has_locking()) {
lock_update_merge_left(
*merge_block, orig_pred, id);
}
}
if (adjust) {
ulint n = page_rec_get_n_recs_before(orig_pred); if (UNIV_UNLIKELY(!n || n == ULINT_UNDEFINED)) { goto corrupted;
}
nth_rec += n;
}
} else {
rec_t* orig_succ;
ibool compressed;
dberr_t err;
byte fil_page_prev[4];
if (index->is_spatial()) { /* For spatial index, we disallow merge of blocks withdifferentparents,sincethemergewouldneed toupdateentry(forMBRandPrimarykey)inthe
parent of block being merged */ if (!rtr_check_same_block(
index, &cursor2,
btr_cur_get_block(&father_cursor), heap)) { goto cannot_merge;
}
/* Set rtr_info for cursor2, since it is
necessary in recursive page merge. */
cursor2.rtr_info = cursor->rtr_info;
cursor2.tree_height = cursor->tree_height;
} elseif (!btr_page_get_father(mtr, &cursor2)) { goto cannot_merge;
}
if (merge_page_zip && left_page_no == FIL_NULL) {
/* The function page_zip_compress(), which will be invokedbypage_copy_rec_list_end()below, requiresthatFIL_PAGE_PREVbeFIL_NULL.
Clear the field, but prepare to restore it. */
static_assert(FIL_PAGE_PREV % 8 == 0, "alignment");
memcpy(fil_page_prev, merge_page + FIL_PAGE_PREV, 4);
compile_time_assert(FIL_NULL == 0xffffffffU);
memset_aligned<4>(merge_page + FIL_PAGE_PREV, 0xff, 4);
}
if (!orig_succ) {
ut_a(merge_page_zip); if (left_page_no == FIL_NULL) { /* FIL_PAGE_PREV was restored from
merge_page_zip. */
ut_ad(!memcmp(fil_page_prev,
merge_page + FIL_PAGE_PREV, 4));
} goto err_exit;
}
btr_search_drop_page_hash_index(block, nullptr);
if (merge_page_zip && left_page_no == FIL_NULL) {
/* Restore FIL_PAGE_PREV in order to avoid an assertion failureinbtr_level_list_remove(),whichwillset thefieldagaintoFIL_NULL.Eventhoughthismakes merge_pageandmerge_page_zipinconsistentfora splitsecond,itisharmless,becausethepages
are X-latched. */
memcpy(merge_page + FIL_PAGE_PREV, fil_page_prev, 4);
}
/* Remove the page from the level list */
err = btr_level_list_remove(*block, *index, mtr);
if (UNIV_UNLIKELY(err != DB_SUCCESS)) { goto err_exit;
}
/* Replace the address of the old child node (= page) with the
address of the merge page to the right */
btr_node_ptr_set_child_page_no(
btr_cur_get_block(&father_cursor),
btr_cur_get_rec(&father_cursor),
offsets, right_page_no, mtr);
/*************************************************************//**
Discards a page that is the only page on its level. This will empty
the whole B-tree, leaving just an empty root page. This function
should almost never be reached, because btr_compress(), which is invoked in delete operations, calls btr_lift_page_up() to flatten the B-tree. */
ATTRIBUTE_COLD static void
btr_discard_only_page_on_level( /*===========================*/
btr_cur_t* cur, /*!< in: cursor on a page which is the
only on its level */
mtr_t* mtr) /*!< in: mtr */
{
dict_index_t* index = cur->index();
buf_block_t* block = btr_cur_get_block(cur);
ulint page_level = 0;
ut_ad(!index->is_dummy);
/* Save the PAGE_MAX_TRX_ID from the leaf page. */ const trx_id_t max_trx_id = page_get_max_trx_id(block->page.frame); const rec_t* r = page_rec_get_next(
page_get_infimum_rec(block->page.frame)); /* In the caller we checked that a valid key exists in the page,
because we were able to look up a parent page. */
ut_ad(r);
ut_ad(rec_is_metadata(r, *index) == index->is_instant());
if (index->is_spatial()) { /* Check any concurrent search having this page */
rtr_check_discard_page(index, NULL, block); if (!rtr_page_get_father(mtr, nullptr, &cursor,
cur->rtr_info->thr)) { return;
}
} else { if (!btr_page_get_father(mtr, &cursor)) { return;
}
}
father = btr_cur_get_block(&cursor);
if (index->has_locking()) {
lock_update_discard(
father, PAGE_HEAP_NO_SUPREMUM, block);
}
/* Free the file page */ if (btr_page_free(index, block, mtr) != DB_SUCCESS) { return;
}
block = father;
page_level++;
}
/* block is the root page, which must be empty, except
for the node pointer to the (now discarded) block(s). */
ut_ad(!page_has_siblings(block->page.frame));
/*************************************************************//**
Discards a page from a B-tree. This is used to remove the last record from
a B-tree page: the whole page must be removed at the same time. This cannot
be used for the root page, which is allowed to be empty. */
dberr_t
btr_discard_page( /*=============*/
btr_cur_t* cursor, /*!< in: cursor on the page to discard: not on
the root page */
mtr_t* mtr) /*!< in: mtr */
{
dict_index_t* index;
buf_block_t* merge_block;
buf_block_t* block;
btr_cur_t parent_cursor;
block = btr_cur_get_block(cursor);
index = btr_cur_get_index(cursor);
parent_cursor.page_cur = cursor->page_cur;
#ifdef UNIV_BTR_PRINT /*************************************************************//**
Prints size info of a B-tree. */ void
btr_print_size( /*===========*/
dict_index_t* index) /*!< in: index tree */
{
page_t* root;
fseg_header_t* seg;
mtr_t mtr;
mtr_start(&mtr);
root = btr_root_get(index, &mtr);
seg = root + PAGE_HEADER + PAGE_BTR_SEG_TOP;
fputs("INFO OF THE NON-LEAF PAGE SEGMENT\n", stderr);
fseg_print(seg, &mtr);
seg = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF;
fputs("INFO OF THE LEAF PAGE SEGMENT\n", stderr);
fseg_print(seg, &mtr);
mtr_commit(&mtr);
}
/************************************************************//**
Prints recursively index tree pages. */ static void
btr_print_recursive( /*================*/
dict_index_t* index, /*!< in: index tree */
buf_block_t* block, /*!< in: index page */
ulint width, /*!< in: print this many entries from start
and end */
mem_heap_t** heap, /*!< in/out: heap for rec_get_offsets() */
rec_offs** offsets,/*!< in/out: buffer for rec_get_offsets() */
mtr_t* mtr) /*!< in: mtr */
{ const page_t* page = buf_block_get_frame(block);
page_cur_t cursor;
ulint n_recs;
ulint i = 0;
mtr_t mtr2;
/**************************************************************//**
Prints directories and other info of all nodes in the tree. */ void
btr_print_index( /*============*/
dict_index_t* index, /*!< in: index */
ulint width) /*!< in: print this many entries from start
and end */
{
mtr_t mtr;
buf_block_t* root;
mem_heap_t* heap = NULL;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs* offsets = offsets_;
rec_offs_init(offsets_);
fputs("--------------------------\n" "INDEX TREE PRINT\n", stderr);
/* For spatial index, the MBR in the parent rec could be different withthatoffirstrecofchild,theirrelationshipshouldbe
"WITHIN" relationship */ if (dict_index_is_spatial(index)) {
ut_a(!cmp_dtuple_rec_with_gis(
tuple, btr_cur_get_rec(&cursor),
PAGE_CUR_WITHIN));
} else {
ut_a(!cmp_dtuple_rec(tuple, btr_cur_get_rec(&cursor), index,
offsets));
}
func_exit:
mem_heap_free(heap);
return(TRUE);
} #endif/* UNIV_DEBUG */
/************************************************************//**
Display identification information for a record. */ static void
btr_index_rec_validate_report( /*==========================*/ const page_t* page, /*!< in: index page */ const rec_t* rec, /*!< in: index record */ const dict_index_t* index) /*!< in: index */
{
ib::info() << "Record in index " << index->name
<< " of table " << index->table->name
<< ", page " << page_id_t(page_get_space_id(page),
page_get_page_no(page))
<< ", at offset " << rec - page;
}
/************************************************************//**
Checks the size and number of fields in a record based on the definition of
the index.
@returnTRUEif ok */ bool
btr_index_rec_validate( /*===================*/ const page_cur_t& cur, /*!< in: cursor to index record */ const dict_index_t* index, /*!< in: index */ bool dump_on_error) /*!< in: true if the function shouldprinthexdumpofrecord
and page on error */
noexcept
{
ulint len; const rec_t* rec = page_cur_get_rec(&cur); const page_t* page = cur.block->page.frame;
mem_heap_t* heap = NULL;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs* offsets = offsets_;
rec_offs_init(offsets_);
ut_ad(index->n_core_fields);
#ifdef VIRTUAL_INDEX_DEBUG if (dict_index_has_virtual(index)) {
fprintf(stderr, "index name is %s\n", index->name());
} #endif if ((ibool)!!page_is_comp(page) != dict_table_is_comp(index->table)) {
btr_index_rec_validate_report(page, rec, index);
ib::error() << "Compact flag=" << !!page_is_comp(page)
<< ", should be " << dict_table_is_comp(index->table);
for (unsigned i = 0; i < rec_offs_n_fields(offsets); i++) {
rec_get_nth_field_offs(offsets, i, &len);
ulint fixed_size;
if (is_alter_metadata && i == index->first_user_field()) {
fixed_size = FIELD_REF_SIZE; if (len != FIELD_REF_SIZE
|| !rec_offs_nth_extern(offsets, i)) { goto len_mismatch;
}
continue;
} else {
fixed_size = dict_col_get_fixed_size(
field->col, page_is_comp(page)); if (rec_offs_nth_extern(offsets, i)) { const byte* data = rec_get_nth_field(
rec, offsets, i, &len);
len -= BTR_EXTERN_FIELD_REF_SIZE;
ulint extern_len = mach_read_from_4(
data + len + BTR_EXTERN_LEN + 4); if (fixed_size == extern_len + len) { goto next_field;
}
}
}
/* Note that if fixed_size != 0, it equals the lengthofafixed-sizecolumnintheclusteredindex. Weshouldadjustithere. Aprefixindexofthecolumnisoffixed,butdifferent length.Whenfixed_size==0,prefix_lenisthemaximum
length of the prefix index column. */
if (len_is_stored(len)
&& (field->prefix_len
? len > field->prefix_len
: (fixed_size && len != fixed_size))) {
len_mismatch:
btr_index_rec_validate_report(page, rec, index);
ib::error error;
error << "Field " << i << " len is " << len
<< ", should be " << fixed_size;
#ifdef VIRTUAL_INDEX_DEBUG if (dict_index_has_virtual(index)) {
rec_print_new(stderr, rec, offsets);
} #endif
if (heap) {
mem_heap_free(heap);
} return(TRUE);
}
/************************************************************//**
Checks the size and number of fields in records based on the definition of
the index.
@returntrueif ok */ static bool
btr_index_page_validate( /*====================*/
buf_block_t* block, /*!< in: index page */
dict_index_t* index) /*!< in: index */
{
page_cur_t cur; #ifndef DBUG_OFF
ulint nth = 1; #endif/* !DBUG_OFF */
page_cur_set_before_first(block, &cur);
/* Directory slot 0 should only contain the infimum record. */
DBUG_EXECUTE_IF("check_table_rec_next",
ut_a(page_rec_get_nth_const(
page_cur_get_page(&cur), 0)
== cur.rec);
ut_a(page_dir_slot_get_n_owned(
page_dir_get_nth_slot(
page_cur_get_page(&cur), 0))
== 1););
while (page_cur_move_to_next(&cur)) { if (page_cur_is_after_last(&cur)) { returntrue;
}
if (!btr_index_rec_validate(cur, index, TRUE)) { break;
}
/* Verify that page_rec_get_nth_const() is correctly
retrieving each record. */
DBUG_EXECUTE_IF("check_table_rec_next",
ut_a(cur.rec == page_rec_get_nth_const(
page_cur_get_page(&cur),
page_rec_get_n_recs_before(
cur.rec)));
ut_a(nth++ == page_rec_get_n_recs_before(
cur.rec)););
}
returnfalse;
}
/************************************************************//**
Report an error on one page of an index tree. */ static void
btr_validate_report1( /*=================*/
dict_index_t* index, /*!< in: index */
ulint level, /*!< in: B-tree level */ const buf_block_t* block) /*!< in: index page */
{
ib::error error;
error << "In page " << block->page.id().page_no()
<< " of index " << index->name
<< " of table " << index->table->name;
if (level > 0) {
error << ", index tree level " << level;
}
}
/************************************************************//**
Report an error on two pages of an index tree. */ static void
btr_validate_report2( /*=================*/ const dict_index_t* index, /*!< in: index */
ulint level, /*!< in: B-tree level */ const buf_block_t* block1, /*!< in: first index page */ const buf_block_t* block2) /*!< in: second index page */
{
ib::error error;
error << "In pages " << block1->page.id()
<< " and " << block2->page.id() << " of index " << index->name
<< " of table " << index->table->name;
if (level)
error << ", index tree level " << level;
}
/* For R-Tree, since record order might not be the same as linkedindexpageinthelowerlevel,weneedtotravers backwardstogetthefirstpagerecinthislevel. Thisisonlyusedforindexvalidation.Spatialindex doesnotusesuchscanforanyofitsDMLorquery
operations */ if (dict_index_is_spatial(index)) {
uint32_t left_page_no = btr_page_get_prev(page);
while (left_page_no != FIL_NULL) { /* To obey latch order of tree blocks, weshouldreleasetheright_blockonceto
obtain lock of the uncle block. */
mtr.release_last_page();
/* For spatial index, we cannot guarantee the key ordering acrosspages,soskiptherecordcompareverificationfor
now. Will enhanced in special R-Tree index validation scheme */ if (index->is_btree()
&& cmp_rec_rec(rec, right_rec,
offsets, offsets2, index) >= 0) {
/* Similarly skip the father node check for spatial index for now, foracoupleofreasons: 1)Asmentioned,thereisnoorderingrelationshipbetweenrecords inparentlevelandlinkedpagesinthechildlevel. 2)SearchparentfromrootisverycostlyforR-tree.
We will add special validation mechanism for R-tree later (WL #7520) */ if (index->is_btree() && block->page.id().page_no() != index->page) { /* Check father node pointers */
rec_t* node_ptr
= page_rec_get_next(page_get_infimum_rec(page)); if (!node_ptr) {
err = DB_CORRUPTION; goto node_ptr_fails;
}
if (right_node_ptr
!= page_get_supremum_rec(father_page)) {
if (btr_cur_get_rec(&right_node_cur)
!= right_node_ptr) {
node_pointer_corrupted:
err = DB_CORRUPTION;
fputs("InnoDB: node pointer to" " the right page is wrong\n",
stderr);
node_ptr_fails: /* Commit the mini-transaction to release the latch on 'page'. Re-acquirethelatchonright_page,whichwillbecome'page'
on the next loop. The page has already been checked. */
mtr.commit();
if (trx_is_interrupted(trx)) { /* On interrupt, return the current status. */
} elseif (right_page_no != FIL_NULL) {
/**************************************************************//**
Checks the consistency of an index tree.
@return DB_SUCCESS if ok, error code ifnot */
dberr_t
btr_validate_index( /*===============*/
dict_index_t* index, /*!< in: index */
trx_t* trx) /*!< in: transaction */
{
mtr_t mtr{trx};
mtr.start();
mtr_x_lock_index(index, &mtr);
dberr_t err; if (page_t *root= btr_root_get(index, &mtr, &err)) for (auto level= btr_page_get_level(root);; level--)
{ if (dberr_t err_level= btr_validate_level(index, trx, level))
err= err_level; if (!level) break;
}
mtr.commit(); return err;
}
/**************************************************************//**
Checks if the page in the cursor can be merged with given page. If necessary, re-organize the merge_page.
@returntrueif possible to merge. */ static bool
btr_can_merge_with_page( /*====================*/
btr_cur_t* cursor, /*!< in: cursor on the page to merge */
uint32_t page_no, /*!< in: a sibling page */
buf_block_t** merge_block, /*!< out: the merge block */
mtr_t* mtr) /*!< in: mini-transaction */
{
dict_index_t* index;
page_t* page;
ulint n_recs;
ulint data_size;
ulint max_ins_size_reorg;
ulint max_ins_size;
buf_block_t* mblock;
page_t* mpage;
DBUG_ENTER("btr_can_merge_with_page");
if (data_size > max_ins_size_reorg) { goto error;
}
/* If compression padding tells us that merging will result in toopackeduppagei.e.:whichislikelytocausecompression
failure then don't merge the pages. */ if (mblock->page.zip.data && page_is_leaf(mpage)
&& (page_get_data_size(mpage) + data_size
>= dict_index_zip_pad_optimal_page_size(index))) {
if (data_size > max_ins_size) { /* We have to reorganize mpage */ if (btr_page_reorganize_block(page_zip_level, mblock, index,
mtr) != DB_SUCCESS) { goto error;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.