~drizzle-trunk/drizzle/development

1 by brian
clean slate
1
/* Copyright (C) 2000 MySQL AB
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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"
212.5.18 by Monty Taylor
Moved m_ctype, m_string and my_bitmap. Removed t_ctype.
21
#include <mystrings/m_string.h>
22
#include <mystrings/m_ctype.h>
1 by brian
clean slate
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 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,
39
                   size_t length);
40
41
static uint calc_hash(const HASH *hash, const uchar *key, size_t length)
42
{
43
  ulong nr1=1, nr2=4;
44
  hash->charset->coll->hash_sort(hash->charset,(uchar*) key,length,&nr1,&nr2);
45
  return nr1;
46
}
47
146 by Brian Aker
my_bool cleanup.
48
bool
1 by brian
clean slate
49
_hash_init(HASH *hash,uint growth_size, CHARSET_INFO *charset,
50
	   ulong size, size_t key_offset, size_t key_length,
51
	   hash_get_key get_key,
52
	   void (*free_element)(void*),uint flags CALLER_INFO_PROTO)
53
{
54
  hash->records=0;
55
  if (my_init_dynamic_array_ci(&hash->array, sizeof(HASH_LINK), size,
56
                               growth_size))
57
  {
58
    hash->free=0;				/* Allow call to hash_free */
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
59
    return(1);
1 by brian
clean slate
60
  }
61
  hash->key_offset=key_offset;
62
  hash->key_length=key_length;
63
  hash->blength=1;
64
  hash->get_key=get_key;
65
  hash->free=free_element;
66
  hash->flags=flags;
67
  hash->charset=charset;
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
68
  return(0);
1 by brian
clean slate
69
}
70
71
72
/*
73
  Call hash->free on all elements in hash.
74
75
  SYNOPSIS
76
    hash_free_elements()
77
    hash   hash table
78
79
  NOTES:
80
    Sets records to 0
81
*/
82
83
static inline void hash_free_elements(HASH *hash)
84
{
85
  if (hash->free)
86
  {
87
    HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
88
    HASH_LINK *end= data + hash->records;
89
    while (data < end)
90
      (*hash->free)((data++)->data);
91
  }
92
  hash->records=0;
93
}
94
95
96
/*
97
  Free memory used by hash.
98
99
  SYNOPSIS
100
    hash_free()
101
    hash   the hash to delete elements of
102
103
  NOTES: Hash can't be reused without calling hash_init again.
104
*/
105
106
void hash_free(HASH *hash)
107
{
108
  hash_free_elements(hash);
109
  hash->free= 0;
110
  delete_dynamic(&hash->array);
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
111
  return;
1 by brian
clean slate
112
}
113
114
115
/*
116
  Delete all elements from the hash (the hash itself is to be reused).
117
118
  SYNOPSIS
119
    my_hash_reset()
120
    hash   the hash to delete elements of
121
*/
122
123
void my_hash_reset(HASH *hash)
124
{
125
  hash_free_elements(hash);
126
  reset_dynamic(&hash->array);
127
  /* Set row pointers so that the hash can be reused at once */
128
  hash->blength= 1;
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
129
  return;
1 by brian
clean slate
130
}
131
132
/* some helper functions */
133
134
/*
135
  This function is char* instead of uchar* as HPUX11 compiler can't
136
  handle inline functions that are not defined as native types
137
*/
138
139
static inline char*
140
hash_key(const HASH *hash, const uchar *record, size_t *length,
146 by Brian Aker
my_bool cleanup.
141
         bool first)
1 by brian
clean slate
142
{
143
  if (hash->get_key)
144
    return (char*) (*hash->get_key)(record,length,first);
145
  *length=hash->key_length;
146
  return (char*) record+hash->key_offset;
147
}
148
149
	/* Calculate pos according to keys */
150
151
static uint hash_mask(uint hashnr,uint buffmax,uint maxlength)
152
{
153
  if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1));
154
  return (hashnr & ((buffmax >> 1) -1));
155
}
156
157
static uint hash_rec_mask(const HASH *hash, HASH_LINK *pos,
158
                          uint buffmax, uint maxlength)
159
{
160
  size_t length;
161
  uchar *key= (uchar*) hash_key(hash,pos->data,&length,0);
162
  return hash_mask(calc_hash(hash,key,length),buffmax,maxlength);
163
}
164
165
166
167
/* for compilers which can not handle inline */
168
static
169
#if !defined(__USLC__) && !defined(__sgi)
170
inline
171
#endif
172
unsigned int rec_hashnr(HASH *hash,const uchar *record)
173
{
174
  size_t length;
175
  uchar *key= (uchar*) hash_key(hash,record,&length,0);
176
  return calc_hash(hash,key,length);
177
}
178
179
180
uchar* hash_search(const HASH *hash, const uchar *key, size_t length)
181
{
182
  HASH_SEARCH_STATE state;
183
  return hash_first(hash, key, length, &state);
184
}
185
186
/*
187
  Search after a record based on a key
188
189
  NOTE
190
   Assigns the number of the found record to HASH_SEARCH_STATE state
191
*/
192
193
uchar* hash_first(const HASH *hash, const uchar *key, size_t length,
194
                HASH_SEARCH_STATE *current_record)
195
{
196
  HASH_LINK *pos;
197
  uint flag,idx;
198
199
  flag=1;
200
  if (hash->records)
201
  {
202
    idx=hash_mask(calc_hash(hash,key,length ? length : hash->key_length),
203
		    hash->blength,hash->records);
204
    do
205
    {
206
      pos= dynamic_element(&hash->array,idx,HASH_LINK*);
207
      if (!hashcmp(hash,pos,key,length))
208
      {
209
	*current_record= idx;
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
210
	return (pos->data);
1 by brian
clean slate
211
      }
212
      if (flag)
213
      {
214
	flag=0;					/* Reset flag */
215
	if (hash_rec_mask(hash,pos,hash->blength,hash->records) != idx)
216
	  break;				/* Wrong link */
217
      }
218
    }
219
    while ((idx=pos->next) != NO_RECORD);
220
  }
221
  *current_record= NO_RECORD;
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
222
  return(0);
1 by brian
clean slate
223
}
224
225
	/* Get next record with identical key */
226
	/* Can only be called if previous calls was hash_search */
227
228
uchar* hash_next(const HASH *hash, const uchar *key, size_t length,
229
               HASH_SEARCH_STATE *current_record)
230
{
231
  HASH_LINK *pos;
232
  uint idx;
233
234
  if (*current_record != NO_RECORD)
235
  {
236
    HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
237
    for (idx=data[*current_record].next; idx != NO_RECORD ; idx=pos->next)
238
    {
239
      pos=data+idx;
240
      if (!hashcmp(hash,pos,key,length))
241
      {
242
	*current_record= idx;
243
	return pos->data;
244
      }
245
    }
246
    *current_record= NO_RECORD;
247
  }
248
  return 0;
249
}
250
251
252
	/* Change link from pos to new_link */
253
254
static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink)
255
{
256
  HASH_LINK *old_link;
257
  do
258
  {
259
    old_link=array+next_link;
260
  }
261
  while ((next_link=old_link->next) != find);
262
  old_link->next= newlink;
263
  return;
264
}
265
266
/*
267
  Compare a key in a record to a whole key. Return 0 if identical
268
269
  SYNOPSIS
270
    hashcmp()
271
    hash   hash table
272
    pos    position of hash record to use in comparison
273
    key    key for comparison
274
    length length of key
275
276
  NOTES:
277
    If length is 0, comparison is done using the length of the
278
    record being compared against.
279
280
  RETURN
281
    = 0  key of record == key
282
    != 0 key of record != key
283
 */
284
285
static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key,
286
                   size_t length)
287
{
288
  size_t rec_keylength;
289
  uchar *rec_key= (uchar*) hash_key(hash,pos->data,&rec_keylength,1);
290
  return ((length && length != rec_keylength) ||
291
	  my_strnncoll(hash->charset, (uchar*) rec_key, rec_keylength,
292
		       (uchar*) key, rec_keylength));
293
}
294
295
296
	/* Write a hash-key to the hash-index */
297
146 by Brian Aker
my_bool cleanup.
298
bool my_hash_insert(HASH *info,const uchar *record)
1 by brian
clean slate
299
{
300
  int flag;
301
  size_t idx;
302
  uint halfbuff,hash_nr,first_index;
77.1.71 by Monty Taylor
Uninitialized use.
303
  uchar *ptr_to_rec=NULL,*ptr_to_rec2=NULL;
304
  HASH_LINK *data,*empty,*gpos=NULL,*gpos2=NULL,*pos;
1 by brian
clean slate
305
306
  if (HASH_UNIQUE & info->flags)
307
  {
308
    uchar *key= (uchar*) hash_key(info, record, &idx, 1);
309
    if (hash_search(info, key, idx))
163 by Brian Aker
Merge Monty's code.
310
      return(true);				/* Duplicate entry */
1 by brian
clean slate
311
  }
312
313
  flag=0;
314
  if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array)))
163 by Brian Aker
Merge Monty's code.
315
    return(true);				/* No more memory */
1 by brian
clean slate
316
317
  data=dynamic_element(&info->array,0,HASH_LINK*);
318
  halfbuff= info->blength >> 1;
319
320
  idx=first_index=info->records-halfbuff;
321
  if (idx != info->records)				/* If some records */
322
  {
323
    do
324
    {
325
      pos=data+idx;
326
      hash_nr=rec_hashnr(info,pos->data);
327
      if (flag == 0)				/* First loop; Check if ok */
328
	if (hash_mask(hash_nr,info->blength,info->records) != first_index)
329
	  break;
330
      if (!(hash_nr & halfbuff))
331
      {						/* Key will not move */
332
	if (!(flag & LOWFIND))
333
	{
334
	  if (flag & HIGHFIND)
335
	  {
336
	    flag=LOWFIND | HIGHFIND;
337
	    /* key shall be moved to the current empty position */
338
	    gpos=empty;
339
	    ptr_to_rec=pos->data;
340
	    empty=pos;				/* This place is now free */
341
	  }
342
	  else
343
	  {
344
	    flag=LOWFIND | LOWUSED;		/* key isn't changed */
345
	    gpos=pos;
346
	    ptr_to_rec=pos->data;
347
	  }
348
	}
349
	else
350
	{
351
	  if (!(flag & LOWUSED))
352
	  {
353
	    /* Change link of previous LOW-key */
354
	    gpos->data=ptr_to_rec;
355
	    gpos->next= (uint) (pos-data);
356
	    flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED);
357
	  }
358
	  gpos=pos;
359
	  ptr_to_rec=pos->data;
360
	}
361
      }
362
      else
363
      {						/* key will be moved */
364
	if (!(flag & HIGHFIND))
365
	{
366
	  flag= (flag & LOWFIND) | HIGHFIND;
367
	  /* key shall be moved to the last (empty) position */
368
	  gpos2 = empty; empty=pos;
369
	  ptr_to_rec2=pos->data;
370
	}
371
	else
372
	{
373
	  if (!(flag & HIGHUSED))
374
	  {
375
	    /* Change link of previous hash-key and save */
376
	    gpos2->data=ptr_to_rec2;
377
	    gpos2->next=(uint) (pos-data);
378
	    flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED);
379
	  }
380
	  gpos2=pos;
381
	  ptr_to_rec2=pos->data;
382
	}
383
      }
384
    }
385
    while ((idx=pos->next) != NO_RECORD);
386
387
    if ((flag & (LOWFIND | LOWUSED)) == LOWFIND)
388
    {
389
      gpos->data=ptr_to_rec;
390
      gpos->next=NO_RECORD;
391
    }
392
    if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND)
393
    {
394
      gpos2->data=ptr_to_rec2;
395
      gpos2->next=NO_RECORD;
396
    }
397
  }
398
  /* Check if we are at the empty position */
399
400
  idx=hash_mask(rec_hashnr(info,record),info->blength,info->records+1);
401
  pos=data+idx;
402
  if (pos == empty)
403
  {
404
    pos->data=(uchar*) record;
405
    pos->next=NO_RECORD;
406
  }
407
  else
408
  {
409
    /* Check if more records in same hash-nr family */
410
    empty[0]=pos[0];
411
    gpos=data+hash_rec_mask(info,pos,info->blength,info->records+1);
412
    if (pos == gpos)
413
    {
414
      pos->data=(uchar*) record;
415
      pos->next=(uint) (empty - data);
416
    }
417
    else
418
    {
419
      pos->data=(uchar*) record;
420
      pos->next=NO_RECORD;
421
      movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data));
422
    }
423
  }
424
  if (++info->records == info->blength)
425
    info->blength+= info->blength;
426
  return(0);
427
}
428
429
430
/******************************************************************************
431
** Remove one record from hash-table. The record with the same record
432
** ptr is removed.
433
** if there is a free-function it's called for record if found
434
******************************************************************************/
435
146 by Brian Aker
my_bool cleanup.
436
bool hash_delete(HASH *hash,uchar *record)
1 by brian
clean slate
437
{
438
  uint blength,pos2,pos_hashnr,lastpos_hashnr,idx,empty_index;
439
  HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty;
440
  if (!hash->records)
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
441
    return(1);
1 by brian
clean slate
442
443
  blength=hash->blength;
444
  data=dynamic_element(&hash->array,0,HASH_LINK*);
445
  /* Search after record with key */
446
  pos=data+ hash_mask(rec_hashnr(hash,record),blength,hash->records);
447
  gpos = 0;
448
449
  while (pos->data != record)
450
  {
451
    gpos=pos;
452
    if (pos->next == NO_RECORD)
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
453
      return(1);			/* Key not found */
1 by brian
clean slate
454
    pos=data+pos->next;
455
  }
456
457
  if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1;
458
  lastpos=data+hash->records;
459
460
  /* Remove link to record */
461
  empty=pos; empty_index=(uint) (empty-data);
462
  if (gpos)
463
    gpos->next=pos->next;		/* unlink current ptr */
464
  else if (pos->next != NO_RECORD)
465
  {
466
    empty=data+(empty_index=pos->next);
467
    pos->data=empty->data;
468
    pos->next=empty->next;
469
  }
470
471
  if (empty == lastpos)			/* last key at wrong pos or no next link */
472
    goto exit;
473
474
  /* Move the last key (lastpos) */
475
  lastpos_hashnr=rec_hashnr(hash,lastpos->data);
476
  /* pos is where lastpos should be */
477
  pos=data+hash_mask(lastpos_hashnr,hash->blength,hash->records);
478
  if (pos == empty)			/* Move to empty position. */
479
  {
480
    empty[0]=lastpos[0];
481
    goto exit;
482
  }
483
  pos_hashnr=rec_hashnr(hash,pos->data);
484
  /* pos3 is where the pos should be */
485
  pos3= data+hash_mask(pos_hashnr,hash->blength,hash->records);
486
  if (pos != pos3)
487
  {					/* pos is on wrong posit */
488
    empty[0]=pos[0];			/* Save it here */
489
    pos[0]=lastpos[0];			/* This should be here */
490
    movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index);
491
    goto exit;
492
  }
493
  pos2= hash_mask(lastpos_hashnr,blength,hash->records+1);
494
  if (pos2 == hash_mask(pos_hashnr,blength,hash->records+1))
495
  {					/* Identical key-positions */
496
    if (pos2 != hash->records)
497
    {
498
      empty[0]=lastpos[0];
499
      movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index);
500
      goto exit;
501
    }
502
    idx= (uint) (pos-data);		/* Link pos->next after lastpos */
503
  }
504
  else idx= NO_RECORD;		/* Different positions merge */
505
506
  empty[0]=lastpos[0];
507
  movelink(data,idx,empty_index,pos->next);
508
  pos->next=empty_index;
509
510
exit:
511
  VOID(pop_dynamic(&hash->array));
512
  if (hash->free)
513
    (*hash->free)((uchar*) record);
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
514
  return(0);
1 by brian
clean slate
515
}
516
517
	/*
518
	  Update keys when record has changed.
519
	  This is much more efficent than using a delete & insert.
520
	  */
521
146 by Brian Aker
my_bool cleanup.
522
bool hash_update(HASH *hash, uchar *record, uchar *old_key,
1 by brian
clean slate
523
                    size_t old_key_length)
524
{
525
  uint new_index,new_pos_index,blength,records,empty;
526
  size_t idx;
527
  HASH_LINK org_link,*data,*previous,*pos;
528
  
529
  if (HASH_UNIQUE & hash->flags)
530
  {
531
    HASH_SEARCH_STATE state;
532
    uchar *found, *new_key= (uchar*) hash_key(hash, record, &idx, 1);
533
    if ((found= hash_first(hash, new_key, idx, &state)))
534
    {
535
      do 
536
      {
537
        if (found != record)
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
538
          return(1);		/* Duplicate entry */
1 by brian
clean slate
539
      } 
540
      while ((found= hash_next(hash, new_key, idx, &state)));
541
    }
542
  }
543
544
  data=dynamic_element(&hash->array,0,HASH_LINK*);
545
  blength=hash->blength; records=hash->records;
546
547
  /* Search after record with key */
548
549
  idx=hash_mask(calc_hash(hash, old_key,(old_key_length ?
550
					      old_key_length :
551
					      hash->key_length)),
552
		  blength,records);
553
  new_index=hash_mask(rec_hashnr(hash,record),blength,records);
554
  if (idx == new_index)
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
555
    return(0);			/* Nothing to do (No record check) */
1 by brian
clean slate
556
  previous=0;
557
  for (;;)
558
  {
559
560
    if ((pos= data+idx)->data == record)
561
      break;
562
    previous=pos;
563
    if ((idx=pos->next) == NO_RECORD)
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
564
      return(1);			/* Not found in links */
1 by brian
clean slate
565
  }
566
  org_link= *pos;
567
  empty=idx;
568
569
  /* Relink record from current chain */
570
571
  if (!previous)
572
  {
573
    if (pos->next != NO_RECORD)
574
    {
575
      empty=pos->next;
576
      *pos= data[pos->next];
577
    }
578
  }
579
  else
580
    previous->next=pos->next;		/* unlink pos */
581
582
  /* Move data to correct position */
583
  if (new_index == empty)
584
  {
585
    /*
586
      At this point record is unlinked from the old chain, thus it holds
587
      random position. By the chance this position is equal to position
588
      for the first element in the new chain. That means updated record
589
      is the only record in the new chain.
590
    */
591
    if (empty != idx)
592
    {
593
      /*
594
        Record was moved while unlinking it from the old chain.
595
        Copy data to a new position.
596
      */
597
      data[empty]= org_link;
598
    }
599
    data[empty].next= NO_RECORD;
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
600
    return(0);
1 by brian
clean slate
601
  }
602
  pos=data+new_index;
603
  new_pos_index=hash_rec_mask(hash,pos,blength,records);
604
  if (new_index != new_pos_index)
605
  {					/* Other record in wrong position */
606
    data[empty] = *pos;
607
    movelink(data,new_index,new_pos_index,empty);
608
    org_link.next=NO_RECORD;
609
    data[new_index]= org_link;
610
  }
611
  else
612
  {					/* Link in chain at right position */
613
    org_link.next=data[new_index].next;
614
    data[empty]=org_link;
615
    data[new_index].next=empty;
616
  }
51.3.13 by Jay Pipes
Phase 1 removal of DBUG in mysys
617
  return(0);
1 by brian
clean slate
618
}
619
620
621
uchar *hash_element(HASH *hash,ulong idx)
622
{
623
  if (idx < hash->records)
624
    return dynamic_element(&hash->array,idx,HASH_LINK*)->data;
625
  return 0;
626
}
627
628
629
/*
630
  Replace old row with new row.  This should only be used when key
631
  isn't changed
632
*/
633
634
void hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record, uchar *new_row)
635
{
636
  if (*current_record != NO_RECORD)            /* Safety */
637
    dynamic_element(&hash->array, *current_record, HASH_LINK*)->data= new_row;
638
}