mariadb/storage/innobase/rem/rem0cmp.cc
Marko Mäkelä e245c6ab11 MDEV-35049: Use CRC-32C and avoid allocating heap
For the adaptive hash index, dtuple_fold() and rec_fold() were employing
a slow rolling hash algorithm, computing hash values ("fold") for one
field and one byte at a time, while depending on calls to
rec_get_offsets().

We already have optimized implementations of CRC-32C and have been
successfully using that function in some other InnoDB tables, but not
yet in the adaptive hash index.

Any linear function such as any CRC will fail the avalanche test that
any cryptographically secure hash function is expected to pass:
any single-bit change in the input key should affect on average half
the bits in the output.

But we always were happy with less than cryptographically secure:
in fact, ut_fold_ulint_pair() or ut_fold_binary() are just about as
linear as any CRC, using a combination of multiplication and addition,
partly carry-less. It is worth noting that exclusive-or corresponds to
carry-less subtraction or addition in a binary Galois field, or GF(2).

We only need some way of reducing key prefixes into hash values.
The CRC-32C should be better than a Rabin–Karp rolling hash algorithm.
Compared to the old hash algorithm, it has the drawback that there will
be only 32 bits of entropy before we choose the hash table cell by a
modulus operation. The size of each adaptive hash index array is
(innodb_buffer_pool_size / 512) / innodb_adaptive_hash_index_parts.
With the maximum number of partitions (512), we would not exceed 1<<32
elements per array until the buffer pool size exceeds 1<<50 bytes (1 PiB).
We would hit other limits before that: the virtual address space on many
contemporary 64-bit processor implementations is only 48 bits (256 TiB).
So, we can simply go for the SIMD accelerated CRC-32C.

rec_fold(): Take a combined parameter n_bytes_fields. Determine the
length of each field on the fly, and compute CRC-32C over a single
contiguous range of bytes, from the start of the record payload area
to the end of the last full or partial field. For secondary index records
in ROW_FORMAT=REDUNDANT, also the data area that is reserved for NULL
values (to facilitate in-place updates between NULL and NOT NULL values)
will be included in the count. Luckily, InnoDB always zero-initialized
such unused area; refer to data_write_sql_null() in
rec_convert_dtuple_to_rec_old(). For other than ROW_FORMAT=REDUNDANT,
no space is allocated for NULL values, and therefore the CRC-32C will
only cover the actual payload of the key prefix.

dtuple_fold(): For ROW_FORMAT=REDUNDANT, include the dummy NULL values
in the CRC-32C, so that the values will be comparable with rec_fold().

innodb_ahi-t: A unit test for rec_fold() and dtuple_fold().

btr_search_build_page_hash_index(), btr_search_drop_page_hash_index():
Use a fixed-size stack buffer for computing the fold values, to avoid
dynamic memory allocation.

btr_search_drop_page_hash_index(): Do not release part.latch if we
need to invoke multiple batches of rec_fold().

dtuple_t: Allocate fewer bits for the fields. The maximum number of
data fields is about 1023, so uint16_t will be fine for them. The
info_bits is stored in less than 1 byte.

ut_pair_min(), ut_pair_cmp(): Remove. We can actually combine and compare
int(n_fields << 16 | n_bytes).

PAGE_CUR_LE_OR_EXTENDS, PAGE_CUR_DBG: Remove. These were never defined,
because they would only work with latin1_swedish_ci if at all.

btr_cur_t::check_mismatch(): Replaces !btr_search_check_guess().

cmp_dtuple_rec_bytes(): Replaces cmp_dtuple_rec_with_match_bytes().
Determine the offsets of fields on the fly.

page_cur_try_search_shortcut_bytes(): This caller of
cmp_dtuple_rec_bytes() will not be invoked on the change buffer tree.

cmp_dtuple_rec_leaf(): Replaces cmp_dtuple_rec_with_match()
for comparing leaf-page records.

buf_block_t::ahi_left_bytes_fields: Consolidated Atomic_relaxed<uint32_t>
of curr_left_side << 31 | curr_n_bytes << 16 | curr_n_fields.
The other set of parameters (n_fields, n_bytes, left_side) was removed
as redundant.

btr_search_update_hash_node_on_insert(): Merged to
btr_search_update_hash_on_insert().

btr_search_build_page_hash_index(): Take combined left_bytes_fields
instead of n_fields, n_bytes, left_side.

btr_search_update_block_hash_info(), btr_search_update_hash_ref():
Merged to btr_search_info_update_hash().

btr_cur_t::n_bytes_fields: Replaces n_bytes << 16 | n_fields.

We also remove many redundant checks of btr_search.enabled.
If we are holding any btr_sea::partition::latch, then a nonnull pointer
in buf_block_t::index must imply that the adaptive hash index is enabled.

Reviewed by: Vladislav Lesin
2025-01-10 14:24:01 +02:00

703 lines
20 KiB
C++

/*****************************************************************************
Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2020, 2022, 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 rem/rem0cmp.cc
Comparison services for records
Created 7/1/1994 Heikki Tuuri
************************************************************************/
#include "rem0cmp.h"
#include "rem0rec.h"
#include "page0page.h"
#include "dict0mem.h"
#include "handler0alter.h"
/* ALPHABETICAL ORDER
==================
The records are put into alphabetical order in the following
way: let F be the first field where two records disagree.
If there is a character in some position n where the
records disagree, the order is determined by comparison of
the characters at position n, possibly after
collating transformation. If there is no such character,
but the corresponding fields have different lengths, then
if the data type of the fields is paddable,
shorter field is padded with a padding character. If the
data type is not paddable, longer field is considered greater.
Finally, the SQL null is bigger than any other value.
At the present, the comparison functions return 0 in the case,
where two records disagree only in the way that one
has more fields than the other. */
#ifndef DBUG_OFF
/** @return whether a data type is compatible with strnncoll() functions */
static bool is_strnncoll_compatible(ulint type)
{
switch (type) {
case MYSQL_TYPE_BIT:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_VARCHAR:
return true;
default:
return false;
}
}
#endif /* DBUG_OFF */
/*************************************************************//**
Returns TRUE if two columns are equal for comparison purposes.
@return TRUE if the columns are considered equal in comparisons */
ibool
cmp_cols_are_equal(
/*===============*/
const dict_col_t* col1, /*!< in: column 1 */
const dict_col_t* col2, /*!< in: column 2 */
ibool check_charsets)
/*!< in: whether to check charsets */
{
if (dtype_is_non_binary_string_type(col1->mtype, col1->prtype)
&& dtype_is_non_binary_string_type(col2->mtype, col2->prtype)) {
/* Both are non-binary string types: they can be compared if
and only if the charset-collation is the same */
if (check_charsets) {
return(dtype_get_charset_coll(col1->prtype)
== dtype_get_charset_coll(col2->prtype));
} else {
return(TRUE);
}
}
if (dtype_is_binary_string_type(col1->mtype, col1->prtype)
&& dtype_is_binary_string_type(col2->mtype, col2->prtype)) {
/* Both are binary string types: they can be compared */
return(TRUE);
}
if (col1->mtype != col2->mtype) {
return(FALSE);
}
if (col1->mtype == DATA_INT
&& (col1->prtype & DATA_UNSIGNED)
!= (col2->prtype & DATA_UNSIGNED)) {
/* The storage format of an unsigned integer is different
from a signed integer: in a signed integer we OR
0x8000... to the value of positive integers. */
return(FALSE);
}
return(col1->mtype != DATA_INT || col1->len == col2->len);
}
/** Compare two DATA_DECIMAL (MYSQL_TYPE_DECIMAL) fields.
TODO: Remove this function. Everything should use MYSQL_TYPE_NEWDECIMAL.
@param[in] a data field
@param[in] a_length length of a, in bytes (not UNIV_SQL_NULL)
@param[in] b data field
@param[in] b_length length of b, in bytes (not UNIV_SQL_NULL)
@return positive, 0, negative, if a is greater, equal, less than b,
respectively */
static ATTRIBUTE_COLD
int
cmp_decimal(const byte* a, ulint a_length, const byte* b, ulint b_length)
{
int swap_flag;
/* Remove preceding spaces */
for (; a_length && *a == ' '; a++, a_length--) { }
for (; b_length && *b == ' '; b++, b_length--) { }
if (*a == '-') {
swap_flag = -1;
if (*b != '-') {
return(swap_flag);
}
a++; b++;
a_length--;
b_length--;
} else {
swap_flag = 1;
if (*b == '-') {
return(swap_flag);
}
}
while (a_length > 0 && (*a == '+' || *a == '0')) {
a++; a_length--;
}
while (b_length > 0 && (*b == '+' || *b == '0')) {
b++; b_length--;
}
if (a_length != b_length) {
if (a_length < b_length) {
return(-swap_flag);
}
return(swap_flag);
}
while (a_length > 0 && *a == *b) {
a++; b++; a_length--;
}
if (a_length == 0) {
return(0);
}
if (*a <= *b) {
swap_flag = -swap_flag;
}
return(swap_flag);
}
int cmp_data_data(ulint mtype, ulint prtype, const byte *data1, ulint len1,
const byte *data2, ulint len2) noexcept
{
ut_ad(len1 != UNIV_SQL_DEFAULT);
ut_ad(len2 != UNIV_SQL_DEFAULT);
if (len1 == UNIV_SQL_NULL || len2 == UNIV_SQL_NULL)
{
if (len1 == len2)
return 0;
/* We define the SQL null to be the smallest possible value of a field. */
return len1 == UNIV_SQL_NULL ? -1 : 1;
}
switch (mtype) {
default:
ib::fatal() << "Unknown data type number " << mtype;
case DATA_DECIMAL:
return cmp_decimal(data1, len1, data2, len2);
case DATA_DOUBLE:
{
const double af= mach_double_read(data1), bf= mach_double_read(data2);
return af > bf ? 1 : bf > af ? -1 : 0;
}
case DATA_FLOAT:
{
const float af= mach_float_read(data1), bf= mach_float_read(data2);
return af > bf ? 1 : bf > af ? -1 : 0;
}
case DATA_FIXBINARY:
case DATA_BINARY:
if (dtype_get_charset_coll(prtype) != DATA_MYSQL_BINARY_CHARSET_COLL)
{
if (ulint len= std::min(len1, len2))
{
if (int cmp= memcmp(data1, data2, len))
return cmp;
data1+= len;
data2+= len;
len1-= len;
len2-= len;
}
int cmp= 0;
if (len1)
{
const byte *end= &data1[len1];
do
cmp= static_cast<int>(*data1++ - byte{0x20});
while (cmp == 0 && data1 < end);
}
else if (len2)
{
const byte *end= &data2[len2];
do
cmp= static_cast<int>(byte{0x20} - *data2++);
while (cmp == 0 && data2 < end);
}
return cmp;
}
/* fall through */
case DATA_INT:
case DATA_SYS_CHILD:
case DATA_SYS:
break;
case DATA_GEOMETRY:
ut_ad(prtype & DATA_BINARY_TYPE);
if (prtype & DATA_GIS_MBR)
{
ut_ad(len1 == DATA_MBR_LEN);
ut_ad(len2 == DATA_MBR_LEN);
return cmp_geometry_field(data1, data2);
}
break;
case DATA_BLOB:
if (prtype & DATA_BINARY_TYPE)
break;
/* fall through */
case DATA_VARMYSQL:
DBUG_ASSERT(is_strnncoll_compatible(prtype & DATA_MYSQL_TYPE_MASK));
if (CHARSET_INFO *cs= all_charsets[dtype_get_charset_coll(prtype)])
return cs->coll->strnncollsp(cs, data1, len1, data2, len2);
no_collation:
ib::fatal() << "Unable to find charset-collation for " << prtype;
case DATA_MYSQL:
DBUG_ASSERT(is_strnncoll_compatible(prtype & DATA_MYSQL_TYPE_MASK));
if (CHARSET_INFO *cs= all_charsets[dtype_get_charset_coll(prtype)])
return cs->coll->strnncollsp_nchars(cs, data1, len1, data2, len2,
std::max(len1, len2),
MY_STRNNCOLLSP_NCHARS_EMULATE_TRIMMED_TRAILING_SPACES);
goto no_collation;
case DATA_VARCHAR:
case DATA_CHAR:
/* latin1_swedish_ci is treated as a special case in InnoDB.
Because it is a fixed-length encoding (mbminlen=mbmaxlen=1),
non-NULL CHAR(n) values will always occupy n bytes and we
can invoke strnncollsp() instead of strnncollsp_nchars(). */
return my_charset_latin1.strnncollsp(data1, len1, data2, len2);
}
if (ulint len= std::min(len1, len2))
if (int cmp= memcmp(data1, data2, len))
return cmp;
return len1 > len2 ? 1 : len2 > len1 ? -1 : 0;
}
/** Compare a data tuple to a physical record.
@param[in] dtuple data tuple
@param[in] rec B-tree record
@param[in] offsets rec_get_offsets(rec)
@param[in] n_cmp number of fields to compare
@param[in,out] matched_fields number of completely matched fields
@return the comparison result of dtuple and rec
@retval 0 if dtuple is equal to rec
@retval negative if dtuple is less than rec
@retval positive if dtuple is greater than rec */
int
cmp_dtuple_rec_with_match_low(
const dtuple_t* dtuple,
const rec_t* rec,
const rec_offs* offsets,
ulint n_cmp,
uint16_t* matched_fields)
{
int ret = 0; /* return value */
ut_ad(dtuple_check_typed(dtuple));
ut_ad(rec_offs_validate(rec, NULL, offsets));
auto cur_field = *matched_fields;
ut_ad(n_cmp > 0);
ut_ad(n_cmp <= dtuple_get_n_fields(dtuple));
ut_ad(cur_field <= n_cmp);
ut_ad(cur_field <= rec_offs_n_fields(offsets));
if (cur_field == 0) {
ulint rec_info = rec_get_info_bits(rec,
rec_offs_comp(offsets));
ulint tup_info = dtuple_get_info_bits(dtuple);
if (UNIV_UNLIKELY(rec_info & REC_INFO_MIN_REC_FLAG)) {
return !(tup_info & REC_INFO_MIN_REC_FLAG);
} else if (UNIV_UNLIKELY(tup_info & REC_INFO_MIN_REC_FLAG)) {
return -1;
}
}
/* Match fields in a loop */
for (; cur_field < n_cmp; cur_field++) {
const byte* rec_b_ptr;
const dfield_t* dtuple_field
= dtuple_get_nth_field(dtuple, cur_field);
const byte* dtuple_b_ptr
= static_cast<const byte*>(
dfield_get_data(dtuple_field));
const dtype_t* type
= dfield_get_type(dtuple_field);
ulint dtuple_f_len
= dfield_get_len(dtuple_field);
ulint rec_f_len;
/* We should never compare against an externally
stored field. Only clustered index records can
contain externally stored fields, and the first fields
(primary key fields) should already differ. */
ut_ad(!rec_offs_nth_extern(offsets, cur_field));
/* We should never compare against instantly added columns.
Columns can only be instantly added to clustered index
leaf page records, and the first fields (primary key fields)
should already differ. */
ut_ad(!rec_offs_nth_default(offsets, cur_field));
rec_b_ptr = rec_get_nth_field(rec, offsets, cur_field,
&rec_f_len);
ut_ad(!dfield_is_ext(dtuple_field));
ret = cmp_data_data(type->mtype, type->prtype,
dtuple_b_ptr, dtuple_f_len,
rec_b_ptr, rec_f_len);
if (ret) {
break;
}
}
*matched_fields = cur_field;
return(ret);
}
/** Compare a data tuple to a physical record.
@see cmp_dtuple_rec_with_match
@param[in] dtuple data tuple
@param[in] rec B-tree record
@param[in] offsets rec_get_offsets(rec); may be NULL
for ROW_FORMAT=REDUNDANT
@return the comparison result of dtuple and rec
@retval 0 if dtuple is equal to rec
@retval negative if dtuple is less than rec
@retval positive if dtuple is greater than rec */
int
cmp_dtuple_rec(
const dtuple_t* dtuple,
const rec_t* rec,
const rec_offs* offsets)
{
uint16_t matched_fields= 0;
return cmp_dtuple_rec_with_match(dtuple, rec, offsets, &matched_fields);
}
/**************************************************************//**
Checks if a dtuple is a prefix of a record. The last field in dtuple
is allowed to be a prefix of the corresponding field in the record.
@return TRUE if prefix */
ibool
cmp_dtuple_is_prefix_of_rec(
/*========================*/
const dtuple_t* dtuple, /*!< in: data tuple */
const rec_t* rec, /*!< in: physical record */
const rec_offs* offsets)/*!< in: array returned by rec_get_offsets() */
{
uint16_t matched_fields = 0;
ut_ad(rec_offs_validate(rec, NULL, offsets));
uint16_t n_fields = dtuple_get_n_fields(dtuple);
if (n_fields > rec_offs_n_fields(offsets)) {
ut_ad(0);
return(FALSE);
}
cmp_dtuple_rec_with_match(dtuple, rec, offsets, &matched_fields);
return(matched_fields == n_fields);
}
/*************************************************************//**
Compare two physical record fields.
@retval positive if rec1 field is greater than rec2
@retval negative if rec1 field is less than rec2
@retval 0 if rec1 field equals to rec2 */
static MY_ATTRIBUTE((nonnull, warn_unused_result))
int
cmp_rec_rec_simple_field(
/*=====================*/
const rec_t* rec1, /*!< in: physical record */
const rec_t* rec2, /*!< in: physical record */
const rec_offs* offsets1,/*!< in: rec_get_offsets(rec1, ...) */
const rec_offs* offsets2,/*!< in: rec_get_offsets(rec2, ...) */
const dict_index_t* index, /*!< in: data dictionary index */
ulint n) /*!< in: field to compare */
{
const byte* rec1_b_ptr;
const byte* rec2_b_ptr;
ulint rec1_f_len;
ulint rec2_f_len;
const dict_col_t* col = dict_index_get_nth_col(index, n);
ut_ad(!rec_offs_nth_extern(offsets1, n));
ut_ad(!rec_offs_nth_extern(offsets2, n));
rec1_b_ptr = rec_get_nth_field(rec1, offsets1, n, &rec1_f_len);
rec2_b_ptr = rec_get_nth_field(rec2, offsets2, n, &rec2_f_len);
return cmp_data_data(col->mtype, col->prtype, rec1_b_ptr, rec1_f_len,
rec2_b_ptr, rec2_f_len);
}
/** Compare two physical records that contain the same number of columns,
none of which are stored externally.
@retval positive if rec1 (including non-ordering columns) is greater than rec2
@retval negative if rec1 (including non-ordering columns) is less than rec2
@retval 0 if rec1 is a duplicate of rec2 */
int
cmp_rec_rec_simple(
/*===============*/
const rec_t* rec1, /*!< in: physical record */
const rec_t* rec2, /*!< in: physical record */
const rec_offs* offsets1,/*!< in: rec_get_offsets(rec1, ...) */
const rec_offs* offsets2,/*!< in: rec_get_offsets(rec2, ...) */
const dict_index_t* index, /*!< in: data dictionary index */
struct TABLE* table) /*!< in: MySQL table, for reporting
duplicate key value if applicable,
or NULL */
{
ulint n;
ulint n_uniq = dict_index_get_n_unique(index);
bool null_eq = false;
ut_ad(rec_offs_n_fields(offsets1) >= n_uniq);
ut_ad(rec_offs_n_fields(offsets2) == rec_offs_n_fields(offsets2));
ut_ad(rec_offs_comp(offsets1) == rec_offs_comp(offsets2));
for (n = 0; n < n_uniq; n++) {
int cmp = cmp_rec_rec_simple_field(
rec1, rec2, offsets1, offsets2, index, n);
if (cmp) {
return(cmp);
}
/* If the fields are internally equal, they must both
be NULL or non-NULL. */
ut_ad(rec_offs_nth_sql_null(offsets1, n)
== rec_offs_nth_sql_null(offsets2, n));
if (rec_offs_nth_sql_null(offsets1, n)) {
ut_ad(!(dict_index_get_nth_col(index, n)->prtype
& DATA_NOT_NULL));
null_eq = true;
}
}
/* If we ran out of fields, the ordering columns of rec1 were
equal to rec2. Issue a duplicate key error if needed. */
if (!null_eq && table && dict_index_is_unique(index)) {
/* Report erroneous row using new version of table. */
innobase_rec_to_mysql(table, rec1, index, offsets1);
return(0);
}
/* Else, keep comparing so that we have the full internal
order. */
for (; n < dict_index_get_n_fields(index); n++) {
int cmp = cmp_rec_rec_simple_field(
rec1, rec2, offsets1, offsets2, index, n);
if (cmp) {
return(cmp);
}
/* If the fields are internally equal, they must both
be NULL or non-NULL. */
ut_ad(rec_offs_nth_sql_null(offsets1, n)
== rec_offs_nth_sql_null(offsets2, n));
}
/* This should never be reached. Internally, an index must
never contain duplicate entries. */
ut_ad(0);
return(0);
}
/** Compare two B-tree or R-tree records.
Only the common first fields are compared, and externally stored field
are treated as equal.
@param[in] rec1 record (possibly not on an index page)
@param[in] rec2 B-tree or R-tree record in an index page
@param[in] offsets1 rec_get_offsets(rec1, index)
@param[in] offsets2 rec_get_offsets(rec2, index)
@param[in] nulls_unequal true if this is for index cardinality
statistics estimation with
innodb_stats_method=nulls_unequal
or innodb_stats_method=nulls_ignored
@param[out] matched_fields number of completely matched fields
within the first field not completely matched
@retval 0 if rec1 is equal to rec2
@retval negative if rec1 is less than rec2
@retval positive if rec1 is greater than rec2 */
int
cmp_rec_rec(
const rec_t* rec1,
const rec_t* rec2,
const rec_offs* offsets1,
const rec_offs* offsets2,
const dict_index_t* index,
bool nulls_unequal,
ulint* matched_fields)
{
ulint rec1_f_len; /* length of current field in rec */
const byte* rec1_b_ptr; /* pointer to the current byte
in rec field */
ulint rec2_f_len; /* length of current field in rec */
const byte* rec2_b_ptr; /* pointer to the current byte
in rec field */
ulint cur_field = 0; /* current field number */
int ret = 0; /* return value */
ut_ad(rec1 != NULL);
ut_ad(rec2 != NULL);
ut_ad(index != NULL);
ut_ad(rec_offs_validate(rec1, index, offsets1));
ut_ad(rec_offs_validate(rec2, index, offsets2));
ut_ad(rec_offs_comp(offsets1) == rec_offs_comp(offsets2));
ut_ad(fil_page_index_page_check(page_align(rec2)));
ut_ad(!!dict_index_is_spatial(index)
== (fil_page_get_type(page_align(rec2)) == FIL_PAGE_RTREE));
ulint comp = rec_offs_comp(offsets1);
ulint n_fields;
/* Test if rec is the predefined minimum record */
if (UNIV_UNLIKELY(rec_get_info_bits(rec1, comp)
& REC_INFO_MIN_REC_FLAG)) {
ret = UNIV_UNLIKELY(rec_get_info_bits(rec2, comp)
& REC_INFO_MIN_REC_FLAG)
? 0 : -1;
goto order_resolved;
} else if (UNIV_UNLIKELY
(rec_get_info_bits(rec2, comp)
& REC_INFO_MIN_REC_FLAG)) {
ret = 1;
goto order_resolved;
}
/* For non-leaf spatial index records, the
dict_index_get_n_unique_in_tree() does include the child page
number, because spatial index node pointers only contain
the MBR (minimum bounding rectangle) and the child page number.
For B-tree node pointers, the key alone (secondary index
columns and PRIMARY KEY columns) must be unique, and there is
no need to compare the child page number. */
n_fields = std::min(rec_offs_n_fields(offsets1),
rec_offs_n_fields(offsets2));
n_fields = std::min<ulint>(n_fields,
dict_index_get_n_unique_in_tree(index));
for (; cur_field < n_fields; cur_field++) {
ulint mtype;
ulint prtype;
if (UNIV_UNLIKELY(dict_index_is_ibuf(index))) {
/* This is for the insert buffer B-tree. */
mtype = DATA_BINARY;
prtype = 0;
} else {
const dict_col_t* col = dict_index_get_nth_col(
index, cur_field);
mtype = col->mtype;
prtype = col->prtype;
if (UNIV_LIKELY(!dict_index_is_spatial(index))) {
} else if (cur_field == 0) {
ut_ad(DATA_GEOMETRY_MTYPE(mtype));
prtype |= DATA_GIS_MBR;
} else if (!page_rec_is_leaf(rec2)) {
/* Compare the child page number. */
ut_ad(cur_field == 1);
mtype = DATA_SYS_CHILD;
prtype = 0;
}
}
/* We should never encounter an externally stored field.
Externally stored fields only exist in clustered index
leaf page records. These fields should already differ
in the primary key columns already, before DB_TRX_ID,
DB_ROLL_PTR, and any externally stored columns. */
ut_ad(!rec_offs_nth_extern(offsets1, cur_field));
ut_ad(!rec_offs_nth_extern(offsets2, cur_field));
ut_ad(!rec_offs_nth_default(offsets1, cur_field));
ut_ad(!rec_offs_nth_default(offsets2, cur_field));
rec1_b_ptr = rec_get_nth_field(rec1, offsets1,
cur_field, &rec1_f_len);
rec2_b_ptr = rec_get_nth_field(rec2, offsets2,
cur_field, &rec2_f_len);
if (nulls_unequal
&& rec1_f_len == UNIV_SQL_NULL
&& rec2_f_len == UNIV_SQL_NULL) {
ret = -1;
goto order_resolved;
}
ret = cmp_data_data(mtype, prtype,
rec1_b_ptr, rec1_f_len,
rec2_b_ptr, rec2_f_len);
if (ret) {
goto order_resolved;
}
}
/* If we ran out of fields, rec1 was equal to rec2 up
to the common fields */
ut_ad(ret == 0);
order_resolved:
if (matched_fields) {
*matched_fields = cur_field;
}
return ret;
}
#ifdef UNIV_COMPILE_TEST_FUNCS
#ifdef HAVE_UT_CHRONO_T
void
test_cmp_data_data(ulint len)
{
int i;
static byte zeros[64];
if (len > sizeof zeros) {
len = sizeof zeros;
}
ut_chrono_t ch(__func__);
for (i = 1000000; i > 0; i--) {
i += cmp_data_data(DATA_INT, 0, zeros, len, zeros, len);
}
}
#endif /* HAVE_UT_CHRONO_T */
#endif /* UNIV_COMPILE_TEST_FUNCS */