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