*** empty log message ***
[idzebra-moved-to-github.git] / isam / isam.c
1 /*
2  * Copyright (C) 1994, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: isam.c,v $
7  * Revision 1.19  1996-02-10 12:20:56  quinn
8  * *** empty log message ***
9  *
10  * Revision 1.18  1996/02/06  10:19:56  quinn
11  * Attempt at fixing bug. Not all blocks were read before they were unlinked
12  * prior to a remap operation.
13  *
14  * Revision 1.17  1995/12/06  15:48:44  quinn
15  * Fixed update-problem.
16  *
17  * Revision 1.16  1995/12/06  14:48:26  quinn
18  * Fixed some strange bugs.
19  *
20  * Revision 1.15  1995/12/06  09:59:45  quinn
21  * Fixed memory-consumption bug in memory.c
22  * Added more blocksizes to the default ISAM configuration.
23  *
24  * Revision 1.14  1995/11/24  17:26:19  quinn
25  * Mostly about making some ISAM stuff in the config file optional.
26  *
27  * Revision 1.13  1995/10/17  18:03:15  adam
28  * Commented out qsort in is_merge.
29  *
30  * Revision 1.12  1995/09/06  16:11:41  adam
31  * Keysize parameter to is_open (if non-zero).
32  *
33  * Revision 1.11  1995/09/04  12:33:46  adam
34  * Various cleanup. YAZ util used instead.
35  *
36  * Revision 1.10  1994/09/28  16:58:32  quinn
37  * Small mod.
38  *
39  * Revision 1.9  1994/09/28  12:56:15  quinn
40  * Added access functions (ISPT)
41  *
42  * Revision 1.8  1994/09/28  12:32:17  quinn
43  * Trivial
44  *
45  * Revision 1.7  1994/09/28  11:56:25  quinn
46  * Added sort of input to is_merge
47  *
48  * Revision 1.6  1994/09/28  11:29:33  quinn
49  * Added cmp parameter.
50  *
51  * Revision 1.5  1994/09/27  20:03:50  quinn
52  * Seems relatively bug-free.
53  *
54  * Revision 1.4  1994/09/26  17:11:29  quinn
55  * Trivial
56  *
57  * Revision 1.3  1994/09/26  17:06:35  quinn
58  * Back again...
59  *
60  * Revision 1.1  1994/09/12  08:02:13  quinn
61  * Not functional yet
62  *
63  */
64
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <ctype.h>
69
70 #include <alexutil.h>
71 #include <bfile.h>
72 #include <isam.h>
73 #include <common.h>
74 #include "isutil.h"
75 #include "rootblk.h"
76 #include "keyops.h"
77
78 static ispt_struct *ispt_freelist = 0;
79
80 static struct
81 {
82     int total_merge_operations;
83     int total_items;
84     int dub_items_removed;
85     int new_items;
86     int failed_deletes;
87     int skipped_inserts;
88     int delete_insert_noop;
89     int delete_replace;
90     int delete;
91     int remaps;
92     int block_jumps;
93     int tab_deletes;
94     int new_tables;
95 } statistics;
96
97 static ISPT ispt_alloc()
98 {
99     ISPT p;
100
101     if (ispt_freelist)
102     {
103         p = ispt_freelist;
104         ispt_freelist = ispt_freelist->next;
105     }
106     else
107         p = xmalloc(sizeof(ispt_struct));
108     return p;
109 }
110
111 static void ispt_free(ISPT pt)
112 {
113     pt->next = ispt_freelist;
114     ispt_freelist = pt;
115 }
116
117 static int splitargs(const char *s, char *bf[], int max)
118 {
119     int ct = 0;
120     for (;;)
121     {
122         while (*s && isspace(*s))
123             s++;
124         bf[ct] = (char *) s;
125         if (!*s)
126                 return ct;
127         ct++;
128         if (ct > max)
129         {
130             logf (LOG_WARN, "Ignoring extra args to is resource");
131             bf[ct] = '\0';
132             return(ct - 1);
133         }
134         while (*s && !isspace(*s))
135             s++;
136     }
137 }
138
139 /*
140  * Open isam file.
141  * Process resources.
142  */
143 ISAM is_open(const char *name, int (*cmp)(const void *p1, const void *p2),
144     int writeflag, int keysize)
145 {
146     ISAM new;
147     char *nm, *r, *pp[IS_MAX_BLOCKTYPES+1], m[2];
148     int num, size, rs, tmp, i;
149     is_type_header th;
150
151     logf (LOG_DEBUG, "is_open(%s, %s)", name, writeflag ? "RW" : "RDONLY");
152     if (writeflag)
153     {
154         statistics.total_merge_operations = 0;
155         statistics.total_items = 0;
156         statistics.dub_items_removed = 0;
157         statistics.new_items = 0;
158         statistics.failed_deletes = 0;
159         statistics.skipped_inserts = 0;
160         statistics.delete_insert_noop = 0;
161         statistics.delete_replace = 0;
162         statistics.delete = 0;
163         statistics.remaps = 0;
164         statistics.new_tables = 0;
165         statistics.block_jumps = 0;
166         statistics.tab_deletes = 0;
167     }
168
169     new = xmalloc(sizeof(*new));
170     new->writeflag = writeflag;
171     for (i = 0; i < IS_MAX_BLOCKTYPES; i++)
172         new->types[i].index = 0;                        /* dummy */
173
174     /* determine number and size of blocktypes */
175     if (!(r = res_get_def(common_resource, nm = strconcat(name, ".",
176         "blocktypes", 0), "64 512 4K 32K")) ||
177         !(num = splitargs(r, pp, IS_MAX_BLOCKTYPES)))
178     {
179         logf (LOG_FATAL, "Failed to locate resource %s", nm);
180         return 0;
181     }
182     new->num_types = num;
183     for (i = 0; i < num; i++)
184     {
185         if ((rs = sscanf(pp[i], "%d%1[bBkKmM]", &size, m)) < 1)
186         {
187             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
188             return 0;
189         }
190         if (rs == 1)
191                 *m = 'b';
192         switch (*m)
193         {
194                 case 'b': case 'B':
195                     new->types[i].blocksize = size; break;
196                 case 'k': case 'K':
197                     new->types[i].blocksize = size * 1024; break;
198                 case 'm': case 'M':
199                     new->types[i].blocksize = size * 1048576; break;
200                 default:
201                     logf (LOG_FATAL, "Illegal size suffix: %c", *m);
202                     return 0;
203         }
204         new->types[i].dbuf = xmalloc(new->types[i].blocksize);
205         m[0] = 'A' + i;
206         m[1] = '\0';
207         if (!(new->types[i].bf = bf_open(strconcat(name, m, 0), 
208             new->types[i].blocksize, writeflag)))
209         {
210             logf (LOG_FATAL, "bf_open failed");
211             return 0;
212         }
213         if ((rs = is_rb_read(&new->types[i], &th)) > 0)
214         {
215             if (th.blocksize != new->types[i].blocksize)
216             {
217                 logf (LOG_FATAL, "File blocksize mismatch in %s", name);
218                 exit(1);
219             }
220             new->types[i].freelist = th.freelist;
221             new->types[i].top = th.top;
222         }
223         else if (writeflag) /* write dummy superblock to determine top */
224         {
225             if ((rs = is_rb_write(&new->types[i], &th)) <=0)  /* dummy */
226             {
227                 logf (LOG_FATAL, "Failed to write initial superblock.");
228                 exit(1);
229             }
230             new->types[i].freelist = -1;
231             new->types[i].top = rs;
232         }
233         /* ELSE: this is an empty file opened in read-only mode. */
234     }
235     if (keysize > 0)
236         new->keysize = keysize;
237     else
238     {
239         if (!(r = res_get_def(common_resource, nm = strconcat(name, ".",
240                                                               "keysize",
241                                                               0), "4")))
242         {
243             logf (LOG_FATAL, "Failed to locate resource %s", nm);
244             return 0;
245         }
246         if ((new->keysize = atoi(r)) <= 0)
247         {
248             logf (LOG_FATAL, "Must specify positive keysize.");
249             return 0;
250         }
251     }
252
253     /* determine repack percent */
254     if (!(r = res_get_def(common_resource, nm = strconcat(name, ".", "repack",
255         0), IS_DEF_REPACK_PERCENT)))
256     {
257         logf (LOG_FATAL, "Failed to locate resource %s", nm);
258         return 0;
259     }
260     new->repack = atoi(r);
261
262     /* determine max keys/blocksize */
263     if (!(r = res_get_def(common_resource, nm = strconcat(name, ".",
264         "maxkeys", 0), "50 640 10000")) || !(num = splitargs(r, pp,
265         IS_MAX_BLOCKTYPES)))
266     {
267         logf (LOG_FATAL, "Failed to locate resource %s", nm);
268         return 0;
269     }
270     if (num < new->num_types -1)
271     {
272         logf (LOG_FATAL, "Not enough elements in %s", nm);
273         return 0;
274     }
275     for (i = 0; i < num; i++)
276     {
277         if ((rs = sscanf(pp[i], "%d", &tmp)) < 1)
278         {
279             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
280             return 0;
281         }
282         new->types[i].max_keys = tmp;
283     }
284
285     /* determine max keys/block */
286     for (i = 0; i < new->num_types; i++)
287     {
288         if (!new->types[i].index)
289         {
290             new->types[i].max_keys_block = (new->types[i].blocksize - 2 *
291                 sizeof(int)) / new->keysize;
292             new->types[i].max_keys_block0 = (new->types[i].blocksize - 3 *
293                 sizeof(int)) / new->keysize;
294         }
295         else
296             new->types[i].max_keys_block = new->types[i].max_keys_block0 /
297                 new->keysize;
298         if (new->types[i].max_keys_block0 < 1)
299         {
300             logf (LOG_FATAL, "Blocksize too small in %s", name);
301             exit(1);
302         }
303     }
304
305     /* determine nice fill rates */
306     if (!(r = res_get_def(common_resource, nm = strconcat(name, ".",
307         "nicefill", 0), "90 90 90 95")) || !(num = splitargs(r, pp,
308         IS_MAX_BLOCKTYPES)))
309     {
310         logf (LOG_FATAL, "Failed to locate resource %s", nm);
311         return 0;
312     }
313     if (num < new->num_types)
314     {
315         logf (LOG_FATAL, "Not enough elements in %s", nm);
316         return 0;
317     }
318     for (i = 0; i < num; i++)
319     {
320         if ((rs = sscanf(pp[i], "%d", &tmp)) < 1)
321         {
322             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
323             return 0;
324         }
325         new->types[i].nice_keys_block = (new->types[i].max_keys_block0 * tmp) /
326             100;
327         if (new->types[i].nice_keys_block < 1)
328                 new->types[i].nice_keys_block = 1;
329     }
330
331     new->cmp = cmp ? cmp : is_default_cmp;
332     return new;
333 }
334
335 /*
336  * Close isam file.
337  */
338 int is_close(ISAM is)
339 {
340     int i;
341     is_type_header th;
342
343     logf (LOG_DEBUG, "is_close()");
344     for (i = 0; i < is->num_types; i++)
345     {
346         if (is->types[i].bf)
347         {
348             if (is->writeflag)
349             {
350                 th.blocksize = is->types[i].blocksize;
351                 th.keysize = is->keysize;
352                 th.freelist = is->types[i].freelist;
353                 th.top = is->types[i].top;
354                 if (is_rb_write(&is->types[i], &th) < 0)
355                 {
356                     logf (LOG_FATAL, "Failed to write headerblock");
357                     exit(1);
358                 }
359             }
360             bf_close(is->types[i].bf);
361         }
362     }
363     xfree(is);
364     if (is->writeflag)
365     {
366         logf(LOG_LOG, "ISAM statistics:");
367         logf(LOG_LOG, "total_merge_operations      %d",
368             statistics.total_merge_operations);
369         logf(LOG_LOG, "total_items                 %d", statistics.total_items);
370         logf(LOG_LOG, "dub_items_removed           %d",
371             statistics.dub_items_removed);
372         logf(LOG_LOG, "new_items                   %d", statistics.new_items);
373         logf(LOG_LOG, "failed_deletes              %d",
374             statistics.failed_deletes);
375         logf(LOG_LOG, "skipped_inserts             %d",
376             statistics.skipped_inserts);
377         logf(LOG_LOG, "delete_insert_noop          %d",
378             statistics.delete_insert_noop);
379         logf(LOG_LOG, "delete_replace              %d",
380             statistics.delete_replace);
381         logf(LOG_LOG, "delete                      %d", statistics.delete);
382         logf(LOG_LOG, "remaps                      %d", statistics.remaps);
383         logf(LOG_LOG, "block_jumps                 %d", statistics.block_jumps);
384         logf(LOG_LOG, "tab_deletes                 %d", statistics.tab_deletes);
385     }
386     return 0;
387 }
388
389 static ISAM_P is_address(int type, int pos)
390 {
391     ISAM_P r;
392
393     r = pos << 2;
394     r |= type;
395     return r;
396 }
397
398 ISAM_P is_merge(ISAM is, ISAM_P pos, int num, char *data)
399 {
400     is_mtable tab;
401     int res;
402     char keybuf[IS_MAX_RECORD];
403     int oldnum, oldtype, i;
404     char operation, *record;
405
406     statistics.total_merge_operations++;
407     statistics.total_items += num;
408     if (!pos)
409         statistics.new_tables++;
410
411     is_m_establish_tab(is, &tab, pos);
412     if (pos)
413         if (is_m_read_full(&tab, tab.data) < 0)
414         {
415             logf (LOG_FATAL, "read_full failed");
416             exit(1);
417         }
418     oldnum = tab.num_records;
419     oldtype = tab.pos_type;
420     while (num)
421     {
422         operation = *(data)++;
423         record = (char*) data;
424         data += is_keysize(is);
425         num--;
426         while (num && !memcmp(record - 1, data, is_keysize(tab.is) + 1))
427         {
428             data += 1 + is_keysize(is);
429             num--;
430             statistics.dub_items_removed++;
431         }
432         if ((res = is_m_seek_record(&tab, record)) > 0)  /* no match */
433         {
434             if (operation == KEYOP_INSERT)
435             {
436                 logf (LOG_DEBUG, "XXInserting new record.");
437                 is_m_write_record(&tab, record);
438                 statistics.new_items++;
439             }
440             else
441             {
442                 logf (LOG_DEBUG, "XXDeletion failed to find match.");
443                 statistics.failed_deletes++;
444             }
445         }
446         else /* match found */
447         {
448             if (operation == KEYOP_INSERT)
449             {
450                 logf (LOG_DEBUG, "XXSkipping insertion - match found.");
451                 statistics.skipped_inserts++;
452                 continue;
453             }
454             else if (operation == KEYOP_DELETE)
455             {
456                 /* try to avoid needlessly moving data */
457                 if (num && *(data) == KEYOP_INSERT)
458                 {
459                     /* next key is identical insert? - NOOP - skip it */
460                     if (!memcmp(record, data + 1, is_keysize(is)))
461                     {
462                         logf (LOG_DEBUG, "XXNoop delete. skipping.");
463                         data += 1 + is_keysize(is);
464                         num--;
465                         while (num && !memcmp(data, data + is_keysize(tab.is) +
466                             1, is_keysize(tab.is) + 1))
467                         {
468                             data += 1 + is_keysize(is);
469                             num--;
470                             statistics.dub_items_removed++;
471                         }
472                         statistics.delete_insert_noop++;
473                         continue;
474                     }
475                     /* else check if next key can fit in this position */
476                     is_m_peek_record(&tab, keybuf);
477                     res = (*is->cmp)(data + 1, keybuf);
478                     if (res < 0)
479                     {
480                         logf (LOG_DEBUG, "XXReplacing record.");
481                         is_m_replace_record(&tab, data + 1);
482                         data += 1 + is_keysize(is);
483                         num--;
484                         while (num && !memcmp(data, data + is_keysize(tab.is) +
485                             1, is_keysize(tab.is) + 1))
486                         {
487                             data += 1 + is_keysize(is);
488                             num--;
489                             statistics.dub_items_removed++;
490                         }
491                         statistics.delete_replace++;
492                         continue;
493                     }
494                 }
495                 logf (LOG_DEBUG, "Deleting record.");
496                 is_m_delete_record(&tab);
497                 statistics.delete++;
498             }
499         }
500     }
501     i = tab.pos_type;
502     while (i < tab.is->num_types - 1 && tab.num_records >
503         tab.is->types[i].max_keys)
504         i++;
505     if (i != tab.pos_type)
506     {
507         /* read remaining blocks */
508         for (; tab.cur_mblock; tab.cur_mblock = tab.cur_mblock->next)
509             if (tab.cur_mblock->state < IS_MBSTATE_CLEAN)
510                 is_m_read_full(&tab, tab.cur_mblock);
511         is_p_unmap(&tab);
512         tab.pos_type = i;
513         if (pos)
514             statistics.block_jumps++;
515     }
516     if (!oldnum || tab.pos_type != oldtype || (abs(oldnum - tab.num_records) *
517         100) / oldnum > tab.is->repack)
518     {
519         is_p_remap(&tab);
520         statistics.remaps++;
521     }
522     else
523         is_p_align(&tab);
524     if (tab.data)
525     {
526         is_p_sync(&tab);
527         pos = is_address(tab.pos_type, tab.data->diskpos);
528     }
529     else
530     {
531         pos = 0;
532         statistics.tab_deletes++;
533     }
534     is_m_release_tab(&tab);
535     return pos;
536 }
537
538 /*
539  * Locate a table of keys in an isam file. The ISPT is an individual
540  * position marker for that table.
541  */
542 ISPT is_position(ISAM is, ISAM_P pos)
543 {
544     ispt_struct *p;
545
546     p = ispt_alloc();
547     is_m_establish_tab(is, &p->tab, pos);
548     return p;
549 }
550
551 /*
552  * Release ISPT.
553  */
554 void is_pt_free(ISPT ip)
555 {
556     is_m_release_tab(&ip->tab);
557     ispt_free(ip);
558 }
559
560 /*
561  * Read a key from a table.
562  */
563 int is_readkey(ISPT ip, void *buf)
564 {
565     return is_m_read_record(&ip->tab, buf, 0);
566 }    
567
568 int is_numkeys(ISPT ip)
569 {
570     return is_m_num_records(&ip->tab);
571 }
572
573 void is_rewind(ISPT ip)
574 {
575     is_m_rewind(&ip->tab);
576 }