MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
hash.c
1 /* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
2 
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.
6 
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.
11 
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15 
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 */
19 
20 #include "mysys_priv.h"
21 #include <m_string.h>
22 #include <m_ctype.h>
23 #include "hash.h"
24 
25 #define NO_RECORD ((uint) -1)
26 #define LOWFIND 1
27 #define LOWUSED 2
28 #define HIGHFIND 4
29 #define HIGHUSED 8
30 
31 typedef struct st_hash_info {
32  uint next; /* index to next key */
33  uchar *data; /* data for current entry */
34 } HASH_LINK;
35 
36 static uint my_hash_mask(my_hash_value_type hashnr,
37  size_t buffmax, size_t maxlength);
38 static void movelink(HASH_LINK *array,uint pos,uint next_link,uint newlink);
39 static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key,
40  size_t length);
41 
42 static my_hash_value_type calc_hash(const HASH *hash,
43  const uchar *key, size_t length)
44 {
45  ulong nr1=1, nr2=4;
46  hash->charset->coll->hash_sort(hash->charset,(uchar*) key,length,&nr1,&nr2);
47  return (my_hash_value_type)nr1;
48 }
49 
75 my_bool
76 _my_hash_init(HASH *hash, uint growth_size, CHARSET_INFO *charset,
77  ulong size, size_t key_offset, size_t key_length,
78  my_hash_get_key get_key,
79  void (*free_element)(void*), uint flags)
80 {
81  DBUG_ENTER("my_hash_init");
82  DBUG_PRINT("enter",("hash: 0x%lx size: %u", (long) hash, (uint) size));
83 
84  hash->records=0;
85  hash->key_offset=key_offset;
86  hash->key_length=key_length;
87  hash->blength=1;
88  hash->get_key=get_key;
89  hash->free=free_element;
90  hash->flags=flags;
91  hash->charset=charset;
92  DBUG_RETURN(my_init_dynamic_array_ci(&hash->array,
93  sizeof(HASH_LINK), size, growth_size));
94 }
95 
96 
97 /*
98  Call hash->free on all elements in hash.
99 
100  SYNOPSIS
101  my_hash_free_elements()
102  hash hash table
103 
104  NOTES:
105  Sets records to 0
106 */
107 
108 static inline void my_hash_free_elements(HASH *hash)
109 {
110  if (hash->free)
111  {
112  HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
113  HASH_LINK *end= data + hash->records;
114  while (data < end)
115  (*hash->free)((data++)->data);
116  }
117  hash->records=0;
118 }
119 
120 
121 /*
122  Free memory used by hash.
123 
124  SYNOPSIS
125  my_hash_free()
126  hash the hash to delete elements of
127 
128  NOTES: Hash can't be reused without calling my_hash_init again.
129 */
130 
131 void my_hash_free(HASH *hash)
132 {
133  DBUG_ENTER("my_hash_free");
134  DBUG_PRINT("enter",("hash: 0x%lx", (long) hash));
135 
136  my_hash_free_elements(hash);
137  hash->free= 0;
138  delete_dynamic(&hash->array);
139  hash->blength= 0;
140  DBUG_VOID_RETURN;
141 }
142 
143 
144 /*
145  Delete all elements from the hash (the hash itself is to be reused).
146 
147  SYNOPSIS
148  my_hash_reset()
149  hash the hash to delete elements of
150 */
151 
152 void my_hash_reset(HASH *hash)
153 {
154  DBUG_ENTER("my_hash_reset");
155  DBUG_PRINT("enter",("hash: 0x%lxd", (long) hash));
156 
157  my_hash_free_elements(hash);
158  reset_dynamic(&hash->array);
159  /* Set row pointers so that the hash can be reused at once */
160  hash->blength= 1;
161  DBUG_VOID_RETURN;
162 }
163 
164 /* some helper functions */
165 
166 /*
167  This function is char* instead of uchar* as HPUX11 compiler can't
168  handle inline functions that are not defined as native types
169 */
170 
171 static inline char*
172 my_hash_key(const HASH *hash, const uchar *record, size_t *length,
173  my_bool first)
174 {
175  if (hash->get_key)
176  return (char*) (*hash->get_key)(record,length,first);
177  *length=hash->key_length;
178  return (char*) record+hash->key_offset;
179 }
180 
181  /* Calculate pos according to keys */
182 
183 static uint my_hash_mask(my_hash_value_type hashnr, size_t buffmax,
184  size_t maxlength)
185 {
186  if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1));
187  return (hashnr & ((buffmax >> 1) -1));
188 }
189 
190 static uint my_hash_rec_mask(const HASH *hash, HASH_LINK *pos,
191  size_t buffmax, size_t maxlength)
192 {
193  size_t length;
194  uchar *key= (uchar*) my_hash_key(hash, pos->data, &length, 0);
195  return my_hash_mask(calc_hash(hash, key, length), buffmax, maxlength);
196 }
197 
198 
199 
200 /* for compilers which can not handle inline */
201 static
202 #if !defined(__USLC__) && !defined(__sgi)
203 inline
204 #endif
205 my_hash_value_type rec_hashnr(HASH *hash,const uchar *record)
206 {
207  size_t length;
208  uchar *key= (uchar*) my_hash_key(hash, record, &length, 0);
209  return calc_hash(hash,key,length);
210 }
211 
212 
213 uchar* my_hash_search(const HASH *hash, const uchar *key, size_t length)
214 {
215  HASH_SEARCH_STATE state;
216  return my_hash_first(hash, key, length, &state);
217 }
218 
219 uchar* my_hash_search_using_hash_value(const HASH *hash,
220  my_hash_value_type hash_value,
221  const uchar *key,
222  size_t length)
223 {
224  HASH_SEARCH_STATE state;
225  return my_hash_first_from_hash_value(hash, hash_value,
226  key, length, &state);
227 }
228 
229 my_hash_value_type my_calc_hash(const HASH *hash,
230  const uchar *key, size_t length)
231 {
232  return calc_hash(hash, key, length ? length : hash->key_length);
233 }
234 
235 
236 /*
237  Search after a record based on a key
238 
239  NOTE
240  Assigns the number of the found record to HASH_SEARCH_STATE state
241 */
242 
243 uchar* my_hash_first(const HASH *hash, const uchar *key, size_t length,
244  HASH_SEARCH_STATE *current_record)
245 {
246  uchar *res;
247  if (my_hash_inited(hash))
248  res= my_hash_first_from_hash_value(hash,
249  calc_hash(hash, key, length ? length : hash->key_length),
250  key, length, current_record);
251  else
252  res= 0;
253  return res;
254 }
255 
256 
257 uchar* my_hash_first_from_hash_value(const HASH *hash,
258  my_hash_value_type hash_value,
259  const uchar *key,
260  size_t length,
261  HASH_SEARCH_STATE *current_record)
262 {
263  HASH_LINK *pos;
264  uint flag,idx;
265  DBUG_ENTER("my_hash_first_from_hash_value");
266 
267  flag=1;
268  if (hash->records)
269  {
270  idx= my_hash_mask(hash_value,
271  hash->blength, hash->records);
272  do
273  {
274  pos= dynamic_element(&hash->array,idx,HASH_LINK*);
275  if (!hashcmp(hash,pos,key,length))
276  {
277  DBUG_PRINT("exit",("found key at %d",idx));
278  *current_record= idx;
279  DBUG_RETURN (pos->data);
280  }
281  if (flag)
282  {
283  flag=0; /* Reset flag */
284  if (my_hash_rec_mask(hash, pos, hash->blength, hash->records) != idx)
285  break; /* Wrong link */
286  }
287  }
288  while ((idx=pos->next) != NO_RECORD);
289  }
290  *current_record= NO_RECORD;
291  DBUG_RETURN(0);
292 }
293 
294  /* Get next record with identical key */
295  /* Can only be called if previous calls was my_hash_search */
296 
297 uchar* my_hash_next(const HASH *hash, const uchar *key, size_t length,
298  HASH_SEARCH_STATE *current_record)
299 {
300  HASH_LINK *pos;
301  uint idx;
302 
303  if (*current_record != NO_RECORD)
304  {
305  HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
306  for (idx=data[*current_record].next; idx != NO_RECORD ; idx=pos->next)
307  {
308  pos=data+idx;
309  if (!hashcmp(hash,pos,key,length))
310  {
311  *current_record= idx;
312  return pos->data;
313  }
314  }
315  *current_record= NO_RECORD;
316  }
317  return 0;
318 }
319 
320 
321  /* Change link from pos to new_link */
322 
323 static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink)
324 {
325  HASH_LINK *old_link;
326  do
327  {
328  old_link=array+next_link;
329  }
330  while ((next_link=old_link->next) != find);
331  old_link->next= newlink;
332  return;
333 }
334 
335 /*
336  Compare a key in a record to a whole key. Return 0 if identical
337 
338  SYNOPSIS
339  hashcmp()
340  hash hash table
341  pos position of hash record to use in comparison
342  key key for comparison
343  length length of key
344 
345  NOTES:
346  If length is 0, comparison is done using the length of the
347  record being compared against.
348 
349  RETURN
350  = 0 key of record == key
351  != 0 key of record != key
352  */
353 
354 static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key,
355  size_t length)
356 {
357  size_t rec_keylength;
358  uchar *rec_key= (uchar*) my_hash_key(hash, pos->data, &rec_keylength, 1);
359  return ((length && length != rec_keylength) ||
360  my_strnncoll(hash->charset, (uchar*) rec_key, rec_keylength,
361  (uchar*) key, rec_keylength));
362 }
363 
364 
365  /* Write a hash-key to the hash-index */
366 
367 my_bool my_hash_insert(HASH *info, const uchar *record)
368 {
369  int flag;
370  size_t idx,halfbuff,first_index;
371  my_hash_value_type hash_nr;
372  uchar *UNINIT_VAR(ptr_to_rec),*UNINIT_VAR(ptr_to_rec2);
373  HASH_LINK *data,*empty,*UNINIT_VAR(gpos),*UNINIT_VAR(gpos2),*pos;
374 
375  if (HASH_UNIQUE & info->flags)
376  {
377  uchar *key= (uchar*) my_hash_key(info, record, &idx, 1);
378  if (my_hash_search(info, key, idx))
379  return(TRUE); /* Duplicate entry */
380  }
381 
382  flag=0;
383  if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array)))
384  return(TRUE); /* No more memory */
385 
386  data=dynamic_element(&info->array,0,HASH_LINK*);
387  halfbuff= info->blength >> 1;
388 
389  idx=first_index=info->records-halfbuff;
390  if (idx != info->records) /* If some records */
391  {
392  do
393  {
394  pos=data+idx;
395  hash_nr=rec_hashnr(info,pos->data);
396  if (flag == 0) /* First loop; Check if ok */
397  if (my_hash_mask(hash_nr, info->blength, info->records) != first_index)
398  break;
399  if (!(hash_nr & halfbuff))
400  { /* Key will not move */
401  if (!(flag & LOWFIND))
402  {
403  if (flag & HIGHFIND)
404  {
405  flag=LOWFIND | HIGHFIND;
406  /* key shall be moved to the current empty position */
407  gpos=empty;
408  ptr_to_rec=pos->data;
409  empty=pos; /* This place is now free */
410  }
411  else
412  {
413  flag=LOWFIND | LOWUSED; /* key isn't changed */
414  gpos=pos;
415  ptr_to_rec=pos->data;
416  }
417  }
418  else
419  {
420  if (!(flag & LOWUSED))
421  {
422  /* Change link of previous LOW-key */
423  gpos->data=ptr_to_rec;
424  gpos->next= (uint) (pos-data);
425  flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED);
426  }
427  gpos=pos;
428  ptr_to_rec=pos->data;
429  }
430  }
431  else
432  { /* key will be moved */
433  if (!(flag & HIGHFIND))
434  {
435  flag= (flag & LOWFIND) | HIGHFIND;
436  /* key shall be moved to the last (empty) position */
437  gpos2 = empty; empty=pos;
438  ptr_to_rec2=pos->data;
439  }
440  else
441  {
442  if (!(flag & HIGHUSED))
443  {
444  /* Change link of previous hash-key and save */
445  gpos2->data=ptr_to_rec2;
446  gpos2->next=(uint) (pos-data);
447  flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED);
448  }
449  gpos2=pos;
450  ptr_to_rec2=pos->data;
451  }
452  }
453  }
454  while ((idx=pos->next) != NO_RECORD);
455 
456  if ((flag & (LOWFIND | LOWUSED)) == LOWFIND)
457  {
458  gpos->data=ptr_to_rec;
459  gpos->next=NO_RECORD;
460  }
461  if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND)
462  {
463  gpos2->data=ptr_to_rec2;
464  gpos2->next=NO_RECORD;
465  }
466  }
467  /* Check if we are at the empty position */
468 
469  idx= my_hash_mask(rec_hashnr(info, record), info->blength, info->records + 1);
470  pos=data+idx;
471  if (pos == empty)
472  {
473  pos->data=(uchar*) record;
474  pos->next=NO_RECORD;
475  }
476  else
477  {
478  /* Check if more records in same hash-nr family */
479  empty[0]=pos[0];
480  gpos= data + my_hash_rec_mask(info, pos, info->blength, info->records + 1);
481  if (pos == gpos)
482  {
483  pos->data=(uchar*) record;
484  pos->next=(uint) (empty - data);
485  }
486  else
487  {
488  pos->data=(uchar*) record;
489  pos->next=NO_RECORD;
490  movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data));
491  }
492  }
493  if (++info->records == info->blength)
494  info->blength+= info->blength;
495  return(0);
496 }
497 
498 
499 /******************************************************************************
500 ** Remove one record from hash-table. The record with the same record
501 ** ptr is removed.
502 ** if there is a free-function it's called for record if found
503 ******************************************************************************/
504 
505 my_bool my_hash_delete(HASH *hash, uchar *record)
506 {
507  uint blength,pos2,idx,empty_index;
508  my_hash_value_type pos_hashnr, lastpos_hashnr;
509  HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty;
510  DBUG_ENTER("my_hash_delete");
511  if (!hash->records)
512  DBUG_RETURN(1);
513 
514  blength=hash->blength;
515  data=dynamic_element(&hash->array,0,HASH_LINK*);
516  /* Search after record with key */
517  pos= data + my_hash_mask(rec_hashnr(hash, record), blength, hash->records);
518  gpos = 0;
519 
520  while (pos->data != record)
521  {
522  gpos=pos;
523  if (pos->next == NO_RECORD)
524  DBUG_RETURN(1); /* Key not found */
525  pos=data+pos->next;
526  }
527 
528  if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1;
529  lastpos=data+hash->records;
530 
531  /* Remove link to record */
532  empty=pos; empty_index=(uint) (empty-data);
533  if (gpos)
534  gpos->next=pos->next; /* unlink current ptr */
535  else if (pos->next != NO_RECORD)
536  {
537  empty=data+(empty_index=pos->next);
538  pos->data=empty->data;
539  pos->next=empty->next;
540  }
541 
542  if (empty == lastpos) /* last key at wrong pos or no next link */
543  goto exit;
544 
545  /* Move the last key (lastpos) */
546  lastpos_hashnr=rec_hashnr(hash,lastpos->data);
547  /* pos is where lastpos should be */
548  pos= data + my_hash_mask(lastpos_hashnr, hash->blength, hash->records);
549  if (pos == empty) /* Move to empty position. */
550  {
551  empty[0]=lastpos[0];
552  goto exit;
553  }
554  pos_hashnr=rec_hashnr(hash,pos->data);
555  /* pos3 is where the pos should be */
556  pos3= data + my_hash_mask(pos_hashnr, hash->blength, hash->records);
557  if (pos != pos3)
558  { /* pos is on wrong posit */
559  empty[0]=pos[0]; /* Save it here */
560  pos[0]=lastpos[0]; /* This should be here */
561  movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index);
562  goto exit;
563  }
564  pos2= my_hash_mask(lastpos_hashnr, blength, hash->records + 1);
565  if (pos2 == my_hash_mask(pos_hashnr, blength, hash->records + 1))
566  { /* Identical key-positions */
567  if (pos2 != hash->records)
568  {
569  empty[0]=lastpos[0];
570  movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index);
571  goto exit;
572  }
573  idx= (uint) (pos-data); /* Link pos->next after lastpos */
574  }
575  else idx= NO_RECORD; /* Different positions merge */
576 
577  empty[0]=lastpos[0];
578  movelink(data,idx,empty_index,pos->next);
579  pos->next=empty_index;
580 
581 exit:
582  (void) pop_dynamic(&hash->array);
583  if (hash->free)
584  (*hash->free)((uchar*) record);
585  DBUG_RETURN(0);
586 }
587 
588  /*
589  Update keys when record has changed.
590  This is much more efficent than using a delete & insert.
591  */
592 
593 my_bool my_hash_update(HASH *hash, uchar *record, uchar *old_key,
594  size_t old_key_length)
595 {
596  uint new_index,new_pos_index,blength,records;
597  size_t idx,empty;
598  HASH_LINK org_link,*data,*previous,*pos;
599  DBUG_ENTER("my_hash_update");
600 
601  if (HASH_UNIQUE & hash->flags)
602  {
603  HASH_SEARCH_STATE state;
604  uchar *found, *new_key= (uchar*) my_hash_key(hash, record, &idx, 1);
605  if ((found= my_hash_first(hash, new_key, idx, &state)))
606  {
607  do
608  {
609  if (found != record)
610  DBUG_RETURN(1); /* Duplicate entry */
611  }
612  while ((found= my_hash_next(hash, new_key, idx, &state)));
613  }
614  }
615 
616  data=dynamic_element(&hash->array,0,HASH_LINK*);
617  blength=hash->blength; records=hash->records;
618 
619  /* Search after record with key */
620 
621  idx= my_hash_mask(calc_hash(hash, old_key, (old_key_length ?
622  old_key_length :
623  hash->key_length)),
624  blength, records);
625  new_index= my_hash_mask(rec_hashnr(hash, record), blength, records);
626  if (idx == new_index)
627  DBUG_RETURN(0); /* Nothing to do (No record check) */
628  previous=0;
629  for (;;)
630  {
631 
632  if ((pos= data+idx)->data == record)
633  break;
634  previous=pos;
635  if ((idx=pos->next) == NO_RECORD)
636  DBUG_RETURN(1); /* Not found in links */
637  }
638  org_link= *pos;
639  empty=idx;
640 
641  /* Relink record from current chain */
642 
643  if (!previous)
644  {
645  if (pos->next != NO_RECORD)
646  {
647  empty=pos->next;
648  *pos= data[pos->next];
649  }
650  }
651  else
652  previous->next=pos->next; /* unlink pos */
653 
654  /* Move data to correct position */
655  if (new_index == empty)
656  {
657  /*
658  At this point record is unlinked from the old chain, thus it holds
659  random position. By the chance this position is equal to position
660  for the first element in the new chain. That means updated record
661  is the only record in the new chain.
662  */
663  if (empty != idx)
664  {
665  /*
666  Record was moved while unlinking it from the old chain.
667  Copy data to a new position.
668  */
669  data[empty]= org_link;
670  }
671  data[empty].next= NO_RECORD;
672  DBUG_RETURN(0);
673  }
674  pos=data+new_index;
675  new_pos_index= my_hash_rec_mask(hash, pos, blength, records);
676  if (new_index != new_pos_index)
677  { /* Other record in wrong position */
678  data[empty] = *pos;
679  movelink(data,new_index,new_pos_index,empty);
680  org_link.next=NO_RECORD;
681  data[new_index]= org_link;
682  }
683  else
684  { /* Link in chain at right position */
685  org_link.next=data[new_index].next;
686  data[empty]=org_link;
687  data[new_index].next=empty;
688  }
689  DBUG_RETURN(0);
690 }
691 
692 
693 uchar *my_hash_element(HASH *hash, ulong idx)
694 {
695  if (idx < hash->records)
696  return dynamic_element(&hash->array,idx,HASH_LINK*)->data;
697  return 0;
698 }
699 
700 
701 /*
702  Replace old row with new row. This should only be used when key
703  isn't changed
704 */
705 
706 void my_hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record,
707  uchar *new_row)
708 {
709  if (*current_record != NO_RECORD) /* Safety */
710  dynamic_element(&hash->array, *current_record, HASH_LINK*)->data= new_row;
711 }
712 
713 
714 #ifndef DBUG_OFF
715 
716 my_bool my_hash_check(HASH *hash)
717 {
718  int error;
719  uint i,rec_link,found,max_links,seek,links,idx;
720  uint records,blength;
721  HASH_LINK *data,*hash_info;
722 
723  records=hash->records; blength=hash->blength;
724  data=dynamic_element(&hash->array,0,HASH_LINK*);
725  error=0;
726 
727  for (i=found=max_links=seek=0 ; i < records ; i++)
728  {
729  if (my_hash_rec_mask(hash, data + i, blength, records) == i)
730  {
731  found++; seek++; links=1;
732  for (idx=data[i].next ;
733  idx != NO_RECORD && found < records + 1;
734  idx=hash_info->next)
735  {
736  if (idx >= records)
737  {
738  DBUG_PRINT("error",
739  ("Found pointer outside array to %d from link starting at %d",
740  idx,i));
741  error=1;
742  }
743  hash_info=data+idx;
744  seek+= ++links;
745  if ((rec_link= my_hash_rec_mask(hash, hash_info,
746  blength, records)) != i)
747  {
748  DBUG_PRINT("error", ("Record in wrong link at %d: Start %d "
749  "Record: 0x%lx Record-link %d",
750  idx, i, (long) hash_info->data, rec_link));
751  error=1;
752  }
753  else
754  found++;
755  }
756  if (links > max_links) max_links=links;
757  }
758  }
759  if (found != records)
760  {
761  DBUG_PRINT("error",("Found %u of %u records", found, records));
762  error=1;
763  }
764  if (records)
765  DBUG_PRINT("info",
766  ("records: %u seeks: %d max links: %d hitrate: %.2f",
767  records,seek,max_links,(float) seek / (float) records));
768  return error;
769 }
770 #endif