xref: /csrg-svn/lib/libc/db/hash/hash_bigkey.c (revision 46644)
1 /*-
2  * Copyright (c) 1990 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Margo Seltzer.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #if defined(LIBC_SCCS) && !defined(lint)
12 static char sccsid[] = "@(#)hash_bigkey.c	5.3 (Berkeley) 02/24/91";
13 #endif /* LIBC_SCCS and not lint */
14 
15 /******************************************************************************
16 
17 PACKAGE: hash
18 
19 DESCRIPTION:
20 	Big key/data handling for the hashing package.
21 
22 ROUTINES:
23     External
24 	__big_keydata
25 	__big_split
26 	__big_insert
27 	__big_return
28 	__big_delete
29 	__find_last_page
30     Internal
31 	collect_key
32 	collect_data
33 ******************************************************************************/
34 /* Includes */
35 #include <sys/param.h>
36 #include <assert.h>
37 #include <errno.h>
38 #include <db.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include "hash.h"
43 #include "page.h"
44 
45 /* Externals */
46 /* buf.c */
47 extern BUFHEAD *__get_buf();
48 
49 /* page.c */
50 extern BUFHEAD *__add_ovflpage();
51 
52 /* My externals */
53 extern int __big_keydata();
54 extern int __big_split();
55 extern int __big_insert();
56 extern int __big_return();
57 extern int __big_delete();
58 extern u_short __find_last_page();
59 extern int __find_bigpair();
60 
61 /* My internals */
62 static int collect_key();
63 static int collect_data();
64 
65 #ifdef HASH_STATISTICS
66 extern long hash_accesses, hash_collisions, hash_expansions, hash_overflows;
67 #endif
68 /*
69 Big_insert
70 
71 You need to do an insert and the key/data pair is too big
72 0 ==> OK
73 -1 ==> ERROR
74 */
75 extern int
76 __big_insert ( bufp, key, val )
77 BUFHEAD *bufp;
78 DBT	*key, *val;
79 {
80     char	*cp = bufp->page;	/* Character pointer of p */
81     register u_short	*p = (u_short *)cp;
82     char	*key_data, *val_data;
83     int		key_size, val_size;
84     int		n;
85     u_short	space, move_bytes, off;
86 
87     key_data = key->data;
88     key_size = key->size;
89     val_data = val->data;
90     val_size = val->size;
91 
92     /* First move the Key */
93     for ( space = FREESPACE(p) - BIGOVERHEAD;
94 	  key_size;
95 	  space = FREESPACE(p) - BIGOVERHEAD ) {
96 	move_bytes = MIN(space, key_size);
97 	off = OFFSET(p) - move_bytes;
98 	bcopy (key_data, cp+off, move_bytes );
99 	key_size -= move_bytes;
100 	key_data += move_bytes;
101 	n = p[0];
102 	p[++n] = off;
103 	p[0] = ++n;
104 	FREESPACE(p) = off - PAGE_META(n);
105 	OFFSET(p) = off;
106 	p[n] = PARTIAL_KEY;
107 	bufp = __add_ovflpage(bufp);
108 	if ( !bufp ) {
109 	    return(-1);
110 	}
111 	n = p[0];
112 	if ( !key_size ) {
113 	    if ( FREESPACE(p) ) {
114 		move_bytes = MIN (FREESPACE(p), val_size);
115 		off = OFFSET(p) - move_bytes;
116 		p[n] = off;
117 		bcopy ( val_data, cp + off, move_bytes );
118 		val_data += move_bytes;
119 		val_size -= move_bytes;
120 		p[n-2] = FULL_KEY_DATA;
121 		FREESPACE(p) = FREESPACE(p) - move_bytes;
122 		OFFSET(p) = off;
123 	    }
124 	    else p[n-2] = FULL_KEY;
125 	}
126 	p = (u_short *)bufp->page;
127 	cp = bufp->page;
128 	bufp->flags |= BUF_MOD;
129     }
130 
131     /* Now move the data */
132     for ( space = FREESPACE(p) - BIGOVERHEAD;
133 	  val_size;
134 	  space = FREESPACE(p) - BIGOVERHEAD ) {
135 	move_bytes = MIN(space, val_size);
136 	/*
137 	    Here's the hack to make sure that if the data ends
138 	    on the same page as the key ends, FREESPACE is
139 	    at least one
140 	*/
141 	if ( space == val_size && val_size == val->size ) {
142 	    move_bytes--;
143 	}
144 	off = OFFSET(p) - move_bytes;
145 	bcopy (val_data, cp+off, move_bytes );
146 	val_size -= move_bytes;
147 	val_data += move_bytes;
148 	n = p[0];
149 	p[++n] = off;
150 	p[0] = ++n;
151 	FREESPACE(p) = off - PAGE_META(n);
152 	OFFSET(p) = off;
153 	if ( val_size ) {
154 	    p[n] = FULL_KEY;
155 	    bufp = __add_ovflpage (bufp);
156 	    if ( !bufp ) {
157 		return(-1);
158 	    }
159 	    cp = bufp->page;
160 	    p = (u_short *)cp;
161 	} else {
162 	    p[n] = FULL_KEY_DATA;
163 	}
164 	bufp->flags |= BUF_MOD;
165     }
166     return(0);
167 }
168 
169 /*
170     Called when bufp's page  contains a partial key (index should be 1)
171 
172     All pages in the big key/data pair except bufp are freed.  We cannot
173     free bufp because the page pointing to it is lost and we can't
174     get rid of its pointer.
175 
176     Returns 0 => OK
177 	    -1 => ERROR
178 */
179 extern int
180 __big_delete (bufp, ndx)
181 BUFHEAD	*bufp;
182 int	ndx;
183 {
184 	register	BUFHEAD		*rbufp = bufp;
185 	register	BUFHEAD		*last_bfp = NULL;
186 	char	*cp;
187 	u_short	*bp = (u_short *)bufp->page;
188 	u_short	*xbp;
189 	u_short	pageno = 0;
190 	u_short	off, free_sp;
191 	int	key_done = 0;
192 	int	n;
193 
194 	while (!key_done || (bp[2] != FULL_KEY_DATA)) {
195 	    if ( bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA ) key_done = 1;
196 
197 	    /*
198 		If there is freespace left on a FULL_KEY_DATA page,
199 		then the data is short and fits entirely on this
200 		page, and this is the last page.
201 	    */
202 	    if ( bp[2] == FULL_KEY_DATA && FREESPACE(bp) ) break;
203 	    pageno = bp[bp[0]-1];
204 	    rbufp->flags |= BUF_MOD;
205 	    rbufp = __get_buf ( pageno, rbufp, 0 );
206 	    if ( last_bfp ) __free_ovflpage(last_bfp);
207 	    last_bfp = rbufp;
208 	    if ( !rbufp ) return(-1);			/* Error */
209 	    bp = (u_short *)rbufp->page;
210 	}
211 
212 	/*
213 	    If we get here then rbufp points to the last page of
214 	    the big key/data pair.  Bufp points to the first
215 	    one -- it should now be empty pointing to the next
216 	    page after this pair.  Can't free it because we don't
217 	    have the page pointing to it.
218 	*/
219 
220 	/* This is information from the last page of the pair */
221 	n = bp[0];
222 	pageno = bp[n-1];
223 
224 	/* Now, bp is the first page of the pair */
225 	bp = (u_short *)bufp->page;
226 	if ( n > 2 ) {
227 	    /* There is an overflow page */
228 	    bp[1] = pageno;
229 	    bp[2] = OVFLPAGE;
230 	    bufp->ovfl = rbufp->ovfl;
231 	} else {
232 	    /* This is the last page */
233 	    bufp->ovfl = NULL;
234 	}
235 	n -= 2;
236 	bp[0] = n;
237 	FREESPACE(bp) = hashp->BSIZE - PAGE_META(n);
238 	OFFSET(bp) = hashp->BSIZE - 1;
239 
240 	bufp->flags |= BUF_MOD;
241 	if ( rbufp ) __free_ovflpage(rbufp);
242 	if ( last_bfp != rbufp ) __free_ovflpage(last_bfp);
243 
244 	hashp->NKEYS--;
245 	return(0);
246 }
247 
248 /*
249     0 = key not found
250     -1 = get next overflow page
251     -2 means key not found and this is big key/data
252     -3 error
253 */
254 extern int
255 __find_bigpair(bufp, ndx, key, size )
256 BUFHEAD	*bufp;
257 int	ndx;
258 char	*key;
259 int	size;
260 {
261     register	u_short	*bp = (u_short *)bufp->page;
262     register	char	*p = bufp->page;
263     int		ksize = size;
264     char	*kkey = key;
265     u_short	bytes;
266 
267 
268     for ( bytes = hashp->BSIZE - bp[ndx];
269 	  bytes <= size && bp[ndx+1] == PARTIAL_KEY;
270 	  bytes = hashp->BSIZE - bp[ndx] ) {
271 
272 	if ( bcmp ( p+bp[ndx], kkey, bytes ))return(-2);
273 	kkey += bytes;
274 	ksize -= bytes;
275 	bufp = __get_buf ( bp[ndx+2], bufp, 0 );
276 	if ( !bufp ) {
277 	    return(-3);
278 	}
279 	p = bufp->page;
280 	bp = (u_short *)p;
281 	ndx = 1;
282     }
283 
284     if ( (bytes != ksize) || bcmp ( p+bp[ndx], kkey, bytes )) {
285 #ifdef HASH_STATISTICS
286 	hash_collisions++;
287 #endif
288 	return(-2);
289     }
290     else return (ndx);
291 }
292 
293 
294 /*
295     Given the buffer pointer of the first overflow page of a big pair,
296     find the end of the big pair
297 
298     This will set bpp to the buffer header of the last page of the big pair.
299     It will return the pageno of the overflow page following the last page of
300     the pair; 0 if there isn't any (i.e. big pair is the last key in the
301     bucket)
302 */
303 extern u_short
304 __find_last_page ( bpp )
305 BUFHEAD	**bpp;
306 {
307 	int	n;
308 	u_short	pageno;
309 	BUFHEAD	*bufp = *bpp;
310 	u_short	*bp = (u_short *)bufp->page;
311 
312 	while ( 1 ) {
313 	    n = bp[0];
314 
315 	    /*
316 		This is the last page if:
317 			the tag is FULL_KEY_DATA and either
318 				only 2 entries
319 				OVFLPAGE marker is explicit
320 				there is freespace on the page
321 	    */
322 	    if ( bp[2] == FULL_KEY_DATA &&
323 		 ((n == 2) ||  (bp[n] == OVFLPAGE) || (FREESPACE(bp)) ) ) break;
324 
325 	    pageno = bp[n-1];
326 	    bufp = __get_buf ( pageno, bufp, 0 );
327 	    if ( !bufp ) return (0);		/* Need to indicate an error! */
328 	    bp = (u_short *)bufp->page;
329 	}
330 
331 	*bpp = bufp;
332 	if ( bp[0] > 2 ) return ( bp[3] );
333 	else return(0);
334 }
335 
336 
337 /*
338     Return the data for the key/data pair
339     that begins on this page at this index
340     (index should always be 1)
341 */
342 extern int
343 __big_return ( bufp, ndx, val, set_current )
344 BUFHEAD	*bufp;
345 int	ndx;
346 DBT	*val;
347 int	set_current;
348 {
349     BUFHEAD	*save_p;
350     u_short	save_addr;
351     u_short	*bp = (u_short *)bufp->page;
352     u_short	off, len;
353     char	*cp, *tp;
354 
355     while ( bp[ndx+1] == PARTIAL_KEY ) {
356 	bufp = __get_buf ( bp[bp[0]-1], bufp, 0 );
357 	if ( !bufp ) return(-1);
358 	bp = (u_short *)bufp->page;
359 	ndx = 1;
360     }
361 
362     if ( bp[ndx+1] == FULL_KEY ) {
363 	bufp = __get_buf ( bp[bp[0]-1], bufp, 0 );
364 	if ( !bufp ) return(-1);
365 	bp = (u_short *)bufp->page;
366 	save_p = bufp;
367 	save_addr = save_p->addr;
368 	off = bp[1];
369 	len = 0;
370     } else if (!FREESPACE(bp)) {
371 	/*
372 	    This is a hack.  We can't distinguish between
373 	    FULL_KEY_DATA that contains complete data or
374 	    incomplete data, so we require that if the
375 	    data  is complete, there is at least 1 byte
376 	    of free space left.
377 	*/
378 	off = bp[bp[0]];
379 	len = bp[1] - off;
380 	save_p = bufp;
381 	save_addr = bufp->addr;
382 	bufp = __get_buf ( bp[bp[0]-1], bufp, 0 );
383 	if ( !bufp ) return(-1);
384 	bp = (u_short *)bufp->page;
385     } else {
386 	/* The data is all on one page */
387 	tp = (char *)bp;
388 	off = bp[bp[0]];
389 	val->data = tp + off;
390 	val->size = bp[1] - off;
391 	if ( set_current ) {
392 	    if ( bp[0] == 2 ) {		/* No more buckets in chain */
393 		hashp->cpage = NULL;
394 		hashp->cbucket++;
395 		hashp->cndx=1;
396 	    } else  {
397 		hashp->cpage = __get_buf ( bp[bp[0]-1], bufp, 0 );
398 		if ( !hashp->cpage )return(-1);
399 		hashp->cndx = 1;
400 		if ( !((u_short *)hashp->cpage->page)[0] ) {
401 		    hashp->cbucket++;
402 		    hashp->cpage = NULL;
403 		}
404 	    }
405 	}
406 	return(0);
407     }
408 
409     val->size = collect_data ( bufp, len, set_current );
410     if ( val->size == -1 ) {
411 	return(-1);
412     }
413     if ( save_p->addr != save_addr ) {
414 	/* We are pretty short on buffers */
415 	errno = EINVAL;		/* OUT OF BUFFERS */
416 	return(-1);
417     }
418     bcopy ( (save_p->page)+off, hashp->tmp_buf, len );
419     val->data = hashp->tmp_buf;
420     return(0);
421 }
422 
423 /*
424     Count how big the total datasize is by
425     recursing through the pages.  Then allocate
426     a buffer and copy the data as you recurse up.
427 */
428 static int
429 collect_data ( bufp, len, set )
430 BUFHEAD	*bufp;
431 int	len;
432 int	set;
433 {
434     register	char	*p = bufp->page;
435     register	u_short	*bp = (u_short *)p;
436     u_short	save_addr;
437     int	mylen, totlen;
438     BUFHEAD	*xbp;
439 
440     mylen = hashp->BSIZE - bp[1];
441     save_addr = bufp->addr;
442 
443     if ( bp[2] == FULL_KEY_DATA ) {	/* End of Data */
444 	totlen = len + mylen;
445 	if ( hashp->tmp_buf ) free (hashp->tmp_buf);
446 	hashp->tmp_buf = (char *)malloc ( totlen );
447 	if ( !hashp->tmp_buf ) {
448 	    return(-1);
449 	}
450 	if ( set ) {
451 	    hashp->cndx = 1;
452 	    if ( bp[0] == 2 ) {		/* No more buckets in chain */
453 		hashp->cpage = NULL;
454 		hashp->cbucket++;
455 	    } else  {
456 		hashp->cpage = __get_buf ( bp[bp[0]-1], bufp, 0 );
457 		if (!hashp->cpage) {
458 		    return(-1);
459 		} else if ( !((u_short *)hashp->cpage->page)[0] ) {
460 		    hashp->cbucket++;
461 		    hashp->cpage = NULL;
462 		}
463 	    }
464 	}
465     } else {
466 	xbp = __get_buf ( bp[bp[0]-1], bufp, 0 );
467 	if ( !xbp || ((totlen = collect_data ( xbp, len + mylen, set )) < 1) ) {
468 	    return(-1);
469 	}
470     }
471     if ( bufp->addr != save_addr ) {
472 	errno = EINVAL;		/* Out of buffers */
473 	return(-1);
474     }
475     bcopy ( (bufp->page) + bp[1], &hashp->tmp_buf[len], mylen );
476     return ( totlen );
477 }
478 
479 /*
480 	Fill in the key and data
481 	for this big pair
482 */
483 extern int
484 __big_keydata ( bufp, ndx, key, val, set )
485 BUFHEAD	*bufp;
486 int	ndx;
487 DBT	*key, *val;
488 int	set;
489 {
490     key->size = collect_key ( bufp, 0, val, set );
491     if ( key->size == -1 ) {
492 	return (-1);
493     }
494     key->data = hashp->tmp_key;
495     return(0);
496 }
497 
498 /*
499     Count how big the total key size is by
500     recursing through the pages.  Then collect
501     the data, allocate a buffer and copy the key as
502     you recurse up.
503 */
504 static int
505 collect_key ( bufp, len, val, set )
506 BUFHEAD	*bufp;
507 int	len;
508 DBT	*val;
509 int	set;
510 {
511     char	*p = bufp->page;
512     u_short	*bp = (u_short *)p;
513     u_short	save_addr;
514     int	mylen, totlen;
515     BUFHEAD	*xbp;
516 
517     mylen = hashp->BSIZE - bp[1];
518 
519     save_addr = bufp->addr;
520     totlen = len + mylen;
521     if ( bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA ) {/* End of Key */
522 	if ( hashp->tmp_key ) free (hashp->tmp_key);
523 	hashp->tmp_key = (char *)malloc ( totlen );
524 	if ( !hashp->tmp_key ) {
525 	    return(-1);
526 	}
527 	__big_return ( bufp, 1, val, set );
528     } else {
529 	xbp = __get_buf (bp[bp[0]-1], bufp, 0);
530 	if ( !xbp || ((totlen = collect_key (xbp, totlen, val, set)) < 1 ) ) {
531 	    return(-1);
532 	}
533     }
534     if ( bufp->addr != save_addr ) {
535 	errno = EINVAL;		/* MIS -- OUT OF BUFFERS */
536 	return (-1);
537     }
538     bcopy ( (bufp->page) + bp[1], &hashp->tmp_key[len], mylen );
539     return ( totlen );
540 }
541 
542 
543 /*
544     return 0 => OK
545 	   -1 => error
546 */
547 extern int
548 __big_split ( op, np, big_keyp, addr, obucket, ret )
549 BUFHEAD	*op;		/* Pointer to where to put keys that go in old bucket */
550 BUFHEAD	*np;		/* Pointer to new bucket page */
551 BUFHEAD	*big_keyp;	/* Pointer to first page containing the big key/data */
552 u_short	addr;		/* Address of big_keyp */
553 int	obucket;	/* Old Bucket */
554 SPLIT_RETURN	*ret;
555 {
556     register	u_short	*prev_pagep;
557     register	BUFHEAD	*tmpp;
558     register	u_short 	*tp;
559     BUFHEAD	*bp = big_keyp;
560     u_short	off, free_space;
561     u_short	n;
562 
563     DBT		key, val;
564 
565     int		change;
566 
567     /* Now figure out where the big key/data goes */
568     if (__big_keydata ( big_keyp, 1, &key, &val, 0 )) {
569 	return(-1);
570     }
571     change = (__call_hash ( key.data, key.size ) != obucket );
572 
573     if ( ret->next_addr = __find_last_page ( &big_keyp ) ) {
574 	if (!(ret->nextp = __get_buf ( ret->next_addr, big_keyp, 0 ))) {
575 	    return(-1);;
576 	}
577     } else {
578 	ret->nextp = NULL;
579     }
580 
581     /* Now make one of np/op point to the big key/data pair */
582     assert(np->ovfl == NULL);
583     if ( change ) tmpp = np;
584     else tmpp = op;
585 
586     tmpp->flags |= BUF_MOD;
587 #ifdef DEBUG1
588     fprintf ( stderr, "BIG_SPLIT: %d->ovfl was %d is now %d\n", tmpp->addr,
589 		(tmpp->ovfl?tmpp->ovfl->addr:0),
590 		(bp?bp->addr:0) );
591 #endif
592     tmpp->ovfl = bp;		/* one of op/np point to big_keyp */
593     tp = (u_short *)tmpp->page;
594     assert ( FREESPACE(tp) >= OVFLSIZE);
595     n = tp[0];
596     off = OFFSET(tp);
597     free_space = FREESPACE(tp);
598     tp[++n] = addr;
599     tp[++n] = OVFLPAGE;
600     tp[0] = n;
601     OFFSET(tp) = off;
602     FREESPACE(tp) = free_space - OVFLSIZE;
603 
604     /*
605 	Finally, set the new and old return values.
606 	BIG_KEYP contains a pointer to the last page of the big key_data pair.
607 	Make sure that big_keyp has no following page (2 elements) or create
608 	an empty following page.
609     */
610 
611     ret->newp = np;
612     ret->oldp = op;
613 
614     tp = (u_short *)big_keyp->page;
615     big_keyp->flags |= BUF_MOD;
616     if ( tp[0] > 2 ) {
617 	/*
618 	    There may be either one or two offsets on this page
619 	    If there is one, then the overflow page is linked on
620 	    normally and tp[4] is OVFLPAGE.  If there are two, tp[4]
621 	    contains the second offset and needs to get stuffed in
622 	    after the next overflow page is added
623 	*/
624 	n = tp[4];
625 	free_space = FREESPACE(tp);
626 	off = OFFSET(tp);
627 	tp[0] -= 2;
628 	FREESPACE(tp) = free_space + OVFLSIZE;
629 	OFFSET(tp) = off;
630 	tmpp = __add_ovflpage ( big_keyp );
631 	if ( !tmpp ) {
632 	    return(-1);
633 	}
634 	tp[4] = n;
635     } else {
636 	tmpp = big_keyp;
637     }
638 
639     if ( change ) ret->newp = tmpp;
640     else ret->oldp = tmpp;
641 
642     return(0);
643 }
644