1
/* Copyright (C) 2000 MySQL AB
3
This program is free software; you can redistribute it and/or modify
4
it under the terms of the GNU General Public License as published by
5
the Free Software Foundation; version 2 of the License.
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
12
You should have received a copy of the GNU General Public License
13
along with this program; if not, write to the Free Software
14
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
16
/* The hash functions used for saveing keys */
17
/* One of key_length or key_length_offset must be given */
18
/* Key length of 0 isn't allowed */
20
#include "mysys_priv.h"
25
#define NO_RECORD ((uint) -1)
31
typedef struct st_hash_info {
32
uint next; /* index to next key */
33
uchar *data; /* data for current entry */
36
static uint hash_mask(uint hashnr,uint buffmax,uint maxlength);
37
static void movelink(HASH_LINK *array,uint pos,uint next_link,uint newlink);
38
static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key,
41
static uint calc_hash(const HASH *hash, const uchar *key, size_t length)
44
hash->charset->coll->hash_sort(hash->charset,(uchar*) key,length,&nr1,&nr2);
49
_hash_init(HASH *hash,uint growth_size, CHARSET_INFO *charset,
50
ulong size, size_t key_offset, size_t key_length,
52
void (*free_element)(void*),uint flags CALLER_INFO_PROTO)
54
DBUG_ENTER("hash_init");
55
DBUG_PRINT("enter",("hash: 0x%lx size: %u", (long) hash, (uint) size));
58
if (my_init_dynamic_array_ci(&hash->array, sizeof(HASH_LINK), size,
61
hash->free=0; /* Allow call to hash_free */
64
hash->key_offset=key_offset;
65
hash->key_length=key_length;
67
hash->get_key=get_key;
68
hash->free=free_element;
70
hash->charset=charset;
76
Call hash->free on all elements in hash.
86
static inline void hash_free_elements(HASH *hash)
90
HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
91
HASH_LINK *end= data + hash->records;
93
(*hash->free)((data++)->data);
100
Free memory used by hash.
104
hash the hash to delete elements of
106
NOTES: Hash can't be reused without calling hash_init again.
109
void hash_free(HASH *hash)
111
DBUG_ENTER("hash_free");
112
DBUG_PRINT("enter",("hash: 0x%lx", (long) hash));
114
hash_free_elements(hash);
116
delete_dynamic(&hash->array);
122
Delete all elements from the hash (the hash itself is to be reused).
126
hash the hash to delete elements of
129
void my_hash_reset(HASH *hash)
131
DBUG_ENTER("my_hash_reset");
132
DBUG_PRINT("enter",("hash: 0x%lxd", (long) hash));
134
hash_free_elements(hash);
135
reset_dynamic(&hash->array);
136
/* Set row pointers so that the hash can be reused at once */
141
/* some helper functions */
144
This function is char* instead of uchar* as HPUX11 compiler can't
145
handle inline functions that are not defined as native types
149
hash_key(const HASH *hash, const uchar *record, size_t *length,
153
return (char*) (*hash->get_key)(record,length,first);
154
*length=hash->key_length;
155
return (char*) record+hash->key_offset;
158
/* Calculate pos according to keys */
160
static uint hash_mask(uint hashnr,uint buffmax,uint maxlength)
162
if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1));
163
return (hashnr & ((buffmax >> 1) -1));
166
static uint hash_rec_mask(const HASH *hash, HASH_LINK *pos,
167
uint buffmax, uint maxlength)
170
uchar *key= (uchar*) hash_key(hash,pos->data,&length,0);
171
return hash_mask(calc_hash(hash,key,length),buffmax,maxlength);
176
/* for compilers which can not handle inline */
178
#if !defined(__USLC__) && !defined(__sgi)
181
unsigned int rec_hashnr(HASH *hash,const uchar *record)
184
uchar *key= (uchar*) hash_key(hash,record,&length,0);
185
return calc_hash(hash,key,length);
189
uchar* hash_search(const HASH *hash, const uchar *key, size_t length)
191
HASH_SEARCH_STATE state;
192
return hash_first(hash, key, length, &state);
196
Search after a record based on a key
199
Assigns the number of the found record to HASH_SEARCH_STATE state
202
uchar* hash_first(const HASH *hash, const uchar *key, size_t length,
203
HASH_SEARCH_STATE *current_record)
207
DBUG_ENTER("hash_first");
212
idx=hash_mask(calc_hash(hash,key,length ? length : hash->key_length),
213
hash->blength,hash->records);
216
pos= dynamic_element(&hash->array,idx,HASH_LINK*);
217
if (!hashcmp(hash,pos,key,length))
219
DBUG_PRINT("exit",("found key at %d",idx));
220
*current_record= idx;
221
DBUG_RETURN (pos->data);
225
flag=0; /* Reset flag */
226
if (hash_rec_mask(hash,pos,hash->blength,hash->records) != idx)
227
break; /* Wrong link */
230
while ((idx=pos->next) != NO_RECORD);
232
*current_record= NO_RECORD;
236
/* Get next record with identical key */
237
/* Can only be called if previous calls was hash_search */
239
uchar* hash_next(const HASH *hash, const uchar *key, size_t length,
240
HASH_SEARCH_STATE *current_record)
245
if (*current_record != NO_RECORD)
247
HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
248
for (idx=data[*current_record].next; idx != NO_RECORD ; idx=pos->next)
251
if (!hashcmp(hash,pos,key,length))
253
*current_record= idx;
257
*current_record= NO_RECORD;
263
/* Change link from pos to new_link */
265
static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink)
270
old_link=array+next_link;
272
while ((next_link=old_link->next) != find);
273
old_link->next= newlink;
278
Compare a key in a record to a whole key. Return 0 if identical
283
pos position of hash record to use in comparison
284
key key for comparison
288
If length is 0, comparison is done using the length of the
289
record being compared against.
292
= 0 key of record == key
293
!= 0 key of record != key
296
static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key,
299
size_t rec_keylength;
300
uchar *rec_key= (uchar*) hash_key(hash,pos->data,&rec_keylength,1);
301
return ((length && length != rec_keylength) ||
302
my_strnncoll(hash->charset, (uchar*) rec_key, rec_keylength,
303
(uchar*) key, rec_keylength));
307
/* Write a hash-key to the hash-index */
309
my_bool my_hash_insert(HASH *info,const uchar *record)
313
uint halfbuff,hash_nr,first_index;
314
uchar *ptr_to_rec,*ptr_to_rec2;
315
HASH_LINK *data,*empty,*gpos,*gpos2,*pos;
317
if (HASH_UNIQUE & info->flags)
319
uchar *key= (uchar*) hash_key(info, record, &idx, 1);
320
if (hash_search(info, key, idx))
321
return(TRUE); /* Duplicate entry */
325
if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array)))
326
return(TRUE); /* No more memory */
328
data=dynamic_element(&info->array,0,HASH_LINK*);
329
halfbuff= info->blength >> 1;
331
idx=first_index=info->records-halfbuff;
332
if (idx != info->records) /* If some records */
337
hash_nr=rec_hashnr(info,pos->data);
338
if (flag == 0) /* First loop; Check if ok */
339
if (hash_mask(hash_nr,info->blength,info->records) != first_index)
341
if (!(hash_nr & halfbuff))
342
{ /* Key will not move */
343
if (!(flag & LOWFIND))
347
flag=LOWFIND | HIGHFIND;
348
/* key shall be moved to the current empty position */
350
ptr_to_rec=pos->data;
351
empty=pos; /* This place is now free */
355
flag=LOWFIND | LOWUSED; /* key isn't changed */
357
ptr_to_rec=pos->data;
362
if (!(flag & LOWUSED))
364
/* Change link of previous LOW-key */
365
gpos->data=ptr_to_rec;
366
gpos->next= (uint) (pos-data);
367
flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED);
370
ptr_to_rec=pos->data;
374
{ /* key will be moved */
375
if (!(flag & HIGHFIND))
377
flag= (flag & LOWFIND) | HIGHFIND;
378
/* key shall be moved to the last (empty) position */
379
gpos2 = empty; empty=pos;
380
ptr_to_rec2=pos->data;
384
if (!(flag & HIGHUSED))
386
/* Change link of previous hash-key and save */
387
gpos2->data=ptr_to_rec2;
388
gpos2->next=(uint) (pos-data);
389
flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED);
392
ptr_to_rec2=pos->data;
396
while ((idx=pos->next) != NO_RECORD);
398
if ((flag & (LOWFIND | LOWUSED)) == LOWFIND)
400
gpos->data=ptr_to_rec;
401
gpos->next=NO_RECORD;
403
if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND)
405
gpos2->data=ptr_to_rec2;
406
gpos2->next=NO_RECORD;
409
/* Check if we are at the empty position */
411
idx=hash_mask(rec_hashnr(info,record),info->blength,info->records+1);
415
pos->data=(uchar*) record;
420
/* Check if more records in same hash-nr family */
422
gpos=data+hash_rec_mask(info,pos,info->blength,info->records+1);
425
pos->data=(uchar*) record;
426
pos->next=(uint) (empty - data);
430
pos->data=(uchar*) record;
432
movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data));
435
if (++info->records == info->blength)
436
info->blength+= info->blength;
441
/******************************************************************************
442
** Remove one record from hash-table. The record with the same record
444
** if there is a free-function it's called for record if found
445
******************************************************************************/
447
my_bool hash_delete(HASH *hash,uchar *record)
449
uint blength,pos2,pos_hashnr,lastpos_hashnr,idx,empty_index;
450
HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty;
451
DBUG_ENTER("hash_delete");
455
blength=hash->blength;
456
data=dynamic_element(&hash->array,0,HASH_LINK*);
457
/* Search after record with key */
458
pos=data+ hash_mask(rec_hashnr(hash,record),blength,hash->records);
461
while (pos->data != record)
464
if (pos->next == NO_RECORD)
465
DBUG_RETURN(1); /* Key not found */
469
if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1;
470
lastpos=data+hash->records;
472
/* Remove link to record */
473
empty=pos; empty_index=(uint) (empty-data);
475
gpos->next=pos->next; /* unlink current ptr */
476
else if (pos->next != NO_RECORD)
478
empty=data+(empty_index=pos->next);
479
pos->data=empty->data;
480
pos->next=empty->next;
483
if (empty == lastpos) /* last key at wrong pos or no next link */
486
/* Move the last key (lastpos) */
487
lastpos_hashnr=rec_hashnr(hash,lastpos->data);
488
/* pos is where lastpos should be */
489
pos=data+hash_mask(lastpos_hashnr,hash->blength,hash->records);
490
if (pos == empty) /* Move to empty position. */
495
pos_hashnr=rec_hashnr(hash,pos->data);
496
/* pos3 is where the pos should be */
497
pos3= data+hash_mask(pos_hashnr,hash->blength,hash->records);
499
{ /* pos is on wrong posit */
500
empty[0]=pos[0]; /* Save it here */
501
pos[0]=lastpos[0]; /* This should be here */
502
movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index);
505
pos2= hash_mask(lastpos_hashnr,blength,hash->records+1);
506
if (pos2 == hash_mask(pos_hashnr,blength,hash->records+1))
507
{ /* Identical key-positions */
508
if (pos2 != hash->records)
511
movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index);
514
idx= (uint) (pos-data); /* Link pos->next after lastpos */
516
else idx= NO_RECORD; /* Different positions merge */
519
movelink(data,idx,empty_index,pos->next);
520
pos->next=empty_index;
523
VOID(pop_dynamic(&hash->array));
525
(*hash->free)((uchar*) record);
530
Update keys when record has changed.
531
This is much more efficent than using a delete & insert.
534
my_bool hash_update(HASH *hash, uchar *record, uchar *old_key,
535
size_t old_key_length)
537
uint new_index,new_pos_index,blength,records,empty;
539
HASH_LINK org_link,*data,*previous,*pos;
540
DBUG_ENTER("hash_update");
542
if (HASH_UNIQUE & hash->flags)
544
HASH_SEARCH_STATE state;
545
uchar *found, *new_key= (uchar*) hash_key(hash, record, &idx, 1);
546
if ((found= hash_first(hash, new_key, idx, &state)))
551
DBUG_RETURN(1); /* Duplicate entry */
553
while ((found= hash_next(hash, new_key, idx, &state)));
557
data=dynamic_element(&hash->array,0,HASH_LINK*);
558
blength=hash->blength; records=hash->records;
560
/* Search after record with key */
562
idx=hash_mask(calc_hash(hash, old_key,(old_key_length ?
566
new_index=hash_mask(rec_hashnr(hash,record),blength,records);
567
if (idx == new_index)
568
DBUG_RETURN(0); /* Nothing to do (No record check) */
573
if ((pos= data+idx)->data == record)
576
if ((idx=pos->next) == NO_RECORD)
577
DBUG_RETURN(1); /* Not found in links */
582
/* Relink record from current chain */
586
if (pos->next != NO_RECORD)
589
*pos= data[pos->next];
593
previous->next=pos->next; /* unlink pos */
595
/* Move data to correct position */
596
if (new_index == empty)
599
At this point record is unlinked from the old chain, thus it holds
600
random position. By the chance this position is equal to position
601
for the first element in the new chain. That means updated record
602
is the only record in the new chain.
607
Record was moved while unlinking it from the old chain.
608
Copy data to a new position.
610
data[empty]= org_link;
612
data[empty].next= NO_RECORD;
616
new_pos_index=hash_rec_mask(hash,pos,blength,records);
617
if (new_index != new_pos_index)
618
{ /* Other record in wrong position */
620
movelink(data,new_index,new_pos_index,empty);
621
org_link.next=NO_RECORD;
622
data[new_index]= org_link;
625
{ /* Link in chain at right position */
626
org_link.next=data[new_index].next;
627
data[empty]=org_link;
628
data[new_index].next=empty;
634
uchar *hash_element(HASH *hash,ulong idx)
636
if (idx < hash->records)
637
return dynamic_element(&hash->array,idx,HASH_LINK*)->data;
643
Replace old row with new row. This should only be used when key
647
void hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record, uchar *new_row)
649
if (*current_record != NO_RECORD) /* Safety */
650
dynamic_element(&hash->array, *current_record, HASH_LINK*)->data= new_row;
656
my_bool hash_check(HASH *hash)
659
uint i,rec_link,found,max_links,seek,links,idx;
660
uint records,blength;
661
HASH_LINK *data,*hash_info;
663
records=hash->records; blength=hash->blength;
664
data=dynamic_element(&hash->array,0,HASH_LINK*);
667
for (i=found=max_links=seek=0 ; i < records ; i++)
669
if (hash_rec_mask(hash,data+i,blength,records) == i)
671
found++; seek++; links=1;
672
for (idx=data[i].next ;
673
idx != NO_RECORD && found < records + 1;
679
("Found pointer outside array to %d from link starting at %d",
685
if ((rec_link=hash_rec_mask(hash,hash_info,blength,records)) != i)
688
("Record in wrong link at %d: Start %d Record: 0x%lx Record-link %d",
689
idx, i, (long) hash_info->data, rec_link));
695
if (links > max_links) max_links=links;
698
if (found != records)
700
DBUG_PRINT("error",("Found %u of %u records", found, records));
705
("records: %u seeks: %d max links: %d hitrate: %.2f",
706
records,seek,max_links,(float) seek / (float) records));