ea73c0de6d2fc589bde26708215a647c331b5c2e
[idzebra-moved-to-github.git] / index / kinput.c
1 /* $Id: kinput.c,v 1.89 2008-01-09 22:18:04 adam Exp $
2    Copyright (C) 1995-2007
3    Index Data ApS
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 */
22  
23 #include <fcntl.h>
24 #ifdef WIN32
25 #include <io.h>
26 #endif
27 #if HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdio.h>
33 #include <assert.h>
34
35 #include "index.h"
36
37 #define KEY_SIZE (1+sizeof(struct it_key))
38 #define INP_NAME_MAX 768
39
40 struct key_file {
41     int   no;            /* file no */
42     off_t offset;        /* file offset */
43     unsigned char *buf;  /* buffer block */
44     size_t buf_size;     /* number of read bytes in block */
45     size_t chunk;        /* number of bytes allocated */
46     size_t buf_ptr;      /* current position in buffer */
47     char *prev_name;     /* last word read */
48     void *decode_handle;
49     off_t length;        /* length of file */
50                          /* handler invoked in each read */
51     void (*readHandler)(struct key_file *keyp, void *rinfo);
52     void *readInfo;
53     Res res;
54 };
55
56 #if 0
57 static void pkey(const char *b, int mode)
58 {
59     key_logdump_txt(YLOG_LOG, b, mode ? "i" : "d");
60 }
61 #endif
62
63
64 void getFnameTmp(Res res, char *fname, int no)
65 {
66     const char *pre;
67     
68     pre = res_get_def(res, "keyTmpDir", ".");
69     sprintf(fname, "%s/key%d.tmp", pre, no);
70 }
71
72 void extract_get_fname_tmp(ZebraHandle zh, char *fname, int no)
73 {
74     const char *pre;
75     
76     pre = res_get_def(zh->res, "keyTmpDir", ".");
77     sprintf(fname, "%s/key%d.tmp", pre, no);
78 }
79
80 void key_file_chunk_read(struct key_file *f)
81 {
82     int nr = 0, r = 0, fd;
83     char fname[1024];
84     getFnameTmp(f->res, fname, f->no);
85     fd = open(fname, O_BINARY|O_RDONLY);
86
87     f->buf_ptr = 0;
88     f->buf_size = 0;
89     if (fd == -1)
90     {
91         yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot open %s", fname);
92         return ;
93     }
94     if (!f->length)
95     {
96         if ((f->length = lseek(fd, 0L, SEEK_END)) == (off_t) -1)
97         {
98             yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot seek %s", fname);
99             close(fd);
100             return ;
101         }
102     }
103     if (lseek(fd, f->offset, SEEK_SET) == -1)
104     {
105         yaz_log(YLOG_WARN|YLOG_ERRNO, "cannot seek %s", fname);
106         close(fd);
107         return ;
108     }
109     while (f->chunk - nr > 0)
110     {
111         r = read(fd, f->buf + nr, f->chunk - nr);
112         if (r <= 0)
113             break;
114         nr += r;
115     }
116     if (r == -1)
117     {
118         yaz_log(YLOG_WARN|YLOG_ERRNO, "read of %s", fname);
119         close(fd);
120         return;
121     }
122     f->buf_size = nr;
123     if (f->readHandler)
124         (*f->readHandler)(f, f->readInfo);
125     close(fd);
126 }
127
128 void key_file_destroy(struct key_file *f)
129 {
130     iscz1_stop(f->decode_handle);
131     xfree(f->buf);
132     xfree(f->prev_name);
133     xfree(f);
134 }
135
136 struct key_file *key_file_init(int no, int chunk, Res res)
137 {
138     struct key_file *f;
139
140     f = (struct key_file *) xmalloc(sizeof(*f));
141     f->res = res;
142     f->decode_handle = iscz1_start();
143     f->no = no;
144     f->chunk = chunk;
145     f->offset = 0;
146     f->length = 0;
147     f->readHandler = NULL;
148     f->buf = (unsigned char *) xmalloc(f->chunk);
149     f->prev_name = (char *) xmalloc(INP_NAME_MAX);
150     *f->prev_name = '\0';
151     key_file_chunk_read(f);
152     return f;
153 }
154
155 int key_file_getc(struct key_file *f)
156 {
157     if (f->buf_ptr < f->buf_size)
158         return f->buf[(f->buf_ptr)++];
159     if (f->buf_size < f->chunk)
160         return EOF;
161     f->offset += f->buf_size;
162     key_file_chunk_read(f);
163     if (f->buf_ptr < f->buf_size)
164         return f->buf[(f->buf_ptr)++];
165     else
166         return EOF;
167 }
168
169 int key_file_read(struct key_file *f, char *key)
170 {
171     int i, c;
172     char srcbuf[128];
173     const char *src = srcbuf;
174     char *dst;
175     int j;
176
177     c = key_file_getc(f);
178     if (c == 0)
179     {
180         strcpy(key, f->prev_name);
181         i = 1+strlen(key);
182     }
183     else if (c == EOF)
184         return 0;
185     else
186     {
187         i = 0;
188         key[i++] = c;
189         while ((c = key_file_getc(f)))
190         {
191             if (i < IT_MAX_WORD)
192                 key[i++] = c;
193         }
194         key[i++] = '\0';
195         strcpy(f->prev_name, key);
196         iscz1_reset(f->decode_handle);
197     }
198     c = key_file_getc(f); /* length +  insert/delete combined */
199     key[i++] = c & 128;
200     c = c & 127;
201     for (j = 0; j < c; j++)
202         srcbuf[j] = key_file_getc(f);
203     dst = key + i;
204     iscz1_decode(f->decode_handle, &dst, &src);
205
206 #if 0
207     /* debugging */
208     if (1)
209     {
210         struct it_key k;
211         memcpy(&k, key+i, sizeof(k));
212         if (!k.mem[1])
213             yaz_log(YLOG_LOG, "00 KEY");
214     }
215 #endif
216     return i + sizeof(struct it_key);
217 }
218
219 struct heap_info {
220     struct {
221         struct key_file **file;
222         char   **buf;
223     } info;
224     int    heapnum;
225     int    *ptr;
226     int    (*cmp)(const void *p1, const void *p2);
227     struct zebra_register *reg;
228     ZebraHandle zh;
229     zint no_diffs;
230     zint no_updates;
231     zint no_deletions;
232     zint no_insertions;
233     zint no_iterations;
234 };
235
236 static struct heap_info *key_heap_malloc(void)
237 {  /* malloc and clear it */
238     struct heap_info *hi;
239     hi = (struct heap_info *) xmalloc(sizeof(*hi));
240     hi->info.file = 0;
241     hi->info.buf = 0;
242     hi->heapnum = 0;
243     hi->ptr = 0;
244     hi->no_diffs = 0;
245     hi->no_diffs = 0;
246     hi->no_updates = 0;
247     hi->no_deletions = 0;
248     hi->no_insertions = 0;
249     hi->no_iterations = 0;
250     return hi;
251 }
252
253 struct heap_info *key_heap_init_file(ZebraHandle zh,
254                                      int nkeys,
255                                      int (*cmp)(const void *p1, const void *p2))
256 {
257     struct heap_info *hi;
258     int i;
259
260     hi = key_heap_malloc();
261     hi->zh = zh;
262     hi->info.file = (struct key_file **)
263         xmalloc(sizeof(*hi->info.file) * (1+nkeys));
264     hi->info.buf = (char **) xmalloc(sizeof(*hi->info.buf) * (1+nkeys));
265     hi->ptr = (int *) xmalloc(sizeof(*hi->ptr) * (1+nkeys));
266     hi->cmp = cmp;
267     for (i = 0; i<= nkeys; i++)
268     {
269         hi->ptr[i] = i;
270         hi->info.buf[i] = (char *) xmalloc(INP_NAME_MAX);
271     }
272     return hi;
273 }
274
275 void key_heap_destroy(struct heap_info *hi, int nkeys)
276 {
277     int i;
278     for (i = 0; i<=nkeys; i++)
279         xfree(hi->info.buf[i]);
280     xfree(hi->info.buf);
281     xfree(hi->ptr);
282     xfree(hi->info.file);
283     xfree(hi);
284 }
285
286 static void key_heap_swap(struct heap_info *hi, int i1, int i2)
287 {
288     int swap;
289
290     swap = hi->ptr[i1];
291     hi->ptr[i1] = hi->ptr[i2];
292     hi->ptr[i2] = swap;
293 }
294
295
296 static void key_heap_delete(struct heap_info *hi)
297 {
298     int cur = 1, child = 2;
299
300     assert(hi->heapnum > 0);
301
302     key_heap_swap(hi, 1, hi->heapnum);
303     hi->heapnum--;
304     while (child <= hi->heapnum) {
305         if (child < hi->heapnum &&
306             (*hi->cmp)(&hi->info.buf[hi->ptr[child]],
307                        &hi->info.buf[hi->ptr[child+1]]) > 0)
308             child++;
309         if ((*hi->cmp)(&hi->info.buf[hi->ptr[cur]],
310                        &hi->info.buf[hi->ptr[child]]) > 0)
311         {            
312             key_heap_swap(hi, cur, child);
313             cur = child;
314             child = 2*cur;
315         }
316         else
317             break;
318     }
319 }
320
321 static void key_heap_insert(struct heap_info *hi, const char *buf, int nbytes,
322                              struct key_file *kf)
323 {
324     int cur, parent;
325
326     cur = ++(hi->heapnum);
327     memcpy(hi->info.buf[hi->ptr[cur]], buf, nbytes);
328     hi->info.file[hi->ptr[cur]] = kf;
329
330     parent = cur/2;
331     while (parent && (*hi->cmp)(&hi->info.buf[hi->ptr[parent]],
332                                 &hi->info.buf[hi->ptr[cur]]) > 0)
333     {
334         key_heap_swap(hi, cur, parent);
335         cur = parent;
336         parent = cur/2;
337     }
338 }
339
340 static int heap_read_one(struct heap_info *hi, char *name, char *key)
341 {
342     int n, r;
343     char rbuf[INP_NAME_MAX];
344     struct key_file *kf;
345
346     if (!hi->heapnum)
347         return 0;
348     n = hi->ptr[1];
349     strcpy(name, hi->info.buf[n]);
350     kf = hi->info.file[n];
351     r = strlen(name);
352     memcpy(key, hi->info.buf[n] + r+1, KEY_SIZE);
353     key_heap_delete(hi);
354     if ((r = key_file_read(kf, rbuf)))
355         key_heap_insert(hi, rbuf, r, kf);
356     hi->no_iterations++;
357     return 1;
358 }
359
360 #define PR_KEY_LOW 0
361 #define PR_KEY_TOP 0
362
363 /* for debugging only */
364 void zebra_log_dict_entry(ZebraHandle zh, const char *s)
365 {
366     char dst[IT_MAX_WORD+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 0
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 0
497     pkey(*dst, *insertMode);
498 #endif
499     (*dst) += p->sz_1;
500     return 1;
501 }
502       
503 int heap_cread_item(void *vp, char **dst, int *insertMode)
504 {
505     struct heap_cread_info *p = (struct heap_cread_info *) vp;
506     struct heap_info *hi = p->hi;
507
508     if (p->first_in_list)
509     {
510         *insertMode = p->key[0];
511         memcpy(*dst, p->key+1, sizeof(struct it_key));
512 #if PR_KEY_LOW
513         zebra_log_dict_entry(hi->zh, p->cur_name);
514         pkey(*dst, *insertMode);
515 #endif
516         (*dst) += sizeof(struct it_key);
517         p->first_in_list = 0;
518         return 1;
519     }
520     strcpy(p->prev_name, p->cur_name);
521     if (!(p->more = heap_read_one(hi, p->cur_name, p->key)))
522         return 0;
523     if (*p->cur_name && strcmp(p->cur_name, p->prev_name))
524     {
525         p->first_in_list = 1;
526         return 0;
527     }
528     *insertMode = p->key[0];
529     memcpy(*dst, p->key+1, sizeof(struct it_key));
530 #if PR_KEY_LOW
531     zebra_log_dict_entry(hi->zh, p->cur_name);
532     pkey(*dst, *insertMode);
533 #endif
534     (*dst) += sizeof(struct it_key);
535     return 1;
536 }
537
538 int heap_inpc(struct heap_cread_info *hci, struct heap_info *hi)
539 {
540     ISAMC_I *isamc_i = (ISAMC_I *) xmalloc(sizeof(*isamc_i));
541
542     isamc_i->clientData = hci;
543     isamc_i->read_item = heap_cread_item2;
544
545     while (hci->more)
546     {
547         char this_name[INP_NAME_MAX];
548         ISAM_P isamc_p, isamc_p2;
549         char *dict_info;
550
551         strcpy(this_name, hci->cur_name);
552         assert(hci->cur_name[0]);
553         hi->no_diffs++;
554         if ((dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
555         {
556             memcpy(&isamc_p, dict_info+1, sizeof(ISAM_P));
557             isamc_p2 = isamc_p;
558             isamc_merge(hi->reg->isamc, &isamc_p2, isamc_i);
559             if (!isamc_p2)
560             {
561                 hi->no_deletions++;
562                 if (!dict_delete(hi->reg->dict, this_name))
563                     abort();
564             }
565             else 
566             {
567                 hi->no_updates++;
568                 if (isamc_p2 != isamc_p)
569                     dict_insert(hi->reg->dict, this_name,
570                                  sizeof(ISAM_P), &isamc_p2);
571             }
572         } 
573         else
574         {
575             isamc_p = 0;
576             isamc_merge(hi->reg->isamc, &isamc_p, isamc_i);
577             hi->no_insertions++;
578             if (isamc_p)
579                 dict_insert(hi->reg->dict, this_name,
580                              sizeof(ISAM_P), &isamc_p);
581         }
582     }
583     xfree(isamc_i);
584     return 0;
585
586
587 int heap_inpb(struct heap_cread_info *hci, struct heap_info *hi)
588 {
589     ISAMC_I *isamc_i = (ISAMC_I *) xmalloc(sizeof(*isamc_i));
590
591     isamc_i->clientData = hci;
592     isamc_i->read_item = heap_cread_item2;
593
594     while (hci->more)
595     {
596         char this_name[INP_NAME_MAX];
597         ISAM_P isamc_p, isamc_p2;
598         char *dict_info;
599
600         strcpy(this_name, hci->cur_name);
601         assert(hci->cur_name[0]);
602         hi->no_diffs++;
603
604 #if 0
605         assert(hi->zh);
606         zebra_log_dict_entry(hi->zh, hci->cur_name);
607 #endif
608         if ((dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
609         {
610             memcpy(&isamc_p, dict_info+1, sizeof(ISAM_P));
611             isamc_p2 = isamc_p;
612             isamb_merge(hi->reg->isamb, &isamc_p2, isamc_i);
613             if (!isamc_p2)
614             {
615                 hi->no_deletions++;
616                 if (!dict_delete(hi->reg->dict, this_name))
617                     abort();
618             }
619             else 
620             {
621                 hi->no_updates++;
622                 if (isamc_p2 != isamc_p)
623                     dict_insert(hi->reg->dict, this_name,
624                                  sizeof(ISAM_P), &isamc_p2);
625             }
626         } 
627         else
628         {
629             isamc_p = 0;
630             isamb_merge(hi->reg->isamb, &isamc_p, isamc_i);
631             hi->no_insertions++;
632             if (isamc_p)
633                 dict_insert(hi->reg->dict, this_name,
634                              sizeof(ISAM_P), &isamc_p);
635         }
636     }
637     xfree(isamc_i);
638     return 0;
639
640
641 int heap_inps(struct heap_cread_info *hci, struct heap_info *hi)
642 {
643     ISAMS_I isams_i = (ISAMS_I) xmalloc(sizeof(*isams_i));
644
645     isams_i->clientData = hci;
646     isams_i->read_item = heap_cread_item;
647
648     while (hci->more)
649     {
650         char this_name[INP_NAME_MAX];
651         ISAM_P isams_p;
652         char *dict_info;
653
654         strcpy(this_name, hci->cur_name);
655         assert(hci->cur_name[0]);
656         hi->no_diffs++;
657         if (!(dict_info = dict_lookup(hi->reg->dict, hci->cur_name)))
658         {
659             isams_p = isams_merge(hi->reg->isams, isams_i);
660             hi->no_insertions++;
661             dict_insert(hi->reg->dict, this_name, sizeof(ISAM_P), &isams_p);
662         }
663         else
664         {
665             yaz_log(YLOG_FATAL, "isams doesn't support this kind of update");
666             break;
667         }
668     }
669     xfree(isams_i);
670     return 0;
671
672
673 struct progressInfo {
674     time_t   startTime;
675     time_t   lastTime;
676     off_t    totalBytes;
677     off_t    totalOffset;
678 };
679
680 void progressFunc(struct key_file *keyp, void *info)
681 {
682     struct progressInfo *p = (struct progressInfo *) info;
683     time_t now, remaining;
684
685     if (keyp->buf_size <= 0 || p->totalBytes <= 0)
686         return ;
687     time (&now);
688
689     if (now >= p->lastTime+10)
690     {
691         p->lastTime = now;
692         remaining = (time_t) ((now - p->startTime)*
693             ((double) p->totalBytes/p->totalOffset - 1.0));
694         if (remaining <= 130)
695             yaz_log(YLOG_LOG, "Merge %2.1f%% completed; %ld seconds remaining",
696                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining);
697         else
698             yaz_log(YLOG_LOG, "Merge %2.1f%% completed; %ld minutes remaining",
699                  (100.0*p->totalOffset) / p->totalBytes, (long) remaining/60);
700     }
701     p->totalOffset += keyp->buf_size;
702 }
703
704 #ifndef R_OK
705 #define R_OK 4
706 #endif
707
708 void zebra_index_merge(ZebraHandle zh)
709 {
710     struct key_file **kf = 0;
711     char rbuf[1024];
712     int i, r;
713     struct heap_info *hi;
714     struct progressInfo progressInfo;
715     int nkeys = key_block_get_no_files(zh->reg->key_block);
716
717     if (nkeys == 0)
718         return;
719     
720     if (nkeys < 0)
721     {
722         char fname[1024];
723         nkeys = 0;
724         while (1)
725         {
726             extract_get_fname_tmp (zh, fname, nkeys+1);
727             if (access(fname, R_OK) == -1)
728                 break;
729             nkeys++;
730         }
731         if (!nkeys)
732             return ;
733     }
734     kf = (struct key_file **) xmalloc((1+nkeys) * sizeof(*kf));
735     progressInfo.totalBytes = 0;
736     progressInfo.totalOffset = 0;
737     time(&progressInfo.startTime);
738     time(&progressInfo.lastTime);
739     for (i = 1; i<=nkeys; i++)
740     {
741         kf[i] = key_file_init(i, 8192, zh->res);
742         kf[i]->readHandler = progressFunc;
743         kf[i]->readInfo = &progressInfo;
744         progressInfo.totalBytes += kf[i]->length;
745         progressInfo.totalOffset += kf[i]->buf_size;
746     }
747     hi = key_heap_init_file(zh, nkeys, key_qsort_compare);
748     hi->reg = zh->reg;
749     
750     for (i = 1; i<=nkeys; i++)
751         if ((r = key_file_read(kf[i], rbuf)))
752             key_heap_insert(hi, rbuf, r, kf[i]);
753
754     if (1)
755     {
756         struct heap_cread_info hci;
757         
758         hci.key = (char *) xmalloc(KEY_SIZE);
759         hci.key_1 = (char *) xmalloc(KEY_SIZE);
760         hci.key_2 = (char *) xmalloc(KEY_SIZE);
761         hci.ret = -1;
762         hci.first_in_list = 1;
763         hci.hi = hi;
764         hci.look_level = 0;
765         hci.more = heap_read_one(hi, hci.cur_name, hci.key);    
766         
767         if (zh->reg->isams)
768             heap_inps(&hci, hi);
769         if (zh->reg->isamc)
770             heap_inpc(&hci, hi);
771         if (zh->reg->isamb)
772             heap_inpb(&hci, hi);
773         
774         xfree(hci.key);
775         xfree(hci.key_1);
776         xfree(hci.key_2);
777     }
778         
779     for (i = 1; i<=nkeys; i++)
780     {
781         extract_get_fname_tmp (zh, rbuf, i);
782         unlink(rbuf);
783     }
784     for (i = 1; i<=nkeys; i++)
785         key_file_destroy(kf[i]);
786     xfree(kf);
787     if (hi->no_iterations)
788     { /* do not log if nothing happened */
789         yaz_log(YLOG_LOG, "Iterations: isam/dict " 
790                 ZINT_FORMAT "/" ZINT_FORMAT,
791                 hi->no_iterations, hi->no_diffs);
792         yaz_log(YLOG_LOG, "Dict: inserts/updates/deletions: "
793                 ZINT_FORMAT "/" ZINT_FORMAT "/" ZINT_FORMAT,
794                 hi->no_insertions, hi->no_updates, hi->no_deletions);
795     }
796     key_block_destroy(&zh->reg->key_block);
797     key_heap_destroy(hi, nkeys);
798 }
799 /*
800  * Local variables:
801  * c-basic-offset: 4
802  * indent-tabs-mode: nil
803  * End:
804  * vim: shiftwidth=4 tabstop=8 expandtab
805  */
806