Changed functions isc_getmethod, isams_getmethod.
[idzebra-moved-to-github.git] / isamc / isamc.c
1 /*
2  * Copyright (c) 1995-1998, Index Data.
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: isamc.c,v $
7  * Revision 1.19  1999-07-14 10:59:27  adam
8  * Changed functions isc_getmethod, isams_getmethod.
9  * Improved fatal error handling (such as missing EXPLAIN schema).
10  *
11  * Revision 1.18  1999/06/30 09:08:23  adam
12  * Added coder to reset.
13  *
14  * Revision 1.17  1999/05/26 07:49:14  adam
15  * C++ compilation.
16  *
17  * Revision 1.16  1998/05/27 14:32:03  adam
18  * Changed default block category layout.
19  *
20  * Revision 1.15  1998/05/20 10:12:25  adam
21  * Implemented automatic EXPLAIN database maintenance.
22  * Modified Zebra to work with ASN.1 compiled version of YAZ.
23  *
24  * Revision 1.14  1998/03/19 10:04:35  adam
25  * Minor changes.
26  *
27  * Revision 1.13  1998/03/18 09:23:55  adam
28  * Blocks are stored in chunks on free list - up to factor 2 in speed.
29  * Fixed bug that could occur in block category rearrangemen.
30  *
31  * Revision 1.12  1998/03/16 10:37:24  adam
32  * Added more statistics.
33  *
34  * Revision 1.11  1998/03/13 15:30:50  adam
35  * New functions isc_block_used and isc_block_size. Fixed 'leak'
36  * in isc_alloc_block.
37  *
38  * Revision 1.10  1998/03/11 11:18:18  adam
39  * Changed the isc_merge to take into account the mfill (minimum-fill).
40  *
41  * Revision 1.9  1998/03/06 13:54:02  adam
42  * Fixed two nasty bugs in isc_merge.
43  *
44  * Revision 1.8  1997/09/17 12:19:20  adam
45  * Zebra version corresponds to YAZ version 1.4.
46  * Changed Zebra server so that it doesn't depend on global common_resource.
47  *
48  * Revision 1.7  1997/02/12 20:42:43  adam
49  * Bug fix: during isc_merge operations, some pages weren't marked dirty
50  * even though they should be. At this point the merge operation marks
51  * a page dirty if the previous page changed at all. A better approach is
52  * to mark it dirty if the last key written changed in previous page.
53  *
54  * Revision 1.6  1996/11/08 11:15:29  adam
55  * Number of keys in chain are stored in first block and the function
56  * to retrieve this information, isc_pp_num is implemented.
57  *
58  * Revision 1.5  1996/11/04 14:08:57  adam
59  * Optimized free block usage.
60  *
61  * Revision 1.4  1996/11/01 13:36:46  adam
62  * New element, max_blocks_mem, that control how many blocks of max size
63  * to store in memory during isc_merge.
64  * Function isc_merge now ignores delete/update of identical keys and
65  * the proper blocks are then non-dirty and not written in flush_blocks.
66  *
67  * Revision 1.3  1996/11/01  08:59:14  adam
68  * First version of isc_merge that supports update/delete.
69  *
70  * Revision 1.2  1996/10/29 16:44:56  adam
71  * Work on isc_merge.
72  *
73  * Revision 1.1  1996/10/29  13:40:48  adam
74  * First work.
75  *
76  */
77
78 /* 
79  * TODO:
80  *   Reduction to lower categories in isc_merge
81  */
82 #include <stdlib.h>
83 #include <assert.h>
84 #include <string.h>
85 #include <stdio.h>
86
87 #include <log.h>
88 #include "isamc-p.h"
89
90 static void flush_block (ISAMC is, int cat);
91 static void release_fc (ISAMC is, int cat);
92 static void init_fc (ISAMC is, int cat);
93
94 #define ISAMC_FREELIST_CHUNK 1
95
96 #define SMALL_TEST 0
97
98 void isc_getmethod (ISAMC_M m)
99 {
100
101     static struct ISAMC_filecat_s def_cat[] = {
102 #if SMALL_TEST
103         {    32,     28,      0,  3 },
104         {    64,     54,     30,  0 },
105 #else
106         {    24,     22,     18,  10 },
107         {   128,    120,    100,  10 },
108         {   512,    490,    350,  10 },
109         {  2048,   1900,   1700,  10 },
110         {  8192,   8000,   7900,  10 },
111         { 32768,  32000,  31000,  0 },
112 #endif
113     };
114     m->filecat = def_cat;
115
116     m->code_start = NULL;
117     m->code_item = NULL;
118     m->code_stop = NULL;
119     m->code_reset = NULL;
120
121     m->compare_item = NULL;
122
123     m->debug = 1;
124
125     m->max_blocks_mem = 10;
126 }
127
128 ISAMC isc_open (BFiles bfs, const char *name, int writeflag, ISAMC_M method)
129 {
130     ISAMC is;
131     ISAMC_filecat filecat;
132     int i = 0;
133     int max_buf_size = 0;
134
135     is = (ISAMC) xmalloc (sizeof(*is));
136
137     is->method = (ISAMC_M) xmalloc (sizeof(*is->method));
138     memcpy (is->method, method, sizeof(*method));
139     filecat = is->method->filecat;
140     assert (filecat);
141
142     /* determine number of block categories */
143     if (is->method->debug)
144         logf (LOG_LOG, "isc: bsize  ifill  mfill mblocks");
145     do
146     {
147         if (is->method->debug)
148             logf (LOG_LOG, "isc:%6d %6d %6d %6d",
149                   filecat[i].bsize, filecat[i].ifill, 
150                   filecat[i].mfill, filecat[i].mblocks);
151         if (max_buf_size < filecat[i].mblocks * filecat[i].bsize)
152             max_buf_size = filecat[i].mblocks * filecat[i].bsize;
153     } while (filecat[i++].mblocks);
154     is->no_files = i;
155     is->max_cat = --i;
156     /* max_buf_size is the larget buffer to be used during merge */
157     max_buf_size = (1 + max_buf_size / filecat[i].bsize) * filecat[i].bsize;
158     if (max_buf_size < (1+is->method->max_blocks_mem) * filecat[i].bsize)
159         max_buf_size = (1+is->method->max_blocks_mem) * filecat[i].bsize;
160     if (is->method->debug)
161         logf (LOG_LOG, "isc: max_buf_size %d", max_buf_size);
162     
163     assert (is->no_files > 0);
164     is->files = (ISAMC_file) xmalloc (sizeof(*is->files)*is->no_files);
165     if (writeflag)
166     {
167         is->merge_buf = (char *) xmalloc (max_buf_size+256);
168         memset (is->merge_buf, 0, max_buf_size+256);
169     }
170     else
171         is->merge_buf = NULL;
172     for (i = 0; i<is->no_files; i++)
173     {
174         char fname[512];
175
176         sprintf (fname, "%s%c", name, i+'A');
177         is->files[i].bf = bf_open (bfs, fname, is->method->filecat[i].bsize,
178                                    writeflag);
179         is->files[i].head_is_dirty = 0;
180         if (!bf_read (is->files[i].bf, 0, 0, sizeof(ISAMC_head),
181                      &is->files[i].head))
182         {
183             is->files[i].head.lastblock = 1;
184             is->files[i].head.freelist = 0;
185         }
186         is->files[i].alloc_entries_num = 0;
187         is->files[i].alloc_entries_max =
188             is->method->filecat[i].bsize / sizeof(int) - 1;
189         is->files[i].alloc_buf = (char *)
190             xmalloc (is->method->filecat[i].bsize);
191         is->files[i].no_writes = 0;
192         is->files[i].no_reads = 0;
193         is->files[i].no_skip_writes = 0;
194         is->files[i].no_allocated = 0;
195         is->files[i].no_released = 0;
196         is->files[i].no_remap = 0;
197         is->files[i].no_forward = 0;
198         is->files[i].no_backward = 0;
199         is->files[i].sum_forward = 0;
200         is->files[i].sum_backward = 0;
201         is->files[i].no_next = 0;
202         is->files[i].no_prev = 0;
203
204         init_fc (is, i);
205     }
206     return is;
207 }
208
209 int isc_block_used (ISAMC is, int type)
210 {
211     if (type < 0 || type >= is->no_files)
212         return -1;
213     return is->files[type].head.lastblock-1;
214 }
215
216 int isc_block_size (ISAMC is, int type)
217 {
218     ISAMC_filecat filecat = is->method->filecat;
219     if (type < 0 || type >= is->no_files)
220         return -1;
221     return filecat[type].bsize;
222 }
223
224 int isc_close (ISAMC is)
225 {
226     int i;
227
228     if (is->method->debug)
229     {
230         logf (LOG_LOG, "isc:    next    forw   mid-f    prev   backw   mid-b");
231         for (i = 0; i<is->no_files; i++)
232             logf (LOG_LOG, "isc:%8d%8d%8.1f%8d%8d%8.1f",
233                   is->files[i].no_next,
234                   is->files[i].no_forward,
235                   is->files[i].no_forward ?
236                   (double) is->files[i].sum_forward/is->files[i].no_forward
237                   : 0.0,
238                   is->files[i].no_prev,
239                   is->files[i].no_backward,
240                   is->files[i].no_backward ?
241                   (double) is->files[i].sum_backward/is->files[i].no_backward
242                   : 0.0);
243     }
244     if (is->method->debug)
245         logf (LOG_LOG, "isc:  writes   reads skipped   alloc released  remap");
246     for (i = 0; i<is->no_files; i++)
247     {
248         release_fc (is, i);
249         assert (is->files[i].bf);
250         if (is->files[i].head_is_dirty)
251             bf_write (is->files[i].bf, 0, 0, sizeof(ISAMC_head),
252                  &is->files[i].head);
253         if (is->method->debug)
254             logf (LOG_LOG, "isc:%8d%8d%8d%8d%8d%8d",
255                   is->files[i].no_writes,
256                   is->files[i].no_reads,
257                   is->files[i].no_skip_writes,
258                   is->files[i].no_allocated,
259                   is->files[i].no_released,
260                   is->files[i].no_remap);
261         xfree (is->files[i].fc_list);
262         flush_block (is, i);
263         bf_close (is->files[i].bf);
264     }
265     xfree (is->files);
266     xfree (is->merge_buf);
267     xfree (is->method);
268     xfree (is);
269     return 0;
270 }
271
272 int isc_read_block (ISAMC is, int cat, int pos, char *dst)
273 {
274     ++(is->files[cat].no_reads);
275     return bf_read (is->files[cat].bf, pos, 0, 0, dst);
276 }
277
278 int isc_write_block (ISAMC is, int cat, int pos, char *src)
279 {
280     ++(is->files[cat].no_writes);
281     if (is->method->debug > 2)
282         logf (LOG_LOG, "isc: write_block %d %d", cat, pos);
283     return bf_write (is->files[cat].bf, pos, 0, 0, src);
284 }
285
286 int isc_write_dblock (ISAMC is, int cat, int pos, char *src,
287                       int nextpos, int offset)
288 {
289     ISAMC_BLOCK_SIZE size = offset + ISAMC_BLOCK_OFFSET_N;
290     if (is->method->debug > 2)
291         logf (LOG_LOG, "isc: write_dblock. size=%d nextpos=%d",
292               (int) size, nextpos);
293     src -= ISAMC_BLOCK_OFFSET_N;
294     memcpy (src, &nextpos, sizeof(int));
295     memcpy (src + sizeof(int), &size, sizeof(size));
296     return isc_write_block (is, cat, pos, src);
297 }
298
299 #if ISAMC_FREELIST_CHUNK
300 static void flush_block (ISAMC is, int cat)
301 {
302     char *abuf = is->files[cat].alloc_buf;
303     int block = is->files[cat].head.freelist;
304     if (block && is->files[cat].alloc_entries_num)
305     {
306         memcpy (abuf, &is->files[cat].alloc_entries_num, sizeof(int));
307         bf_write (is->files[cat].bf, block, 0, 0, abuf);
308         is->files[cat].alloc_entries_num = 0;
309     }
310     xfree (abuf);
311 }
312
313 static int alloc_block (ISAMC is, int cat)
314 {
315     int block = is->files[cat].head.freelist;
316     char *abuf = is->files[cat].alloc_buf;
317
318     (is->files[cat].no_allocated)++;
319
320     if (!block)
321     {
322         block = (is->files[cat].head.lastblock)++;   /* no free list */
323         is->files[cat].head_is_dirty = 1;
324     }
325     else
326     {
327         if (!is->files[cat].alloc_entries_num) /* read first time */
328         {
329             bf_read (is->files[cat].bf, block, 0, 0, abuf);
330             memcpy (&is->files[cat].alloc_entries_num, abuf,
331                     sizeof(is->files[cat].alloc_entries_num));
332             assert (is->files[cat].alloc_entries_num > 0);
333         }
334         /* have some free blocks now */
335         assert (is->files[cat].alloc_entries_num > 0);
336         is->files[cat].alloc_entries_num--;
337         if (!is->files[cat].alloc_entries_num)  /* last one in block? */
338         {
339             memcpy (&is->files[cat].head.freelist, abuf + sizeof(int),
340                     sizeof(int));
341             is->files[cat].head_is_dirty = 1;
342
343             if (is->files[cat].head.freelist)
344             {
345                 bf_read (is->files[cat].bf, is->files[cat].head.freelist,
346                          0, 0, abuf);
347                 memcpy (&is->files[cat].alloc_entries_num, abuf,
348                         sizeof(is->files[cat].alloc_entries_num));
349                 assert (is->files[cat].alloc_entries_num);
350             }
351         }
352         else
353             memcpy (&block, abuf + sizeof(int) + sizeof(int) *
354                     is->files[cat].alloc_entries_num, sizeof(int));
355     }
356     return block;
357 }
358
359 static void release_block (ISAMC is, int cat, int pos)
360 {
361     char *abuf = is->files[cat].alloc_buf;
362     int block = is->files[cat].head.freelist;
363
364     (is->files[cat].no_released)++;
365
366     if (block && !is->files[cat].alloc_entries_num) /* must read block */
367     {
368         bf_read (is->files[cat].bf, block, 0, 0, abuf);
369         memcpy (&is->files[cat].alloc_entries_num, abuf,
370                 sizeof(is->files[cat].alloc_entries_num));
371         assert (is->files[cat].alloc_entries_num > 0);
372     }
373     assert (is->files[cat].alloc_entries_num <= is->files[cat].alloc_entries_max);
374     if (is->files[cat].alloc_entries_num == is->files[cat].alloc_entries_max)
375     {
376         assert (block);
377         memcpy (abuf, &is->files[cat].alloc_entries_num, sizeof(int));
378         bf_write (is->files[cat].bf, block, 0, 0, abuf);
379         is->files[cat].alloc_entries_num = 0;
380     }
381     if (!is->files[cat].alloc_entries_num) /* make new buffer? */
382     {
383         memcpy (abuf + sizeof(int), &block, sizeof(int));
384         is->files[cat].head.freelist = pos;
385         is->files[cat].head_is_dirty = 1; 
386     }
387     else
388     {
389         memcpy (abuf + sizeof(int) +
390                 is->files[cat].alloc_entries_num*sizeof(int),
391                 &pos, sizeof(int));
392     }
393     is->files[cat].alloc_entries_num++;
394 }
395 #else
396 static void flush_block (ISAMC is, int cat)
397 {
398     char *abuf = is->files[cat].alloc_buf;
399     xfree (abuf);
400 }
401
402 static int alloc_block (ISAMC is, int cat)
403 {
404     int block;
405     char buf[sizeof(int)];
406
407     is->files[cat].head_is_dirty = 1;
408     (is->files[cat].no_allocated)++;
409     if ((block = is->files[cat].head.freelist))
410     {
411         bf_read (is->files[cat].bf, block, 0, sizeof(int), buf);
412         memcpy (&is->files[cat].head.freelist, buf, sizeof(int));
413     }
414     else
415         block = (is->files[cat].head.lastblock)++;
416     return block;
417 }
418
419 static void release_block (ISAMC is, int cat, int pos)
420 {
421     char buf[sizeof(int)];
422    
423     (is->files[cat].no_released)++;
424     is->files[cat].head_is_dirty = 1; 
425     memcpy (buf, &is->files[cat].head.freelist, sizeof(int));
426     is->files[cat].head.freelist = pos;
427     bf_write (is->files[cat].bf, pos, 0, sizeof(int), buf);
428 }
429 #endif
430
431 int isc_alloc_block (ISAMC is, int cat)
432 {
433     int block = 0;
434
435     if (is->files[cat].fc_list)
436     {
437         int j, nb;
438         for (j = 0; j < is->files[cat].fc_max; j++)
439             if ((nb = is->files[cat].fc_list[j]) && (!block || nb < block))
440             {
441                 is->files[cat].fc_list[j] = 0;
442                 block = nb;
443                 break;
444             }
445     }
446     if (!block)
447         block = alloc_block (is, cat);
448     if (is->method->debug > 3)
449         logf (LOG_LOG, "isc: alloc_block in cat %d: %d", cat, block);
450     return block;
451 }
452
453 void isc_release_block (ISAMC is, int cat, int pos)
454 {
455     if (is->method->debug > 3)
456         logf (LOG_LOG, "isc: release_block in cat %d: %d", cat, pos);
457     if (is->files[cat].fc_list)
458     {
459         int j;
460         for (j = 0; j<is->files[cat].fc_max; j++)
461             if (!is->files[cat].fc_list[j])
462             {
463                 is->files[cat].fc_list[j] = pos;
464                 return;
465             }
466     }
467     release_block (is, cat, pos);
468 }
469
470 static void init_fc (ISAMC is, int cat)
471 {
472     int j = 100;
473         
474     is->files[cat].fc_max = j;
475     is->files[cat].fc_list = (int *)
476         xmalloc (sizeof(*is->files[0].fc_list) * j);
477     while (--j >= 0)
478         is->files[cat].fc_list[j] = 0;
479 }
480
481 static void release_fc (ISAMC is, int cat)
482 {
483     int b, j = is->files[cat].fc_max;
484
485     while (--j >= 0)
486         if ((b = is->files[cat].fc_list[j]))
487         {
488             release_block (is, cat, b);
489             is->files[cat].fc_list[j] = 0;
490         }
491 }
492
493 void isc_pp_close (ISAMC_PP pp)
494 {
495     ISAMC is = pp->is;
496
497     (*is->method->code_stop)(ISAMC_DECODE, pp->decodeClientData);
498     xfree (pp->buf);
499     xfree (pp);
500 }
501
502 ISAMC_PP isc_pp_open (ISAMC is, ISAMC_P ipos)
503 {
504     ISAMC_PP pp = (ISAMC_PP) xmalloc (sizeof(*pp));
505     char *src;
506    
507     pp->cat = isc_type(ipos);
508     pp->pos = isc_block(ipos); 
509
510     src = pp->buf = (char *) xmalloc (is->method->filecat[pp->cat].bsize);
511
512     pp->next = 0;
513     pp->size = 0;
514     pp->offset = 0;
515     pp->is = is;
516     pp->decodeClientData = (*is->method->code_start)(ISAMC_DECODE);
517     pp->deleteFlag = 0;
518     pp->numKeys = 0;
519
520     if (pp->pos)
521     {
522         src = pp->buf;
523         isc_read_block (is, pp->cat, pp->pos, src);
524         memcpy (&pp->next, src, sizeof(pp->next));
525         src += sizeof(pp->next);
526         memcpy (&pp->size, src, sizeof(pp->size));
527         src += sizeof(pp->size);
528         memcpy (&pp->numKeys, src, sizeof(pp->numKeys));
529         src += sizeof(pp->numKeys);
530         assert (pp->next != pp->pos);
531         pp->offset = src - pp->buf; 
532         assert (pp->offset == ISAMC_BLOCK_OFFSET_1);
533         if (is->method->debug > 2)
534             logf (LOG_LOG, "isc: read_block size=%d %d %d next=%d",
535                  pp->size, pp->cat, pp->pos, pp->next);
536     }
537     return pp;
538 }
539
540 /* returns non-zero if item could be read; 0 otherwise */
541 int isc_pp_read (ISAMC_PP pp, void *buf)
542 {
543     return isc_read_item (pp, (char **) &buf);
544 }
545
546 /* read one item from file - decode and store it in *dst.
547    Returns
548      0 if end-of-file
549      1 if item could be read ok and NO boundary
550      2 if item could be read ok and boundary */
551 int isc_read_item (ISAMC_PP pp, char **dst)
552 {
553     ISAMC is = pp->is;
554     char *src = pp->buf + pp->offset;
555
556     if (pp->offset >= pp->size)
557     {
558         if (!pp->next)
559         {
560             pp->pos = 0;
561             return 0; /* end of file */
562         }
563         if (pp->next > pp->pos)
564         {
565             if (pp->next == pp->pos + 1)
566                 is->files[pp->cat].no_next++;
567             else
568             {
569                 is->files[pp->cat].no_forward++;
570                 is->files[pp->cat].sum_forward += pp->next - pp->pos;
571             }
572         }
573         else
574         {
575             if (pp->next + 1 == pp->pos)
576                 is->files[pp->cat].no_prev++;
577             else
578             {
579                 is->files[pp->cat].no_backward++;
580                 is->files[pp->cat].sum_backward += pp->pos - pp->next;
581             }
582         }
583         /* out new block position */
584         pp->pos = pp->next;
585         src = pp->buf;
586         /* read block and save 'next' and 'size' entry */
587         isc_read_block (is, pp->cat, pp->pos, src);
588         memcpy (&pp->next, src, sizeof(pp->next));
589         src += sizeof(pp->next);
590         memcpy (&pp->size, src, sizeof(pp->size));
591         src += sizeof(pp->size);
592         /* assume block is non-empty */
593         assert (src - pp->buf == ISAMC_BLOCK_OFFSET_N);
594         assert (pp->next != pp->pos);
595         if (pp->deleteFlag)
596             isc_release_block (is, pp->cat, pp->pos);
597         (*is->method->code_item)(ISAMC_DECODE, pp->decodeClientData, dst, &src);
598         pp->offset = src - pp->buf; 
599         if (is->method->debug > 2)
600             logf (LOG_LOG, "isc: read_block size=%d %d %d next=%d",
601                  pp->size, pp->cat, pp->pos, pp->next);
602         return 2;
603     }
604     (*is->method->code_item)(ISAMC_DECODE, pp->decodeClientData, dst, &src);
605     pp->offset = src - pp->buf; 
606     return 1;
607 }
608
609 int isc_pp_num (ISAMC_PP pp)
610 {
611     return pp->numKeys;
612 }
613