mirror of
https://github.com/MariaDB/server.git
synced 2025-02-23 05:43:08 +01:00

The purpose of the change buffer was to reduce random disk access, which could be useful on rotational storage, but maybe less so on solid-state storage. When we wished to (1) insert a record into a non-unique secondary index, (2) delete-mark a secondary index record, (3) delete a secondary index record as part of purge (but not ROLLBACK), and the B-tree leaf page where the record belongs to is not in the buffer pool, we inserted a record into the change buffer B-tree, indexed by the page identifier. When the page was eventually read into the buffer pool, we looked up the change buffer B-tree for any modifications to the page, applied these upon the completion of the read operation. This was called the insert buffer merge. We remove the change buffer, because it has been the source of various hard-to-reproduce corruption bugs, including those fixed in commit5b9ee8d819
and commit165564d3c3
but not limited to them. A downgrade will fail with a clear message starting with commitdb14eb16f9
(MDEV-30106). buf_page_t::state: Merge IBUF_EXIST to UNFIXED and WRITE_FIX_IBUF to WRITE_FIX. buf_pool_t::watch[]: Remove. trx_t: Move isolation_level, check_foreigns, check_unique_secondary, bulk_insert into the same bit-field. The only purpose of trx_t::check_unique_secondary is to enable bulk insert into an empty table. It no longer enables insert buffering for UNIQUE INDEX. btr_cur_t::thr: Remove. This field was originally needed for change buffering. Later, its use was extended to cover SPATIAL INDEX. Much of the time, rtr_info::thr holds this field. When it does not, we will add parameters to SPATIAL INDEX specific functions. ibuf_upgrade_needed(): Check if the change buffer needs to be updated. ibuf_upgrade(): Merge and upgrade the change buffer after all redo log has been applied. Free any pages consumed by the change buffer, and zero out the change buffer root page to mark the upgrade completed, and to prevent a downgrade to an earlier version. dict_load_tablespaces(): Renamed from dict_check_tablespaces_and_store_max_id(). This needs to be invoked before ibuf_upgrade(). btr_cur_open_at_rnd_pos(): Specialize for use in persistent statistics. The change buffer merge does not need this function anymore. btr_page_alloc(): Renamed from btr_page_alloc_low(). We no longer allocate any change buffer pages. btr_cur_open_at_rnd_pos(): Specialize for use in persistent statistics. The change buffer merge does not need this function anymore. row_search_index_entry(), btr_lift_page_up(): Add a parameter thr for the SPATIAL INDEX case. rtr_page_split_and_insert(): Specialized from btr_page_split_and_insert(). rtr_root_raise_and_insert(): Specialized from btr_root_raise_and_insert(). Note: The support for upgrading from the MySQL 3.23 or MySQL 4.0 change buffer format that predates the MySQL 4.1 introduction of the option innodb_file_per_table was removed in MySQL 5.6.5 as part of mysql/mysql-server@69b6241a79 and MariaDB 10.0.11 as part of1d0f70c2f8
. In the tests innodb.log_upgrade and innodb.log_corruption, we create valid (upgraded) change buffer pages. Tested by: Matthias Leich
1132 lines
32 KiB
C++
1132 lines
32 KiB
C++
/*****************************************************************************
|
|
|
|
Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved.
|
|
Copyright (c) 2017, 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 include/rem0rec.ic
|
|
Record manager
|
|
|
|
Created 5/30/1994 Heikki Tuuri
|
|
*************************************************************************/
|
|
|
|
#include "mach0data.h"
|
|
#include "ut0byte.h"
|
|
#include "dict0boot.h"
|
|
#include "btr0types.h"
|
|
|
|
/* Offsets of the bit-fields in an old-style record. NOTE! In the table the
|
|
most significant bytes and bits are written below less significant.
|
|
|
|
(1) byte offset (2) bit usage within byte
|
|
downward from
|
|
origin -> 1 8 bits pointer to next record
|
|
2 8 bits pointer to next record
|
|
3 1 bit short flag
|
|
7 bits number of fields
|
|
4 3 bits number of fields
|
|
5 bits heap number
|
|
5 8 bits heap number
|
|
6 4 bits n_owned
|
|
4 bits info bits
|
|
*/
|
|
|
|
/* Offsets of the bit-fields in a new-style record. NOTE! In the table the
|
|
most significant bytes and bits are written below less significant.
|
|
|
|
(1) byte offset (2) bit usage within byte
|
|
downward from
|
|
origin -> 1 8 bits relative offset of next record
|
|
2 8 bits relative offset of next record
|
|
the relative offset is an unsigned 16-bit
|
|
integer:
|
|
(offset_of_next_record
|
|
- offset_of_this_record) mod 64Ki,
|
|
where mod is the modulo as a non-negative
|
|
number;
|
|
we can calculate the offset of the next
|
|
record with the formula:
|
|
relative_offset + offset_of_this_record
|
|
mod srv_page_size
|
|
3 3 bits status:
|
|
000=REC_STATUS_ORDINARY
|
|
001=REC_STATUS_NODE_PTR
|
|
010=REC_STATUS_INFIMUM
|
|
011=REC_STATUS_SUPREMUM
|
|
100=REC_STATUS_INSTANT
|
|
1xx=reserved
|
|
5 bits heap number
|
|
4 8 bits heap number
|
|
5 4 bits n_owned
|
|
4 bits info bits
|
|
*/
|
|
|
|
/* We list the byte offsets from the origin of the record, the mask,
|
|
and the shift needed to obtain each bit-field of the record. */
|
|
|
|
#define REC_NEXT 2
|
|
#define REC_NEXT_MASK 0xFFFFUL
|
|
#define REC_NEXT_SHIFT 0
|
|
|
|
#define REC_OLD_SHORT 3 /* This is single byte bit-field */
|
|
#define REC_OLD_SHORT_MASK 0x1UL
|
|
#define REC_OLD_SHORT_SHIFT 0
|
|
|
|
#define REC_OLD_N_FIELDS 4
|
|
#define REC_OLD_N_FIELDS_MASK 0x7FEUL
|
|
#define REC_OLD_N_FIELDS_SHIFT 1
|
|
|
|
#define REC_OLD_HEAP_NO 5
|
|
#define REC_HEAP_NO_MASK 0xFFF8UL
|
|
#if 0 /* defined in rem0rec.h for use of page0zip.cc */
|
|
#define REC_NEW_HEAP_NO 4
|
|
#define REC_HEAP_NO_SHIFT 3
|
|
#endif
|
|
|
|
#define REC_OLD_N_OWNED 6 /* This is single byte bit-field */
|
|
#define REC_NEW_N_OWNED 5 /* This is single byte bit-field */
|
|
#define REC_N_OWNED_MASK 0xFUL
|
|
#define REC_N_OWNED_SHIFT 0
|
|
|
|
#define REC_OLD_INFO_BITS 6 /* This is single byte bit-field */
|
|
#define REC_NEW_INFO_BITS 5 /* This is single byte bit-field */
|
|
#define REC_INFO_BITS_MASK 0xF0UL
|
|
#define REC_INFO_BITS_SHIFT 0
|
|
|
|
#if REC_OLD_SHORT_MASK << (8 * (REC_OLD_SHORT - 3)) \
|
|
^ REC_OLD_N_FIELDS_MASK << (8 * (REC_OLD_N_FIELDS - 4)) \
|
|
^ REC_HEAP_NO_MASK << (8 * (REC_OLD_HEAP_NO - 4)) \
|
|
^ REC_N_OWNED_MASK << (8 * (REC_OLD_N_OWNED - 3)) \
|
|
^ REC_INFO_BITS_MASK << (8 * (REC_OLD_INFO_BITS - 3)) \
|
|
^ 0xFFFFFFFFUL
|
|
# error "sum of old-style masks != 0xFFFFFFFFUL"
|
|
#endif
|
|
#if REC_NEW_STATUS_MASK << (8 * (REC_NEW_STATUS - 3)) \
|
|
^ REC_HEAP_NO_MASK << (8 * (REC_NEW_HEAP_NO - 4)) \
|
|
^ REC_N_OWNED_MASK << (8 * (REC_NEW_N_OWNED - 3)) \
|
|
^ REC_INFO_BITS_MASK << (8 * (REC_NEW_INFO_BITS - 3)) \
|
|
^ 0xFFFFFFUL
|
|
# error "sum of new-style masks != 0xFFFFFFUL"
|
|
#endif
|
|
|
|
/******************************************************//**
|
|
Gets a bit field from within 1 byte. */
|
|
UNIV_INLINE
|
|
byte
|
|
rec_get_bit_field_1(
|
|
/*================*/
|
|
const rec_t* rec, /*!< in: pointer to record origin */
|
|
ulint offs, /*!< in: offset from the origin down */
|
|
ulint mask, /*!< in: mask used to filter bits */
|
|
ulint shift) /*!< in: shift right applied after masking */
|
|
{
|
|
return static_cast<byte>((*(rec - offs) & mask) >> shift);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Sets a bit field within 1 byte. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_bit_field_1(
|
|
/*================*/
|
|
rec_t* rec, /*!< in: pointer to record origin */
|
|
ulint val, /*!< in: value to set */
|
|
ulint offs, /*!< in: offset from the origin down */
|
|
ulint mask, /*!< in: mask used to filter bits */
|
|
ulint shift) /*!< in: shift right applied after masking */
|
|
{
|
|
ut_ad(rec);
|
|
ut_ad(offs <= REC_N_OLD_EXTRA_BYTES);
|
|
ut_ad(mask);
|
|
ut_ad(mask <= 0xFFUL);
|
|
ut_ad(((mask >> shift) << shift) == mask);
|
|
ut_ad(((val << shift) & mask) == (val << shift));
|
|
|
|
mach_write_to_1(rec - offs,
|
|
(mach_read_from_1(rec - offs) & ~mask)
|
|
| (val << shift));
|
|
}
|
|
|
|
/******************************************************//**
|
|
Gets a bit field from within 2 bytes. */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_bit_field_2(
|
|
/*================*/
|
|
const rec_t* rec, /*!< in: pointer to record origin */
|
|
ulint offs, /*!< in: offset from the origin down */
|
|
ulint mask, /*!< in: mask used to filter bits */
|
|
ulint shift) /*!< in: shift right applied after masking */
|
|
{
|
|
ut_ad(rec);
|
|
|
|
return((mach_read_from_2(rec - offs) & mask) >> shift);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Sets a bit field within 2 bytes. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_bit_field_2(
|
|
/*================*/
|
|
rec_t* rec, /*!< in: pointer to record origin */
|
|
ulint val, /*!< in: value to set */
|
|
ulint offs, /*!< in: offset from the origin down */
|
|
ulint mask, /*!< in: mask used to filter bits */
|
|
ulint shift) /*!< in: shift right applied after masking */
|
|
{
|
|
ut_ad(rec);
|
|
ut_ad(offs <= REC_N_OLD_EXTRA_BYTES);
|
|
ut_ad(mask > 0xFFUL);
|
|
ut_ad(mask <= 0xFFFFUL);
|
|
ut_ad((mask >> shift) & 1);
|
|
ut_ad(0 == ((mask >> shift) & ((mask >> shift) + 1)));
|
|
ut_ad(((mask >> shift) << shift) == mask);
|
|
ut_ad(((val << shift) & mask) == (val << shift));
|
|
|
|
mach_write_to_2(rec - offs,
|
|
(mach_read_from_2(rec - offs) & ~mask)
|
|
| (val << shift));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the offset of the next chained record
|
|
on the same page.
|
|
@return the page offset of the next chained record, or 0 if none */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_next_offs(
|
|
/*==============*/
|
|
const rec_t* rec, /*!< in: physical record */
|
|
ulint comp) /*!< in: nonzero=compact page format */
|
|
{
|
|
ulint field_value;
|
|
compile_time_assert(REC_NEXT_MASK == 0xFFFFUL);
|
|
compile_time_assert(REC_NEXT_SHIFT == 0);
|
|
|
|
field_value = mach_read_from_2(rec - REC_NEXT);
|
|
|
|
if (comp) {
|
|
#if UNIV_PAGE_SIZE_MAX <= 32768
|
|
/* Note that for 64 KiB pages, field_value can 'wrap around'
|
|
and the debug assertion is not valid */
|
|
|
|
/* In the following assertion, field_value is interpreted
|
|
as signed 16-bit integer in 2's complement arithmetics.
|
|
If all platforms defined int16_t in the standard headers,
|
|
the expression could be written simpler as
|
|
(int16_t) field_value + ut_align_offset(...) < srv_page_size
|
|
*/
|
|
ut_ad((field_value >= 32768
|
|
? field_value - 65536
|
|
: field_value)
|
|
+ ut_align_offset(rec, srv_page_size)
|
|
< srv_page_size);
|
|
#endif
|
|
if (field_value == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
/* There must be at least REC_N_NEW_EXTRA_BYTES + 1
|
|
between each record. */
|
|
ut_ad((field_value > REC_N_NEW_EXTRA_BYTES
|
|
&& field_value < 32768)
|
|
|| field_value < (uint16) -REC_N_NEW_EXTRA_BYTES);
|
|
|
|
return(ut_align_offset(rec + field_value, srv_page_size));
|
|
} else {
|
|
ut_ad(field_value < srv_page_size);
|
|
|
|
return(field_value);
|
|
}
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to set the next record offset field
|
|
of an old-style record. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_next_offs_old(
|
|
/*==================*/
|
|
rec_t* rec, /*!< in: old-style physical record */
|
|
ulint next) /*!< in: offset of the next record */
|
|
{
|
|
ut_ad(srv_page_size > next);
|
|
compile_time_assert(REC_NEXT_MASK == 0xFFFFUL);
|
|
compile_time_assert(REC_NEXT_SHIFT == 0);
|
|
mach_write_to_2(rec - REC_NEXT, next);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to set the next record offset field
|
|
of a new-style record. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_next_offs_new(
|
|
/*==================*/
|
|
rec_t* rec, /*!< in/out: new-style physical record */
|
|
ulint next) /*!< in: offset of the next record */
|
|
{
|
|
ulint field_value;
|
|
|
|
ut_ad(srv_page_size > next);
|
|
|
|
if (!next) {
|
|
field_value = 0;
|
|
} else {
|
|
/* The following two statements calculate
|
|
next - offset_of_rec mod 64Ki, where mod is the modulo
|
|
as a non-negative number */
|
|
|
|
field_value = (ulint)
|
|
((lint) next
|
|
- (lint) ut_align_offset(rec, srv_page_size));
|
|
field_value &= REC_NEXT_MASK;
|
|
}
|
|
|
|
mach_write_to_2(rec - REC_NEXT, field_value);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the number of fields
|
|
in an old-style record.
|
|
@return number of data fields */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_n_fields_old(
|
|
/*=================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
ulint ret;
|
|
|
|
ut_ad(rec);
|
|
|
|
ret = rec_get_bit_field_2(rec, REC_OLD_N_FIELDS,
|
|
REC_OLD_N_FIELDS_MASK,
|
|
REC_OLD_N_FIELDS_SHIFT);
|
|
ut_ad(ret <= REC_MAX_N_FIELDS);
|
|
ut_ad(ret > 0);
|
|
|
|
return(ret);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to set the number of fields
|
|
in an old-style record. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_n_fields_old(
|
|
/*=================*/
|
|
rec_t* rec, /*!< in: physical record */
|
|
ulint n_fields) /*!< in: the number of fields */
|
|
{
|
|
ut_ad(rec);
|
|
ut_ad(n_fields <= REC_MAX_N_FIELDS);
|
|
ut_ad(n_fields > 0);
|
|
|
|
rec_set_bit_field_2(rec, n_fields, REC_OLD_N_FIELDS,
|
|
REC_OLD_N_FIELDS_MASK, REC_OLD_N_FIELDS_SHIFT);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the number of fields
|
|
in a record.
|
|
@return number of data fields */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_n_fields(
|
|
/*=============*/
|
|
const rec_t* rec, /*!< in: physical record */
|
|
const dict_index_t* index) /*!< in: record descriptor */
|
|
{
|
|
ut_ad(rec);
|
|
ut_ad(index);
|
|
|
|
if (!dict_table_is_comp(index->table)) {
|
|
return(rec_get_n_fields_old(rec));
|
|
}
|
|
|
|
switch (rec_get_status(rec)) {
|
|
case REC_STATUS_INSTANT:
|
|
case REC_STATUS_ORDINARY:
|
|
return(dict_index_get_n_fields(index));
|
|
case REC_STATUS_NODE_PTR:
|
|
return(dict_index_get_n_unique_in_tree(index) + 1);
|
|
case REC_STATUS_INFIMUM:
|
|
case REC_STATUS_SUPREMUM:
|
|
return(1);
|
|
}
|
|
|
|
ut_error;
|
|
return(ULINT_UNDEFINED);
|
|
}
|
|
|
|
/** Confirms the n_fields of the entry is sane with comparing the other
|
|
record in the same page specified
|
|
@param[in] index index
|
|
@param[in] rec record of the same page
|
|
@param[in] entry index entry
|
|
@return true if n_fields is sane */
|
|
UNIV_INLINE
|
|
bool
|
|
rec_n_fields_is_sane(
|
|
dict_index_t* index,
|
|
const rec_t* rec,
|
|
const dtuple_t* entry)
|
|
{
|
|
const ulint n_fields = rec_get_n_fields(rec, index);
|
|
|
|
return(n_fields == dtuple_get_n_fields(entry)
|
|
|| (index->is_instant()
|
|
&& n_fields >= index->n_core_fields)
|
|
/* a record for older SYS_INDEXES table
|
|
(missing merge_threshold column) is acceptable. */
|
|
|| (index->table->id == DICT_INDEXES_ID
|
|
&& n_fields == dtuple_get_n_fields(entry) - 1));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the number of records owned by the
|
|
previous directory record.
|
|
@return number of owned records */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_n_owned_old(
|
|
/*================*/
|
|
const rec_t* rec) /*!< in: old-style physical record */
|
|
{
|
|
return(rec_get_bit_field_1(rec, REC_OLD_N_OWNED,
|
|
REC_N_OWNED_MASK, REC_N_OWNED_SHIFT));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the number of records owned by the
|
|
previous directory record.
|
|
@return number of owned records */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_n_owned_new(
|
|
/*================*/
|
|
const rec_t* rec) /*!< in: new-style physical record */
|
|
{
|
|
return(rec_get_bit_field_1(rec, REC_NEW_N_OWNED,
|
|
REC_N_OWNED_MASK, REC_N_OWNED_SHIFT));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to retrieve the info bits of a record.
|
|
@return info bits */
|
|
UNIV_INLINE
|
|
byte
|
|
rec_get_info_bits(
|
|
/*==============*/
|
|
const rec_t* rec, /*!< in: physical record */
|
|
ulint comp) /*!< in: nonzero=compact page format */
|
|
{
|
|
return rec_get_bit_field_1(
|
|
rec, comp ? REC_NEW_INFO_BITS : REC_OLD_INFO_BITS,
|
|
REC_INFO_BITS_MASK, REC_INFO_BITS_SHIFT);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to retrieve the info and status
|
|
bits of a record. (Only compact records have status bits.)
|
|
@return info and status bits */
|
|
UNIV_INLINE
|
|
byte
|
|
rec_get_info_and_status_bits(
|
|
/*=========================*/
|
|
const rec_t* rec, /*!< in: physical record */
|
|
ulint comp) /*!< in: nonzero=compact page format */
|
|
{
|
|
compile_time_assert(!((REC_NEW_STATUS_MASK >> REC_NEW_STATUS_SHIFT)
|
|
& (REC_INFO_BITS_MASK >> REC_INFO_BITS_SHIFT)));
|
|
if (comp)
|
|
return static_cast<byte>(rec_get_info_bits(rec, TRUE) |
|
|
rec_get_status(rec));
|
|
else
|
|
return rec_get_info_bits(rec, FALSE);
|
|
}
|
|
/******************************************************//**
|
|
The following function is used to set the info and status
|
|
bits of a record. (Only compact records have status bits.) */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_info_and_status_bits(
|
|
/*=========================*/
|
|
rec_t* rec, /*!< in/out: physical record */
|
|
ulint bits) /*!< in: info bits */
|
|
{
|
|
compile_time_assert(!((REC_NEW_STATUS_MASK >> REC_NEW_STATUS_SHIFT)
|
|
& (REC_INFO_BITS_MASK >> REC_INFO_BITS_SHIFT)));
|
|
rec_set_status(rec, bits & REC_NEW_STATUS_MASK);
|
|
rec_set_bit_field_1(rec, bits & ~REC_NEW_STATUS_MASK,
|
|
REC_NEW_INFO_BITS,
|
|
REC_INFO_BITS_MASK, REC_INFO_BITS_SHIFT);
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function tells if record is delete marked.
|
|
@return nonzero if delete marked */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_deleted_flag(
|
|
/*=================*/
|
|
const rec_t* rec, /*!< in: physical record */
|
|
ulint comp) /*!< in: nonzero=compact page format */
|
|
{
|
|
if (comp) {
|
|
return(rec_get_bit_field_1(rec, REC_NEW_INFO_BITS,
|
|
REC_INFO_DELETED_FLAG,
|
|
REC_INFO_BITS_SHIFT));
|
|
} else {
|
|
return(rec_get_bit_field_1(rec, REC_OLD_INFO_BITS,
|
|
REC_INFO_DELETED_FLAG,
|
|
REC_INFO_BITS_SHIFT));
|
|
}
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function tells if a new-style record is a node pointer.
|
|
@return TRUE if node pointer */
|
|
UNIV_INLINE
|
|
bool
|
|
rec_get_node_ptr_flag(
|
|
/*==================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
return(REC_STATUS_NODE_PTR == rec_get_status(rec));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the order number
|
|
of an old-style record in the heap of the index page.
|
|
@return heap order number */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_heap_no_old(
|
|
/*================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
return(rec_get_bit_field_2(rec, REC_OLD_HEAP_NO,
|
|
REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to get the order number
|
|
of a new-style record in the heap of the index page.
|
|
@return heap order number */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_heap_no_new(
|
|
/*================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
return(rec_get_bit_field_2(rec, REC_NEW_HEAP_NO,
|
|
REC_HEAP_NO_MASK, REC_HEAP_NO_SHIFT));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to test whether the data offsets in the record
|
|
are stored in one-byte or two-byte format.
|
|
@return TRUE if 1-byte form */
|
|
UNIV_INLINE
|
|
ibool
|
|
rec_get_1byte_offs_flag(
|
|
/*====================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
return(rec_get_bit_field_1(rec, REC_OLD_SHORT, REC_OLD_SHORT_MASK,
|
|
REC_OLD_SHORT_SHIFT));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to set the 1-byte offsets flag. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_set_1byte_offs_flag(
|
|
/*====================*/
|
|
rec_t* rec, /*!< in: physical record */
|
|
ibool flag) /*!< in: TRUE if 1byte form */
|
|
{
|
|
ut_ad(flag <= 1);
|
|
|
|
rec_set_bit_field_1(rec, flag, REC_OLD_SHORT, REC_OLD_SHORT_MASK,
|
|
REC_OLD_SHORT_SHIFT);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of nth field end if the record is stored in the 1-byte
|
|
offsets form. If the field is SQL null, the flag is ORed in the returned
|
|
value.
|
|
@return offset of the start of the field, SQL null flag ORed */
|
|
UNIV_INLINE
|
|
uint8_t
|
|
rec_1_get_field_end_info(
|
|
/*=====================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n < rec_get_n_fields_old(rec));
|
|
|
|
return(mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n + 1)));
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of nth field end if the record is stored in the 2-byte
|
|
offsets form. If the field is SQL null, the flag is ORed in the returned
|
|
value.
|
|
@return offset of the start of the field, SQL null flag and extern
|
|
storage flag ORed */
|
|
UNIV_INLINE
|
|
uint16_t
|
|
rec_2_get_field_end_info(
|
|
/*=====================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(!rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n < rec_get_n_fields_old(rec));
|
|
|
|
return(mach_read_from_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n + 2)));
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns nonzero if the field is stored off-page.
|
|
@retval 0 if the field is stored in-page
|
|
@retval REC_2BYTE_EXTERN_MASK if the field is stored externally */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_2_is_field_extern(
|
|
/*==================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
return(rec_2_get_field_end_info(rec, n) & REC_2BYTE_EXTERN_MASK);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
The following function sets the number of allocated elements
|
|
for an array of offsets. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_offs_set_n_alloc(
|
|
/*=================*/
|
|
rec_offs*offsets, /*!< out: array for rec_get_offsets(),
|
|
must be allocated */
|
|
ulint n_alloc) /*!< in: number of elements */
|
|
{
|
|
ut_ad(n_alloc > REC_OFFS_HEADER_SIZE);
|
|
MEM_UNDEFINED(offsets, n_alloc * sizeof *offsets);
|
|
offsets[0] = static_cast<rec_offs>(n_alloc);
|
|
}
|
|
|
|
/************************************************************//**
|
|
The following function is used to get an offset to the nth
|
|
data field in a record.
|
|
@return offset from the origin of rec */
|
|
UNIV_INLINE
|
|
rec_offs
|
|
rec_get_nth_field_offs(
|
|
/*===================*/
|
|
const rec_offs* offsets,/*!< in: array returned by rec_get_offsets() */
|
|
ulint n, /*!< in: index of the field */
|
|
ulint* len) /*!< out: length of the field; UNIV_SQL_NULL
|
|
if SQL null; UNIV_SQL_DEFAULT is default value */
|
|
{
|
|
ut_ad(n < rec_offs_n_fields(offsets));
|
|
|
|
rec_offs offs = n == 0 ? 0 : get_value(rec_offs_base(offsets)[n]);
|
|
rec_offs next_offs = rec_offs_base(offsets)[1 + n];
|
|
|
|
if (get_type(next_offs) == SQL_NULL) {
|
|
*len = UNIV_SQL_NULL;
|
|
} else if (get_type(next_offs) == DEFAULT) {
|
|
*len = UNIV_SQL_DEFAULT;
|
|
} else {
|
|
*len = get_value(next_offs) - offs;
|
|
}
|
|
|
|
return(offs);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Determine if the offsets are for a record containing null BLOB pointers.
|
|
@return first field containing a null BLOB pointer, or NULL if none found */
|
|
UNIV_INLINE
|
|
const byte*
|
|
rec_offs_any_null_extern(
|
|
/*=====================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
const rec_offs* offsets) /*!< in: rec_get_offsets(rec) */
|
|
{
|
|
ulint i;
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
|
|
if (!rec_offs_any_extern(offsets)) {
|
|
return(NULL);
|
|
}
|
|
|
|
for (i = 0; i < rec_offs_n_fields(offsets); i++) {
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
ulint len;
|
|
const byte* field
|
|
= rec_get_nth_field(rec, offsets, i, &len);
|
|
|
|
ut_a(len >= BTR_EXTERN_FIELD_REF_SIZE);
|
|
if (!memcmp(field + len
|
|
- BTR_EXTERN_FIELD_REF_SIZE,
|
|
field_ref_zero,
|
|
BTR_EXTERN_FIELD_REF_SIZE)) {
|
|
return(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Gets the physical size of a field.
|
|
@return length of field */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_offs_nth_size(
|
|
/*==============*/
|
|
const rec_offs* offsets,/*!< in: array returned by rec_get_offsets() */
|
|
ulint n) /*!< in: nth field */
|
|
{
|
|
ut_ad(rec_offs_validate(NULL, NULL, offsets));
|
|
ut_ad(n < rec_offs_n_fields(offsets));
|
|
if (!n) {
|
|
return get_value(rec_offs_base(offsets)[1 + n]);
|
|
}
|
|
return get_value((rec_offs_base(offsets)[1 + n]))
|
|
- get_value(rec_offs_base(offsets)[n]);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the number of extern bits set in a record.
|
|
@return number of externally stored fields */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_offs_n_extern(
|
|
/*==============*/
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
ulint n = 0;
|
|
|
|
if (rec_offs_any_extern(offsets)) {
|
|
ulint i;
|
|
|
|
for (i = rec_offs_n_fields(offsets); i--; ) {
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return(n);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of n - 1th field end if the record is stored in the 1-byte
|
|
offsets form. If the field is SQL null, the flag is ORed in the returned
|
|
value. This function and the 2-byte counterpart are defined here because the
|
|
C-compiler was not able to sum negative and positive constant offsets, and
|
|
warned of constant arithmetic overflow within the compiler.
|
|
@return offset of the start of the PREVIOUS field, SQL null flag ORed */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_1_get_prev_field_end_info(
|
|
/*==========================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n <= rec_get_n_fields_old(rec));
|
|
|
|
return(mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n)));
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of n - 1th field end if the record is stored in the 2-byte
|
|
offsets form. If the field is SQL null, the flag is ORed in the returned
|
|
value.
|
|
@return offset of the start of the PREVIOUS field, SQL null flag ORed */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_2_get_prev_field_end_info(
|
|
/*==========================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(!rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n <= rec_get_n_fields_old(rec));
|
|
|
|
return(mach_read_from_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n)));
|
|
}
|
|
|
|
/******************************************************//**
|
|
Sets the field end info for the nth field if the record is stored in the
|
|
1-byte format. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_1_set_field_end_info(
|
|
/*=====================*/
|
|
rec_t* rec, /*!< in: record */
|
|
ulint n, /*!< in: field index */
|
|
ulint info) /*!< in: value to set */
|
|
{
|
|
ut_ad(rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n < rec_get_n_fields_old(rec));
|
|
|
|
mach_write_to_1(rec - (REC_N_OLD_EXTRA_BYTES + n + 1), info);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Sets the field end info for the nth field if the record is stored in the
|
|
2-byte format. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_2_set_field_end_info(
|
|
/*=====================*/
|
|
rec_t* rec, /*!< in: record */
|
|
ulint n, /*!< in: field index */
|
|
ulint info) /*!< in: value to set */
|
|
{
|
|
ut_ad(!rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n < rec_get_n_fields_old(rec));
|
|
|
|
mach_write_to_2(rec - (REC_N_OLD_EXTRA_BYTES + 2 * n + 2), info);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of nth field start if the record is stored in the 1-byte
|
|
offsets form.
|
|
@return offset of the start of the field */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_1_get_field_start_offs(
|
|
/*=======================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n <= rec_get_n_fields_old(rec));
|
|
|
|
if (n == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
return(rec_1_get_prev_field_end_info(rec, n)
|
|
& ~REC_1BYTE_SQL_NULL_MASK);
|
|
}
|
|
|
|
/******************************************************//**
|
|
Returns the offset of nth field start if the record is stored in the 2-byte
|
|
offsets form.
|
|
@return offset of the start of the field */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_2_get_field_start_offs(
|
|
/*=======================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(!rec_get_1byte_offs_flag(rec));
|
|
ut_ad(n <= rec_get_n_fields_old(rec));
|
|
|
|
if (n == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
return(rec_2_get_prev_field_end_info(rec, n)
|
|
& ~(REC_2BYTE_SQL_NULL_MASK | REC_2BYTE_EXTERN_MASK));
|
|
}
|
|
|
|
/******************************************************//**
|
|
The following function is used to read the offset of the start of a data field
|
|
in the record. The start of an SQL null field is the end offset of the
|
|
previous non-null field, or 0, if none exists. If n is the number of the last
|
|
field + 1, then the end offset of the last field is returned.
|
|
@return offset of the start of the field */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_field_start_offs(
|
|
/*=====================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: field index */
|
|
{
|
|
ut_ad(rec);
|
|
ut_ad(n <= rec_get_n_fields_old(rec));
|
|
|
|
if (n == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
if (rec_get_1byte_offs_flag(rec)) {
|
|
|
|
return(rec_1_get_field_start_offs(rec, n));
|
|
}
|
|
|
|
return(rec_2_get_field_start_offs(rec, n));
|
|
}
|
|
|
|
/************************************************************//**
|
|
Gets the physical size of an old-style field.
|
|
Also an SQL null may have a field of size > 0,
|
|
if the data type is of a fixed size.
|
|
@return field size in bytes */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_nth_field_size(
|
|
/*===================*/
|
|
const rec_t* rec, /*!< in: record */
|
|
ulint n) /*!< in: index of the field */
|
|
{
|
|
ulint os;
|
|
ulint next_os;
|
|
|
|
os = rec_get_field_start_offs(rec, n);
|
|
next_os = rec_get_field_start_offs(rec, n + 1);
|
|
|
|
ut_ad(next_os - os < srv_page_size);
|
|
|
|
return(next_os - os);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
The following function returns the data size of an old-style physical
|
|
record, that is the sum of field lengths. SQL null fields
|
|
are counted as length 0 fields. The value returned by the function
|
|
is the distance from record origin to record end in bytes.
|
|
@return size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_data_size_old(
|
|
/*==================*/
|
|
const rec_t* rec) /*!< in: physical record */
|
|
{
|
|
ut_ad(rec);
|
|
|
|
return(rec_get_field_start_offs(rec, rec_get_n_fields_old(rec)));
|
|
}
|
|
|
|
/**********************************************************//**
|
|
The following function sets the number of fields in offsets. */
|
|
UNIV_INLINE
|
|
void
|
|
rec_offs_set_n_fields(
|
|
/*==================*/
|
|
rec_offs* offsets, /*!< in/out: array returned by
|
|
rec_get_offsets() */
|
|
ulint n_fields) /*!< in: number of fields */
|
|
{
|
|
ut_ad(offsets);
|
|
ut_ad(n_fields > 0);
|
|
ut_ad(n_fields <= REC_MAX_N_FIELDS);
|
|
ut_ad(n_fields + REC_OFFS_HEADER_SIZE
|
|
<= rec_offs_get_n_alloc(offsets));
|
|
offsets[1] = static_cast<rec_offs>(n_fields);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
The following function returns the data size of a physical
|
|
record, that is the sum of field lengths. SQL null fields
|
|
are counted as length 0 fields. The value returned by the function
|
|
is the distance from record origin to record end in bytes.
|
|
@return size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_offs_data_size(
|
|
/*===============*/
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
ulint size;
|
|
|
|
ut_ad(rec_offs_validate(NULL, NULL, offsets));
|
|
size = get_value(rec_offs_base(offsets)[rec_offs_n_fields(offsets)]);
|
|
ut_ad(size < srv_page_size);
|
|
return(size);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
Returns the total size of record minus data size of record. The value
|
|
returned by the function is the distance from record start to record origin
|
|
in bytes.
|
|
@return size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_offs_extra_size(
|
|
/*================*/
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
ulint size;
|
|
ut_ad(rec_offs_validate(NULL, NULL, offsets));
|
|
size = *rec_offs_base(offsets) & REC_OFFS_MASK;
|
|
ut_ad(size < srv_page_size);
|
|
return(size);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
Returns the total size of a physical record.
|
|
@return size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_offs_size(
|
|
/*==========*/
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
return(rec_offs_data_size(offsets) + rec_offs_extra_size(offsets));
|
|
}
|
|
|
|
#ifdef UNIV_DEBUG
|
|
/**********************************************************//**
|
|
Returns a pointer to the end of the record.
|
|
@return pointer to end */
|
|
UNIV_INLINE
|
|
byte*
|
|
rec_get_end(
|
|
/*========*/
|
|
const rec_t* rec, /*!< in: pointer to record */
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
return(const_cast<rec_t*>(rec + rec_offs_data_size(offsets)));
|
|
}
|
|
|
|
/**********************************************************//**
|
|
Returns a pointer to the start of the record.
|
|
@return pointer to start */
|
|
UNIV_INLINE
|
|
byte*
|
|
rec_get_start(
|
|
/*==========*/
|
|
const rec_t* rec, /*!< in: pointer to record */
|
|
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
|
|
{
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
return(const_cast<rec_t*>(rec - rec_offs_extra_size(offsets)));
|
|
}
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
/** Copy a physical record to a buffer.
|
|
@param[in] buf buffer
|
|
@param[in] rec physical record
|
|
@param[in] offsets array returned by rec_get_offsets()
|
|
@return pointer to the origin of the copy */
|
|
UNIV_INLINE
|
|
rec_t*
|
|
rec_copy(
|
|
void* buf,
|
|
const rec_t* rec,
|
|
const rec_offs* offsets)
|
|
{
|
|
ulint extra_len;
|
|
ulint data_len;
|
|
|
|
ut_ad(rec != NULL);
|
|
ut_ad(buf != NULL);
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
ut_ad(rec_validate(rec, offsets));
|
|
|
|
extra_len = rec_offs_extra_size(offsets);
|
|
data_len = rec_offs_data_size(offsets);
|
|
|
|
memcpy(buf, rec - extra_len, extra_len + data_len);
|
|
|
|
return((byte*) buf + extra_len);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
Returns the extra size of an old-style physical record if we know its
|
|
data size and number of fields.
|
|
@return extra size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_converted_extra_size(
|
|
/*=========================*/
|
|
ulint data_size, /*!< in: data size */
|
|
ulint n_fields, /*!< in: number of fields */
|
|
ulint n_ext) /*!< in: number of externally stored columns */
|
|
{
|
|
if (!n_ext && data_size <= REC_1BYTE_OFFS_LIMIT) {
|
|
|
|
return(REC_N_OLD_EXTRA_BYTES + n_fields);
|
|
}
|
|
|
|
return(REC_N_OLD_EXTRA_BYTES + 2 * n_fields);
|
|
}
|
|
|
|
/**********************************************************//**
|
|
The following function returns the size of a data tuple when converted to
|
|
a physical record.
|
|
@return size */
|
|
UNIV_INLINE
|
|
ulint
|
|
rec_get_converted_size(
|
|
/*===================*/
|
|
dict_index_t* index, /*!< in: record descriptor */
|
|
const dtuple_t* dtuple, /*!< in: data tuple */
|
|
ulint n_ext) /*!< in: number of externally stored columns */
|
|
{
|
|
ulint data_size;
|
|
ulint extra_size;
|
|
|
|
ut_ad(dtuple_check_typed(dtuple));
|
|
#ifdef UNIV_DEBUG
|
|
if ((dtuple_get_info_bits(dtuple) & REC_NEW_STATUS_MASK)
|
|
== REC_STATUS_NODE_PTR) {
|
|
ut_ad(dtuple->n_fields - 1
|
|
== dict_index_get_n_unique_in_tree_nonleaf(index));
|
|
} else if (index->table->id == DICT_INDEXES_ID) {
|
|
/* The column SYS_INDEXES.MERGE_THRESHOLD was
|
|
instantly added in MariaDB 10.2.2 (MySQL 5.7). */
|
|
ut_ad(!index->table->is_temporary());
|
|
ut_ad(index->n_fields == DICT_NUM_FIELDS__SYS_INDEXES);
|
|
ut_ad(dtuple->n_fields == DICT_NUM_FIELDS__SYS_INDEXES
|
|
|| dtuple->n_fields
|
|
== DICT_FLD__SYS_INDEXES__MERGE_THRESHOLD);
|
|
} else {
|
|
ut_ad(dtuple->n_fields >= index->n_core_fields);
|
|
ut_ad(dtuple->n_fields <= index->n_fields
|
|
|| dtuple->is_alter_metadata());
|
|
}
|
|
#endif
|
|
|
|
if (dict_table_is_comp(index->table)) {
|
|
return rec_get_converted_size_comp(index, dtuple, NULL);
|
|
}
|
|
|
|
data_size = dtuple_get_data_size(dtuple, 0);
|
|
|
|
/* If primary key is being updated then the new record inherits
|
|
externally stored fields from the delete-marked old record.
|
|
In that case, n_ext may be less value than
|
|
dtuple_get_n_ext(tuple). */
|
|
ut_ad(n_ext <= dtuple_get_n_ext(dtuple));
|
|
extra_size = rec_get_converted_extra_size(
|
|
data_size, dtuple_get_n_fields(dtuple), n_ext);
|
|
|
|
return(data_size + extra_size);
|
|
}
|