Keysize parameter to is_open (if non-zero).
[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.12  1995-09-06 16:11:41  adam
8  * Keysize parameter to is_open (if non-zero).
9  *
10  * Revision 1.11  1995/09/04  12:33:46  adam
11  * Various cleanup. YAZ util used instead.
12  *
13  * Revision 1.10  1994/09/28  16:58:32  quinn
14  * Small mod.
15  *
16  * Revision 1.9  1994/09/28  12:56:15  quinn
17  * Added access functions (ISPT)
18  *
19  * Revision 1.8  1994/09/28  12:32:17  quinn
20  * Trivial
21  *
22  * Revision 1.7  1994/09/28  11:56:25  quinn
23  * Added sort of input to is_merge
24  *
25  * Revision 1.6  1994/09/28  11:29:33  quinn
26  * Added cmp parameter.
27  *
28  * Revision 1.5  1994/09/27  20:03:50  quinn
29  * Seems relatively bug-free.
30  *
31  * Revision 1.4  1994/09/26  17:11:29  quinn
32  * Trivial
33  *
34  * Revision 1.3  1994/09/26  17:06:35  quinn
35  * Back again...
36  *
37  * Revision 1.1  1994/09/12  08:02:13  quinn
38  * Not functional yet
39  *
40  */
41
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <ctype.h>
46
47 #include <alexutil.h>
48 #include <bfile.h>
49 #include <isam.h>
50 #include <common.h>
51 #include "isutil.h"
52 #include "rootblk.h"
53 #include "keyops.h"
54
55 static int (*extcmp)(const void *p1, const void *p2);
56 static ispt_struct *ispt_freelist = 0;
57
58 static ISPT ispt_alloc()
59 {
60     ISPT p;
61
62     if (ispt_freelist)
63     {
64         p = ispt_freelist;
65         ispt_freelist = ispt_freelist->next;
66     }
67     else
68         p = xmalloc(sizeof(ispt_struct));
69     return p;
70 }
71
72 static void ispt_free(ISPT pt)
73 {
74     pt->next = ispt_freelist;
75     ispt_freelist = pt;
76 }
77
78 static int splitargs(const char *s, char *bf[], int max)
79 {
80     int ct = 0;
81     for (;;)
82     {
83         while (*s && isspace(*s))
84             s++;
85         bf[ct] = (char *) s;
86         if (!*s)
87                 return ct;
88         ct++;
89         if (ct > max)
90         {
91             logf (LOG_WARN, "Ignoring extra args to is resource");
92             bf[ct] = '\0';
93             return(ct - 1);
94         }
95         while (*s && !isspace(*s))
96             s++;
97     }
98 }
99
100 /*
101  * Open isam file.
102  * Process resources.
103  */
104 ISAM is_open(const char *name, int (*cmp)(const void *p1, const void *p2),
105     int writeflag, int keysize)
106 {
107     ISAM new;
108     char *nm, *r, *pp[IS_MAX_BLOCKTYPES+1], m[2];
109     int num, size, rs, tmp, i;
110     is_type_header th;
111
112     logf (LOG_DEBUG, "is_open(%s, %s)", name, writeflag ? "RW" : "RDONLY");
113     new = xmalloc(sizeof(*new));
114     new->writeflag = writeflag;
115     for (i = 0; i < IS_MAX_BLOCKTYPES; i++)
116         new->types[i].index = 0;                        /* dummy */
117
118     /* determine number and size of blocktypes */
119     if (!(r = res_get(common_resource, nm = strconcat(name, ".",
120         "blocktypes", 0))) || !(num = splitargs(r, pp, IS_MAX_BLOCKTYPES)))
121     {
122         logf (LOG_FATAL, "Failed to locate resource %s", nm);
123         return 0;
124     }
125     new->num_types = num;
126     for (i = 0; i < num; i++)
127     {
128         if ((rs = sscanf(pp[i], "%d%1[bBkKmM]", &size, m)) < 1)
129         {
130             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
131             return 0;
132         }
133         if (rs == 1)
134                 *m = 'b';
135         switch (*m)
136         {
137                 case 'b': case 'B':
138                     new->types[i].blocksize = size; break;
139                 case 'k': case 'K':
140                     new->types[i].blocksize = size * 1024; break;
141                 case 'm': case 'M':
142                     new->types[i].blocksize = size * 1048576; break;
143                 default:
144                     logf (LOG_FATAL, "Illegal size suffix: %c", *m);
145                     return 0;
146         }
147         new->types[i].dbuf = xmalloc(new->types[i].blocksize);
148         m[0] = 'A' + i;
149         m[1] = '\0';
150         if (!(new->types[i].bf = bf_open(strconcat(name, m, 0), 
151             new->types[i].blocksize, writeflag)))
152         {
153             logf (LOG_FATAL, "bf_open failed");
154             return 0;
155         }
156         if ((rs = is_rb_read(&new->types[i], &th)) > 0)
157         {
158             if (th.blocksize != new->types[i].blocksize)
159             {
160                 logf (LOG_FATAL, "File blocksize mismatch in %s", name);
161                 exit(1);
162             }
163             new->types[i].freelist = th.freelist;
164             new->types[i].top = th.top;
165         }
166         else if (writeflag) /* write dummy superblock to determine top */
167         {
168             if ((rs = is_rb_write(&new->types[i], &th)) <=0)  /* dummy */
169             {
170                 logf (LOG_FATAL, "Failed to write initial superblock.");
171                 exit(1);
172             }
173             new->types[i].freelist = -1;
174             new->types[i].top = rs;
175         }
176         /* ELSE: this is an empty file opened in read-only mode. */
177     }
178     if (keysize > 0)
179         new->keysize = keysize;
180     else
181     {
182         if (!(r = res_get_def(common_resource, nm = strconcat(name, ".",
183                                                               "keysize",
184                                                               0), "4")))
185         {
186             logf (LOG_FATAL, "Failed to locate resource %s", nm);
187             return 0;
188         }
189         if ((new->keysize = atoi(r)) <= 0)
190         {
191             logf (LOG_FATAL, "Must specify positive keysize.");
192             return 0;
193         }
194     }
195
196     /* determine repack percent */
197     if (!(r = res_get_def(common_resource, nm = strconcat(name, ".", "repack",
198         0), IS_DEF_REPACK_PERCENT)))
199     {
200         logf (LOG_FATAL, "Failed to locate resource %s", nm);
201         return 0;
202     }
203     new->repack = atoi(r);
204
205     /* determine max keys/blocksize */
206     if (!(r = res_get(common_resource, nm = strconcat(name, ".",
207         "maxkeys", 0))) || !(num = splitargs(r, pp, IS_MAX_BLOCKTYPES)))
208     {
209         logf (LOG_FATAL, "Failed to locate resource %s", nm);
210         return 0;
211     }
212     if (num < new->num_types -1)
213     {
214         logf (LOG_FATAL, "Not enough elements in %s", nm);
215         return 0;
216     }
217     for (i = 0; i < num; i++)
218     {
219         if ((rs = sscanf(pp[i], "%d", &tmp)) < 1)
220         {
221             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
222             return 0;
223         }
224         new->types[i].max_keys = tmp;
225     }
226
227     /* determine max keys/block */
228     for (i = 0; i < new->num_types; i++)
229     {
230         if (!new->types[i].index)
231         {
232             new->types[i].max_keys_block = (new->types[i].blocksize - 2 *
233                 sizeof(int)) / new->keysize;
234             new->types[i].max_keys_block0 = (new->types[i].blocksize - 3 *
235                 sizeof(int)) / new->keysize;
236         }
237         else
238             new->types[i].max_keys_block = new->types[i].max_keys_block0 /
239                 new->keysize;
240         if (new->types[i].max_keys_block0 < 1)
241         {
242             logf (LOG_FATAL, "Blocksize too small in %s", name);
243             exit(1);
244         }
245     }
246
247     /* determine nice fill rates */
248     if (!(r = res_get(common_resource, nm = strconcat(name, ".",
249         "nicefill", 0))) || !(num = splitargs(r, pp, IS_MAX_BLOCKTYPES)))
250     {
251         logf (LOG_FATAL, "Failed to locate resource %s", nm);
252         return 0;
253     }
254     if (num < new->num_types)
255     {
256         logf (LOG_FATAL, "Not enough elements in %s", nm);
257         return 0;
258     }
259     for (i = 0; i < num; i++)
260     {
261         if ((rs = sscanf(pp[i], "%d", &tmp)) < 1)
262         {
263             logf (LOG_FATAL, "Error in resource %s: %s", r, pp[i]);
264             return 0;
265         }
266         new->types[i].nice_keys_block = (new->types[i].max_keys_block0 * tmp) /
267             100;
268         if (new->types[i].nice_keys_block < 1)
269                 new->types[i].nice_keys_block = 1;
270     }
271
272     new->cmp = cmp ? cmp : is_default_cmp;
273     return new;
274 }
275
276 /*
277  * Close isam file.
278  */
279 int is_close(ISAM is)
280 {
281     int i;
282     is_type_header th;
283
284     logf (LOG_DEBUG, "is_close()");
285     for (i = 0; i < is->num_types; i++)
286     {
287         if (is->types[i].bf)
288         {
289             if (is->writeflag)
290             {
291                 th.blocksize = is->types[i].blocksize;
292                 th.keysize = is->keysize;
293                 th.freelist = is->types[i].freelist;
294                 th.top = is->types[i].top;
295                 if (is_rb_write(&is->types[i], &th) < 0)
296                 {
297                     logf (LOG_FATAL, "Failed to write headerblock");
298                     exit(1);
299                 }
300             }
301             bf_close(is->types[i].bf);
302         }
303     }
304     xfree(is);
305     return 0;
306 }
307
308 static ISAM_P is_address(int type, int pos)
309 {
310     ISAM_P r;
311
312     r = pos << 2;
313     r |= type;
314     return r;
315 }
316
317 int sort_input(const void *p1, const void *p2)
318 {
319     int rs;
320
321     if ((rs = (*extcmp)(((char *)p1) + 1, ((char *)p2) + 1)))
322         return rs;
323     return *((char *)p1) - *((char*)p2);
324 }
325
326 ISAM_P is_merge(ISAM is, ISAM_P pos, int num, char *data)
327 {
328     is_mtable tab;
329     int res;
330     char keybuf[IS_MAX_RECORD];
331     int oldnum, oldtype, i;
332     char operation, *record;
333
334     extcmp = is->cmp;
335     qsort(data, num, is_keysize(is) + 1, sort_input);
336     is_m_establish_tab(is, &tab, pos);
337     if (pos)
338         if (is_m_read_full(&tab, tab.data) < 0)
339         {
340             logf (LOG_FATAL, "read_full failed");
341             exit(1);
342         }
343     oldnum = tab.num_records;
344     oldtype = tab.pos_type;
345     while (num)
346     {
347         operation = *(data)++;
348         record = (char*) data;
349         data += is_keysize(is);
350         num--;
351         while (num && !memcmp(record - 1, data, is_keysize(tab.is) + 1))
352         {
353             data += 1 + is_keysize(is);
354             num--;
355         }
356         if ((res = is_m_seek_record(&tab, record)) > 0)  /* no match */
357         {
358             if (operation == KEYOP_INSERT)
359             {
360                 logf (LOG_DEBUG, "XXInserting new record.");
361                 is_m_write_record(&tab, record);
362             }
363             else
364                 logf (LOG_DEBUG, "XXDeletion failed to find match.");
365         }
366         else /* match found */
367         {
368             if (operation == KEYOP_INSERT)
369             {
370                 logf (LOG_DEBUG, "XXSkipping insertion - match found.");
371                 continue;
372             }
373             else if (operation == KEYOP_DELETE)
374             {
375                 /* try to avoid needlessly moving data */
376                 if (num && *(data) == KEYOP_INSERT)
377                 {
378                     /* next key is identical insert? - NOOP - skip it */
379                     if (!memcmp(record, data + 1, is_keysize(is)))
380                     {
381                         logf (LOG_DEBUG, "XXNoop delete. skipping.");
382                         data += 1 + is_keysize(is);
383                         num--;
384                         continue;
385                     }
386                     /* else check if next key can fit in this position */
387                     is_m_peek_record(&tab, keybuf);
388                     res = (*is->cmp)(data + 1, keybuf);
389                     if (res < 0)
390                     {
391                         logf (LOG_DEBUG, "XXReplacing record.");
392                         is_m_replace_record(&tab, data + 1);
393                         data += 1 + is_keysize(is);
394                         num--;
395                         continue;
396                     }
397                 }
398                 logf (LOG_DEBUG, "Deleting record.");
399                 is_m_delete_record(&tab);
400             }
401         }
402     }
403     i = tab.pos_type;
404     while (i < tab.is->num_types - 1 && tab.num_records >
405         tab.is->types[i].max_keys)
406         i++;
407     if (i != tab.pos_type)
408     {
409         is_p_unmap(&tab);
410         tab.pos_type = i;
411     }
412     if (!oldnum || tab.pos_type != oldtype || (abs(oldnum - tab.num_records) *
413         100) / oldnum > tab.is->repack)
414         is_p_remap(&tab);
415     else
416         is_p_align(&tab);
417     if (tab.data)
418     {
419         is_p_sync(&tab);
420         pos = is_address(tab.pos_type, tab.data->diskpos);
421     }
422     else
423         pos = 0;
424     is_m_release_tab(&tab);
425     return pos;
426 }
427
428 /*
429  * Locate a table of keys in an isam file. The ISPT is an individual
430  * position marker for that table.
431  */
432 ISPT is_position(ISAM is, ISAM_P pos)
433 {
434     ispt_struct *p;
435
436     p = ispt_alloc();
437     is_m_establish_tab(is, &p->tab, pos);
438     return p;
439 }
440
441 /*
442  * Release ISPT.
443  */
444 void is_pt_free(ISPT ip)
445 {
446     is_m_release_tab(&ip->tab);
447     ispt_free(ip);
448 }
449
450 /*
451  * Read a key from a table.
452  */
453 int is_readkey(ISPT ip, void *buf)
454 {
455     return is_m_read_record(&ip->tab, buf);
456 }    
457
458 int is_numkeys(ISPT ip)
459 {
460     return is_m_num_records(&ip->tab);
461 }
462
463 void is_rewind(ISPT ip)
464 {
465     is_m_rewind(&ip->tab);
466 }