808 lines
27 KiB
C++
808 lines
27 KiB
C++
/* Copyright (c) 2000, 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 */
|
|
|
|
/* The hash functions used for saveing keys */
|
|
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "m_ctype.h"
|
|
#include "my_byteorder.h"
|
|
#include "my_compiler.h"
|
|
#include "my_dbug.h"
|
|
#include "my_inttypes.h"
|
|
#include "my_macros.h"
|
|
#include "storage/heap/heapdef.h"
|
|
|
|
/*
|
|
Find out how many rows there is in the given range
|
|
|
|
SYNOPSIS
|
|
hp_rb_records_in_range()
|
|
info HEAP handler
|
|
inx Index to use
|
|
min_key Min key. Is = 0 if no min range
|
|
max_key Max key. Is = 0 if no max range
|
|
|
|
NOTES
|
|
min_key.flag can have one of the following values:
|
|
HA_READ_KEY_EXACT Include the key in the range
|
|
HA_READ_AFTER_KEY Don't include key in range
|
|
|
|
max_key.flag can have one of the following values:
|
|
HA_READ_BEFORE_KEY Don't include key in range
|
|
HA_READ_AFTER_KEY Include all 'end_key' values in the range
|
|
|
|
RETURN
|
|
HA_POS_ERROR Something is wrong with the index tree.
|
|
0 There is no matching keys in the given range
|
|
number > 0 There is approximately 'number' matching rows in
|
|
the range.
|
|
*/
|
|
|
|
ha_rows hp_rb_records_in_range(HP_INFO *info, int inx, key_range *min_key,
|
|
key_range *max_key) {
|
|
ha_rows start_pos, end_pos;
|
|
HP_KEYDEF *keyinfo = info->s->keydef + inx;
|
|
TREE *rb_tree = &keyinfo->rb_tree;
|
|
heap_rb_param custom_arg;
|
|
DBUG_TRACE;
|
|
|
|
info->lastinx = inx;
|
|
custom_arg.keyseg = keyinfo->seg;
|
|
custom_arg.search_flag = SEARCH_FIND | SEARCH_SAME;
|
|
if (min_key) {
|
|
custom_arg.key_length = hp_rb_pack_key(keyinfo, info->recbuf, min_key->key,
|
|
min_key->keypart_map);
|
|
start_pos =
|
|
tree_record_pos(rb_tree, info->recbuf, min_key->flag, &custom_arg);
|
|
} else {
|
|
start_pos = 0;
|
|
}
|
|
|
|
if (max_key) {
|
|
custom_arg.key_length = hp_rb_pack_key(keyinfo, info->recbuf, max_key->key,
|
|
max_key->keypart_map);
|
|
end_pos =
|
|
tree_record_pos(rb_tree, info->recbuf, max_key->flag, &custom_arg);
|
|
} else {
|
|
end_pos = rb_tree->elements_in_tree + (ha_rows)1;
|
|
}
|
|
|
|
DBUG_PRINT("info", ("start_pos: %lu end_pos: %lu", (ulong)start_pos,
|
|
(ulong)end_pos));
|
|
if (start_pos == HA_POS_ERROR || end_pos == HA_POS_ERROR) return HA_POS_ERROR;
|
|
return end_pos < start_pos
|
|
? (ha_rows)0
|
|
: (end_pos == start_pos ? (ha_rows)1 : end_pos - start_pos);
|
|
}
|
|
|
|
/* Search after a record based on a key */
|
|
/* Sets info->current_ptr to found record */
|
|
/* next_flag: Search=0, next=1, prev =2, same =3 */
|
|
|
|
uchar *hp_search(HP_INFO *info, HP_KEYDEF *keyinfo, const uchar *key,
|
|
uint nextflag) {
|
|
HASH_INFO *pos, *prev_ptr;
|
|
int flag;
|
|
uint old_nextflag;
|
|
HP_SHARE *share = info->s;
|
|
DBUG_TRACE;
|
|
old_nextflag = nextflag;
|
|
flag = 1;
|
|
prev_ptr = 0;
|
|
|
|
if (share->records) {
|
|
pos = hp_find_hash(
|
|
&keyinfo->block,
|
|
hp_mask(hp_hashnr(keyinfo, key), share->blength, share->records));
|
|
do {
|
|
if (!hp_key_cmp(keyinfo, pos->ptr_to_rec, key)) {
|
|
switch (nextflag) {
|
|
case 0: /* Search after key */
|
|
DBUG_PRINT("exit", ("found key at %p", pos->ptr_to_rec));
|
|
info->current_hash_ptr = pos;
|
|
return info->current_ptr = pos->ptr_to_rec;
|
|
case 1: /* Search next */
|
|
if (pos->ptr_to_rec == info->current_ptr) nextflag = 0;
|
|
break;
|
|
case 2: /* Search previous */
|
|
if (pos->ptr_to_rec == info->current_ptr) {
|
|
set_my_errno(HA_ERR_KEY_NOT_FOUND); /* If gpos == 0 */
|
|
info->current_hash_ptr = prev_ptr;
|
|
return info->current_ptr = prev_ptr ? prev_ptr->ptr_to_rec : 0;
|
|
}
|
|
prev_ptr = pos; /* Prev. record found */
|
|
break;
|
|
case 3: /* Search same */
|
|
if (pos->ptr_to_rec == info->current_ptr) {
|
|
info->current_hash_ptr = pos;
|
|
return info->current_ptr;
|
|
}
|
|
}
|
|
}
|
|
if (flag) {
|
|
flag = 0; /* Reset flag */
|
|
if (hp_find_hash(&keyinfo->block,
|
|
hp_mask(hp_rec_hashnr(keyinfo, pos->ptr_to_rec),
|
|
share->blength, share->records)) != pos)
|
|
break; /* Wrong link */
|
|
}
|
|
} while ((pos = pos->next_key));
|
|
}
|
|
set_my_errno(HA_ERR_KEY_NOT_FOUND);
|
|
if (nextflag == 2 && !info->current_ptr) {
|
|
/* Do a previous from end */
|
|
info->current_hash_ptr = prev_ptr;
|
|
return info->current_ptr = prev_ptr ? prev_ptr->ptr_to_rec : 0;
|
|
}
|
|
|
|
if (old_nextflag && nextflag)
|
|
set_my_errno(HA_ERR_RECORD_CHANGED); /* Didn't find old record */
|
|
DBUG_PRINT("exit", ("Error: %d", my_errno()));
|
|
info->current_hash_ptr = 0;
|
|
return (info->current_ptr = 0);
|
|
}
|
|
|
|
/*
|
|
Search next after last read; Assumes that the table hasn't changed
|
|
since last read !
|
|
*/
|
|
|
|
uchar *hp_search_next(HP_INFO *info, HP_KEYDEF *keyinfo, const uchar *key,
|
|
HASH_INFO *pos) {
|
|
DBUG_TRACE;
|
|
|
|
while ((pos = pos->next_key)) {
|
|
if (!hp_key_cmp(keyinfo, pos->ptr_to_rec, key)) {
|
|
info->current_hash_ptr = pos;
|
|
return info->current_ptr = pos->ptr_to_rec;
|
|
}
|
|
}
|
|
set_my_errno(HA_ERR_KEY_NOT_FOUND);
|
|
DBUG_PRINT("exit", ("Error: %d", my_errno()));
|
|
info->current_hash_ptr = 0;
|
|
return (info->current_ptr = 0);
|
|
}
|
|
|
|
/*
|
|
Calculate position number for hash value.
|
|
SYNOPSIS
|
|
hp_mask()
|
|
hashnr Hash value
|
|
buffmax Value such that
|
|
2^(n-1) < maxlength <= 2^n = buffmax
|
|
maxlength
|
|
|
|
RETURN
|
|
Array index, in [0..maxlength)
|
|
*/
|
|
|
|
uint64 hp_mask(uint64 hashnr, uint64 buffmax, uint64 maxlength) {
|
|
if ((hashnr & (buffmax - 1)) < maxlength) return (hashnr & (buffmax - 1));
|
|
return (hashnr & ((buffmax >> 1) - 1));
|
|
}
|
|
|
|
/*
|
|
Change
|
|
next_link -> ... -> X -> pos
|
|
to
|
|
next_link -> ... -> X -> newlink
|
|
*/
|
|
|
|
void hp_movelink(HASH_INFO *pos, HASH_INFO *next_link, HASH_INFO *newlink) {
|
|
HASH_INFO *old_link;
|
|
do {
|
|
old_link = next_link;
|
|
} while ((next_link = next_link->next_key) != pos);
|
|
old_link->next_key = newlink;
|
|
return;
|
|
}
|
|
|
|
/* Calc hashvalue for a key */
|
|
|
|
uint64 hp_hashnr(HP_KEYDEF *keydef, const uchar *key) {
|
|
/*register*/
|
|
uint64 nr = 1, nr2 = 4;
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
const uchar *pos = key;
|
|
key += seg->length;
|
|
if (seg->null_bit) {
|
|
key++; /* Skip null byte */
|
|
if (*pos) /* Found null */
|
|
{
|
|
nr ^= (nr << 1) | 1;
|
|
/* Add key pack length (2) to key for VARCHAR segments */
|
|
if (seg->type == HA_KEYTYPE_VARTEXT1) key += 2;
|
|
continue;
|
|
}
|
|
pos++;
|
|
}
|
|
if (seg->type == HA_KEYTYPE_TEXT) {
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
size_t length = seg->length;
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
size_t char_length;
|
|
char_length = my_charpos(cs, pos, pos + length, length / cs->mbmaxlen);
|
|
set_if_smaller(length, char_length);
|
|
}
|
|
if (cs->pad_attribute == NO_PAD) {
|
|
/*
|
|
MySQL specifies that CHAR fields are stripped of
|
|
trailing spaces before being returned from the database.
|
|
Normally this is done in Field_string::val_str(),
|
|
but since we don't involve the Field classes for
|
|
hashing, we need to do the same thing here
|
|
for NO PAD collations. (If not, hash_sort will ignore
|
|
the spaces for us, so we don't need to do it here.)
|
|
*/
|
|
length = cs->cset->lengthsp(cs, (const char *)pos, length);
|
|
}
|
|
cs->coll->hash_sort(cs, pos, length, &nr, &nr2);
|
|
} else if (seg->type == HA_KEYTYPE_VARTEXT1) /* Any VARCHAR segments */
|
|
{
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
uint pack_length = 2; /* Key packing is constant */
|
|
size_t length = uint2korr(pos);
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
size_t char_length;
|
|
char_length =
|
|
my_charpos(cs, pos + pack_length, pos + pack_length + length,
|
|
seg->length / cs->mbmaxlen);
|
|
set_if_smaller(length, char_length);
|
|
}
|
|
cs->coll->hash_sort(cs, pos + pack_length, length, &nr, &nr2);
|
|
key += pack_length;
|
|
} else {
|
|
for (; pos < key; pos++) {
|
|
nr ^= (uint64)((((uint)nr & 63) + nr2) * ((uint)*pos)) + (nr << 8);
|
|
nr2 += 3;
|
|
}
|
|
}
|
|
}
|
|
DBUG_PRINT("exit", ("hash: 0x%" PRIx64, nr));
|
|
return nr;
|
|
}
|
|
|
|
/* Calc hashvalue for a key in a record */
|
|
|
|
uint64 hp_rec_hashnr(HP_KEYDEF *keydef, const uchar *rec) {
|
|
uint64 nr = 1, nr2 = 4;
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
const uchar *pos = rec + seg->start, *end = pos + seg->length;
|
|
if (seg->null_bit) {
|
|
if (rec[seg->null_pos] & seg->null_bit) {
|
|
nr ^= (nr << 1) | 1;
|
|
continue;
|
|
}
|
|
}
|
|
if (seg->type == HA_KEYTYPE_TEXT) {
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
size_t char_length = seg->length;
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
char_length =
|
|
my_charpos(cs, pos, pos + char_length, char_length / cs->mbmaxlen);
|
|
set_if_smaller(char_length, seg->length); /* QQ: ok to remove? */
|
|
}
|
|
if (cs->pad_attribute == NO_PAD) {
|
|
/*
|
|
MySQL specifies that CHAR fields are stripped of
|
|
trailing spaces before being returned from the database.
|
|
Normally this is done in Field_string::val_str(),
|
|
but since we don't involve the Field classes for
|
|
hashing, we need to do the same thing here
|
|
for NO PAD collations. (If not, hash_sort will ignore
|
|
the spaces for us, so we don't need to do it here.)
|
|
*/
|
|
char_length = cs->cset->lengthsp(cs, (const char *)pos, char_length);
|
|
}
|
|
cs->coll->hash_sort(cs, pos, char_length, &nr, &nr2);
|
|
} else if (seg->type == HA_KEYTYPE_VARTEXT1) /* Any VARCHAR segments */
|
|
{
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
uint pack_length = seg->bit_start;
|
|
size_t length = (pack_length == 1 ? (uint)*pos : uint2korr(pos));
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
size_t char_length;
|
|
char_length =
|
|
my_charpos(cs, pos + pack_length, pos + pack_length + length,
|
|
seg->length / cs->mbmaxlen);
|
|
set_if_smaller(length, char_length);
|
|
}
|
|
cs->coll->hash_sort(cs, pos + pack_length, length, &nr, &nr2);
|
|
} else {
|
|
for (; pos < end; pos++) {
|
|
nr ^= (uint64)((((uint)nr & 63) + nr2) * ((uint)*pos)) + (nr << 8);
|
|
nr2 += 3;
|
|
}
|
|
}
|
|
}
|
|
DBUG_PRINT("exit", ("hash: 0x%" PRIx64, nr));
|
|
return (nr);
|
|
}
|
|
|
|
/*
|
|
Compare keys for two records. Returns 0 if they are identical
|
|
|
|
SYNOPSIS
|
|
hp_rec_key_cmp()
|
|
keydef Key definition
|
|
rec1 Record to compare
|
|
rec2 Other record to compare
|
|
|
|
RETURN
|
|
0 Key is identical
|
|
<> 0 Key differes
|
|
*/
|
|
|
|
int hp_rec_key_cmp(HP_KEYDEF *keydef, const uchar *rec1, const uchar *rec2) {
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
if (seg->null_bit) {
|
|
if ((rec1[seg->null_pos] & seg->null_bit) !=
|
|
(rec2[seg->null_pos] & seg->null_bit))
|
|
return 1;
|
|
if (rec1[seg->null_pos] & seg->null_bit) continue;
|
|
}
|
|
if (seg->type == HA_KEYTYPE_TEXT) {
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
size_t char_length1;
|
|
size_t char_length2;
|
|
const uchar *pos1 = rec1 + seg->start;
|
|
const uchar *pos2 = rec2 + seg->start;
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
size_t char_length = seg->length / cs->mbmaxlen;
|
|
char_length1 = my_charpos(cs, pos1, pos1 + seg->length, char_length);
|
|
set_if_smaller(char_length1, seg->length);
|
|
char_length2 = my_charpos(cs, pos2, pos2 + seg->length, char_length);
|
|
set_if_smaller(char_length2, seg->length);
|
|
} else {
|
|
char_length1 = char_length2 = seg->length;
|
|
}
|
|
if (cs->pad_attribute == NO_PAD) {
|
|
/*
|
|
MySQL specifies that CHAR fields are stripped of
|
|
trailing spaces before being returned from the database.
|
|
Normally this is done in Field_string::val_str(),
|
|
but since we don't involve the Field classes for
|
|
internal comparisons, we need to do the same thing here
|
|
for NO PAD collations. (If not, strnncollsp will ignore
|
|
the spaces for us, so we don't need to do it here.)
|
|
*/
|
|
char_length1 = cs->cset->lengthsp(cs, (const char *)pos1, char_length1);
|
|
char_length2 = cs->cset->lengthsp(cs, (const char *)pos2, char_length2);
|
|
}
|
|
if (cs->coll->strnncollsp(cs, pos1, char_length1, pos2, char_length2))
|
|
return 1;
|
|
} else if (seg->type == HA_KEYTYPE_VARTEXT1) /* Any VARCHAR segments */
|
|
{
|
|
const uchar *pos1 = rec1 + seg->start;
|
|
const uchar *pos2 = rec2 + seg->start;
|
|
uint char_length1, char_length2;
|
|
uint pack_length = seg->bit_start;
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
if (pack_length == 1) {
|
|
char_length1 = (uint) * (pos1++);
|
|
char_length2 = (uint) * (pos2++);
|
|
} else {
|
|
char_length1 = uint2korr(pos1);
|
|
char_length2 = uint2korr(pos2);
|
|
pos1 += 2;
|
|
pos2 += 2;
|
|
}
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
uint safe_length1 = char_length1;
|
|
uint safe_length2 = char_length2;
|
|
uint char_length = seg->length / cs->mbmaxlen;
|
|
char_length1 = my_charpos(cs, pos1, pos1 + char_length1, char_length);
|
|
set_if_smaller(char_length1, safe_length1);
|
|
char_length2 = my_charpos(cs, pos2, pos2 + char_length2, char_length);
|
|
set_if_smaller(char_length2, safe_length2);
|
|
}
|
|
|
|
if (cs->coll->strnncollsp(seg->charset, pos1, char_length1, pos2,
|
|
char_length2))
|
|
return 1;
|
|
} else {
|
|
if (memcmp(rec1 + seg->start, rec2 + seg->start, seg->length)) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Compare a key in a record to a whole key */
|
|
|
|
int hp_key_cmp(HP_KEYDEF *keydef, const uchar *rec, const uchar *key) {
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg;
|
|
key += (seg++)->length) {
|
|
if (seg->null_bit) {
|
|
bool found_null = (rec[seg->null_pos] & seg->null_bit);
|
|
if (found_null != (int)*key++) return 1;
|
|
if (found_null) {
|
|
/* Add key pack length (2) to key for VARCHAR segments */
|
|
if (seg->type == HA_KEYTYPE_VARTEXT1) key += 2;
|
|
continue;
|
|
}
|
|
}
|
|
if (seg->type == HA_KEYTYPE_TEXT) {
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
uint char_length_key;
|
|
uint char_length_rec;
|
|
const uchar *pos = rec + seg->start;
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
uint char_length = seg->length / cs->mbmaxlen;
|
|
char_length_key = my_charpos(cs, key, key + seg->length, char_length);
|
|
set_if_smaller(char_length_key, seg->length);
|
|
char_length_rec = my_charpos(cs, pos, pos + seg->length, char_length);
|
|
set_if_smaller(char_length_rec, seg->length);
|
|
} else {
|
|
char_length_key = seg->length;
|
|
char_length_rec = seg->length;
|
|
}
|
|
|
|
if (cs->pad_attribute == NO_PAD) {
|
|
/*
|
|
MySQL specifies that CHAR fields are stripped of
|
|
trailing spaces before being returned from the database.
|
|
Normally this is done in Field_string::val_str(),
|
|
but since we don't involve the Field classes for
|
|
internal comparisons, we need to do the same thing here
|
|
for NO PAD collations. (If not, strnncollsp will ignore
|
|
the spaces for us, so we don't need to do it here.)
|
|
*/
|
|
char_length_rec =
|
|
cs->cset->lengthsp(cs, (const char *)pos, char_length_rec);
|
|
char_length_key =
|
|
cs->cset->lengthsp(cs, (const char *)key, char_length_key);
|
|
}
|
|
|
|
if (cs->coll->strnncollsp(cs, pos, char_length_rec, key, char_length_key))
|
|
return 1;
|
|
} else if (seg->type == HA_KEYTYPE_VARTEXT1) /* Any VARCHAR segments */
|
|
{
|
|
const uchar *pos = rec + seg->start;
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
uint pack_length = seg->bit_start;
|
|
uint char_length_rec = (pack_length == 1 ? (uint)*pos : uint2korr(pos));
|
|
/* Key segments are always packed with 2 bytes */
|
|
uint char_length_key = uint2korr(key);
|
|
pos += pack_length;
|
|
key += 2; /* skip key pack length */
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
uint char_length1, char_length2;
|
|
char_length1 = char_length2 = seg->length / cs->mbmaxlen;
|
|
char_length1 = my_charpos(cs, key, key + char_length_key, char_length1);
|
|
set_if_smaller(char_length_key, char_length1);
|
|
char_length2 = my_charpos(cs, pos, pos + char_length_rec, char_length2);
|
|
set_if_smaller(char_length_rec, char_length2);
|
|
} else {
|
|
set_if_smaller(char_length_rec, seg->length);
|
|
}
|
|
|
|
if (cs->coll->strnncollsp(seg->charset, pos, char_length_rec, key,
|
|
char_length_key))
|
|
return 1;
|
|
} else {
|
|
if (memcmp(rec + seg->start, key, seg->length)) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Copy a key from a record to a keybuffer */
|
|
|
|
void hp_make_key(HP_KEYDEF *keydef, uchar *key, const uchar *rec) {
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
uint char_length = seg->length;
|
|
const uchar *pos = rec + seg->start;
|
|
if (seg->null_bit) *key++ = MY_TEST(rec[seg->null_pos] & seg->null_bit);
|
|
if (cs->mbmaxlen > 1 && (seg->flag & HA_PART_KEY_SEG)) {
|
|
char_length =
|
|
my_charpos(cs, pos, pos + seg->length, char_length / cs->mbmaxlen);
|
|
set_if_smaller(char_length, seg->length); /* QQ: ok to remove? */
|
|
}
|
|
if (seg->type == HA_KEYTYPE_VARTEXT1)
|
|
char_length += seg->bit_start; /* Copy also length */
|
|
memcpy(key, rec + seg->start, (size_t)char_length);
|
|
key += char_length;
|
|
}
|
|
}
|
|
|
|
#define FIX_LENGTH(cs, pos, length, char_length) \
|
|
do { \
|
|
if (length > char_length) \
|
|
char_length = my_charpos(cs, pos, pos + length, char_length); \
|
|
set_if_smaller(char_length, length); \
|
|
} while (0)
|
|
|
|
uint hp_rb_make_key(HP_KEYDEF *keydef, uchar *key, const uchar *rec,
|
|
uchar *recpos) {
|
|
uchar *start_key = key;
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
size_t char_length;
|
|
if (seg->null_bit) {
|
|
if (!(*key++ = 1 - MY_TEST(rec[seg->null_pos] & seg->null_bit))) continue;
|
|
}
|
|
if (seg->flag & HA_SWAP_KEY) {
|
|
uint length = seg->length;
|
|
const uchar *pos = rec + seg->start;
|
|
if (seg->type == HA_KEYTYPE_FLOAT) {
|
|
float nr;
|
|
float4get(&nr, pos);
|
|
if (isnan(nr)) {
|
|
/* Replace NAN with zero */
|
|
memset(key, 0, length);
|
|
key += length;
|
|
continue;
|
|
}
|
|
} else if (seg->type == HA_KEYTYPE_DOUBLE) {
|
|
double nr;
|
|
float8get(&nr, pos);
|
|
if (isnan(nr)) {
|
|
memset(key, 0, length);
|
|
key += length;
|
|
continue;
|
|
}
|
|
}
|
|
pos += length;
|
|
while (length--) {
|
|
*key++ = *--pos;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (seg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) {
|
|
const uchar *pos = rec + seg->start;
|
|
uint length = seg->length;
|
|
uint pack_length = seg->bit_start;
|
|
uint tmp_length = (pack_length == 1 ? (uint)*pos : uint2korr(pos));
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
char_length = length / cs->mbmaxlen;
|
|
|
|
pos += pack_length; /* Skip VARCHAR length */
|
|
set_if_smaller(length, tmp_length);
|
|
FIX_LENGTH(cs, pos, length, char_length);
|
|
store_key_length_inc(key, char_length);
|
|
memcpy(key, pos, char_length);
|
|
key += char_length;
|
|
continue;
|
|
}
|
|
|
|
char_length = seg->length;
|
|
if (seg->charset->mbmaxlen > 1) {
|
|
char_length = my_charpos(seg->charset, rec + seg->start,
|
|
rec + seg->start + char_length,
|
|
char_length / seg->charset->mbmaxlen);
|
|
set_if_smaller(char_length, seg->length); /* QQ: ok to remove? */
|
|
if (char_length < seg->length)
|
|
seg->charset->cset->fill(seg->charset, (char *)key + char_length,
|
|
seg->length - char_length, ' ');
|
|
}
|
|
memcpy(key, rec + seg->start, (size_t)char_length);
|
|
key += seg->length;
|
|
}
|
|
memcpy(key, &recpos, sizeof(uchar *));
|
|
return (uint)(key - start_key);
|
|
}
|
|
|
|
uint hp_rb_pack_key(const HP_KEYDEF *keydef, uchar *key, const uchar *old,
|
|
key_part_map keypart_map) {
|
|
HA_KEYSEG *seg, *endseg;
|
|
uchar *start_key = key;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs;
|
|
seg < endseg && keypart_map; old += seg->length, seg++) {
|
|
size_t char_length;
|
|
keypart_map >>= 1;
|
|
if (seg->null_bit) {
|
|
/* Convert NULL from MySQL representation into HEAP's. */
|
|
if (!(*key++ = (char)1 - *old++)) {
|
|
/*
|
|
Skip length part of a variable length field.
|
|
Length of key-part used with heap_rkey() always 2.
|
|
See also hp_hashnr().
|
|
*/
|
|
if (seg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) old += 2;
|
|
continue;
|
|
}
|
|
}
|
|
if (seg->flag & HA_SWAP_KEY) {
|
|
uint length = seg->length;
|
|
const uchar *pos = old + length;
|
|
|
|
while (length--) {
|
|
*key++ = *--pos;
|
|
}
|
|
continue;
|
|
}
|
|
if (seg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) {
|
|
/* Length of key-part used with heap_rkey() always 2 */
|
|
uint tmp_length = uint2korr(old);
|
|
uint length = seg->length;
|
|
const CHARSET_INFO *cs = seg->charset;
|
|
char_length = length / cs->mbmaxlen;
|
|
|
|
old += 2;
|
|
set_if_smaller(length, tmp_length); /* Safety */
|
|
FIX_LENGTH(cs, old, length, char_length);
|
|
store_key_length_inc(key, char_length);
|
|
memcpy((uchar *)key, old, (size_t)char_length);
|
|
key += char_length;
|
|
continue;
|
|
}
|
|
char_length = seg->length;
|
|
if (seg->charset->mbmaxlen > 1) {
|
|
char_length = my_charpos(seg->charset, old, old + char_length,
|
|
char_length / seg->charset->mbmaxlen);
|
|
set_if_smaller(char_length, seg->length); /* QQ: ok to remove? */
|
|
if (char_length < seg->length)
|
|
seg->charset->cset->fill(seg->charset, (char *)key + char_length,
|
|
seg->length - char_length, ' ');
|
|
}
|
|
memcpy(key, old, (size_t)char_length);
|
|
key += seg->length;
|
|
}
|
|
return (uint)(key - start_key);
|
|
}
|
|
|
|
uint hp_rb_key_length(HP_KEYDEF *keydef,
|
|
const uchar *key MY_ATTRIBUTE((unused))) {
|
|
return keydef->length;
|
|
}
|
|
|
|
uint hp_rb_null_key_length(HP_KEYDEF *keydef, const uchar *key) {
|
|
const uchar *start_key = key;
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
if (seg->null_bit && !*key++) continue;
|
|
key += seg->length;
|
|
}
|
|
return (uint)(key - start_key);
|
|
}
|
|
|
|
uint hp_rb_var_key_length(HP_KEYDEF *keydef, const uchar *key) {
|
|
const uchar *start_key = key;
|
|
HA_KEYSEG *seg, *endseg;
|
|
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
uint length = seg->length;
|
|
if (seg->null_bit && !*key++) continue;
|
|
if (seg->flag & (HA_VAR_LENGTH_PART | HA_BLOB_PART)) {
|
|
length = get_key_length(&key);
|
|
}
|
|
key += length;
|
|
}
|
|
return (uint)(key - start_key);
|
|
}
|
|
|
|
/*
|
|
Test if any of the key parts are NULL.
|
|
Return:
|
|
1 if any of the key parts was NULL
|
|
0 otherwise
|
|
*/
|
|
|
|
bool hp_if_null_in_key(HP_KEYDEF *keydef, const uchar *record) {
|
|
HA_KEYSEG *seg, *endseg;
|
|
for (seg = keydef->seg, endseg = seg + keydef->keysegs; seg < endseg; seg++) {
|
|
if (seg->null_bit && (record[seg->null_pos] & seg->null_bit)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Update auto_increment info
|
|
|
|
SYNOPSIS
|
|
update_auto_increment()
|
|
info MyISAM handler
|
|
record Row to update
|
|
|
|
IMPLEMENTATION
|
|
Only replace the auto_increment value if it is higher than the previous
|
|
one. For signed columns we don't update the auto increment value if it's
|
|
less than zero.
|
|
*/
|
|
|
|
void heap_update_auto_increment(HP_INFO *info, const uchar *record) {
|
|
ulonglong value = 0; /* Store unsigned values here */
|
|
longlong s_value = 0; /* Store signed values here */
|
|
|
|
HA_KEYSEG *keyseg = info->s->keydef[info->s->auto_key - 1].seg;
|
|
const uchar *key = record + keyseg->start;
|
|
|
|
switch (info->s->auto_key_type) {
|
|
case HA_KEYTYPE_INT8:
|
|
s_value = (longlong) static_cast<char>(*key);
|
|
break;
|
|
case HA_KEYTYPE_BINARY:
|
|
value = (ulonglong)*key;
|
|
break;
|
|
case HA_KEYTYPE_SHORT_INT:
|
|
s_value = (longlong)sint2korr(key);
|
|
break;
|
|
case HA_KEYTYPE_USHORT_INT:
|
|
value = (ulonglong)uint2korr(key);
|
|
break;
|
|
case HA_KEYTYPE_LONG_INT:
|
|
s_value = (longlong)sint4korr(key);
|
|
break;
|
|
case HA_KEYTYPE_ULONG_INT:
|
|
value = (ulonglong)uint4korr(key);
|
|
break;
|
|
case HA_KEYTYPE_INT24:
|
|
s_value = (longlong)sint3korr(key);
|
|
break;
|
|
case HA_KEYTYPE_UINT24:
|
|
value = (ulonglong)uint3korr(key);
|
|
break;
|
|
case HA_KEYTYPE_FLOAT: /* This shouldn't be used */
|
|
{
|
|
float f_1;
|
|
float4get(&f_1, key);
|
|
/* Ignore negative values */
|
|
value = (f_1 < (float)0.0) ? 0 : (ulonglong)f_1;
|
|
break;
|
|
}
|
|
case HA_KEYTYPE_DOUBLE: /* This shouldn't be used */
|
|
{
|
|
double f_1;
|
|
float8get(&f_1, key);
|
|
/* Ignore negative values */
|
|
value = (f_1 < 0.0) ? 0 : (ulonglong)f_1;
|
|
break;
|
|
}
|
|
case HA_KEYTYPE_LONGLONG:
|
|
s_value = sint8korr(key);
|
|
break;
|
|
case HA_KEYTYPE_ULONGLONG:
|
|
value = uint8korr(key);
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
value = 0; /* Error */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
The following code works becasue if s_value < 0 then value is 0
|
|
and if s_value == 0 then value will contain either s_value or the
|
|
correct value.
|
|
*/
|
|
set_if_bigger(info->s->auto_increment,
|
|
(s_value > 0) ? (ulonglong)s_value : value);
|
|
}
|