mariadb/storage/innobase/rem/rem0cmp.cc
Marko Mäkelä 4dcb1b575b 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 16:39:44 +02:00

675 lines
20 KiB
C++

/*****************************************************************************
Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2020, 2023, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
*****************************************************************************/
/*******************************************************************//**
@file 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);
}
/** Compare two data fields.
@param mtype main type
@param prtype precise type
@param descending whether to use descending order
@param data1 data field
@param len1 length of data1 in bytes, or UNIV_SQL_NULL
@param data2 data field
@param len2 length of data2 in bytes, or UNIV_SQL_NULL
@return the comparison result of data1 and data2
@retval 0 if data1 is equal to data2
@retval negative if data1 is less than data2
@retval positive if data1 is greater than data2 */
int cmp_data(ulint mtype, ulint prtype, bool descending,
const byte *data1, size_t len1, const byte *data2, size_t len2)
noexcept
{
ut_ad(len1 != UNIV_SQL_DEFAULT);
ut_ad(len2 != UNIV_SQL_DEFAULT);
int cmp= 0;
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. */
cmp= len1 == UNIV_SQL_NULL ? -1 : 1;
func_exit:
return UNIV_UNLIKELY(descending) ? -cmp : cmp;
}
switch (mtype) {
default:
ib::fatal() << "Unknown data type number " << mtype;
case DATA_DECIMAL:
cmp= cmp_decimal(data1, len1, data2, len2);
goto func_exit;
case DATA_DOUBLE:
{
const double af= mach_double_read(data1), bf= mach_double_read(data2);
cmp= af > bf ? 1 : bf > af ? -1 : 0;
}
goto func_exit;
case DATA_FLOAT:
{
const float af= mach_float_read(data1), bf= mach_float_read(data2);
cmp= af > bf ? 1 : bf > af ? -1 : 0;
}
goto func_exit;
case DATA_FIXBINARY:
case DATA_BINARY:
if (dtype_get_charset_coll(prtype) != DATA_MYSQL_BINARY_CHARSET_COLL)
{
if (ulint len= std::min(len1, len2))
{
cmp= memcmp(data1, data2, len);
if (cmp)
goto func_exit;
data1+= len;
data2+= len;
len1-= len;
len2-= len;
}
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);
}
goto func_exit;
}
/* 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);
cmp= cmp_geometry_field(data1, data2);
goto func_exit;
}
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)])
{
cmp= cs->coll->strnncollsp(cs, data1, len1, data2, len2);
goto func_exit;
}
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)])
{
cmp= cs->coll->
strnncollsp_nchars(cs, data1, len1, data2, len2, std::max(len1, len2),
MY_STRNNCOLLSP_NCHARS_EMULATE_TRIMMED_TRAILING_SPACES);
goto func_exit;
}
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(). */
cmp= my_charset_latin1.strnncollsp(data1, len1, data2, len2);
goto func_exit;
}
if (ulint len= std::min(len1, len2))
{
cmp= memcmp(data1, data2, len);
if (cmp)
goto func_exit;
}
cmp= int(len1 - len2);
goto func_exit;
}
/** Compare a data tuple to a physical record.
@param dtuple data tuple
@param rec B-tree index record
@param index B-tree index
@param offsets rec_get_offsets(rec,index)
@param n_cmp number of fields to compare
@param 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 dict_index_t *index,
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, index, 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);
/* The "infimum node pointer" is always first. */
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(type->mtype, type->prtype,
index->fields[cur_field].descending,
dtuple_b_ptr, dtuple_f_len,
rec_b_ptr, rec_f_len);
if (ret) {
break;
}
}
*matched_fields = cur_field;
return ret;
}
/** Check if a dtuple is a prefix of a record.
@param dtuple data tuple
@param rec index record
@param index index
@param offsets rec_get_offsets(rec)
@return whether dtuple is a prefix of rec */
bool cmp_dtuple_is_prefix_of_rec(const dtuple_t *dtuple, const rec_t *rec,
const dict_index_t *index,
const rec_offs *offsets)
{
uint16_t matched_fields= 0, n_fields= dtuple_get_n_fields(dtuple);
ut_ad(n_fields <= rec_offs_n_fields(offsets));
cmp_dtuple_rec_with_match(dtuple, rec, index, 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_field_t* field = dict_index_get_nth_field(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(field->col->mtype, field->col->prtype,
field->descending,
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 && index->is_unique()) {
if (table) {
/* 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++) {
const dict_field_t* field = dict_index_get_nth_field(
index, cur_field);
bool descending = field->descending;
ulint mtype = field->col->mtype;
ulint prtype = field->col->prtype;
if (UNIV_LIKELY(!index->is_spatial())) {
} 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(mtype, prtype, descending,
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;
}