Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (c) 2000, 2010, Oracle and/or its affiliates. |
2 | | Copyright (c) 2011, 2020, MariaDB Corporation. |
3 | | |
4 | | This program is free software; you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation; version 2 of the License. |
7 | | |
8 | | This program is distributed in the hope that it will be useful, |
9 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | | GNU General Public License for more details. |
12 | | |
13 | | You should have received a copy of the GNU General Public License |
14 | | along with this program; if not, write to the Free Software |
15 | | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ |
16 | | |
17 | | /* The hash functions used for saveing keys */ |
18 | | /* One of key_length or key_length_offset must be given */ |
19 | | /* Key length of 0 isn't allowed */ |
20 | | |
21 | | #include "mysys_priv.h" |
22 | | #include <m_string.h> |
23 | | #include <m_ctype.h> |
24 | | #include "hash.h" |
25 | | |
26 | 0 | #define NO_RECORD ~((my_hash_value_type) 0) |
27 | 0 | #define LOWFIND 1 |
28 | 0 | #define LOWUSED 2 |
29 | 0 | #define HIGHFIND 4 |
30 | 0 | #define HIGHUSED 8 |
31 | | |
32 | | typedef struct st_hash_info { |
33 | | uint32 next; /* index to next key */ |
34 | | my_hash_value_type hash_nr; |
35 | | uchar *data; /* data for current entry */ |
36 | | } HASH_LINK; |
37 | | |
38 | | static uint my_hash_mask(my_hash_value_type hashnr, |
39 | | size_t buffmax, size_t maxlength); |
40 | | static void movelink(HASH_LINK *array,uint pos,uint next_link,uint newlink); |
41 | | static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key, |
42 | | size_t length); |
43 | | |
44 | | my_hash_value_type my_hash_sort(CHARSET_INFO *cs, const uchar *key, |
45 | | size_t length) |
46 | 0 | { |
47 | 0 | ulong nr1= 1, nr2= 4; |
48 | 0 | my_ci_hash_sort(cs, (uchar*) key, length, &nr1, &nr2); |
49 | 0 | return (my_hash_value_type) nr1; |
50 | 0 | } |
51 | | |
52 | | /** |
53 | | @brief Initialize the hash |
54 | | |
55 | | @details |
56 | | |
57 | | Initialize the hash, by defining and giving valid values for |
58 | | its elements. The failure to allocate memory for the |
59 | | hash->array element will not result in a fatal failure. The |
60 | | dynamic array that is part of the hash will allocate memory |
61 | | as required during insertion. |
62 | | |
63 | | @param[in] psi_key The key to register instrumented memory |
64 | | @param[in,out] hash The hash that is initialized |
65 | | @param[in] growth_size size incrememnt for the underlying dynarray |
66 | | @param[in] charset The character set information |
67 | | @param[in] size The hash size |
68 | | @param[in] key_offest The key offset for the hash |
69 | | @param[in] key_length The length of the key used in |
70 | | the hash |
71 | | @param[in] get_key get the key for the hash |
72 | | @param[in] free_element pointer to the function that |
73 | | does cleanup |
74 | | @param[in] flags flags set in the hash |
75 | | @return indicates success or failure of initialization |
76 | | @retval 0 success |
77 | | @retval 1 failure |
78 | | */ |
79 | | my_bool |
80 | | my_hash_init2(PSI_memory_key psi_key, HASH *hash, size_t growth_size, |
81 | | CHARSET_INFO *charset, size_t size, size_t key_offset, |
82 | | size_t key_length, my_hash_get_key get_key, |
83 | | my_hash_function hash_function, |
84 | | void (*free_element)(void*), uint flags) |
85 | 0 | { |
86 | 0 | my_bool res; |
87 | 0 | DBUG_ENTER("my_hash_init2"); |
88 | 0 | DBUG_PRINT("enter",("hash:%p size: %u", hash, (uint) size)); |
89 | |
|
90 | 0 | hash->records=0; |
91 | 0 | hash->key_offset=key_offset; |
92 | 0 | hash->key_length=key_length; |
93 | 0 | hash->blength=1; |
94 | 0 | hash->get_key=get_key; |
95 | 0 | hash->hash_function= hash_function ? hash_function : my_hash_sort; |
96 | 0 | hash->free=free_element; |
97 | 0 | hash->flags=flags; |
98 | 0 | hash->charset=charset; |
99 | 0 | res= init_dynamic_array2(psi_key, &hash->array, sizeof(HASH_LINK), NULL, size, |
100 | 0 | growth_size, MYF((flags & HASH_THREAD_SPECIFIC ? |
101 | 0 | MY_THREAD_SPECIFIC : 0))); |
102 | 0 | DBUG_RETURN(res); |
103 | 0 | } |
104 | | |
105 | | |
106 | | /* |
107 | | Call hash->free on all elements in hash. |
108 | | |
109 | | SYNOPSIS |
110 | | my_hash_free_elements() |
111 | | hash hash table |
112 | | |
113 | | NOTES: |
114 | | Sets records to 0 |
115 | | */ |
116 | | |
117 | | static inline void my_hash_free_elements(HASH *hash) |
118 | 0 | { |
119 | 0 | uint records= hash->records; |
120 | 0 | if (records == 0) |
121 | 0 | return; |
122 | | |
123 | | /* |
124 | | Set records to 0 early to guard against anyone looking at the structure |
125 | | during the free process |
126 | | */ |
127 | 0 | hash->records= 0; |
128 | |
|
129 | 0 | if (hash->free) |
130 | 0 | { |
131 | 0 | HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*); |
132 | 0 | HASH_LINK *end= data + records; |
133 | 0 | do |
134 | 0 | { |
135 | 0 | (*hash->free)((data++)->data); |
136 | 0 | } while (data < end); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | |
141 | | /* |
142 | | Free memory used by hash. |
143 | | |
144 | | SYNOPSIS |
145 | | my_hash_free() |
146 | | hash the hash to delete elements of |
147 | | |
148 | | NOTES: Hash can't be reused without calling my_hash_init again. |
149 | | */ |
150 | | |
151 | | void my_hash_free(HASH *hash) |
152 | 0 | { |
153 | 0 | DBUG_ENTER("my_hash_free"); |
154 | 0 | DBUG_PRINT("enter",("hash:%p elements: %ld", |
155 | 0 | hash, hash->records)); |
156 | |
|
157 | 0 | my_hash_free_elements(hash); |
158 | 0 | hash->free= 0; |
159 | 0 | delete_dynamic(&hash->array); |
160 | 0 | hash->blength= 0; |
161 | 0 | DBUG_VOID_RETURN; |
162 | 0 | } |
163 | | |
164 | | |
165 | | /* |
166 | | Delete all elements from the hash (the hash itself is to be reused). |
167 | | |
168 | | SYNOPSIS |
169 | | my_hash_reset() |
170 | | hash the hash to delete elements of |
171 | | */ |
172 | | |
173 | | void my_hash_reset(HASH *hash) |
174 | 0 | { |
175 | 0 | DBUG_ENTER("my_hash_reset"); |
176 | 0 | DBUG_PRINT("enter",("hash:%p", hash)); |
177 | |
|
178 | 0 | my_hash_free_elements(hash); |
179 | 0 | reset_dynamic(&hash->array); |
180 | | /* Set row pointers so that the hash can be reused at once */ |
181 | 0 | hash->blength= 1; |
182 | 0 | DBUG_VOID_RETURN; |
183 | 0 | } |
184 | | |
185 | | /* some helper functions */ |
186 | | |
187 | | /* |
188 | | This function is char* instead of uchar* as HPUX11 compiler can't |
189 | | handle inline functions that are not defined as native types |
190 | | */ |
191 | | |
192 | | static inline char* |
193 | | my_hash_key(const HASH *hash, const uchar *record, size_t *length, |
194 | | my_bool first) |
195 | 0 | { |
196 | 0 | if (hash->get_key) |
197 | 0 | return (char*) (*hash->get_key)(record,length,first); |
198 | 0 | *length=hash->key_length; |
199 | 0 | return (char*) record+hash->key_offset; |
200 | 0 | } |
201 | | |
202 | | /* Calculate pos according to keys */ |
203 | | |
204 | | static uint my_hash_mask(my_hash_value_type hashnr, size_t buffmax, |
205 | | size_t maxlength) |
206 | 0 | { |
207 | 0 | if ((hashnr & (buffmax-1)) < maxlength) |
208 | 0 | return (uint) (hashnr & (buffmax-1)); |
209 | 0 | return (uint) (hashnr & ((buffmax >> 1) -1)); |
210 | 0 | } |
211 | | |
212 | | static inline uint my_hash_rec_mask(HASH_LINK *pos, |
213 | | size_t buffmax, size_t maxlength) |
214 | 0 | { |
215 | 0 | return my_hash_mask(pos->hash_nr, buffmax, maxlength); |
216 | 0 | } |
217 | | |
218 | | |
219 | | |
220 | | /* for compilers which can not handle inline */ |
221 | | static |
222 | | #if !defined(__USLC__) && !defined(__sgi) |
223 | | inline |
224 | | #endif |
225 | | my_hash_value_type rec_hashnr(HASH *hash,const uchar *record) |
226 | 0 | { |
227 | 0 | size_t length; |
228 | 0 | uchar *key= (uchar*) my_hash_key(hash, record, &length, 0); |
229 | 0 | return hash->hash_function(hash->charset, key, length); |
230 | 0 | } |
231 | | |
232 | | |
233 | | uchar* my_hash_search(const HASH *hash, const uchar *key, size_t length) |
234 | 0 | { |
235 | 0 | HASH_SEARCH_STATE state; |
236 | 0 | return my_hash_first(hash, key, length, &state); |
237 | 0 | } |
238 | | |
239 | | uchar* my_hash_search_using_hash_value(const HASH *hash, |
240 | | my_hash_value_type hash_value, |
241 | | const uchar *key, |
242 | | size_t length) |
243 | 0 | { |
244 | 0 | HASH_SEARCH_STATE state; |
245 | 0 | return my_hash_first_from_hash_value(hash, hash_value, |
246 | 0 | key, length, &state); |
247 | 0 | } |
248 | | |
249 | | |
250 | | /* |
251 | | Search after a record based on a key |
252 | | |
253 | | NOTE |
254 | | Assigns the number of the found record to HASH_SEARCH_STATE state |
255 | | */ |
256 | | |
257 | | uchar* my_hash_first(const HASH *hash, const uchar *key, size_t length, |
258 | | HASH_SEARCH_STATE *current_record) |
259 | 0 | { |
260 | 0 | uchar *res; |
261 | 0 | DBUG_ASSERT(my_hash_inited(hash)); |
262 | |
|
263 | 0 | res= my_hash_first_from_hash_value(hash, |
264 | 0 | hash->hash_function(hash->charset, key, |
265 | 0 | length ? length : |
266 | 0 | hash->key_length), |
267 | 0 | key, length, current_record); |
268 | 0 | return res; |
269 | 0 | } |
270 | | |
271 | | |
272 | | uchar* my_hash_first_from_hash_value(const HASH *hash, |
273 | | my_hash_value_type hash_value, |
274 | | const uchar *key, |
275 | | size_t length, |
276 | | HASH_SEARCH_STATE *current_record) |
277 | 0 | { |
278 | 0 | HASH_LINK *pos; |
279 | 0 | DBUG_ENTER("my_hash_first_from_hash_value"); |
280 | |
|
281 | 0 | if (hash->records) |
282 | 0 | { |
283 | 0 | uint flag= 1; |
284 | 0 | uint idx= my_hash_mask(hash_value, |
285 | 0 | hash->blength, hash->records); |
286 | 0 | if (!length) |
287 | 0 | length= hash->key_length; // length for fixed length keys or 0 |
288 | 0 | do |
289 | 0 | { |
290 | 0 | pos= dynamic_element(&hash->array,idx,HASH_LINK*); |
291 | 0 | if (!hashcmp(hash,pos,key,length)) |
292 | 0 | { |
293 | 0 | DBUG_PRINT("exit",("found key at %d",idx)); |
294 | 0 | *current_record= idx; |
295 | 0 | DBUG_RETURN (pos->data); |
296 | 0 | } |
297 | 0 | if (flag) |
298 | 0 | { |
299 | 0 | flag=0; /* Reset flag */ |
300 | 0 | if (my_hash_rec_mask(pos, hash->blength, hash->records) != idx) |
301 | 0 | break; /* Wrong link */ |
302 | 0 | } |
303 | 0 | } |
304 | 0 | while ((idx=pos->next) != NO_RECORD); |
305 | 0 | } |
306 | 0 | *current_record= NO_RECORD; |
307 | 0 | DBUG_RETURN(0); |
308 | 0 | } |
309 | | |
310 | | /* Get next record with identical key */ |
311 | | /* Can only be called if previous calls was my_hash_search */ |
312 | | |
313 | | uchar* my_hash_next(const HASH *hash, const uchar *key, size_t length, |
314 | | HASH_SEARCH_STATE *current_record) |
315 | 0 | { |
316 | 0 | HASH_LINK *pos; |
317 | 0 | uint idx; |
318 | |
|
319 | 0 | if (*current_record != NO_RECORD) |
320 | 0 | { |
321 | 0 | HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*); |
322 | 0 | if (!length) |
323 | 0 | length= hash->key_length; // length for fixed length keys or 0 |
324 | 0 | for (idx=data[*current_record].next; idx != NO_RECORD ; idx=pos->next) |
325 | 0 | { |
326 | 0 | pos=data+idx; |
327 | 0 | if (!hashcmp(hash,pos,key,length)) |
328 | 0 | { |
329 | 0 | *current_record= idx; |
330 | 0 | return pos->data; |
331 | 0 | } |
332 | 0 | } |
333 | 0 | *current_record= NO_RECORD; |
334 | 0 | } |
335 | 0 | return 0; |
336 | 0 | } |
337 | | |
338 | | |
339 | | /* Change link from pos to new_link */ |
340 | | |
341 | | static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink) |
342 | 0 | { |
343 | 0 | HASH_LINK *old_link; |
344 | 0 | do |
345 | 0 | { |
346 | 0 | old_link=array+next_link; |
347 | 0 | } |
348 | 0 | while ((next_link=old_link->next) != find); |
349 | 0 | old_link->next= newlink; |
350 | 0 | return; |
351 | 0 | } |
352 | | |
353 | | /* |
354 | | Compare a key in a record to a whole key. Return 0 if identical |
355 | | |
356 | | SYNOPSIS |
357 | | hashcmp() |
358 | | hash hash table |
359 | | pos position of hash record to use in comparison |
360 | | key key for comparison |
361 | | length length of key |
362 | | |
363 | | NOTES: |
364 | | length equal 0 can mean 2 things: |
365 | | 1) it is fixed key length hash (HASH::key_length != 0) and |
366 | | default length should be taken in this case |
367 | | 2) it is really 0 length key for variable key length hash |
368 | | (HASH::key_length == 0) |
369 | | |
370 | | RETURN |
371 | | = 0 key of record == key |
372 | | != 0 key of record != key |
373 | | */ |
374 | | |
375 | | static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key, |
376 | | size_t length) |
377 | 0 | { |
378 | 0 | size_t rec_keylength; |
379 | 0 | uchar *rec_key; |
380 | 0 | rec_key= (uchar*) my_hash_key(hash, pos->data, &rec_keylength, 1); |
381 | 0 | return my_strnncoll(hash->charset, (uchar*) rec_key, rec_keylength, |
382 | 0 | (uchar*) key, length); |
383 | 0 | } |
384 | | |
385 | | |
386 | | /** |
387 | | Write a hash-key to the hash-index |
388 | | |
389 | | @return |
390 | | @retval 0 ok |
391 | | @retval 1 Duplicate key or out of memory |
392 | | */ |
393 | | |
394 | | my_bool my_hash_insert(HASH *info, const uchar *record) |
395 | 0 | { |
396 | 0 | int flag; |
397 | 0 | size_t idx, halfbuff, first_index; |
398 | 0 | size_t length; |
399 | 0 | my_hash_value_type current_hash_nr, UNINIT_VAR(rec_hash_nr), |
400 | 0 | UNINIT_VAR(rec2_hash_nr); |
401 | 0 | uchar *UNINIT_VAR(rec_data),*UNINIT_VAR(rec2_data), *key; |
402 | 0 | HASH_LINK *data,*empty,*UNINIT_VAR(gpos),*UNINIT_VAR(gpos2),*pos; |
403 | |
|
404 | 0 | key= (uchar*) my_hash_key(info, record, &length, 1); |
405 | 0 | current_hash_nr= info->hash_function(info->charset, key, length); |
406 | |
|
407 | 0 | if (info->flags & HASH_UNIQUE) |
408 | 0 | { |
409 | 0 | if (my_hash_search_using_hash_value(info, current_hash_nr, key, length)) |
410 | 0 | return(TRUE); /* Duplicate entry */ |
411 | 0 | } |
412 | | |
413 | 0 | flag=0; |
414 | 0 | if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array))) |
415 | 0 | return(TRUE); /* No more memory */ |
416 | | |
417 | 0 | data=dynamic_element(&info->array,0,HASH_LINK*); |
418 | 0 | halfbuff= info->blength >> 1; |
419 | |
|
420 | 0 | idx=first_index=info->records-halfbuff; |
421 | 0 | if (idx != info->records) /* If some records */ |
422 | 0 | { |
423 | 0 | do |
424 | 0 | { |
425 | 0 | my_hash_value_type hash_nr; |
426 | 0 | pos=data+idx; |
427 | 0 | hash_nr= pos->hash_nr; |
428 | 0 | if (flag == 0) /* First loop; Check if ok */ |
429 | 0 | if (my_hash_mask(hash_nr, info->blength, info->records) != first_index) |
430 | 0 | break; |
431 | 0 | if (!(hash_nr & halfbuff)) |
432 | 0 | { /* Key will not move */ |
433 | 0 | if (!(flag & LOWFIND)) |
434 | 0 | { |
435 | 0 | if (flag & HIGHFIND) |
436 | 0 | { |
437 | 0 | flag= LOWFIND | HIGHFIND; |
438 | | /* key shall be moved to the current empty position */ |
439 | 0 | gpos= empty; |
440 | 0 | rec_data= pos->data; |
441 | 0 | rec_hash_nr= pos->hash_nr; |
442 | 0 | empty=pos; /* This place is now free */ |
443 | 0 | } |
444 | 0 | else |
445 | 0 | { |
446 | 0 | flag= LOWFIND | LOWUSED; /* key isn't changed */ |
447 | 0 | gpos= pos; |
448 | 0 | rec_data= pos->data; |
449 | 0 | rec_hash_nr= pos->hash_nr; |
450 | 0 | } |
451 | 0 | } |
452 | 0 | else |
453 | 0 | { |
454 | 0 | if (!(flag & LOWUSED)) |
455 | 0 | { |
456 | | /* Change link of previous LOW-key */ |
457 | 0 | gpos->data= rec_data; |
458 | 0 | gpos->hash_nr= rec_hash_nr; |
459 | 0 | gpos->next= (uint) (pos-data); |
460 | 0 | flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED); |
461 | 0 | } |
462 | 0 | gpos= pos; |
463 | 0 | rec_data= pos->data; |
464 | 0 | rec_hash_nr= pos->hash_nr; |
465 | 0 | } |
466 | 0 | } |
467 | 0 | else |
468 | 0 | { /* key will be moved */ |
469 | 0 | if (!(flag & HIGHFIND)) |
470 | 0 | { |
471 | 0 | flag= (flag & LOWFIND) | HIGHFIND; |
472 | | /* key shall be moved to the last (empty) position */ |
473 | 0 | gpos2= empty; |
474 | 0 | empty= pos; |
475 | 0 | rec2_data= pos->data; |
476 | 0 | rec2_hash_nr= pos->hash_nr; |
477 | 0 | } |
478 | 0 | else |
479 | 0 | { |
480 | 0 | if (!(flag & HIGHUSED)) |
481 | 0 | { |
482 | | /* Change link of previous hash-key and save */ |
483 | 0 | gpos2->data= rec2_data; |
484 | 0 | gpos2->hash_nr= rec2_hash_nr; |
485 | 0 | gpos2->next= (uint) (pos-data); |
486 | 0 | flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED); |
487 | 0 | } |
488 | 0 | gpos2= pos; |
489 | 0 | rec2_data= pos->data; |
490 | 0 | rec2_hash_nr= pos->hash_nr; |
491 | 0 | } |
492 | 0 | } |
493 | 0 | } |
494 | 0 | while ((idx=pos->next) != NO_RECORD); |
495 | | |
496 | 0 | if ((flag & (LOWFIND | LOWUSED)) == LOWFIND) |
497 | 0 | { |
498 | 0 | gpos->data= rec_data; |
499 | 0 | gpos->hash_nr= rec_hash_nr; |
500 | 0 | gpos->next= NO_RECORD; |
501 | 0 | } |
502 | 0 | if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND) |
503 | 0 | { |
504 | 0 | gpos2->data= rec2_data; |
505 | 0 | gpos2->hash_nr= rec2_hash_nr; |
506 | 0 | gpos2->next= NO_RECORD; |
507 | 0 | } |
508 | 0 | } |
509 | | |
510 | 0 | idx= my_hash_mask(current_hash_nr, info->blength, info->records + 1); |
511 | 0 | pos= data+idx; |
512 | | /* Check if we are at the empty position */ |
513 | 0 | if (pos == empty) |
514 | 0 | { |
515 | 0 | pos->next=NO_RECORD; |
516 | 0 | } |
517 | 0 | else |
518 | 0 | { |
519 | | /* Move conflicting record to empty position (last) */ |
520 | 0 | empty[0]= pos[0]; |
521 | | /* Check if the moved record was in same hash-nr family */ |
522 | 0 | gpos= data + my_hash_rec_mask(pos, info->blength, info->records + 1); |
523 | 0 | if (pos == gpos) |
524 | 0 | { |
525 | | /* Point to moved record */ |
526 | 0 | pos->next= (uint32) (empty - data); |
527 | 0 | } |
528 | 0 | else |
529 | 0 | { |
530 | 0 | pos->next= NO_RECORD; |
531 | 0 | movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data)); |
532 | 0 | } |
533 | 0 | } |
534 | 0 | pos->data= (uchar*) record; |
535 | 0 | pos->hash_nr= current_hash_nr; |
536 | 0 | if (++info->records == info->blength) |
537 | 0 | info->blength+= info->blength; |
538 | 0 | return(0); |
539 | 0 | } |
540 | | |
541 | | |
542 | | /** |
543 | | Remove one record from hash-table. |
544 | | |
545 | | @fn hash_delete() |
546 | | @param hash Hash tree |
547 | | @param record Row to be deleted |
548 | | |
549 | | @notes |
550 | | The record with the same record ptr is removed. |
551 | | If there is a free-function it's called if record was found. |
552 | | |
553 | | hash->free() is guarantee to be called only after the row has been |
554 | | deleted from the hash and the hash can be reused by other threads. |
555 | | |
556 | | @return |
557 | | @retval 0 ok |
558 | | @retval 1 Record not found |
559 | | */ |
560 | | |
561 | | my_bool my_hash_delete(HASH *hash, uchar *record) |
562 | 0 | { |
563 | 0 | uint pos2,idx,empty_index; |
564 | 0 | my_hash_value_type pos_hashnr, lastpos_hashnr; |
565 | 0 | size_t blength; |
566 | 0 | HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty; |
567 | 0 | DBUG_ENTER("my_hash_delete"); |
568 | 0 | if (!hash->records) |
569 | 0 | DBUG_RETURN(1); |
570 | | |
571 | 0 | blength=hash->blength; |
572 | 0 | data=dynamic_element(&hash->array,0,HASH_LINK*); |
573 | | /* Search after record with key */ |
574 | 0 | pos= data + my_hash_mask(rec_hashnr(hash, record), blength, hash->records); |
575 | 0 | gpos = 0; |
576 | |
|
577 | 0 | while (pos->data != record) |
578 | 0 | { |
579 | 0 | gpos=pos; |
580 | 0 | if (pos->next == NO_RECORD) |
581 | 0 | DBUG_RETURN(1); /* Key not found */ |
582 | 0 | pos=data+pos->next; |
583 | 0 | } |
584 | | |
585 | 0 | if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1; |
586 | 0 | lastpos=data+hash->records; |
587 | | |
588 | | /* Remove link to record */ |
589 | 0 | empty=pos; empty_index=(uint) (empty-data); |
590 | 0 | if (gpos) |
591 | 0 | gpos->next=pos->next; /* unlink current ptr */ |
592 | 0 | else if (pos->next != NO_RECORD) |
593 | 0 | { |
594 | 0 | empty=data+(empty_index=pos->next); |
595 | 0 | pos[0]= empty[0]; |
596 | 0 | } |
597 | |
|
598 | 0 | if (empty == lastpos) /* last key at wrong pos or no next link */ |
599 | 0 | goto exit; |
600 | | |
601 | | /* Move the last key (lastpos) */ |
602 | 0 | lastpos_hashnr= lastpos->hash_nr; |
603 | | /* pos is where lastpos should be */ |
604 | 0 | pos= data + my_hash_mask(lastpos_hashnr, hash->blength, hash->records); |
605 | 0 | if (pos == empty) /* Move to empty position. */ |
606 | 0 | { |
607 | 0 | empty[0]=lastpos[0]; |
608 | 0 | goto exit; |
609 | 0 | } |
610 | 0 | pos_hashnr= pos->hash_nr; |
611 | | /* pos3 is where the pos should be */ |
612 | 0 | pos3= data + my_hash_mask(pos_hashnr, hash->blength, hash->records); |
613 | 0 | if (pos != pos3) |
614 | 0 | { /* pos is on wrong posit */ |
615 | 0 | empty[0]=pos[0]; /* Save it here */ |
616 | 0 | pos[0]=lastpos[0]; /* This should be here */ |
617 | 0 | movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index); |
618 | 0 | goto exit; |
619 | 0 | } |
620 | 0 | pos2= my_hash_mask(lastpos_hashnr, blength, hash->records + 1); |
621 | 0 | if (pos2 == my_hash_mask(pos_hashnr, blength, hash->records + 1)) |
622 | 0 | { /* Identical key-positions */ |
623 | 0 | if (pos2 != hash->records) |
624 | 0 | { |
625 | 0 | empty[0]=lastpos[0]; |
626 | 0 | movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index); |
627 | 0 | goto exit; |
628 | 0 | } |
629 | 0 | idx= (uint) (pos-data); /* Link pos->next after lastpos */ |
630 | 0 | } |
631 | 0 | else idx= NO_RECORD; /* Different positions merge */ |
632 | | |
633 | 0 | empty[0]=lastpos[0]; |
634 | 0 | movelink(data,idx,empty_index,pos->next); |
635 | 0 | pos->next=empty_index; |
636 | |
|
637 | 0 | exit: |
638 | 0 | (void) pop_dynamic(&hash->array); |
639 | 0 | if (hash->free) |
640 | 0 | (*hash->free)((uchar*) record); |
641 | 0 | DBUG_RETURN(0); |
642 | 0 | } |
643 | | |
644 | | |
645 | | /** |
646 | | Update keys when record has changed. |
647 | | This is much more efficient than using a delete & insert. |
648 | | */ |
649 | | |
650 | | my_bool my_hash_update(HASH *hash, uchar *record, uchar *old_key, |
651 | | size_t old_key_length) |
652 | 0 | { |
653 | 0 | uint new_index, new_pos_index, org_index, records, idx; |
654 | 0 | size_t length, empty, blength; |
655 | 0 | my_hash_value_type hash_nr; |
656 | 0 | HASH_LINK org_link,*data,*previous,*pos; |
657 | 0 | uchar *new_key; |
658 | 0 | DBUG_ENTER("my_hash_update"); |
659 | |
|
660 | 0 | new_key= (uchar*) my_hash_key(hash, record, &length, 1); |
661 | 0 | hash_nr= hash->hash_function(hash->charset, new_key, length); |
662 | | |
663 | 0 | if (HASH_UNIQUE & hash->flags) |
664 | 0 | { |
665 | 0 | HASH_SEARCH_STATE state; |
666 | 0 | uchar *found; |
667 | |
|
668 | 0 | if ((found= my_hash_first_from_hash_value(hash, hash_nr, new_key, length, |
669 | 0 | &state))) |
670 | 0 | { |
671 | 0 | do |
672 | 0 | { |
673 | 0 | if (found != record) |
674 | 0 | DBUG_RETURN(1); /* Duplicate entry */ |
675 | 0 | } |
676 | 0 | while ((found= my_hash_next(hash, new_key, length, &state))); |
677 | 0 | } |
678 | 0 | } |
679 | | |
680 | 0 | data=dynamic_element(&hash->array,0,HASH_LINK*); |
681 | 0 | blength=hash->blength; records=hash->records; |
682 | | |
683 | | /* Search after record with key */ |
684 | |
|
685 | 0 | idx= my_hash_mask(hash->hash_function(hash->charset, old_key, |
686 | 0 | (old_key_length ? old_key_length : |
687 | 0 | hash->key_length)), |
688 | 0 | blength, records); |
689 | 0 | org_index= idx; |
690 | 0 | new_index= my_hash_mask(hash_nr, blength, records); |
691 | 0 | previous=0; |
692 | 0 | for (;;) |
693 | 0 | { |
694 | 0 | if ((pos= data+idx)->data == record) |
695 | 0 | break; |
696 | 0 | previous=pos; |
697 | 0 | if ((idx=pos->next) == NO_RECORD) |
698 | 0 | DBUG_RETURN(1); /* Not found in links */ |
699 | 0 | } |
700 | | |
701 | 0 | if (org_index == new_index) |
702 | 0 | { |
703 | 0 | data[idx].hash_nr= hash_nr; /* Hash number may have changed */ |
704 | 0 | DBUG_RETURN(0); /* Record is in right position */ |
705 | 0 | } |
706 | | |
707 | 0 | org_link= *pos; |
708 | 0 | empty=idx; |
709 | | |
710 | | /* Relink record from current chain */ |
711 | |
|
712 | 0 | if (!previous) |
713 | 0 | { |
714 | 0 | if (pos->next != NO_RECORD) |
715 | 0 | { |
716 | 0 | empty=pos->next; |
717 | 0 | *pos= data[pos->next]; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | else |
721 | 0 | previous->next=pos->next; /* unlink pos */ |
722 | | |
723 | | /* Move data to correct position */ |
724 | 0 | if (new_index == empty) |
725 | 0 | { |
726 | | /* |
727 | | At this point record is unlinked from the old chain, thus it holds |
728 | | random position. By the chance this position is equal to position |
729 | | for the first element in the new chain. That means updated record |
730 | | is the only record in the new chain. |
731 | | */ |
732 | 0 | if (empty != idx) |
733 | 0 | { |
734 | | /* |
735 | | Record was moved while unlinking it from the old chain. |
736 | | Copy data to a new position. |
737 | | */ |
738 | 0 | data[empty]= org_link; |
739 | 0 | } |
740 | 0 | data[empty].next= NO_RECORD; |
741 | 0 | data[empty].hash_nr= hash_nr; |
742 | 0 | DBUG_RETURN(0); |
743 | 0 | } |
744 | 0 | pos=data+new_index; |
745 | 0 | new_pos_index= my_hash_rec_mask(pos, blength, records); |
746 | 0 | if (new_index != new_pos_index) |
747 | 0 | { /* Other record in wrong position */ |
748 | 0 | data[empty]= *pos; |
749 | 0 | movelink(data,new_index,new_pos_index, (uint) empty); |
750 | 0 | org_link.next=NO_RECORD; |
751 | 0 | data[new_index]= org_link; |
752 | 0 | data[new_index].hash_nr= hash_nr; |
753 | 0 | } |
754 | 0 | else |
755 | 0 | { /* Link in chain at right position */ |
756 | 0 | org_link.next=data[new_index].next; |
757 | 0 | data[empty]=org_link; |
758 | 0 | data[empty].hash_nr= hash_nr; |
759 | 0 | data[new_index].next= (uint) empty; |
760 | 0 | } |
761 | 0 | DBUG_RETURN(0); |
762 | 0 | } |
763 | | |
764 | | |
765 | | uchar *my_hash_element(HASH *hash, size_t idx) |
766 | 0 | { |
767 | 0 | if (idx < hash->records) |
768 | 0 | return dynamic_element(&hash->array,idx,HASH_LINK*)->data; |
769 | 0 | return 0; |
770 | 0 | } |
771 | | |
772 | | |
773 | | /* |
774 | | Replace old row with new row. This should only be used when key |
775 | | isn't changed |
776 | | */ |
777 | | |
778 | | void my_hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record, |
779 | | uchar *new_row) |
780 | 0 | { |
781 | 0 | if (*current_record != NO_RECORD) /* Safety */ |
782 | 0 | dynamic_element(&hash->array, *current_record, HASH_LINK*)->data= new_row; |
783 | 0 | } |
784 | | |
785 | | |
786 | | /** |
787 | | Iterate over all elements in hash and call function with the element |
788 | | |
789 | | @param hash hash array |
790 | | @param action function to call for each argument |
791 | | @param argument second argument for call to action |
792 | | |
793 | | @notes |
794 | | If one of functions calls returns 1 then the iteration aborts |
795 | | |
796 | | @retval 0 ok |
797 | | @retval 1 iteration aborted becasue action returned 1 |
798 | | */ |
799 | | |
800 | | my_bool my_hash_iterate(HASH *hash, my_hash_walk_action action, void *argument) |
801 | 0 | { |
802 | 0 | uint records, i; |
803 | |
|
804 | 0 | records= hash->records; |
805 | |
|
806 | 0 | for (i= 0 ; i < records ; i++) |
807 | 0 | { |
808 | 0 | if ((*action)(dynamic_element(&hash->array, i, HASH_LINK *)->data, |
809 | 0 | argument)) |
810 | 0 | return 1; |
811 | 0 | } |
812 | 0 | return 0; |
813 | 0 | } |
814 | | |
815 | | |
816 | | #if !defined(DBUG_OFF) || defined(MAIN) |
817 | | |
818 | | my_bool my_hash_check(HASH *hash) |
819 | | { |
820 | | int error; |
821 | | uint i,rec_link,found,max_links,seek,links,idx; |
822 | | uint records; |
823 | | size_t blength; |
824 | | HASH_LINK *data,*hash_info; |
825 | | |
826 | | records=hash->records; blength=hash->blength; |
827 | | data=dynamic_element(&hash->array,0,HASH_LINK*); |
828 | | error=0; |
829 | | |
830 | | for (i=found=max_links=seek=0 ; i < records ; i++) |
831 | | { |
832 | | size_t length; |
833 | | uchar *key= (uchar*) my_hash_key(hash, data[i].data, &length, 0); |
834 | | if (data[i].hash_nr != hash->hash_function(hash->charset, key, length)) |
835 | | { |
836 | | DBUG_PRINT("error", ("record at %d has wrong hash", i)); |
837 | | error= 1; |
838 | | } |
839 | | |
840 | | if (my_hash_rec_mask(data + i, blength, records) == i) |
841 | | { |
842 | | found++; seek++; links=1; |
843 | | for (idx=data[i].next ; |
844 | | idx != NO_RECORD && found < records + 1; |
845 | | idx=hash_info->next) |
846 | | { |
847 | | if (idx >= records) |
848 | | { |
849 | | DBUG_PRINT("error", |
850 | | ("Found pointer outside array to %d from link starting at %d", |
851 | | idx,i)); |
852 | | error=1; |
853 | | } |
854 | | hash_info=data+idx; |
855 | | seek+= ++links; |
856 | | if ((rec_link= my_hash_rec_mask(hash_info, |
857 | | blength, records)) != i) |
858 | | { |
859 | | DBUG_PRINT("error", ("Record in wrong link at %d: Start %d " |
860 | | "Record:%p Record-link %d", |
861 | | idx, i, hash_info->data, rec_link)); |
862 | | error=1; |
863 | | } |
864 | | else |
865 | | found++; |
866 | | } |
867 | | if (links > max_links) max_links=links; |
868 | | } |
869 | | } |
870 | | if (found != records) |
871 | | { |
872 | | DBUG_PRINT("error",("Found %u of %u records", found, records)); |
873 | | error=1; |
874 | | } |
875 | | if (records) |
876 | | DBUG_PRINT("info", |
877 | | ("records: %u seeks: %d max links: %d hitrate: %.2f", |
878 | | records,seek,max_links,(float) seek / (float) records)); |
879 | | DBUG_ASSERT(error == 0); |
880 | | return error; |
881 | | } |
882 | | #endif |
883 | | |
884 | | #ifdef MAIN |
885 | | |
886 | | #define RECORDS 1000 |
887 | | |
888 | | uchar *test_get_key(uchar *data, size_t *length, |
889 | | my_bool not_used __attribute__((unused))) |
890 | | { |
891 | | *length= 2; |
892 | | return data; |
893 | | } |
894 | | |
895 | | |
896 | | int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) |
897 | | { |
898 | | uchar records[RECORDS][2], copy[2]; |
899 | | HASH hash_test; |
900 | | uint i; |
901 | | MY_INIT(argv[0]); |
902 | | DBUG_PUSH("d:t:O,/tmp/test_hash.trace"); |
903 | | |
904 | | printf("my_hash_init\n"); |
905 | | if (my_hash_init2(PSI_INSTRUMENT_ME, &hash_test, 100, &my_charset_bin, 20, |
906 | | 0, 0, (my_hash_get_key) test_get_key, 0, 0, HASH_UNIQUE)) |
907 | | { |
908 | | fprintf(stderr, "hash init failed\n"); |
909 | | exit(1); |
910 | | } |
911 | | |
912 | | printf("my_hash_insert\n"); |
913 | | for (i= 0 ; i < RECORDS ; i++) |
914 | | { |
915 | | int2store(records[i],i); |
916 | | my_hash_insert(&hash_test, records[i]); |
917 | | my_hash_check(&hash_test); |
918 | | } |
919 | | printf("my_hash_update\n"); |
920 | | for (i= 0 ; i < RECORDS ; i+=2) |
921 | | { |
922 | | memcpy(copy, records[i], 2); |
923 | | int2store(records[i],i + RECORDS); |
924 | | if (my_hash_update(&hash_test, records[i], copy, 2)) |
925 | | { |
926 | | fprintf(stderr, "hash update failed\n"); |
927 | | exit(1); |
928 | | } |
929 | | my_hash_check(&hash_test); |
930 | | } |
931 | | printf("my_hash_delete\n"); |
932 | | for (i= 0 ; i < RECORDS ; i++) |
933 | | { |
934 | | if (my_hash_delete(&hash_test, records[i])) |
935 | | { |
936 | | fprintf(stderr, "hash delete failed\n"); |
937 | | exit(1); |
938 | | } |
939 | | my_hash_check(&hash_test); |
940 | | } |
941 | | my_hash_free(&hash_test); |
942 | | printf("ok\n"); |
943 | | my_end(MY_CHECK_ERROR); |
944 | | return(0); |
945 | | } |
946 | | #endif /* MAIN */ |