/***************************************************************************** Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ /** @file rem/rem0rec.cc Record manager Created 5/30/1994 Heikki Tuuri *************************************************************************/ #include "rem0rec.h" #include #ifndef UNIV_HOTBACKUP #include "data0data.h" #include "fts0fts.h" #endif /* !UNIV_HOTBACKUP */ #include "gis0geo.h" #include "mach0data.h" #include "mtr0log.h" #include "mtr0mtr.h" #include "page0page.h" #include "trx0sys.h" #include "my_dbug.h" /* PHYSICAL RECORD (OLD STYLE) =========================== The physical record, which is the data type of all the records found in index pages of the database, has the following format (lower addresses and more significant bits inside a byte are below represented on a higher text line): | offset of the end of the last field of data, the most significant bit is set to 1 if and only if the field is SQL-null, if the offset is 2-byte, then the second most significant bit is set to 1 if the field is stored on another page: mostly this will occur in the case of big BLOB fields | ... | offset of the end of the first field of data + the SQL-null bit | | 4 bits used to delete mark a record, and mark a predefined minimum record in alphabetical order | | 4 bits giving the number of records owned by this record (this term is explained in page0page.h) | | 13 bits giving the order number of this record in the heap of the index page | | 10 bits giving the number of fields in this record | | 1 bit which is set to 1 if the offsets above are given in one byte format, 0 if in two byte format | | two bytes giving an absolute pointer to the next record in the page | ORIGIN of the record | first field of data | ... | last field of data | The origin of the record is the start address of the first field of data. The offsets are given relative to the origin. The offsets of the data fields are stored in an inverted order because then the offset of the first fields are near the origin, giving maybe a better processor cache hit rate in searches. The offsets of the data fields are given as one-byte (if there are less than 127 bytes of data in the record) or two-byte unsigned integers. The most significant bit is not part of the offset, instead it indicates the SQL-null if the bit is set to 1. */ /* PHYSICAL RECORD (NEW STYLE) =========================== The physical record, which is the data type of all the records found in index pages of the database, has the following format (lower addresses and more significant bits inside a byte are below represented on a higher text line): | length of the last non-null variable-length field of data: if the maximum length is 255, one byte; otherwise, 0xxxxxxx (one byte, length=0..127), or 1exxxxxxxxxxxxxx (two bytes, length=128..16383, extern storage flag) | ... | length of first variable-length field of data | | SQL-null flags (1 bit per nullable field), padded to full bytes | | 1 or 2 bytes to indicate number of fields in the record if the table where the record resides has undergone an instant ADD COLUMN before this record gets inserted; If no instant ADD COLUMN ever happened, here should be no byte; So parsing this optional number requires the index or table information | | 4 bits used to delete mark a record, and mark a predefined minimum record in alphabetical order | | 4 bits giving the number of records owned by this record (this term is explained in page0page.h) | | 13 bits giving the order number of this record in the heap of the index page | | 3 bits record type: 000=conventional, 001=node pointer (inside B-tree), 010=infimum, 011=supremum, 1xx=reserved | | two bytes giving a relative pointer to the next record in the page | ORIGIN of the record | first field of data | ... | last field of data | The origin of the record is the start address of the first field of data. The offsets are given relative to the origin. The offsets of the data fields are stored in an inverted order because then the offset of the first fields are near the origin, giving maybe a better processor cache hit rate in searches. The offsets of the data fields are given as one-byte (if there are less than 127 bytes of data in the record) or two-byte unsigned integers. The most significant bit is not part of the offset, instead it indicates the SQL-null if the bit is set to 1. */ /* CANONICAL COORDINATES. A record can be seen as a single string of 'characters' in the following way: catenate the bytes in each field, in the order of fields. An SQL-null field is taken to be an empty sequence of bytes. Then after the position of each field insert in the string the 'character' , except that after an SQL-null field insert . Now the ordinal position of each byte in this canonical string is its canonical coordinate. So, for the record ("AA", SQL-NULL, "BB", ""), the canonical string is "AABB". We identify prefixes (= initial segments) of a record with prefixes of the canonical string. The canonical length of the prefix is the length of the corresponding prefix of the canonical string. The canonical length of a record is the length of its canonical string. For example, the maximal common prefix of records ("AA", SQL-NULL, "BB", "C") and ("AA", SQL-NULL, "B", "C") is "AAB", and its canonical length is 5. A complete-field prefix of a record is a prefix which ends at the end of some field (containing also ). A record is a complete-field prefix of another record, if the corresponding canonical strings have the same property. */ /** Validates the consistency of an old-style physical record. @return true if ok */ static ibool rec_validate_old(const rec_t *rec); /*!< in: physical record */ /** Determine how many of the first n columns in a compact physical record are stored externally. @return number of externally stored columns */ ulint rec_get_n_extern_new( const rec_t *rec, /*!< in: compact physical record */ const dict_index_t *index, /*!< in: record descriptor */ ulint n) /*!< in: number of columns to scan */ { const byte *nulls; const byte *lens; ulint null_mask; ulint n_extern; ulint i; ut_ad(dict_table_is_comp(index->table)); ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY); ut_ad(n == ULINT_UNDEFINED || n <= dict_index_get_n_fields(index)); if (n == ULINT_UNDEFINED) { n = dict_index_get_n_fields(index); } nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); lens = nulls - UT_BITS_IN_BYTES(index->n_nullable); null_mask = 1; n_extern = 0; i = 0; /* read the lengths of fields 0..n */ do { const dict_field_t *field = index->get_field(i); const dict_col_t *col = field->col; ulint len; if (!(col->prtype & DATA_NOT_NULL)) { /* nullable field => read the null flag */ if (UNIV_UNLIKELY(!(byte)null_mask)) { nulls--; null_mask = 1; } if (*nulls & null_mask) { null_mask <<= 1; /* No length is stored for NULL fields. */ continue; } null_mask <<= 1; } if (UNIV_UNLIKELY(!field->fixed_len)) { /* Variable-length field: read the length */ len = *lens--; /* If the maximum length of the field is up to 255 bytes, the actual length is always stored in one byte. If the maximum length is more than 255 bytes, the actual length is stored in one byte for 0..127. The length will be encoded in two bytes when it is 128 or more, or when the field is stored externally. */ if (DATA_BIG_COL(col)) { if (len & 0x80) { /* 1exxxxxxx xxxxxxxx */ if (len & 0x40) { n_extern++; } lens--; } } } } while (++i < n); return (n_extern); } /** The following function is used to get the offset to the nth data field in an old-style record. @return offset to the field */ ulint rec_get_nth_field_offs_old(const rec_t *rec, /*!< in: record */ ulint n, /*!< in: index of the field */ ulint *len) /*!< out: length of the field; UNIV_SQL_NULL if SQL null */ { ulint os; ulint next_os; ut_ad(len); ut_a(rec); ut_a(n < rec_get_n_fields_old_raw(rec)); if (rec_get_1byte_offs_flag(rec)) { os = rec_1_get_field_start_offs(rec, n); next_os = rec_1_get_field_end_info(rec, n); if (next_os & REC_1BYTE_SQL_NULL_MASK) { *len = UNIV_SQL_NULL; return (os); } next_os = next_os & ~REC_1BYTE_SQL_NULL_MASK; } else { os = rec_2_get_field_start_offs(rec, n); next_os = rec_2_get_field_end_info(rec, n); if (next_os & REC_2BYTE_SQL_NULL_MASK) { *len = UNIV_SQL_NULL; return (os); } next_os = next_os & ~(REC_2BYTE_SQL_NULL_MASK | REC_2BYTE_EXTERN_MASK); } *len = next_os - os; ut_ad(*len < UNIV_PAGE_SIZE); return (os); } /** Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT. @return total size */ UNIV_INLINE MY_ATTRIBUTE((warn_unused_result)) ulint rec_get_converted_size_comp_prefix_low( const dict_index_t *index, /*!< in: record descriptor; dict_table_is_comp() is assumed to hold, even if it does not */ const dfield_t *fields, /*!< in: array of data fields */ ulint n_fields, /*!< in: number of data fields */ const dtuple_t *v_entry, /*!< in: dtuple contains virtual column data */ ulint *extra, /*!< out: extra size */ ulint *status, /*!< in: status bits of the record, can be nullptr if unnecessary */ bool temp) /*!< in: whether this is a temporary file record */ { ulint extra_size = 0; ulint data_size; ulint i; ulint n_null = 0; ulint n_v_fields; ut_ad(n_fields <= dict_index_get_n_fields(index)); ut_ad(!temp || extra); /* At the time being, only temp file record could possible store virtual columns */ ut_ad(!v_entry || (index->is_clustered() && temp)); n_v_fields = v_entry ? dtuple_get_n_v_fields(v_entry) : 0; if (n_fields > 0) { n_null = index->has_instant_cols() ? index->get_n_nullable_before(static_cast(n_fields)) : index->n_nullable; } if (index->has_instant_cols() && status != nullptr) { switch (UNIV_EXPECT(*status, REC_STATUS_ORDINARY)) { case REC_STATUS_ORDINARY: ut_ad(!temp && n_fields > 0); extra_size += rec_get_n_fields_length(n_fields); break; case REC_STATUS_NODE_PTR: ut_ad(!temp && n_fields > 0); n_null = index->n_instant_nullable; break; case REC_STATUS_INFIMUM: case REC_STATUS_SUPREMUM: break; } } extra_size += temp ? UT_BITS_IN_BYTES(n_null) : REC_N_NEW_EXTRA_BYTES + UT_BITS_IN_BYTES(n_null); data_size = 0; if (temp && dict_table_is_comp(index->table)) { /* No need to do adjust fixed_len=0. We only need to adjust it for ROW_FORMAT=REDUNDANT. */ temp = false; } /* read the lengths of fields 0..n */ for (i = 0; i < n_fields; i++) { const dict_field_t *field; ulint len; ulint fixed_len; const dict_col_t *col; field = index->get_field(i); len = dfield_get_len(&fields[i]); col = field->col; #ifdef UNIV_DEBUG dtype_t *type; type = dfield_get_type(&fields[i]); if (dict_index_is_spatial(index)) { if (DATA_GEOMETRY_MTYPE(col->mtype) && i == 0) { ut_ad(type->prtype & DATA_GIS_MBR); } else { ut_ad(type->mtype == DATA_SYS_CHILD || col->assert_equal(type)); } } else { ut_ad(col->assert_equal(type)); } #endif /* All NULLable fields must be included in the n_null count. */ ut_ad((col->prtype & DATA_NOT_NULL) || n_null--); if (dfield_is_null(&fields[i])) { /* No length is stored for NULL fields. */ ut_ad(!(col->prtype & DATA_NOT_NULL)); continue; } ut_ad(len <= col->len || DATA_LARGE_MTYPE(col->mtype) || (DATA_POINT_MTYPE(col->mtype) && len == DATA_MBR_LEN) || (col->len == 0 && col->mtype == DATA_VARCHAR)); fixed_len = field->fixed_len; if (temp && fixed_len && !col->get_fixed_size(temp)) { fixed_len = 0; } /* If the maximum length of a variable-length field is up to 255 bytes, the actual length is always stored in one byte. If the maximum length is more than 255 bytes, the actual length is stored in one byte for 0..127. The length will be encoded in two bytes when it is 128 or more, or when the field is stored externally. */ if (fixed_len) { #ifdef UNIV_DEBUG ulint mbminlen = DATA_MBMINLEN(col->mbminmaxlen); ulint mbmaxlen = DATA_MBMAXLEN(col->mbminmaxlen); ut_ad(len <= fixed_len); if (dict_index_is_spatial(index)) { ut_ad(type->mtype == DATA_SYS_CHILD || !mbmaxlen || len >= mbminlen * (fixed_len / mbmaxlen)); } else { ut_ad(type->mtype != DATA_SYS_CHILD); ut_ad(!mbmaxlen || len >= mbminlen * (fixed_len / mbmaxlen)); } /* dict_index_add_col() should guarantee this */ ut_ad(!field->prefix_len || fixed_len == field->prefix_len); #endif /* UNIV_DEBUG */ } else if (dfield_is_ext(&fields[i])) { ut_ad(DATA_BIG_COL(col)); extra_size += 2; } else if (len < 128 || !DATA_BIG_COL(col)) { extra_size++; } else { /* For variable-length columns, we look up the maximum length from the column itself. If this is a prefix index column shorter than 256 bytes, this will waste one byte. */ extra_size += 2; } data_size += len; } if (extra) { *extra = extra_size; } /* Log virtual columns */ if (n_v_fields != 0) { /* length marker */ data_size += 2; for (i = 0; i < n_v_fields; i++) { dfield_t *vfield; ulint flen; const dict_v_col_t *col = dict_table_get_nth_v_col(index->table, i); /* Only those indexed needs to be logged */ if (col->m_col.ord_part || !dict_table_is_comp(index->table)) { data_size += mach_get_compressed_size(i + REC_MAX_N_FIELDS); vfield = dtuple_get_nth_v_field(v_entry, col->v_pos); if (dfield_is_multi_value(vfield)) { Multi_value_logger mv_logger( static_cast(dfield_get_data(vfield)), dfield_get_len(vfield)); data_size += mv_logger.get_log_len(true); } else { flen = vfield->len; if (flen != UNIV_SQL_NULL) { flen = ut_min( flen, static_cast(DICT_MAX_FIELD_LEN_BY_FORMAT(index->table))); data_size += flen; } data_size += mach_get_compressed_size(flen); } } } } return (extra_size + data_size); } /** Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT. @return total size */ ulint rec_get_converted_size_comp_prefix( const dict_index_t *index, /*!< in: record descriptor */ const dfield_t *fields, /*!< in: array of data fields */ ulint n_fields, /*!< in: number of data fields */ ulint *extra) /*!< out: extra size */ { ut_ad(dict_table_is_comp(index->table)); return (rec_get_converted_size_comp_prefix_low(index, fields, n_fields, NULL, extra, nullptr, false)); } /** Determines the size of a data tuple in ROW_FORMAT=COMPACT. @return total size */ ulint rec_get_converted_size_comp( const dict_index_t *index, /*!< in: record descriptor; dict_table_is_comp() is assumed to hold, even if it does not */ ulint status, /*!< in: status bits of the record */ const dfield_t *fields, /*!< in: array of data fields */ ulint n_fields, /*!< in: number of data fields */ ulint *extra) /*!< out: extra size */ { ulint size; ut_ad(n_fields > 0); switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) { case REC_STATUS_ORDINARY: /* If this is a record for instant index, it could has less fields when it comes from update path */ ut_ad(n_fields == dict_index_get_n_fields(index) || index->has_instant_cols()); size = 0; break; case REC_STATUS_NODE_PTR: n_fields--; ut_ad(n_fields == dict_index_get_n_unique_in_tree_nonleaf(index)); ut_ad(dfield_get_len(&fields[n_fields]) == REC_NODE_PTR_SIZE); size = REC_NODE_PTR_SIZE; /* child page number */ break; case REC_STATUS_INFIMUM: case REC_STATUS_SUPREMUM: /* infimum or supremum record, 8 data bytes */ if (UNIV_LIKELY_NULL(extra)) { *extra = REC_N_NEW_EXTRA_BYTES; } return (REC_N_NEW_EXTRA_BYTES + 8); default: ut_error; } return (size + rec_get_converted_size_comp_prefix_low( index, fields, n_fields, NULL, extra, &status, false)); } /** Sets the value of the ith field SQL null bit of an old-style record. */ void rec_set_nth_field_null_bit(rec_t *rec, /*!< in: record */ ulint i, /*!< in: ith field */ ibool val) /*!< in: value to set */ { ulint info; if (rec_get_1byte_offs_flag(rec)) { info = rec_1_get_field_end_info(rec, i); if (val) { info = info | REC_1BYTE_SQL_NULL_MASK; } else { info = info & ~REC_1BYTE_SQL_NULL_MASK; } rec_1_set_field_end_info(rec, i, info); return; } info = rec_2_get_field_end_info(rec, i); if (val) { info = info | REC_2BYTE_SQL_NULL_MASK; } else { info = info & ~REC_2BYTE_SQL_NULL_MASK; } rec_2_set_field_end_info(rec, i, info); } /** Sets an old-style record field to SQL null. The physical size of the field is not changed. */ void rec_set_nth_field_sql_null(rec_t *rec, /*!< in: record */ ulint n) /*!< in: index of the field */ { ulint offset; offset = rec_get_field_start_offs(rec, n); data_write_sql_null(rec + offset, rec_get_nth_field_size(rec, n)); rec_set_nth_field_null_bit(rec, n, TRUE); } /** Builds an old-style physical record out of a data tuple and stores it beginning from the start of the given buffer. @return pointer to the origin of physical record */ static rec_t *rec_convert_dtuple_to_rec_old( byte *buf, /*!< in: start address of the physical record */ const dtuple_t *dtuple, /*!< in: data tuple */ ulint n_ext) /*!< in: number of externally stored columns */ { const dfield_t *field; ulint n_fields; ulint data_size; rec_t *rec; ulint end_offset; ulint ored_offset; ulint len; ulint i; ut_ad(buf && dtuple); ut_ad(dtuple_validate(dtuple)); ut_ad(dtuple_check_typed(dtuple)); n_fields = dtuple_get_n_fields(dtuple); data_size = dtuple_get_data_size(dtuple, 0); ut_ad(n_fields > 0); /* Calculate the offset of the origin in the physical record */ rec = buf + rec_get_converted_extra_size(data_size, n_fields, n_ext); #ifdef UNIV_DEBUG /* Suppress Valgrind warnings of ut_ad() in mach_write_to_1(), mach_write_to_2() et al. */ memset(buf, 0xff, rec - buf + data_size); #endif /* UNIV_DEBUG */ /* Store the number of fields */ rec_set_n_fields_old(rec, n_fields); /* Set the info bits of the record */ rec_set_info_bits_old(rec, dtuple_get_info_bits(dtuple) & REC_INFO_BITS_MASK); /* Store the data and the offsets */ end_offset = 0; if (!n_ext && data_size <= REC_1BYTE_OFFS_LIMIT) { rec_set_1byte_offs_flag(rec, TRUE); for (i = 0; i < n_fields; i++) { field = dtuple_get_nth_field(dtuple, i); if (dfield_is_null(field)) { len = dtype_get_sql_null_size(dfield_get_type(field), 0); data_write_sql_null(rec + end_offset, len); end_offset += len; ored_offset = end_offset | REC_1BYTE_SQL_NULL_MASK; } else { /* If the data is not SQL null, store it */ len = dfield_get_len(field); memcpy(rec + end_offset, dfield_get_data(field), len); end_offset += len; ored_offset = end_offset; } rec_1_set_field_end_info(rec, i, ored_offset); } } else { rec_set_1byte_offs_flag(rec, FALSE); for (i = 0; i < n_fields; i++) { field = dtuple_get_nth_field(dtuple, i); if (dfield_is_null(field)) { len = dtype_get_sql_null_size(dfield_get_type(field), 0); data_write_sql_null(rec + end_offset, len); end_offset += len; ored_offset = end_offset | REC_2BYTE_SQL_NULL_MASK; } else { /* If the data is not SQL null, store it */ len = dfield_get_len(field); memcpy(rec + end_offset, dfield_get_data(field), len); end_offset += len; ored_offset = end_offset; if (dfield_is_ext(field)) { ored_offset |= REC_2BYTE_EXTERN_MASK; } } rec_2_set_field_end_info(rec, i, ored_offset); } } return (rec); } /** Builds a ROW_FORMAT=COMPACT record out of a data tuple. @param[in] rec origin of record @param[in] index record descriptor @param[in] fields array of data fields @param[in] n_fields number of data fields @param[in] v_entry dtuple contains virtual column data @param[in] status status bits of the record @param[in] temp whether to use the format for temporary files in index creation @return true if this record is an instant record on leaf page @retval false if not an instant record */ UNIV_INLINE bool rec_convert_dtuple_to_rec_comp(rec_t *rec, const dict_index_t *index, const dfield_t *fields, ulint n_fields, const dtuple_t *v_entry, ulint status, bool temp) { const dfield_t *field; const dtype_t *type; byte *end; byte *nulls; byte *lens = NULL; ulint len; ulint i; ulint n_node_ptr_field; ulint fixed_len; ulint null_mask = 1; ulint n_null = 0; ulint num_v = v_entry ? dtuple_get_n_v_fields(v_entry) : 0; bool instant = false; ut_ad(temp || dict_table_is_comp(index->table)); if (n_fields != 0) { n_null = index->has_instant_cols() ? index->get_n_nullable_before(static_cast(n_fields)) : index->n_nullable; } if (temp) { ut_ad(status == REC_STATUS_ORDINARY); ut_ad(n_fields <= dict_index_get_n_fields(index)); n_node_ptr_field = ULINT_UNDEFINED; nulls = rec - 1; if (dict_table_is_comp(index->table)) { /* No need to do adjust fixed_len=0. We only need to adjust it for ROW_FORMAT=REDUNDANT. */ temp = false; } } else { ut_ad(v_entry == NULL); ut_ad(num_v == 0); nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) { case REC_STATUS_ORDINARY: ut_ad(n_fields <= dict_index_get_n_fields(index)); n_node_ptr_field = ULINT_UNDEFINED; if (index->has_instant_cols()) { uint32_t n_fields_len; n_fields_len = rec_set_n_fields(rec, n_fields); nulls -= n_fields_len; instant = true; } break; case REC_STATUS_NODE_PTR: ut_ad(n_fields == static_cast( dict_index_get_n_unique_in_tree_nonleaf(index) + 1)); n_node_ptr_field = n_fields - 1; n_null = index->n_instant_nullable; break; case REC_STATUS_INFIMUM: case REC_STATUS_SUPREMUM: ut_ad(n_fields == 1); n_node_ptr_field = ULINT_UNDEFINED; break; default: ut_error; return (instant); } } end = rec; if (n_fields != 0) { lens = nulls - UT_BITS_IN_BYTES(n_null); /* clear the SQL-null flags */ memset(lens + 1, 0, nulls - lens); } /* Store the data and the offsets */ for (i = 0; i < n_fields; i++) { const dict_field_t *ifield; dict_col_t *col = NULL; field = &fields[i]; type = dfield_get_type(field); len = dfield_get_len(field); if (UNIV_UNLIKELY(i == n_node_ptr_field)) { ut_ad(dtype_get_prtype(type) & DATA_NOT_NULL); ut_ad(len == REC_NODE_PTR_SIZE); memcpy(end, dfield_get_data(field), len); end += REC_NODE_PTR_SIZE; break; } if (!(dtype_get_prtype(type) & DATA_NOT_NULL)) { /* nullable field */ ut_ad(n_null--); if (UNIV_UNLIKELY(!(byte)null_mask)) { nulls--; null_mask = 1; } ut_ad(*nulls < null_mask); /* set the null flag if necessary */ if (dfield_is_null(field)) { *nulls |= null_mask; null_mask <<= 1; continue; } null_mask <<= 1; } /* only nullable fields can be null */ ut_ad(!dfield_is_null(field)); ifield = index->get_field(i); fixed_len = ifield->fixed_len; col = ifield->col; if (temp && fixed_len && !col->get_fixed_size(temp)) { fixed_len = 0; } /* If the maximum length of a variable-length field is up to 255 bytes, the actual length is always stored in one byte. If the maximum length is more than 255 bytes, the actual length is stored in one byte for 0..127. The length will be encoded in two bytes when it is 128 or more, or when the field is stored externally. */ if (fixed_len) { #ifdef UNIV_DEBUG ulint mbminlen = DATA_MBMINLEN(col->mbminmaxlen); ulint mbmaxlen = DATA_MBMAXLEN(col->mbminmaxlen); ut_ad(len <= fixed_len); ut_ad(!mbmaxlen || len >= mbminlen * (fixed_len / mbmaxlen)); ut_ad(!dfield_is_ext(field)); #endif /* UNIV_DEBUG */ } else if (dfield_is_ext(field)) { ut_ad(DATA_BIG_COL(col)); ut_ad(len <= REC_ANTELOPE_MAX_INDEX_COL_LEN + BTR_EXTERN_FIELD_REF_SIZE); *lens-- = (byte)(len >> 8) | 0xc0; *lens-- = (byte)len; } else { /* DATA_POINT would have a fixed_len */ ut_ad(dtype_get_mtype(type) != DATA_POINT); #ifndef UNIV_HOTBACKUP ut_ad(len <= dtype_get_len(type) || DATA_LARGE_MTYPE(dtype_get_mtype(type)) || !strcmp(index->name, FTS_INDEX_TABLE_IND_NAME)); #endif /* !UNIV_HOTBACKUP */ if (len < 128 || !DATA_BIG_LEN_MTYPE(dtype_get_len(type), dtype_get_mtype(type))) { *lens-- = (byte)len; } else { ut_ad(len < 16384); *lens-- = (byte)(len >> 8) | 0x80; *lens-- = (byte)len; } } if (len > 0) memcpy(end, dfield_get_data(field), len); end += len; } if (!num_v) { return (instant); } /* reserve 2 bytes for writing length */ byte *ptr = end; ptr += 2; /* Now log information on indexed virtual columns */ for (ulint col_no = 0; col_no < num_v; col_no++) { dfield_t *vfield; ulint flen; const dict_v_col_t *col = dict_table_get_nth_v_col(index->table, col_no); if (col->m_col.ord_part || !dict_table_is_comp(index->table)) { ulint pos = col_no; pos += REC_MAX_N_FIELDS; ptr += mach_write_compressed(ptr, pos); vfield = dtuple_get_nth_v_field(v_entry, col->v_pos); if (dfield_is_multi_value(vfield)) { Multi_value_logger mv_logger( static_cast(dfield_get_data(vfield)), dfield_get_len(vfield)); mv_logger.log(&ptr); } else { flen = vfield->len; if (flen != UNIV_SQL_NULL) { /* The virtual column can only be in sec index, and index key length is bound by DICT_MAX_FIELD_LEN_BY_FORMAT */ flen = ut_min(flen, static_cast( DICT_MAX_FIELD_LEN_BY_FORMAT(index->table))); } ptr += mach_write_compressed(ptr, flen); if (flen != UNIV_SQL_NULL) { ut_memcpy(ptr, dfield_get_data(vfield), flen); ptr += flen; } } } } mach_write_to_2(end, ptr - end); return (instant); } /** Builds a new-style physical record out of a data tuple and stores it beginning from the start of the given buffer. @return pointer to the origin of physical record */ static rec_t *rec_convert_dtuple_to_rec_new( byte *buf, /*!< in: start address of the physical record */ const dict_index_t *index, /*!< in: record descriptor */ const dtuple_t *dtuple) /*!< in: data tuple */ { ulint extra_size; ulint status; rec_t *rec; bool instant; status = dtuple_get_info_bits(dtuple) & REC_NEW_STATUS_MASK; rec_get_converted_size_comp(index, status, dtuple->fields, dtuple->n_fields, &extra_size); rec = buf + extra_size; instant = rec_convert_dtuple_to_rec_comp( rec, index, dtuple->fields, dtuple->n_fields, NULL, status, false); /* Set the info bits of the record */ rec_set_info_and_status_bits(rec, dtuple_get_info_bits(dtuple)); if (instant) { ut_ad(index->has_instant_cols()); rec_set_instant_flag_new(rec, true); } else { rec_set_instant_flag_new(rec, false); } return (rec); } /** Builds a physical record out of a data tuple and stores it beginning from the start of the given buffer. @return pointer to the origin of physical record */ rec_t *rec_convert_dtuple_to_rec( byte *buf, /*!< in: start address of the physical record */ const dict_index_t *index, /*!< in: record descriptor */ const dtuple_t *dtuple, /*!< in: data tuple */ ulint n_ext) /*!< in: number of externally stored columns */ { rec_t *rec; ut_ad(buf != NULL); ut_ad(index != NULL); ut_ad(dtuple != NULL); ut_ad(dtuple_validate(dtuple)); ut_ad(dtuple_check_typed(dtuple)); if (dict_table_is_comp(index->table)) { rec = rec_convert_dtuple_to_rec_new(buf, index, dtuple); } else { rec = rec_convert_dtuple_to_rec_old(buf, dtuple, n_ext); } #ifdef UNIV_DEBUG /* Can't check this if it's an index with instantly added columns, because if it comes from UPDATE, the fields of dtuple may be less than the on from index itself. */ if (!index->has_instant_cols()) { mem_heap_t *heap = NULL; ulint offsets_[REC_OFFS_NORMAL_SIZE]; const ulint *offsets; ulint i; rec_offs_init(offsets_); offsets = rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap); ut_ad(rec_validate(rec, offsets)); ut_ad(dtuple_get_n_fields(dtuple) == rec_offs_n_fields(offsets)); for (i = 0; i < rec_offs_n_fields(offsets); i++) { ut_ad(!dfield_is_ext(dtuple_get_nth_field(dtuple, i)) == !rec_offs_nth_extern(offsets, i)); } if (UNIV_LIKELY_NULL(heap)) { mem_heap_free(heap); } } #endif /* UNIV_DEBUG */ return (rec); } #ifndef UNIV_HOTBACKUP /** Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT. @return total size */ ulint rec_get_converted_size_temp( const dict_index_t *index, /*!< in: record descriptor */ const dfield_t *fields, /*!< in: array of data fields */ ulint n_fields, /*!< in: number of data fields */ const dtuple_t *v_entry, /*!< in: dtuple contains virtual column data */ ulint *extra) /*!< out: extra size */ { return (rec_get_converted_size_comp_prefix_low( index, fields, n_fields, v_entry, extra, nullptr, true)); } /** Determine the offset to each field in temporary file. @see rec_convert_dtuple_to_temp() */ void rec_init_offsets_temp( const rec_t *rec, /*!< in: temporary file record */ const dict_index_t *index, /*!< in: record descriptor */ ulint *offsets) /*!< in/out: array of offsets; in: n=rec_offs_n_fields(offsets) */ { rec_init_offsets_comp_ordinary(rec, true, index, offsets); } /** Builds a temporary file record out of a data tuple. @see rec_init_offsets_temp() */ void rec_convert_dtuple_to_temp( rec_t *rec, /*!< out: record */ const dict_index_t *index, /*!< in: record descriptor */ const dfield_t *fields, /*!< in: array of data fields */ ulint n_fields, /*!< in: number of fields */ const dtuple_t *v_entry) /*!< in: dtuple contains virtual column data */ { rec_convert_dtuple_to_rec_comp(rec, index, fields, n_fields, v_entry, REC_STATUS_ORDINARY, true); } /** Copies the first n fields of a physical record to a data tuple. The fields are copied to the memory heap. */ void rec_copy_prefix_to_dtuple( dtuple_t *tuple, /*!< out: data tuple */ const rec_t *rec, /*!< in: physical record */ const dict_index_t *index, /*!< in: record descriptor */ ulint n_fields, /*!< in: number of fields to copy */ mem_heap_t *heap) /*!< in: memory heap */ { ulint i; ulint offsets_[REC_OFFS_NORMAL_SIZE]; ulint *offsets = offsets_; rec_offs_init(offsets_); offsets = rec_get_offsets(rec, index, offsets, n_fields, &heap); ut_ad(rec_validate(rec, offsets)); ut_ad(dtuple_check_typed(tuple)); dtuple_set_info_bits( tuple, rec_get_info_bits(rec, dict_table_is_comp(index->table))); for (i = 0; i < n_fields; i++) { dfield_t *field; const byte *data; ulint len; field = dtuple_get_nth_field(tuple, i); data = rec_get_nth_field_instant(rec, offsets, i, index, &len); if (len != UNIV_SQL_NULL) { dfield_set_data(field, mem_heap_dup(heap, data, len), len); ut_ad(!rec_offs_nth_extern(offsets, i)); } else { dfield_set_null(field); } } } /** Copies the first n fields of an old-style physical record to a new physical record in a buffer. @return own: copied record */ static rec_t *rec_copy_prefix_to_buf_old( const rec_t *rec, /*!< in: physical record */ ulint n_fields, /*!< in: number of fields to copy */ ulint area_end, /*!< in: end of the prefix data */ byte **buf, /*!< in/out: memory buffer for the copied prefix, or NULL */ size_t *buf_size) /*!< in/out: buffer size */ { rec_t *copy_rec; ulint area_start; ulint prefix_len; if (rec_get_1byte_offs_flag(rec)) { area_start = REC_N_OLD_EXTRA_BYTES + n_fields; } else { area_start = REC_N_OLD_EXTRA_BYTES + 2 * n_fields; } prefix_len = area_start + area_end; if ((*buf == NULL) || (*buf_size < prefix_len)) { ut_free(*buf); *buf_size = prefix_len; *buf = static_cast(ut_malloc_nokey(prefix_len)); } ut_memcpy(*buf, rec - area_start, prefix_len); copy_rec = *buf + area_start; rec_set_n_fields_old(copy_rec, n_fields); return (copy_rec); } rec_t *rec_copy_prefix_to_buf(const rec_t *rec, const dict_index_t *index, ulint n_fields, byte **buf, size_t *buf_size) { const byte *nulls; const byte *lens; uint16_t n_null; ulint i; ulint prefix_len; ulint null_mask; ulint status; bool is_rtr_node_ptr = false; UNIV_PREFETCH_RW(*buf); if (!dict_table_is_comp(index->table)) { ut_ad(rec_validate_old(rec)); return (rec_copy_prefix_to_buf_old( rec, n_fields, rec_get_field_start_offs(rec, n_fields), buf, buf_size)); } status = rec_get_status(rec); switch (status) { case REC_STATUS_ORDINARY: ut_ad(n_fields <= dict_index_get_n_fields(index)); break; case REC_STATUS_NODE_PTR: /* For R-tree, we need to copy the child page number field. */ if (dict_index_is_spatial(index)) { ut_ad(n_fields == DICT_INDEX_SPATIAL_NODEPTR_SIZE + 1); is_rtr_node_ptr = true; } else { /* it doesn't make sense to copy the child page number field */ ut_ad(n_fields <= dict_index_get_n_unique_in_tree_nonleaf(index)); } break; case REC_STATUS_INFIMUM: case REC_STATUS_SUPREMUM: /* infimum or supremum record: no sense to copy anything */ default: ut_error; return (NULL); } ut_d(uint16_t non_default_fields =) rec_init_null_and_len_comp(rec, index, &nulls, &lens, &n_null); ut_ad(!rec_get_instant_flag_new(rec) || n_fields <= non_default_fields); UNIV_PREFETCH_R(lens); prefix_len = 0; null_mask = 1; /* read the lengths of fields 0..n */ for (i = 0; i < n_fields; i++) { const dict_field_t *field; const dict_col_t *col; field = index->get_field(i); col = field->col; if (!(col->prtype & DATA_NOT_NULL)) { /* nullable field => read the null flag */ if (UNIV_UNLIKELY(!(byte)null_mask)) { nulls--; null_mask = 1; } if (*nulls & null_mask) { null_mask <<= 1; continue; } null_mask <<= 1; } if (is_rtr_node_ptr && i == 1) { /* For rtree node ptr rec, we need to copy the page no field with 4 bytes len. */ prefix_len += 4; } else if (field->fixed_len) { prefix_len += field->fixed_len; } else { ulint len = *lens--; /* If the maximum length of the column is up to 255 bytes, the actual length is always stored in one byte. If the maximum length is more than 255 bytes, the actual length is stored in one byte for 0..127. The length will be encoded in two bytes when it is 128 or more, or when the column is stored externally. */ if (DATA_BIG_COL(col)) { if (len & 0x80) { /* 1exxxxxx */ len &= 0x3f; len <<= 8; len |= *lens--; UNIV_PREFETCH_R(lens); } } prefix_len += len; } } UNIV_PREFETCH_R(rec + prefix_len); prefix_len += rec - (lens + 1); if ((*buf == NULL) || (*buf_size < prefix_len)) { ut_free(*buf); *buf_size = prefix_len; *buf = static_cast(ut_malloc_nokey(prefix_len)); } memcpy(*buf, lens + 1, prefix_len); return (*buf + (rec - (lens + 1))); } #endif /* UNIV_HOTBACKUP */ /** Validates the consistency of an old-style physical record. @return true if ok */ static ibool rec_validate_old(const rec_t *rec) /*!< in: physical record */ { ulint len; ulint n_fields; ulint len_sum = 0; ulint i; ut_a(rec); n_fields = rec_get_n_fields_old_raw(rec); if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) { ib::error(ER_IB_MSG_922) << "Record has " << n_fields << " fields"; return (FALSE); } for (i = 0; i < n_fields; i++) { rec_get_nth_field_offs_old(rec, i, &len); if (!((len < UNIV_PAGE_SIZE) || (len == UNIV_SQL_NULL))) { ib::error(ER_IB_MSG_923) << "Record field " << i << " len " << len; return (FALSE); } if (len != UNIV_SQL_NULL) { len_sum += len; } else { len_sum += rec_get_nth_field_size(rec, i); } } if (len_sum != rec_get_data_size_old(rec)) { ib::error(ER_IB_MSG_924) << "Record len should be " << len_sum << ", len " << rec_get_data_size_old(rec); return (FALSE); } return (TRUE); } /** Validates the consistency of a physical record. @return true if ok */ ibool rec_validate( const rec_t *rec, /*!< in: physical record */ const ulint *offsets) /*!< in: array returned by rec_get_offsets() */ { ulint len; uint16_t n_fields; ulint len_sum = 0; ulint i; uint16_t n_defaults = 0; ut_a(rec); n_fields = static_cast(rec_offs_n_fields(offsets)); if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) { ib::error(ER_IB_MSG_925) << "Record has " << n_fields << " fields"; return (FALSE); } for (i = 0; i < n_fields; i++) { rec_get_nth_field_offs(offsets, i, &len); switch (len) { case UNIV_SQL_ADD_COL_DEFAULT: ++n_defaults; break; case UNIV_SQL_NULL: if (!rec_offs_comp(offsets)) { /* If a default value is NULL, it will come here, however, this is not inlined, so don't count it */ if (i < rec_get_n_fields_old_raw(rec)) { len_sum += rec_get_nth_field_size(rec, i); } else { ++n_defaults; } } break; default: ut_a(n_defaults == 0); if (len >= UNIV_PAGE_SIZE) { ib::error(ER_IB_MSG_926) << "Record field " << i << " len " << len; return (FALSE); } len_sum += len; break; } } ut_a(rec_offs_comp(offsets) || n_fields <= rec_get_n_fields_old_raw(rec) + n_defaults); if (len_sum != rec_offs_data_size(offsets)) { ib::error(ER_IB_MSG_927) << "Record len should be " << len_sum << ", len " << rec_offs_data_size(offsets); return (FALSE); } if (!rec_offs_comp(offsets)) { ut_a(rec_validate_old(rec)); } return (TRUE); } /** Prints an old-style physical record. */ void rec_print_old(FILE *file, /*!< in: file where to print */ const rec_t *rec) /*!< in: physical record */ { const byte *data; ulint len; ulint n; ulint i; ut_ad(rec); n = rec_get_n_fields_old_raw(rec); fprintf(file, "PHYSICAL RECORD: n_fields %lu;" " %u-byte offsets; info bits %lu\n", (ulong)n, rec_get_1byte_offs_flag(rec) ? 1 : 2, (ulong)rec_get_info_bits(rec, FALSE)); for (i = 0; i < n; i++) { data = rec_get_nth_field_old(rec, i, &len); fprintf(file, " %lu:", (ulong)i); if (len != UNIV_SQL_NULL) { if (len <= 30) { ut_print_buf(file, data, len); } else { ut_print_buf(file, data, 30); fprintf(file, " (total %lu bytes)", (ulong)len); } } else { fprintf(file, " SQL NULL, size " ULINTPF " ", rec_get_nth_field_size(rec, i)); } putc(';', file); putc('\n', file); } rec_validate_old(rec); } #ifndef UNIV_HOTBACKUP /** Prints a physical record in ROW_FORMAT=COMPACT. Ignores the record header. */ static void rec_print_comp( FILE *file, /*!< in: file where to print */ const rec_t *rec, /*!< in: physical record */ const ulint *offsets) /*!< in: array returned by rec_get_offsets() */ { ulint i; for (i = 0; i < rec_offs_n_fields(offsets); i++) { const byte *data = nullptr; ulint len; if (rec_offs_nth_default(offsets, i)) { len = UNIV_SQL_ADD_COL_DEFAULT; } else { data = rec_get_nth_field(rec, offsets, i, &len); } fprintf(file, " %lu:", (ulong)i); switch (len) { case UNIV_SQL_NULL: fputs(" SQL NULL", file); break; case UNIV_SQL_ADD_COL_DEFAULT: fputs(" SQL DEFAULT", file); break; default: if (len <= 30) { ut_print_buf(file, data, len); } else if (rec_offs_nth_extern(offsets, i)) { ut_print_buf(file, data, 30); fprintf(file, " (total %lu bytes, external)", (ulong)len); ut_print_buf(file, data + len - BTR_EXTERN_FIELD_REF_SIZE, BTR_EXTERN_FIELD_REF_SIZE); } else { ut_print_buf(file, data, 30); fprintf(file, " (total %lu bytes)", (ulong)len); } } putc(';', file); putc('\n', file); } } /** Prints an old-style spatial index record. */ static void rec_print_mbr_old(FILE *file, /*!< in: file where to print */ const rec_t *rec) /*!< in: physical record */ { const byte *data; ulint len; ulint n; ulint i; ut_ad(rec); n = rec_get_n_fields_old_raw(rec); fprintf(file, "PHYSICAL RECORD: n_fields %lu;" " %u-byte offsets; info bits %lu\n", (ulong)n, rec_get_1byte_offs_flag(rec) ? 1 : 2, (ulong)rec_get_info_bits(rec, FALSE)); for (i = 0; i < n; i++) { data = rec_get_nth_field_old(rec, i, &len); fprintf(file, " %lu:", (ulong)i); if (len != UNIV_SQL_NULL) { if (i == 0) { fprintf(file, " MBR:"); for (; len > 0; len -= sizeof(double)) { double d = mach_double_read(data); if (len != sizeof(double)) { fprintf(file, "%.2lf,", d); } else { fprintf(file, "%.2lf", d); } data += sizeof(double); } } else { if (len <= 30) { ut_print_buf(file, data, len); } else { ut_print_buf(file, data, 30); fprintf(file, " (total %lu bytes)", (ulong)len); } } } else { fprintf(file, " SQL NULL, size " ULINTPF " ", rec_get_nth_field_size(rec, i)); } putc(';', file); putc('\n', file); } if (rec_get_deleted_flag(rec, false)) { fprintf(file, " Deleted"); } if (rec_get_info_bits(rec, true) & REC_INFO_MIN_REC_FLAG) { fprintf(file, " First rec"); } rec_validate_old(rec); } /** Prints a spatial index record. */ void rec_print_mbr_rec( FILE *file, /*!< in: file where to print */ const rec_t *rec, /*!< in: physical record */ const ulint *offsets) /*!< in: array returned by rec_get_offsets() */ { ut_ad(rec); ut_ad(offsets); ut_ad(rec_offs_validate(rec, NULL, offsets)); if (!rec_offs_comp(offsets)) { rec_print_mbr_old(file, rec); return; } for (ulint i = 0; i < rec_offs_n_fields(offsets); i++) { const byte *data; ulint len; ut_ad(!rec_offs_nth_default(offsets, i)); data = rec_get_nth_field(rec, offsets, i, &len); if (i == 0) { fprintf(file, " MBR:"); for (; len > 0; len -= sizeof(double)) { double d = mach_double_read(data); if (len != sizeof(double)) { fprintf(file, "%.2lf,", d); } else { fprintf(file, "%.2lf", d); } data += sizeof(double); } } else { fprintf(file, " %lu:", (ulong)i); if (len != UNIV_SQL_NULL) { if (len <= 30) { ut_print_buf(file, data, len); } else { ut_print_buf(file, data, 30); fprintf(file, " (total %lu bytes)", (ulong)len); } } else { fputs(" SQL NULL", file); } } putc(';', file); } if (rec_get_info_bits(rec, true) & REC_INFO_DELETED_FLAG) { fprintf(file, " Deleted"); } if (rec_get_info_bits(rec, true) & REC_INFO_MIN_REC_FLAG) { fprintf(file, " First rec"); } rec_validate(rec, offsets); } /** Prints a physical record. */ /** Prints a physical record. */ void rec_print_new( FILE *file, /*!< in: file where to print */ const rec_t *rec, /*!< in: physical record */ const ulint *offsets) /*!< in: array returned by rec_get_offsets() */ { ut_ad(rec); ut_ad(offsets); ut_ad(rec_offs_validate(rec, NULL, offsets)); #ifdef UNIV_DEBUG if (rec_get_deleted_flag(rec, rec_offs_comp(offsets))) { DBUG_PRINT("info", ("deleted ")); } else { DBUG_PRINT("info", ("not-deleted ")); } #endif /* UNIV_DEBUG */ if (!rec_offs_comp(offsets)) { rec_print_old(file, rec); return; } fprintf(file, "PHYSICAL RECORD: n_fields %lu;" " compact format; info bits %lu\n", (ulong)rec_offs_n_fields(offsets), (ulong)rec_get_info_bits(rec, TRUE)); rec_print_comp(file, rec, offsets); rec_validate(rec, offsets); } /** Prints a physical record. */ void rec_print(FILE *file, /*!< in: file where to print */ const rec_t *rec, /*!< in: physical record */ const dict_index_t *index) /*!< in: record descriptor */ { ut_ad(index); if (!dict_table_is_comp(index->table)) { rec_print_old(file, rec); return; } else { mem_heap_t *heap = NULL; ulint offsets_[REC_OFFS_NORMAL_SIZE]; rec_offs_init(offsets_); rec_print_new( file, rec, rec_get_offsets(rec, index, offsets_, ULINT_UNDEFINED, &heap)); if (UNIV_LIKELY_NULL(heap)) { mem_heap_free(heap); } } } /** Pretty-print a record. @param[in,out] o output stream @param[in] rec physical record @param[in] info rec_get_info_bits(rec) @param[in] offsets rec_get_offsets(rec) */ void rec_print(std::ostream &o, const rec_t *rec, ulint info, const ulint *offsets) { const ulint comp = rec_offs_comp(offsets); const ulint n = rec_offs_n_fields(offsets); ut_ad(rec_offs_validate(rec, NULL, offsets)); o << (comp ? "COMPACT RECORD" : "RECORD") << "(info_bits=" << info << ", " << n << " fields): {"; for (ulint i = 0; i < n; i++) { const byte *data; ulint len; if (i) { o << ','; } if (rec_offs_nth_default(offsets, i)) { o << "DEFAULT"; continue; } data = rec_get_nth_field(rec, offsets, i, &len); if (len == UNIV_SQL_NULL) { o << "NULL"; continue; } if (rec_offs_nth_extern(offsets, i)) { ulint local_len = len - BTR_EXTERN_FIELD_REF_SIZE; ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE); o << '[' << local_len << '+' << BTR_EXTERN_FIELD_REF_SIZE << ']'; ut_print_buf(o, data, local_len); ut_print_buf_hex(o, data + local_len, BTR_EXTERN_FIELD_REF_SIZE); } else { o << '[' << len << ']'; ut_print_buf(o, data, len); } } o << "}"; } /** Display a record. @param[in,out] o output stream @param[in] r record to display @return the output stream */ std::ostream &operator<<(std::ostream &o, const rec_index_print &r) { mem_heap_t *heap = NULL; ulint *offsets = rec_get_offsets(r.m_rec, r.m_index, NULL, ULINT_UNDEFINED, &heap); rec_print(o, r.m_rec, rec_get_info_bits(r.m_rec, rec_offs_comp(offsets)), offsets); mem_heap_free(heap); return (o); } /** Display a record. @param[in,out] o output stream @param[in] r record to display @return the output stream */ std::ostream &operator<<(std::ostream &o, const rec_offsets_print &r) { rec_print(o, r.m_rec, rec_get_info_bits(r.m_rec, rec_offs_comp(r.m_offsets)), r.m_offsets); return (o); } /** Reads the DB_TRX_ID of a clustered index record. @return the value of DB_TRX_ID */ trx_id_t rec_get_trx_id(const rec_t *rec, /*!< in: record */ const dict_index_t *index) /*!< in: clustered index */ { ulint trx_id_col = index->get_sys_col_pos(DATA_TRX_ID); const byte *trx_id; ulint len; mem_heap_t *heap = NULL; ulint offsets_[REC_OFFS_NORMAL_SIZE]; ulint *offsets = offsets_; rec_offs_init(offsets_); ut_ad(index->is_clustered()); ut_ad(trx_id_col > 0); ut_ad(trx_id_col != ULINT_UNDEFINED); #ifdef UNIV_DEBUG const page_t *page = page_align(rec); if (fil_page_index_page_check(page)) { ut_ad(mach_read_from_8(page + PAGE_HEADER + PAGE_INDEX_ID) == index->id); } #endif /* UNIV_DEBUG */ offsets = rec_get_offsets(rec, index, offsets, trx_id_col + 1, &heap); trx_id = rec_get_nth_field(rec, offsets, trx_id_col, &len); ut_ad(len == DATA_TRX_ID_LEN); if (heap) { mem_heap_free(heap); } return (trx_read_trx_id(trx_id)); } #endif /* !UNIV_HOTBACKUP */ /** Mark the nth field as externally stored. @param[in] offsets array returned by rec_get_offsets() @param[in] n nth field */ void rec_offs_make_nth_extern(ulint *offsets, const ulint n) { ut_ad(!rec_offs_nth_sql_null(offsets, n)); rec_offs_base(offsets)[1 + n] |= REC_OFFS_EXTERNAL; }