Happy new year.
[idzebra-moved-to-github.git] / index / kinput.c
1 /* This file is part of the Zebra server.
2    Copyright (C) 1994-2011 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 #include <fcntl.h>
21 #ifdef WIN32
22 #include <io.h>
23 #endif
24 #if HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <assert.h>
31
32 #include "index.h"
33
34 #define KEY_SIZE (1+sizeof(struct it_key))
35 #define INP_NAME_MAX 768
36
37 struct key_file {
38     int   no;            /* file no */
39     off_t offset;        /* file offset */
40     unsigned char *buf;  /* buffer block */
41     size_t buf_size;     /* number of read bytes in block */
42     size_t chunk;        /* number of bytes allocated */
43     size_t buf_ptr;      /* current position in buffer */
44     char *prev_name;     /* last word read */
45     void *decode_handle;
46     off_t length;        /* length of file */
47                          /* handler invoked in each read */
48     void (*readHandler)(struct key_file *keyp, void *rinfo);
49     void *readInfo;
50     Res res;
51 };
52
53
54 #define PR_KEY_LOW 0
55 #define PR_KEY_TOP 0
56
57 #if PR_KEY_LOW || PR_KEY_TOP
58
59 static void pkey(const char *b, int mode)
60 {
61     key_logdump_txt(YLOG_LOG, b, mode ? "i" : "d");
62 }
63 #endif
64
65
66 void getFnameTmp(Res res, char *fname, int no)
67 {
68     const char *pre;
69     
70     pre = res_get_def(res, "keyTmpDir", ".");
71     sprintf(fname, "%s/key%d.tmp", pre, no);
72 }
73
74 void extract_get_fname_tmp(ZebraHandle zh, char *fname, int no)
75 {
76     const char *pre;
77     
78     pre = res_get_def(zh->res, "keyTmpDir", ".");
79     sprintf(fname, "%s/key%d.tmp", pre, no);
80 }
81
82 void key_file_chunk_read(struct key_file *f)
83 {
84     int nr = 0, r = 0, fd;
85     char fname[1024];
86     getFnameTmp(f->res, fname, f->no);
87     fd = open(fname, O_BINARY|O_RDONLY);
88
89     f->buf_ptr = 0;
90     f->buf_size = 0;
91     if (fd == -1)
92     {
93         yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot open %s", fname);
94         return ;
95     }
96     if (!f->length)
97     {
98         if ((f->length = lseek(fd, 0L, SEEK_END)) == (off_t) -1)
99         {
100             yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot seek %s", fname);
101             close(fd);
102             return ;
103         }
104     }
105     if (lseek(fd, f->offset, SEEK_SET) == -1)
106     {
107         yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot seek %s", fname);
108         close(fd);
109         return ;
110     }
111     while (f->chunk - nr > 0)
112     {
113         r = read(fd, f->buf + nr, f->chunk - nr);
114         if (r <= 0)
115             break;
116         nr += r;
117     }
118     if (r == -1)
119     {
120         yaz_log(YLOG_WARN|YLOG_ERRNO, "read of %s", fname);
121         close(fd);
122         return;
123     }
124     f->buf_size = nr;
125     if (f->readHandler)
126         (*f->readHandler)(f, f->readInfo);
127     close(fd);
128 }
129
130 void key_file_destroy(struct key_file *f)
131 {
132     iscz1_stop(f->decode_handle);
133     xfree(f->buf);
134     xfree(f->prev_name);
135     xfree(f);
136 }
137
138 struct key_file *key_file_init(int no, int chunk, Res res)
139 {
140     struct key_file *f;
141
142     f = (struct key_file *) xmalloc(sizeof(*f));
143     f->res = res;
144     f->decode_handle = iscz1_start();
145     f->no = no;
146     f->chunk = chunk;
147     f->offset = 0;
148     f->length = 0;
149     f->readHandler = NULL;
150     f->buf = (unsigned char *) xmalloc(f->chunk);
151     f->prev_name = (char *) xmalloc(INP_NAME_MAX);
152     *f->prev_name = '\0';
153     key_file_chunk_read(f);
154     return f;
155 }
156
157 int key_file_getc(struct key_file *f)
158 {
159     if (f->buf_ptr < f->buf_size)
160         return f->buf[(f->buf_ptr)++];
161     if (f->buf_size < f->chunk)
162         return EOF;
163     f->offset += f->buf_size;
164     key_file_chunk_read(f);
165     if (f->buf_ptr < f->buf_size)
166         return f->buf[(f->buf_ptr)++];
167     else
168         return EOF;
169 }
170
171 int key_file_read(struct key_file *f, char *key)
172 {
173     int i, c;
174     char srcbuf[128];
175     const char *src = srcbuf;
176     char *dst;
177     int j;
178
179     c = key_file_getc(f);
180     if (c == 0)
181     {
182         strcpy(key, f->prev_name);
183         i = 1+strlen(key);
184     }
185     else if (c == EOF)
186         return 0;
187     else
188     {
189         i = 0;
190         key[i++] = c;
191         while ((c = key_file_getc(f)))
192         {
193             if (i < INP_NAME_MAX-2)
194                 key[i++] = c;
195         }
196         key[i++] = '\0';
197         strcpy(f->prev_name, key);
198         iscz1_reset(f->decode_handle);
199     }
200     c = key_file_getc(f); /* length +  insert/delete combined */
201     key[i++] = c & 128;
202     c = c & 127;
203     for (j = 0; j < c; j++)
204         srcbuf[j] = key_file_getc(f);
205     dst = key + i;
206     iscz1_decode(f->decode_handle, &dst, &src);
207
208 #if 0
209     /* debugging */
210     if (1)
211     {
212         struct it_key k;
213         memcpy(&k, key+i, sizeof(k));
214         if (!k.mem[1])
215             yaz_log(YLOG_LOG, "00 KEY");
216     }
217 #endif
218     return i + sizeof(struct it_key);
219 }
220
221 struct heap_info {
222     struct {
223         struct key_file **file;
224         char   **buf;
225     } info;
226     int    heapnum;
227     int    *ptr;
228     int    (*cmp)(const void *p1, const void *p2);
229     struct zebra_register *reg;
230     ZebraHandle zh;
231     zint no_diffs;
232     zint no_updates;
233     zint no_deletions;
234     zint no_insertions;
235     zint no_iterations;
236 };
237
238 static struct heap_info *key_heap_malloc(void)
239 {  /* malloc and clear it */
240     struct heap_info *hi;
241     hi = (struct heap_info *) xmalloc(sizeof(*hi));
242     hi->info.file = 0;
243     hi->info.buf = 0;
244     hi->heapnum = 0;
245     hi->ptr = 0;
246     hi->no_diffs = 0;
247     hi->no_diffs = 0;
248     hi->no_updates = 0;
249     hi->no_deletions = 0;
250     hi->no_insertions = 0;
251     hi->no_iterations = 0;
252     return hi;
253 }
254
255 struct heap_info *key_heap_init_file(ZebraHandle zh,
256                                      int nkeys,
257                                      int (*cmp)(const void *p1, const void *p2))
258 {
259     struct heap_info *hi;
260     int i;
261
262     hi = key_heap_malloc();
263     hi->zh = zh;
264     hi->info.file = (struct key_file **)
265         xmalloc(sizeof(*hi->info.file) * (1+nkeys));
266     hi->info.buf = (char **) xmalloc(sizeof(*hi->info.buf) * (1+nkeys));
267     hi->ptr = (int *) xmalloc(sizeof(*hi->ptr) * (1+nkeys));
268     hi->cmp = cmp;
269     for (i = 0; i<= nkeys; i++)
270     {
271         hi->ptr[i] = i;
272         hi->info.buf[i] = (char *) xmalloc(INP_NAME_MAX);
273     }
274     return hi;
275 }
276
277 void key_heap_destroy(struct heap_info *hi, int nkeys)
278 {
279     int i;
280     for (i = 0; i<=nkeys; i++)
281         xfree(hi->info.buf[i]);
282     xfree(hi->info.buf);
283     xfree(hi->ptr);
284     xfree(hi->info.file);
285     xfree(hi);
286 }
287
288 static void key_heap_swap(struct heap_info *hi, int i1, int i2)
289 {
290     int swap;
291
292     swap = hi->ptr[i1];
293     hi->ptr[i1] = hi->ptr[i2];
294     hi->ptr[i2] = swap;
295 }
296
297
298 static void key_heap_delete(struct heap_info *hi)
299 {
300     int cur = 1, child = 2;
301
302     assert(hi->heapnum > 0);
303
304     key_heap_swap(hi, 1, hi->heapnum);
305     hi->heapnum--;
306     while (child <= hi->heapnum) {
307         if (child < hi->heapnum &&
308             (*hi->cmp)(&hi->info.buf[hi->ptr[child]],
309                        &hi->info.buf[hi->ptr[child+1]]) > 0)
310             child++;
311         if ((*hi->cmp)(&hi->info.buf[hi->ptr[cur]],
312                        &hi->info.buf[hi->ptr[child]]) > 0)
313         {            
314             key_heap_swap(hi, cur, child);
315             cur = child;
316             child = 2*cur;
317         }
318         else
319             break;
320     }
321 }
322
323 static void key_heap_insert(struct heap_info *hi, const char *buf, int nbytes,
324                              struct key_file *kf)
325 {
326     int cur, parent;
327
328     cur = ++(hi->heapnum);
329     memcpy(hi->info.buf[hi->ptr[cur]], buf, nbytes);
330     hi->info.file[hi->ptr[cur]] = kf;
331
332     parent = cur/2;
333     while (parent && (*hi->cmp)(&hi->info.buf[hi->ptr[parent]],
334                                 &hi->info.buf[hi->ptr[cur]]) > 0)
335     {
336         key_heap_swap(hi, cur, parent);
337         cur = parent;
338         parent = cur/2;
339     }
340 }
341
342 static int heap_read_one(struct heap_info *hi, char *name, char *key)
343 {
344     int n, r;
345     char rbuf[INP_NAME_MAX];
346     struct key_file *kf;
347
348     if (!hi->heapnum)
349         return 0;
350     n = hi->ptr[1];
351     strcpy(name, hi->info.buf[n]);
352     kf = hi->info.file[n];
353     r = strlen(name);
354     memcpy(key, hi->info.buf[n] + r+1, KEY_SIZE);
355     key_heap_delete(hi);
356     if ((r = key_file_read(kf, rbuf)))
357         key_heap_insert(hi, rbuf, r, kf);
358     hi->no_iterations++;
359     return 1;
360 }
361
362
363 /* for debugging only */
364 void zebra_log_dict_entry(ZebraHandle zh, const char *s)
365 {
366     char dst[INP_NAME_MAX+1];
367     int ord;
368     int len = key_SU_decode(&ord, (const unsigned char *) s);
369     const char *index_type;
370
371     if (!zh)
372         yaz_log(YLOG_LOG, "ord=%d", ord);
373     else
374     {
375         const char *string_index;
376         const char *db = 0;
377         zebraExplain_lookup_ord(zh->reg->zei,
378                                 ord, &index_type, &db, &string_index);
379
380         zebra_term_untrans(zh, index_type, dst, s + len);
381
382         yaz_log(YLOG_LOG, "ord=%d index_type=%s index=%s term=%s",
383                 ord, index_type, string_index, dst);
384     }
385 }
386
387 struct heap_cread_info {
388     char prev_name[INP_NAME_MAX];
389     char cur_name[INP_NAME_MAX];
390     char *key;
391     char *key_1, *key_2;
392     int mode_1, mode_2;
393     int sz_1, sz_2;
394     struct heap_info *hi;
395     int first_in_list;
396     int more;
397     int ret;
398     int look_level;
399 };
400
401 static int heap_cread_item(void *vp, char **dst, int *insertMode);
402
403 int heap_cread_item2(void *vp, char **dst, int *insertMode)
404 {
405     struct heap_cread_info *p = (struct heap_cread_info *) vp;
406     int level = 0;
407
408     if (p->look_level)
409     {
410         if (p->look_level > 0)
411         {
412             *insertMode = 1;
413             p->look_level--;
414         }
415         else
416         {
417             *insertMode = 0;
418             p->look_level++;
419         }
420         memcpy(*dst, p->key_1, p->sz_1);
421 #if PR_KEY_TOP
422         yaz_log(YLOG_LOG, "DUP level=%d", p->look_level);
423         pkey(*dst, *insertMode);
424 #endif
425         (*dst) += p->sz_1;
426         return 1;
427     }
428     if (p->ret == 0)    /* lookahead was 0?. Return that in read next round */
429     {
430         p->ret = -1;
431         return 0;
432     }
433     else if (p->ret == -1) /* Must read new item ? */
434     {
435         char *dst_1 = p->key_1;
436         p->ret = heap_cread_item(vp, &dst_1, &p->mode_1);
437         p->sz_1 = dst_1 - p->key_1;
438     }
439     else
440     {        /* lookahead in 2 . Now in 1. */
441         p->sz_1 = p->sz_2;
442         p->mode_1 = p->mode_2;
443         memcpy(p->key_1, p->key_2, p->sz_2);
444     }
445     if (p->mode_1)
446         level = 1;     /* insert */
447     else
448         level = -1;    /* delete */
449     while(1)
450     {
451         char *dst_2 = p->key_2;
452         p->ret = heap_cread_item(vp, &dst_2, &p->mode_2);
453         if (!p->ret)
454         {
455             if (level)
456                 break;
457             p->ret = -1;
458             return 0;
459         }
460         p->sz_2 = dst_2 - p->key_2;
461
462         if (key_compare(p->key_1, p->key_2) == 0)
463         {
464             if (p->mode_2) /* adjust level according to deletes/inserts */
465                 level++;
466             else
467                 level--;
468         }
469         else
470         {
471             if (level)
472                 break;
473             /* all the same. new round .. */
474             p->sz_1 = p->sz_2;
475             p->mode_1 = p->mode_2;
476             memcpy(p->key_1, p->key_2, p->sz_1);
477             if (p->mode_1)
478                 level = 1;     /* insert */
479             else
480                 level = -1;    /* delete */
481         }
482     }
483     /* outcome is insert (1) or delete (0) depending on final level */
484     if (level > 0)
485     {
486         *insertMode = 1;
487         level--;
488     }
489     else
490     {
491         *insertMode = 0;
492         level++;
493     }
494     p->look_level = level;
495     memcpy(*dst, p->key_1, p->sz_1);
496 #if PR_KEY_TOP
497     yaz_log(YLOG_LOG, "TOP");
498     pkey(*dst, *insertMode);
499 #endif
500     (*dst) += p->sz_1;
501     return 1;
502 }
503       
504 int heap_cread_item(void *vp, char **dst, int *insertMode)
505 {
506     struct heap_cread_info *p = (struct heap_cread_info *) vp;
507     struct heap_info *hi = p->hi;
508
509     if (p->first_in_list)
510     {
511         *insertMode = p->key[0];
512         memcpy(*dst, p->key+1, sizeof(struct it_key));
513 #if PR_KEY_LOW
514         zebra_log_dict_entry(hi->zh, p->cur_name);
515         pkey(*dst, *insertMode);
516 #endif
517         (*dst) += sizeof(struct it_key);
518         p->first_in_list = 0;
519         return 1;
520     }
521     strcpy(p->prev_name, p->cur_name);
522     if (!(p->more = heap_read_one(hi, p->cur_name, p->key)))
523         return 0;
524     if (*p->cur_name && strcmp(p->cur_name, p->prev_name))
525     {
526         p->first_in_list = 1;
527         return 0;
528     }
529     *insertMode = p->key[0];
530     memcpy(*dst, p->key+1, sizeof(struct it_key));
531 #if PR_KEY_LOW
532     zebra_log_dict_entry(hi->zh, p->cur_name);
533     pkey(*dst, *insertMode);
534 #endif
535     (*dst) += sizeof(struct it_key);
536     return 1;
537 }
538
539 int heap_inpc(struct heap_cread_info *hci, struct heap_info *hi)
540 {
541     ISAMC_I *isamc_i = (ISAMC_I *) xmalloc(sizeof(*isamc_i));
542
543     isamc_i->clientData = hci;
544     isamc_i->read_item = heap_cread_item2;
545
546     while (hci->more)
547     {
548         char this_name[INP_NAME_MAX];
549         ISAM_P isamc_p, isamc_p2;
550         char *dict_info;
551
552         strcpy(this_name, hci->cur_name);
553         assert(hci->cur_name[0]);
554         hi->no_diffs++;
555         if ((dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
556         {
557             memcpy(&isamc_p, dict_info+1, sizeof(ISAM_P));
558             isamc_p2 = isamc_p;
559             isamc_merge(hi->reg->isamc, &isamc_p2, isamc_i);
560             if (!isamc_p2)
561             {
562                 hi->no_deletions++;
563                 if (!dict_delete(hi->reg->dict, this_name))
564                     abort();
565             }
566             else 
567             {
568                 hi->no_updates++;
569                 if (isamc_p2 != isamc_p)
570                     dict_insert(hi->reg->dict, this_name,
571                                  sizeof(ISAM_P), &isamc_p2);
572             }
573         } 
574         else
575         {
576             isamc_p = 0;
577             isamc_merge(hi->reg->isamc, &isamc_p, isamc_i);
578             hi->no_insertions++;
579             if (isamc_p)
580                 dict_insert(hi->reg->dict, this_name,
581                              sizeof(ISAM_P), &isamc_p);
582         }
583     }
584     xfree(isamc_i);
585     return 0;
586
587
588 int heap_inpb(struct heap_cread_info *hci, struct heap_info *hi)
589 {
590     ISAMC_I *isamc_i = (ISAMC_I *) xmalloc(sizeof(*isamc_i));
591
592     isamc_i->clientData = hci;
593     isamc_i->read_item = heap_cread_item2;
594
595     while (hci->more)
596     {
597         char this_name[INP_NAME_MAX];
598         ISAM_P isamc_p, isamc_p2;
599         char *dict_info;
600
601         strcpy(this_name, hci->cur_name);
602         assert(hci->cur_name[0]);
603         hi->no_diffs++;
604
605 #if 0
606         assert(hi->zh);
607         zebra_log_dict_entry(hi->zh, hci->cur_name);
608 #endif
609         if ((dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
610         {
611             memcpy(&isamc_p, dict_info+1, sizeof(ISAM_P));
612             isamc_p2 = isamc_p;
613             isamb_merge(hi->reg->isamb, &isamc_p2, isamc_i);
614             if (!isamc_p2)
615             {
616                 hi->no_deletions++;
617                 if (!dict_delete(hi->reg->dict, this_name))
618                     abort();
619             }
620             else 
621             {
622                 hi->no_updates++;
623                 if (isamc_p2 != isamc_p)
624                     dict_insert(hi->reg->dict, this_name,
625                                  sizeof(ISAM_P), &isamc_p2);
626             }
627         } 
628         else
629         {
630             isamc_p = 0;
631             isamb_merge(hi->reg->isamb, &isamc_p, isamc_i);
632             hi->no_insertions++;
633             if (isamc_p)
634                 dict_insert(hi->reg->dict, this_name,
635                              sizeof(ISAM_P), &isamc_p);
636         }
637     }
638     xfree(isamc_i);
639     return 0;
640
641
642 int heap_inps(struct heap_cread_info *hci, struct heap_info *hi)
643 {
644     ISAMS_I isams_i = (ISAMS_I) xmalloc(sizeof(*isams_i));
645
646     isams_i->clientData = hci;
647     isams_i->read_item = heap_cread_item;
648
649     while (hci->more)
650     {
651         char this_name[INP_NAME_MAX];
652         ISAM_P isams_p;
653         char *dict_info;
654
655         strcpy(this_name, hci->cur_name);
656         assert(hci->cur_name[0]);
657         hi->no_diffs++;
658         if (!(dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
659         {
660             isams_p = isams_merge(hi->reg->isams, isams_i);
661             hi->no_insertions++;
662             dict_insert(hi->reg->dict, this_name, sizeof(ISAM_P), &isams_p);
663         }
664         else
665         {
666             yaz_log(YLOG_FATAL, "isams doesn't support this kind of update");
667             break;
668         }
669     }
670     xfree(isams_i);
671     return 0;
672
673
674 struct progressInfo {
675     time_t   startTime;
676     time_t   lastTime;
677     off_t    totalBytes;
678     off_t    totalOffset;
679 };
680
681 void progressFunc(struct key_file *keyp, void *info)
682 {
683     struct progressInfo *p = (struct progressInfo *) info;
684     time_t now, remaining;
685
686     if (keyp->buf_size <= 0 || p->totalBytes <= 0)
687         return ;
688     time (&now);
689
690     if (now >= p->lastTime+10)
691     {
692         p->lastTime = now;
693         remaining = (time_t) ((now - p->startTime)*
694             ((double) p->totalBytes/p->totalOffset - 1.0));
695         if (remaining <= 130)
696             yaz_log(YLOG_LOG, "Merge %2.1f%% completed; %ld seconds remaining",
697                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining);
698         else
699             yaz_log(YLOG_LOG, "Merge %2.1f%% completed; %ld minutes remaining",
700                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining/60);
701     }
702     p->totalOffset += keyp->buf_size;
703 }
704
705 #ifndef R_OK
706 #define R_OK 4
707 #endif
708
709 void zebra_index_merge(ZebraHandle zh)
710 {
711     struct key_file **kf = 0;
712     char rbuf[1024];
713     int i, r;
714     struct heap_info *hi;
715     struct progressInfo progressInfo;
716     int nkeys = key_block_get_no_files(zh->reg->key_block);
717
718     if (nkeys == 0)
719         return;
720     
721     if (nkeys < 0)
722     {
723         char fname[1024];
724         nkeys = 0;
725         while (1)
726         {
727             extract_get_fname_tmp (zh, fname, nkeys+1);
728             if (access(fname, R_OK) == -1)
729                 break;
730             nkeys++;
731         }
732         if (!nkeys)
733             return ;
734     }
735     kf = (struct key_file **) xmalloc((1+nkeys) * sizeof(*kf));
736     progressInfo.totalBytes = 0;
737     progressInfo.totalOffset = 0;
738     time(&progressInfo.startTime);
739     time(&progressInfo.lastTime);
740     for (i = 1; i<=nkeys; i++)
741     {
742         kf[i] = key_file_init(i, 8192, zh->res);
743         kf[i]->readHandler = progressFunc;
744         kf[i]->readInfo = &progressInfo;
745         progressInfo.totalBytes += kf[i]->length;
746         progressInfo.totalOffset += kf[i]->buf_size;
747     }
748     hi = key_heap_init_file(zh, nkeys, key_qsort_compare);
749     hi->reg = zh->reg;
750     
751     for (i = 1; i<=nkeys; i++)
752         if ((r = key_file_read(kf[i], rbuf)))
753             key_heap_insert(hi, rbuf, r, kf[i]);
754
755     if (1)
756     {
757         struct heap_cread_info hci;
758         
759         hci.key = (char *) xmalloc(KEY_SIZE);
760         hci.key_1 = (char *) xmalloc(KEY_SIZE);
761         hci.key_2 = (char *) xmalloc(KEY_SIZE);
762         hci.ret = -1;
763         hci.first_in_list = 1;
764         hci.hi = hi;
765         hci.look_level = 0;
766         hci.more = heap_read_one(hi, hci.cur_name, hci.key);    
767         
768         if (zh->reg->isams)
769             heap_inps(&hci, hi);
770         if (zh->reg->isamc)
771             heap_inpc(&hci, hi);
772         if (zh->reg->isamb)
773             heap_inpb(&hci, hi);
774         
775         xfree(hci.key);
776         xfree(hci.key_1);
777         xfree(hci.key_2);
778     }
779         
780     for (i = 1; i<=nkeys; i++)
781     {
782         extract_get_fname_tmp (zh, rbuf, i);
783         unlink(rbuf);
784     }
785     for (i = 1; i<=nkeys; i++)
786         key_file_destroy(kf[i]);
787     xfree(kf);
788     if (hi->no_iterations)
789     { /* do not log if nothing happened */
790         yaz_log(YLOG_LOG, "Iterations: isam/dict " 
791                 ZINT_FORMAT "/" ZINT_FORMAT,
792                 hi->no_iterations, hi->no_diffs);
793         yaz_log(YLOG_LOG, "Dict: inserts/updates/deletions: "
794                 ZINT_FORMAT "/" ZINT_FORMAT "/" ZINT_FORMAT,
795                 hi->no_insertions, hi->no_updates, hi->no_deletions);
796     }
797     key_block_destroy(&zh->reg->key_block);
798     key_heap_destroy(hi, nkeys);
799 }
800 /*
801  * Local variables:
802  * c-basic-offset: 4
803  * c-file-style: "Stroustrup"
804  * indent-tabs-mode: nil
805  * End:
806  * vim: shiftwidth=4 tabstop=8 expandtab
807  */
808