mirror of
https://github.com/MariaDB/server.git
synced 2025-01-28 17:54:16 +01:00
960 lines
27 KiB
C++
960 lines
27 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);
|
|
}
|
|
|
|
/** Compare two data fields.
|
|
@param mtype main type
|
|
@param prtype precise type
|
|
@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 */
|
|
static int cmp_data(ulint mtype, ulint prtype, const byte *data1, ulint len1,
|
|
const byte *data2, ulint len2)
|
|
{
|
|
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= get_charset(dtype_get_charset_coll(prtype),
|
|
MYF(MY_WME)))
|
|
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= get_charset(dtype_get_charset_coll(prtype),
|
|
MYF(MY_WME)))
|
|
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 two data fields.
|
|
@param[in] mtype main type
|
|
@param[in] prtype precise type
|
|
@param[in] data1 data field
|
|
@param[in] len1 length of data1 in bytes, or UNIV_SQL_NULL
|
|
@param[in] data2 data field
|
|
@param[in] 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_data(
|
|
ulint mtype,
|
|
ulint prtype,
|
|
const byte* data1,
|
|
ulint len1,
|
|
const byte* data2,
|
|
ulint len2)
|
|
{
|
|
return(cmp_data(mtype, prtype, data1, len1, data2, len2));
|
|
}
|
|
|
|
/** 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,
|
|
ulint* matched_fields)
|
|
{
|
|
ulint cur_field; /* current field number */
|
|
int ret; /* return value */
|
|
|
|
ut_ad(dtuple_check_typed(dtuple));
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
|
|
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)) {
|
|
ret = !(tup_info & REC_INFO_MIN_REC_FLAG);
|
|
goto order_resolved;
|
|
} else if (UNIV_UNLIKELY(tup_info & REC_INFO_MIN_REC_FLAG)) {
|
|
ret = -1;
|
|
goto order_resolved;
|
|
}
|
|
}
|
|
|
|
/* 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,
|
|
dtuple_b_ptr, dtuple_f_len,
|
|
rec_b_ptr, rec_f_len);
|
|
if (ret) {
|
|
goto order_resolved;
|
|
}
|
|
}
|
|
|
|
ret = 0; /* If we ran out of fields, dtuple was equal to rec
|
|
up to the common fields */
|
|
order_resolved:
|
|
*matched_fields = cur_field;
|
|
return(ret);
|
|
}
|
|
|
|
/** Get the pad character code point for a type.
|
|
@param[in] type
|
|
@return pad character code point
|
|
@retval ULINT_UNDEFINED if no padding is specified */
|
|
UNIV_INLINE
|
|
ulint
|
|
cmp_get_pad_char(
|
|
const dtype_t* type)
|
|
{
|
|
switch (type->mtype) {
|
|
case DATA_FIXBINARY:
|
|
case DATA_BINARY:
|
|
if (dtype_get_charset_coll(type->prtype)
|
|
== DATA_MYSQL_BINARY_CHARSET_COLL) {
|
|
/* Starting from 5.0.18, do not pad
|
|
VARBINARY or BINARY columns. */
|
|
return(ULINT_UNDEFINED);
|
|
}
|
|
/* Fall through */
|
|
case DATA_CHAR:
|
|
case DATA_VARCHAR:
|
|
case DATA_MYSQL:
|
|
case DATA_VARMYSQL:
|
|
/* Space is the padding character for all char and binary
|
|
strings, and starting from 5.0.3, also for TEXT strings. */
|
|
return(0x20);
|
|
case DATA_GEOMETRY:
|
|
/* DATA_GEOMETRY is binary data, not ASCII-based. */
|
|
return(ULINT_UNDEFINED);
|
|
case DATA_BLOB:
|
|
if (!(type->prtype & DATA_BINARY_TYPE)) {
|
|
return(0x20);
|
|
}
|
|
/* Fall through */
|
|
default:
|
|
/* No padding specified */
|
|
return(ULINT_UNDEFINED);
|
|
}
|
|
}
|
|
|
|
/** Compare a data tuple to a physical record.
|
|
@param[in] dtuple data tuple
|
|
@param[in] rec B-tree or R-tree index record
|
|
@param[in] index index tree
|
|
@param[in] offsets rec_get_offsets(rec)
|
|
@param[in,out] matched_fields number of completely matched fields
|
|
@param[in,out] matched_bytes number of matched bytes in the first
|
|
field that is not matched
|
|
@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_bytes(
|
|
const dtuple_t* dtuple,
|
|
const rec_t* rec,
|
|
const dict_index_t* index,
|
|
const rec_offs* offsets,
|
|
ulint* matched_fields,
|
|
ulint* matched_bytes)
|
|
{
|
|
ut_ad(dtuple_check_typed(dtuple));
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
ut_ad(!(REC_INFO_MIN_REC_FLAG
|
|
& dtuple_get_info_bits(dtuple)));
|
|
|
|
if (UNIV_UNLIKELY(REC_INFO_MIN_REC_FLAG
|
|
& rec_get_info_bits(rec, rec_offs_comp(offsets)))) {
|
|
ut_ad(page_rec_is_first(rec, page_align(rec)));
|
|
ut_ad(!page_has_prev(page_align(rec)));
|
|
ut_ad(rec_is_metadata(rec, *index));
|
|
return 1;
|
|
}
|
|
|
|
ulint cur_field = *matched_fields;
|
|
ulint cur_bytes = *matched_bytes;
|
|
ulint n_cmp = dtuple_get_n_fields_cmp(dtuple);
|
|
int ret;
|
|
|
|
ut_ad(n_cmp <= dtuple_get_n_fields(dtuple));
|
|
ut_ad(cur_field <= n_cmp);
|
|
ut_ad(cur_field + (cur_bytes > 0) <= rec_offs_n_fields(offsets));
|
|
|
|
/* Match fields in a loop; stop if we run out of fields in dtuple
|
|
or find an externally stored field */
|
|
|
|
while (cur_field < n_cmp) {
|
|
const dfield_t* dfield = dtuple_get_nth_field(
|
|
dtuple, cur_field);
|
|
const dtype_t* type = dfield_get_type(dfield);
|
|
ulint dtuple_f_len = dfield_get_len(dfield);
|
|
const byte* dtuple_b_ptr;
|
|
const byte* rec_b_ptr;
|
|
ulint rec_f_len;
|
|
|
|
dtuple_b_ptr = static_cast<const byte*>(
|
|
dfield_get_data(dfield));
|
|
|
|
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(!rec_offs_nth_extern(offsets, cur_field));
|
|
|
|
/* If we have matched yet 0 bytes, it may be that one or
|
|
both the fields are SQL null, or the record or dtuple may be
|
|
the predefined minimum record. */
|
|
if (cur_bytes == 0) {
|
|
if (dtuple_f_len == UNIV_SQL_NULL) {
|
|
if (rec_f_len == UNIV_SQL_NULL) {
|
|
|
|
goto next_field;
|
|
}
|
|
|
|
ret = -1;
|
|
goto order_resolved;
|
|
} else if (rec_f_len == UNIV_SQL_NULL) {
|
|
/* We define the SQL null to be the
|
|
smallest possible value of a field
|
|
in the alphabetical order */
|
|
|
|
ret = 1;
|
|
goto order_resolved;
|
|
}
|
|
}
|
|
|
|
switch (type->mtype) {
|
|
case DATA_FIXBINARY:
|
|
case DATA_BINARY:
|
|
case DATA_INT:
|
|
case DATA_SYS_CHILD:
|
|
case DATA_SYS:
|
|
break;
|
|
case DATA_BLOB:
|
|
if (type->prtype & DATA_BINARY_TYPE) {
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
ret = cmp_data(type->mtype, type->prtype,
|
|
dtuple_b_ptr, dtuple_f_len,
|
|
rec_b_ptr, rec_f_len);
|
|
|
|
if (!ret) {
|
|
goto next_field;
|
|
}
|
|
|
|
cur_bytes = 0;
|
|
goto order_resolved;
|
|
}
|
|
|
|
/* Set the pointers at the current byte */
|
|
|
|
rec_b_ptr += cur_bytes;
|
|
dtuple_b_ptr += cur_bytes;
|
|
/* Compare then the fields */
|
|
|
|
for (const ulint pad = cmp_get_pad_char(type);;
|
|
cur_bytes++) {
|
|
ulint rec_byte = pad;
|
|
ulint dtuple_byte = pad;
|
|
|
|
if (rec_f_len <= cur_bytes) {
|
|
if (dtuple_f_len <= cur_bytes) {
|
|
|
|
goto next_field;
|
|
}
|
|
|
|
if (rec_byte == ULINT_UNDEFINED) {
|
|
ret = 1;
|
|
|
|
goto order_resolved;
|
|
}
|
|
} else {
|
|
rec_byte = *rec_b_ptr++;
|
|
}
|
|
|
|
if (dtuple_f_len <= cur_bytes) {
|
|
if (dtuple_byte == ULINT_UNDEFINED) {
|
|
ret = -1;
|
|
|
|
goto order_resolved;
|
|
}
|
|
} else {
|
|
dtuple_byte = *dtuple_b_ptr++;
|
|
}
|
|
|
|
if (dtuple_byte < rec_byte) {
|
|
ret = -1;
|
|
goto order_resolved;
|
|
} else if (dtuple_byte > rec_byte) {
|
|
ret = 1;
|
|
goto order_resolved;
|
|
}
|
|
}
|
|
|
|
next_field:
|
|
cur_field++;
|
|
cur_bytes = 0;
|
|
}
|
|
|
|
ut_ad(cur_bytes == 0);
|
|
|
|
ret = 0; /* If we ran out of fields, dtuple was equal to rec
|
|
up to the common fields */
|
|
order_resolved:
|
|
*matched_fields = cur_field;
|
|
*matched_bytes = cur_bytes;
|
|
|
|
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)
|
|
{
|
|
ulint matched_fields = 0;
|
|
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
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() */
|
|
{
|
|
ulint n_fields;
|
|
ulint matched_fields = 0;
|
|
|
|
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
|
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(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(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_INT, 0, zeros, len, zeros, len);
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_UT_CHRONO_T */
|
|
|
|
#endif /* UNIV_COMPILE_TEST_FUNCS */
|