mariadb/storage/innobase/include/rem0rec.inl
Marko Mäkelä f27e9c8947 MDEV-29694 Remove the InnoDB change buffer
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
commit 5b9ee8d819 and
commit 165564d3c3 but not limited to them.

A downgrade will fail with a clear message starting with
commit db14eb16f9 (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 of 1d0f70c2f8.

In the tests innodb.log_upgrade and innodb.log_corruption, we create
valid (upgraded) change buffer pages.

Tested by: Matthias Leich
2023-01-11 17:59:36 +02:00

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);
}