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