/***************************************************************************** Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2017, 2020, 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((*(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 pointer of the next chained record on the same page. @return pointer to the next chained record, or NULL if none */ UNIV_INLINE const rec_t* rec_get_next_ptr_const( /*===================*/ 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 (field_value == 0) { return(NULL); } 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 /* 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((byte*) ut_align_down(rec, srv_page_size) + ut_align_offset(rec + field_value, srv_page_size)); } else { ut_ad(field_value < srv_page_size); return((byte*) ut_align_down(rec, srv_page_size) + field_value); } } /******************************************************//** The following function is used to get the pointer of the next chained record on the same page. @return pointer to the next chained record, or NULL if none */ UNIV_INLINE rec_t* rec_get_next_ptr( /*=============*/ rec_t* rec, /*!< in: physical record */ ulint comp) /*!< in: nonzero=compact page format */ { return(const_cast(rec_get_next_ptr_const(rec, comp))); } /******************************************************//** 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(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(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(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 + 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 - 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 (dict_index_is_ibuf(index)) { ut_ad(dtuple->n_fields > 1); } else 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); }