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