Happy new year
[idzebra-moved-to-github.git] / isamc / isamc.c
1 /* This file is part of the Zebra server.
2    Copyright (C) 1994-2009 Index Data
3
4 Zebra is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /* 
21  * TODO:
22  *   Reduction to lower categories in isamc_merge
23  */
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <stdio.h>
28
29 #include <yaz/log.h>
30 #include <yaz/xmalloc.h>
31 #include "isamc-p.h"
32
33 static void flush_block (ISAMC is, int cat);
34 static void release_fc (ISAMC is, int cat);
35 static void init_fc (ISAMC is, int cat);
36
37 #define ISAMC_FREELIST_CHUNK 0
38
39 #define SMALL_TEST 0
40
41 void isamc_getmethod (ISAMC_M *m)
42 {
43
44     static struct ISAMC_filecat_s def_cat[] = {
45 #if SMALL_TEST
46         {    32,     28,      0,  3 },
47         {    64,     54,     30,  0 },
48 #else
49         {    64,     56,     40,  5 },
50         {   128,    120,    100,  10 },
51         {   512,    490,    350,  10 },
52         {  2048,   1900,   1700,  10 },
53         {  8192,   8000,   7900,  10 },
54         { 32768,  32000,  31000,  0 },
55 #endif
56     };
57     m->filecat = def_cat;
58
59     m->codec.start = NULL;
60     m->codec.decode  = NULL;
61     m->codec.encode = NULL;
62     m->codec.stop = NULL;
63     m->codec.reset = NULL;
64
65     m->compare_item = NULL;
66     m->log_item = NULL;
67
68     m->debug = 1;
69
70     m->max_blocks_mem = 10;
71 }
72
73 ISAMC isamc_open (BFiles bfs, const char *name, int writeflag, ISAMC_M *method)
74 {
75     ISAMC is;
76     ISAMC_filecat filecat;
77     int i = 0;
78     int max_buf_size = 0;
79
80     is = (ISAMC) xmalloc (sizeof(*is));
81
82     is->method = (ISAMC_M *) xmalloc (sizeof(*is->method));
83     memcpy (is->method, method, sizeof(*method));
84     filecat = is->method->filecat;
85     assert (filecat);
86
87     /* determine number of block categories */
88     if (is->method->debug)
89         yaz_log (YLOG_LOG, "isc: bsize  ifill  mfill mblocks");
90     do
91     {
92         if (is->method->debug)
93             yaz_log (YLOG_LOG, "isc:%6d %6d %6d %6d",
94                   filecat[i].bsize, filecat[i].ifill, 
95                   filecat[i].mfill, filecat[i].mblocks);
96         if (max_buf_size < filecat[i].mblocks * filecat[i].bsize)
97             max_buf_size = filecat[i].mblocks * filecat[i].bsize;
98     } while (filecat[i++].mblocks);
99     is->no_files = i;
100     is->max_cat = --i;
101     /* max_buf_size is the larget buffer to be used during merge */
102     max_buf_size = (1 + max_buf_size / filecat[i].bsize) * filecat[i].bsize;
103     if (max_buf_size < (1+is->method->max_blocks_mem) * filecat[i].bsize)
104         max_buf_size = (1+is->method->max_blocks_mem) * filecat[i].bsize;
105     if (is->method->debug)
106         yaz_log (YLOG_LOG, "isc: max_buf_size %d", max_buf_size);
107     
108     assert (is->no_files > 0);
109     is->files = (ISAMC_file) xmalloc (sizeof(*is->files)*is->no_files);
110     if (writeflag)
111     {
112         is->merge_buf = (char *) xmalloc (max_buf_size+256);
113         memset (is->merge_buf, 0, max_buf_size+256);
114     }
115     else
116         is->merge_buf = NULL;
117     for (i = 0; i<is->no_files; i++)
118     {
119         is->files[i].bf = 0;
120         is->files[i].head_is_dirty = 0;
121         is->files[i].head.lastblock = 1;
122         is->files[i].head.freelist = 0;
123         is->files[i].alloc_entries_num = 0;
124         is->files[i].alloc_entries_max =
125             is->method->filecat[i].bsize / sizeof(zint) - 1;
126         is->files[i].alloc_buf = (char *)
127             xmalloc (is->method->filecat[i].bsize);
128         is->files[i].no_writes = 0;
129         is->files[i].no_reads = 0;
130         is->files[i].no_skip_writes = 0;
131         is->files[i].no_allocated = 0;
132         is->files[i].no_released = 0;
133         is->files[i].no_remap = 0;
134         is->files[i].no_forward = 0;
135         is->files[i].no_backward = 0;
136         is->files[i].sum_forward = 0;
137         is->files[i].sum_backward = 0;
138         is->files[i].no_next = 0;
139         is->files[i].no_prev = 0;
140
141         init_fc (is, i);
142     }
143
144     for (i = 0; i<is->no_files; i++)
145     {
146         char fname[FILENAME_MAX];
147         int r;
148
149         sprintf (fname, "%s%c", name, i+'A');
150         is->files[i].bf = bf_open (bfs, fname, is->method->filecat[i].bsize,
151                                    writeflag);
152         if (!is->files[i].bf)
153         {
154             isamc_close(is);
155             return 0;
156         }
157         r = bf_read(is->files[i].bf, 0, 0, sizeof(ISAMC_head),
158                      &is->files[i].head);
159         if (r == -1)
160         {
161             isamc_close(is);
162             return 0;
163         }
164     }
165     return is;
166 }
167
168 zint isamc_block_used (ISAMC is, int type)
169 {
170     if (type < 0 || type >= is->no_files)
171         return -1;
172     return is->files[type].head.lastblock-1;
173 }
174
175 int isamc_block_size (ISAMC is, int type)
176 {
177     ISAMC_filecat filecat = is->method->filecat;
178     if (type < 0 || type >= is->no_files)
179         return -1;
180     return filecat[type].bsize;
181 }
182
183 int isamc_close (ISAMC is)
184 {
185     int i;
186
187     if (is->method->debug)
188     {
189         yaz_log (YLOG_LOG, "isc:    next    forw   mid-f    prev   backw   mid-b");
190         for (i = 0; i<is->no_files; i++)
191             yaz_log (YLOG_LOG, "isc:%8d%8d%8.1f%8d%8d%8.1f",
192                   is->files[i].no_next,
193                   is->files[i].no_forward,
194                   is->files[i].no_forward ?
195                   (double) is->files[i].sum_forward/is->files[i].no_forward
196                   : 0.0,
197                   is->files[i].no_prev,
198                   is->files[i].no_backward,
199                   is->files[i].no_backward ?
200                   (double) is->files[i].sum_backward/is->files[i].no_backward
201                   : 0.0);
202     }
203     if (is->method->debug)
204         yaz_log (YLOG_LOG, "isc:  writes   reads skipped   alloc released  remap");
205     for (i = 0; i<is->no_files; i++)
206     {
207         release_fc (is, i);
208         if (is->method->debug)
209             yaz_log (YLOG_LOG, "isc:%8d%8d%8d%8d%8d%8d",
210                      is->files[i].no_writes,
211                      is->files[i].no_reads,
212                      is->files[i].no_skip_writes,
213                      is->files[i].no_allocated,
214                      is->files[i].no_released,
215                      is->files[i].no_remap);
216         if (is->files[i].bf)
217         {
218             if (is->files[i].head_is_dirty)
219                 bf_write (is->files[i].bf, 0, 0, sizeof(ISAMC_head),
220                           &is->files[i].head);
221             flush_block (is, i);
222             bf_close (is->files[i].bf);
223         }
224         xfree(is->files[i].fc_list);
225         xfree(is->files[i].alloc_buf);
226     }
227     xfree (is->files);
228     xfree (is->merge_buf);
229     xfree (is->method);
230     xfree (is);
231     return 0;
232 }
233
234 int isamc_read_block (ISAMC is, int cat, zint pos, char *dst)
235 {
236     ++(is->files[cat].no_reads);
237     return bf_read (is->files[cat].bf, pos, 0, 0, dst);
238 }
239
240 int isamc_write_block (ISAMC is, int cat, zint pos, char *src)
241 {
242     ++(is->files[cat].no_writes);
243     if (is->method->debug > 2)
244         yaz_log (YLOG_LOG, "isc: write_block %d " ZINT_FORMAT, cat, pos);
245     return bf_write (is->files[cat].bf, pos, 0, 0, src);
246 }
247
248 int isamc_write_dblock (ISAMC is, int cat, zint pos, char *src,
249                       zint nextpos, int offset)
250 {
251     ISAMC_BLOCK_SIZE size = offset + ISAMC_BLOCK_OFFSET_N;
252     if (is->method->debug > 2)
253         yaz_log (YLOG_LOG, "isc: write_dblock. size=%d nextpos=" ZINT_FORMAT,
254               (int) size, nextpos);
255     src -= ISAMC_BLOCK_OFFSET_N;
256     memcpy (src, &nextpos, sizeof(nextpos));
257     memcpy (src + sizeof(nextpos), &size, sizeof(size));
258     return isamc_write_block (is, cat, pos, src);
259 }
260
261 #if ISAMC_FREELIST_CHUNK
262 static void flush_block (ISAMC is, int cat)
263 {
264     char *abuf = is->files[cat].alloc_buf;
265     zint block = is->files[cat].head.freelist;
266     if (block && is->files[cat].alloc_entries_num)
267     {
268         memcpy (abuf, &is->files[cat].alloc_entries_num, sizeof(block));
269         bf_write (is->files[cat].bf, block, 0, 0, abuf);
270         is->files[cat].alloc_entries_num = 0;
271     }
272 }
273
274 static zint alloc_block (ISAMC is, int cat)
275 {
276     zint block = is->files[cat].head.freelist;
277     char *abuf = is->files[cat].alloc_buf;
278
279     (is->files[cat].no_allocated)++;
280
281     if (!block)
282     {
283         block = (is->files[cat].head.lastblock)++;   /* no free list */
284         is->files[cat].head_is_dirty = 1;
285     }
286     else
287     {
288         if (!is->files[cat].alloc_entries_num) /* read first time */
289         {
290             bf_read (is->files[cat].bf, block, 0, 0, abuf);
291             memcpy (&is->files[cat].alloc_entries_num, abuf,
292                     sizeof(is->files[cat].alloc_entries_num));
293             assert (is->files[cat].alloc_entries_num > 0);
294         }
295         /* have some free blocks now */
296         assert (is->files[cat].alloc_entries_num > 0);
297         is->files[cat].alloc_entries_num--;
298         if (!is->files[cat].alloc_entries_num)  /* last one in block? */
299         {
300             memcpy (&is->files[cat].head.freelist, abuf + sizeof(int),
301                     sizeof(zint));
302             is->files[cat].head_is_dirty = 1;
303
304             if (is->files[cat].head.freelist)
305             {
306                 bf_read (is->files[cat].bf, is->files[cat].head.freelist,
307                          0, 0, abuf);
308                 memcpy (&is->files[cat].alloc_entries_num, abuf,
309                         sizeof(is->files[cat].alloc_entries_num));
310                 assert (is->files[cat].alloc_entries_num);
311             }
312         }
313         else
314             memcpy (&block, abuf + sizeof(zint) + sizeof(int) *
315                     is->files[cat].alloc_entries_num, sizeof(zint));
316     }
317     return block;
318 }
319
320 static void release_block (ISAMC is, int cat, zint pos)
321 {
322     char *abuf = is->files[cat].alloc_buf;
323     zint block = is->files[cat].head.freelist;
324
325     (is->files[cat].no_released)++;
326
327     if (block && !is->files[cat].alloc_entries_num) /* must read block */
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     assert (is->files[cat].alloc_entries_num <= is->files[cat].alloc_entries_max);
335     if (is->files[cat].alloc_entries_num == is->files[cat].alloc_entries_max)
336     {
337         assert (block);
338         memcpy (abuf, &is->files[cat].alloc_entries_num, sizeof(int));
339         bf_write (is->files[cat].bf, block, 0, 0, abuf);
340         is->files[cat].alloc_entries_num = 0;
341     }
342     if (!is->files[cat].alloc_entries_num) /* make new buffer? */
343     {
344         memcpy (abuf + sizeof(int), &block, sizeof(zint));
345         is->files[cat].head.freelist = pos;
346         is->files[cat].head_is_dirty = 1; 
347     }
348     else
349     {
350         memcpy (abuf + sizeof(int) +
351                 is->files[cat].alloc_entries_num*sizeof(zint),
352                 &pos, sizeof(zint));
353     }
354     is->files[cat].alloc_entries_num++;
355 }
356 #else
357 static void flush_block (ISAMC is, int cat)
358 {
359 }
360
361 static zint alloc_block (ISAMC is, int cat)
362 {
363     zint block;
364     char buf[sizeof(zint)];
365
366     is->files[cat].head_is_dirty = 1;
367     (is->files[cat].no_allocated)++;
368     if ((block = is->files[cat].head.freelist))
369     {
370         bf_read (is->files[cat].bf, block, 0, sizeof(zint), buf);
371         memcpy (&is->files[cat].head.freelist, buf, sizeof(zint));
372     }
373     else
374         block = (is->files[cat].head.lastblock)++;
375     return block;
376 }
377
378 static void release_block (ISAMC is, int cat, zint pos)
379 {
380     char buf[sizeof(zint)];
381    
382     (is->files[cat].no_released)++;
383     is->files[cat].head_is_dirty = 1; 
384     memcpy (buf, &is->files[cat].head.freelist, sizeof(zint));
385     is->files[cat].head.freelist = pos;
386     bf_write (is->files[cat].bf, pos, 0, sizeof(zint), buf);
387 }
388 #endif
389
390 zint isamc_alloc_block (ISAMC is, int cat)
391 {
392     zint block = 0;
393
394     if (is->files[cat].fc_list)
395     {
396         int j;
397         zint nb;
398         for (j = 0; j < is->files[cat].fc_max; j++)
399             if ((nb = is->files[cat].fc_list[j]) && (!block || nb < block))
400             {
401                 is->files[cat].fc_list[j] = 0;
402                 block = nb;
403                 break;
404             }
405     }
406     if (!block)
407         block = alloc_block (is, cat);
408     if (is->method->debug > 3)
409         yaz_log (YLOG_LOG, "isc: alloc_block in cat %d: " ZINT_FORMAT, cat, block);
410     return block;
411 }
412
413 void isamc_release_block (ISAMC is, int cat, zint pos)
414 {
415     if (is->method->debug > 3)
416         yaz_log (YLOG_LOG, "isc: release_block in cat %d:" ZINT_FORMAT, cat, pos);
417     if (is->files[cat].fc_list)
418     {
419         int j;
420         for (j = 0; j<is->files[cat].fc_max; j++)
421             if (!is->files[cat].fc_list[j])
422             {
423                 is->files[cat].fc_list[j] = pos;
424                 return;
425             }
426     }
427     release_block (is, cat, pos);
428 }
429
430 static void init_fc (ISAMC is, int cat)
431 {
432     int j = 100;
433         
434     is->files[cat].fc_max = j;
435     is->files[cat].fc_list = (zint *)
436         xmalloc (sizeof(*is->files[0].fc_list) * j);
437     while (--j >= 0)
438         is->files[cat].fc_list[j] = 0;
439 }
440
441 static void release_fc (ISAMC is, int cat)
442 {
443     int j = is->files[cat].fc_max;
444     zint b;
445
446     while (--j >= 0)
447         if ((b = is->files[cat].fc_list[j]))
448         {
449             release_block (is, cat, b);
450             is->files[cat].fc_list[j] = 0;
451         }
452 }
453
454 void isamc_pp_close (ISAMC_PP pp)
455 {
456     ISAMC is = pp->is;
457
458     (*is->method->codec.stop)(pp->decodeClientData);
459     xfree (pp->buf);
460     xfree (pp);
461 }
462
463 ISAMC_PP isamc_pp_open (ISAMC is, ISAM_P ipos)
464 {
465     ISAMC_PP pp = (ISAMC_PP) xmalloc (sizeof(*pp));
466     char *src;
467    
468     pp->cat = (int) isamc_type(ipos);
469     pp->pos = isamc_block(ipos); 
470
471     src = pp->buf = (char *) xmalloc (is->method->filecat[pp->cat].bsize);
472
473     pp->next = 0;
474     pp->size = 0;
475     pp->offset = 0;
476     pp->is = is;
477     pp->decodeClientData = (*is->method->codec.start)();
478     pp->deleteFlag = 0;
479     pp->numKeys = 0;
480
481     if (pp->pos)
482     {
483         src = pp->buf;
484         isamc_read_block (is, pp->cat, pp->pos, src);
485         memcpy (&pp->next, src, sizeof(pp->next));
486         src += sizeof(pp->next);
487         memcpy (&pp->size, src, sizeof(pp->size));
488         src += sizeof(pp->size);
489         memcpy (&pp->numKeys, src, sizeof(pp->numKeys));
490         src += sizeof(pp->numKeys);
491         if (pp->next == pp->pos)
492         {
493             yaz_log(YLOG_FATAL|YLOG_LOG, "pp->next = " ZINT_FORMAT, pp->next);
494             yaz_log(YLOG_FATAL|YLOG_LOG, "pp->pos = " ZINT_FORMAT, pp->pos);
495             assert (pp->next != pp->pos);
496         }
497         pp->offset = src - pp->buf; 
498         assert (pp->offset == ISAMC_BLOCK_OFFSET_1);
499         if (is->method->debug > 2)
500             yaz_log (YLOG_LOG, "isc: read_block size=%d %d " ZINT_FORMAT " next="
501                   ZINT_FORMAT, pp->size, pp->cat, pp->pos, pp->next);
502     }
503     return pp;
504 }
505
506 /* returns non-zero if item could be read; 0 otherwise */
507 int isamc_pp_read (ISAMC_PP pp, void *buf)
508 {
509     char *cp = buf;
510     return isamc_read_item (pp, &cp);
511 }
512
513 /* read one item from file - decode and store it in *dst.
514    Returns
515      0 if end-of-file
516      1 if item could be read ok and NO boundary
517      2 if item could be read ok and boundary */
518 int isamc_read_item (ISAMC_PP pp, char **dst)
519 {
520     ISAMC is = pp->is;
521     const char *src = pp->buf + pp->offset;
522
523     if (pp->offset >= pp->size)
524     {
525         if (!pp->next)
526         {
527             pp->pos = 0;
528             return 0; /* end of file */
529         }
530         if (pp->next > pp->pos)
531         {
532             if (pp->next == pp->pos + 1)
533                 is->files[pp->cat].no_next++;
534             else
535             {
536                 is->files[pp->cat].no_forward++;
537                 is->files[pp->cat].sum_forward += pp->next - pp->pos;
538             }
539         }
540         else
541         {
542             if (pp->next + 1 == pp->pos)
543                 is->files[pp->cat].no_prev++;
544             else
545             {
546                 is->files[pp->cat].no_backward++;
547                 is->files[pp->cat].sum_backward += pp->pos - pp->next;
548             }
549         }
550         /* out new block position */
551         pp->pos = pp->next;
552         src = pp->buf;
553         /* read block and save 'next' and 'size' entry */
554         isamc_read_block (is, pp->cat, pp->pos, pp->buf);
555         memcpy (&pp->next, src, sizeof(pp->next));
556         src += sizeof(pp->next);
557         memcpy (&pp->size, src, sizeof(pp->size));
558         src += sizeof(pp->size);
559         /* assume block is non-empty */
560         assert (src - pp->buf == ISAMC_BLOCK_OFFSET_N);
561
562         if (pp->next == pp->pos)
563         {
564             yaz_log(YLOG_FATAL|YLOG_LOG, "pp->next = " ZINT_FORMAT, pp->next);
565             yaz_log(YLOG_FATAL|YLOG_LOG, "pp->pos = " ZINT_FORMAT, pp->pos);
566             assert (pp->next != pp->pos);
567         }
568
569         if (pp->deleteFlag)
570             isamc_release_block (is, pp->cat, pp->pos);
571         (*is->method->codec.decode)(pp->decodeClientData, dst, &src);
572         pp->offset = src - pp->buf; 
573         if (is->method->debug > 2)
574             yaz_log (YLOG_LOG, "isc: read_block size=%d %d " ZINT_FORMAT " next="
575                   ZINT_FORMAT, pp->size, pp->cat, pp->pos, pp->next);
576         return 2;
577     }
578     (*is->method->codec.decode)(pp->decodeClientData, dst, &src);
579     pp->offset = src - pp->buf; 
580     return 1;
581 }
582
583 zint isamc_pp_num (ISAMC_PP pp)
584 {
585     return pp->numKeys;
586 }
587
588 /*
589  * Local variables:
590  * c-basic-offset: 4
591  * indent-tabs-mode: nil
592  * End:
593  * vim: shiftwidth=4 tabstop=8 expandtab
594  */
595