New general match criteria implemented.
[idzebra-moved-to-github.git] / index / extract.c
1 /*
2  * Copyright (C) 1994-1995, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: extract.c,v $
7  * Revision 1.29  1995-11-21 15:01:14  adam
8  * New general match criteria implemented.
9  * New feature: document groups.
10  *
11  * Revision 1.28  1995/11/21  09:20:30  adam
12  * Yet more work on record match.
13  *
14  * Revision 1.27  1995/11/20  16:59:45  adam
15  * New update method: the 'old' keys are saved for each records.
16  *
17  * Revision 1.26  1995/11/20  11:56:24  adam
18  * Work on new traversal.
19  *
20  * Revision 1.25  1995/11/16  15:34:54  adam
21  * Uses new record management system in both indexer and server.
22  *
23  * Revision 1.24  1995/11/15  19:13:08  adam
24  * Work on record management.
25  *
26  * Revision 1.23  1995/10/27  14:00:10  adam
27  * Implemented detection of database availability.
28  *
29  * Revision 1.22  1995/10/17  18:02:07  adam
30  * New feature: databases. Implemented as prefix to words in dictionary.
31  *
32  * Revision 1.21  1995/10/10  12:24:38  adam
33  * Temporary sort files are compressed.
34  *
35  * Revision 1.20  1995/10/06  13:52:05  adam
36  * Bug fixes. Handler may abort further scanning.
37  *
38  * Revision 1.19  1995/10/04  12:55:16  adam
39  * Bug fix in ranked search. Use=Any keys inserted.
40  *
41  * Revision 1.18  1995/10/04  09:37:08  quinn
42  * Fixed bug.
43  *
44  * Revision 1.17  1995/10/03  14:28:57  adam
45  * Buffered read in extract works.
46  *
47  * Revision 1.16  1995/10/03  14:28:45  adam
48  * Work on more effecient read handler in extract.
49  *
50  * Revision 1.15  1995/10/02  15:42:53  adam
51  * Extract uses file descriptors instead of FILE pointers.
52  *
53  * Revision 1.14  1995/10/02  15:29:13  adam
54  * More logging in file_extract.
55  *
56  * Revision 1.13  1995/09/29  14:01:39  adam
57  * Bug fixes.
58  *
59  * Revision 1.12  1995/09/28  14:22:56  adam
60  * Sort uses smaller temporary files.
61  *
62  * Revision 1.11  1995/09/28  12:10:31  adam
63  * Bug fixes. Field prefix used in queries.
64  *
65  * Revision 1.10  1995/09/28  09:19:41  adam
66  * xfree/xmalloc used everywhere.
67  * Extract/retrieve method seems to work for text records.
68  *
69  * Revision 1.9  1995/09/27  12:22:28  adam
70  * More work on extract in record control.
71  * Field name is not in isam keys but in prefix in dictionary words.
72  *
73  * Revision 1.8  1995/09/14  07:48:22  adam
74  * Record control management.
75  *
76  * Revision 1.7  1995/09/11  13:09:32  adam
77  * More work on relevance feedback.
78  *
79  * Revision 1.6  1995/09/08  14:52:27  adam
80  * Minor changes. Dictionary is lower case now.
81  *
82  * Revision 1.5  1995/09/06  16:11:16  adam
83  * Option: only one word key per file.
84  *
85  * Revision 1.4  1995/09/05  15:28:39  adam
86  * More work on search engine.
87  *
88  * Revision 1.3  1995/09/04  12:33:41  adam
89  * Various cleanup. YAZ util used instead.
90  *
91  * Revision 1.2  1995/09/04  09:10:34  adam
92  * More work on index add/del/update.
93  * Merge sort implemented.
94  * Initial work on z39 server.
95  *
96  * Revision 1.1  1995/09/01  14:06:35  adam
97  * Split of work into more files.
98  *
99  */
100 #include <stdio.h>
101 #include <assert.h>
102 #include <unistd.h>
103 #include <fcntl.h>
104 #include <ctype.h>
105
106 #include <alexutil.h>
107 #include <recctrl.h>
108 #include "index.h"
109
110 #include "recindex.h"
111
112 static Dict matchDict;
113
114 static Records records = NULL;
115
116 static char **key_buf;
117 static size_t ptr_top;
118 static size_t ptr_i;
119 static size_t key_buf_used;
120 static int key_file_no;
121
122 static int records_inserted = 0;
123 static int records_updated = 0;
124 static int records_deleted = 0;
125
126 #define MATCH_DICT "match"
127
128 void key_open (int mem)
129 {
130     if (mem < 50000)
131         mem = 50000;
132     key_buf = xmalloc (mem);
133     ptr_top = mem/sizeof(char*);
134     ptr_i = 0;
135
136     key_buf_used = 0;
137     key_file_no = 0;
138
139     if (!(matchDict = dict_open (MATCH_DICT, 20, 1)))
140     {
141         logf (LOG_FATAL, "dict_open fail of %s", MATCH_DICT);
142         exit (1);
143     }
144     assert (!records);
145     records = rec_open (1);
146 }
147
148 struct encode_info {
149     int  sysno;
150     int  seqno;
151     char buf[512];
152 };
153
154 void encode_key_init (struct encode_info *i)
155 {
156     i->sysno = 0;
157     i->seqno = 0;
158 }
159
160 char *encode_key_int (int d, char *bp)
161 {
162     if (d <= 63)
163         *bp++ = d;
164     else if (d <= 16383)
165     {
166         *bp++ = 64 + (d>>8);
167         *bp++ = d  & 255;
168     }
169     else if (d <= 4194303)
170     {
171         *bp++ = 128 + (d>>16);
172         *bp++ = (d>>8) & 255;
173         *bp++ = d & 255;
174     }
175     else
176     {
177         *bp++ = 192 + (d>>24);
178         *bp++ = (d>>16) & 255;
179         *bp++ = (d>>8) & 255;
180         *bp++ = d & 255;
181     }
182     return bp;
183 }
184
185 void encode_key_write (char *k, struct encode_info *i, FILE *outf)
186 {
187     struct it_key key;
188     char *bp = i->buf;
189
190     while ((*bp++ = *k++))
191         ;
192     memcpy (&key, k+1, sizeof(struct it_key));
193     bp = encode_key_int ( (key.sysno - i->sysno) * 2 + *k, bp);
194     if (i->sysno != key.sysno)
195     {
196         i->sysno = key.sysno;
197         i->seqno = 0;
198     }
199     bp = encode_key_int (key.seqno - i->seqno, bp);
200     i->seqno = key.seqno;
201     if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
202     {
203         logf (LOG_FATAL|LOG_ERRNO, "fwrite");
204         exit (1);
205     }
206 }
207
208 void key_flush (void)
209 {
210     FILE *outf;
211     char out_fname[200];
212     char *prevcp, *cp;
213     struct encode_info encode_info;
214     
215     if (ptr_i <= 0)
216         return;
217
218     key_file_no++;
219     logf (LOG_LOG, "sorting section %d", key_file_no);
220     qsort (key_buf + ptr_top-ptr_i, ptr_i, sizeof(char*), key_qsort_compare);
221     sprintf (out_fname, TEMP_FNAME, key_file_no);
222
223     if (!(outf = fopen (out_fname, "w")))
224     {
225         logf (LOG_FATAL|LOG_ERRNO, "fopen (4) %s", out_fname);
226         exit (1);
227     }
228     logf (LOG_LOG, "writing section %d", key_file_no);
229     prevcp = cp = key_buf[ptr_top-ptr_i];
230     
231     encode_key_init (&encode_info);
232     encode_key_write (cp, &encode_info, outf);
233     while (--ptr_i > 0)
234     {
235         cp = key_buf[ptr_top-ptr_i];
236         if (strcmp (cp, prevcp))
237         {
238             encode_key_init (&encode_info);
239             encode_key_write (cp, &encode_info, outf);
240             prevcp = cp;
241         }
242         else
243             encode_key_write (cp + strlen(cp), &encode_info, outf);
244     }
245     if (fclose (outf))
246     {
247         logf (LOG_FATAL|LOG_ERRNO, "fclose %s", out_fname);
248         exit (1);
249     }
250     logf (LOG_LOG, "finished section %d", key_file_no);
251     ptr_i = 0;
252     key_buf_used = 0;
253 }
254
255 int key_close (void)
256 {
257     key_flush ();
258     xfree (key_buf);
259     rec_close (&records);
260     dict_close (matchDict);
261
262     logf (LOG_LOG, "Records inserted %6d", records_inserted);
263     logf (LOG_LOG, "Records updated  %6d", records_updated);
264     logf (LOG_LOG, "Records deleted  %6d", records_deleted);
265     return key_file_no;
266 }
267
268 static void wordInit (RecWord *p)
269 {
270     p->attrSet = 1;
271     p->attrUse = 1016;
272     p->which = Word_String;
273 }
274
275 struct recKeys {
276     int buf_used;
277     int buf_max;
278     char *buf;
279 } reckeys;
280
281 static void addRecordKey (const RecWord *p)
282 {
283     char *dst;
284     char attrSet;
285     short attrUse;
286     size_t i;
287
288     if (reckeys.buf_used+1024 > reckeys.buf_max)
289     {
290         char *b;
291
292         b = malloc (reckeys.buf_max += 65000);
293         if (reckeys.buf_used > 0)
294             memcpy (b, reckeys.buf, reckeys.buf_used);
295         free (reckeys.buf);
296         reckeys.buf = b;
297     }
298     dst = reckeys.buf + reckeys.buf_used;
299     switch (p->which)
300     {
301     case Word_String:
302         attrSet = p->attrSet;
303         memcpy (dst, &attrSet, sizeof(attrSet));
304         dst += sizeof(attrSet);
305
306         attrUse = p->attrUse;
307         memcpy (dst, &attrUse, sizeof(attrUse));
308         dst += sizeof(attrUse);
309         
310         for (i = 0; p->u.string[i]; i++)
311             *dst++ = p->u.string[i];
312         *dst++ = '\0';
313
314         memcpy (dst, &p->seqno, sizeof(p->seqno));
315         dst += sizeof(p->seqno);
316
317         break;
318     default:
319         return;
320     }
321     reckeys.buf_used = dst - reckeys.buf;
322 }
323
324 static void flushRecordKeys (SYSNO sysno, int cmd, struct recKeys *reckeys, 
325                              const char *databaseName)
326 {
327     int off = 0;
328     while (off < reckeys->buf_used)
329     {
330         const char *src = reckeys->buf + off;
331         char attrSet;
332         short attrUse;
333         struct it_key key;
334         
335         memcpy (&attrSet, src, sizeof(attrSet));
336         src += sizeof(attrSet);
337
338         memcpy (&attrUse, src, sizeof(attrUse));
339         src += sizeof(attrUse);
340
341         if (key_buf_used + 1024 > (ptr_top-ptr_i)*sizeof(char*))
342             key_flush ();
343         ++ptr_i;
344         key_buf[ptr_top-ptr_i] = (char*)key_buf + key_buf_used;
345         key_buf_used += index_word_prefix ((char*)key_buf + key_buf_used,
346                                            attrSet, attrUse, databaseName);
347         while (*src)
348             ((char*)key_buf) [key_buf_used++] = index_char_cvt (*src++);
349         src++;
350         ((char*)key_buf) [key_buf_used++] = '\0';
351         
352         ((char*) key_buf)[key_buf_used++] = cmd;
353
354         memcpy (&key.seqno, src, sizeof(key.seqno));
355         src += sizeof(key.seqno);
356         key.sysno = sysno;
357         memcpy ((char*)key_buf + key_buf_used, &key, sizeof(key));
358         key_buf_used += sizeof(key);
359         off = src - reckeys->buf;
360     }
361     assert (off == reckeys->buf_used);
362 }
363
364 static const char **searchRecordKey (struct recKeys *reckeys,
365                                int attrSetS, int attrUseS)
366 {
367     static const char *ws[32];
368     int off = 0;
369     int startSeq = -1;
370     int i;
371
372     for (i = 0; i<32; i++)
373         ws[i] = NULL;
374     
375     while (off < reckeys->buf_used)
376     {
377         const char *src = reckeys->buf + off;
378         char attrSet;
379         short attrUse;
380         int seqno;
381         const char *wstart;
382         
383         memcpy (&attrSet, src, sizeof(attrSet));
384         src += sizeof(attrSet);
385
386         memcpy (&attrUse, src, sizeof(attrUse));
387         src += sizeof(attrUse);
388
389         wstart = src;
390         while (*src++)
391             ;
392
393         memcpy (&seqno, src, sizeof(seqno));
394         src += sizeof(seqno);
395
396 #if 0
397         logf (LOG_LOG, "(%d,%d) %d %s", attrSet, attrUse, seqno, wstart);
398 #endif
399         if (attrUseS == attrUse && attrSetS == attrSet)
400         {
401             int woff;
402
403
404             if (startSeq == -1)
405                 startSeq = seqno;
406             woff = seqno - startSeq;
407             if (woff >= 0 && woff < 31)
408                 ws[woff] = wstart;
409         }
410
411         off = src - reckeys->buf;
412     }
413     assert (off == reckeys->buf_used);
414     return ws;
415 }
416
417 static void addRecordKeyAny (const RecWord *p)
418 {
419     if (p->attrSet != 1 || p->attrUse != 1016)
420     {
421         RecWord w;
422
423         memcpy (&w, p, sizeof(w));
424         w.attrSet = 1;
425         w.attrUse = 1016;
426         addRecordKey (&w);
427     }
428     addRecordKey (p);
429 }
430
431 static char *file_buf;
432 static int file_offset;
433 static int file_bufsize;
434
435 static void file_read_start (int fd)
436 {
437     file_offset = 0;
438     file_buf = xmalloc (4096);
439     file_bufsize = read (fd, file_buf, 4096);
440 }
441
442 static void file_read_stop (int fd)
443 {
444     xfree (file_buf);
445 }
446
447 static int file_read (int fd, char *buf, size_t count)
448 {
449     int l = file_bufsize - file_offset;
450
451     if (count > l)
452     {
453         int r;
454         if (l > 0)
455             memcpy (buf, file_buf + file_offset, l);
456         count = count-l;
457         if (count > file_bufsize)
458         {
459             if ((r = read (fd, buf + l, count)) == -1)
460             {
461                 logf (LOG_FATAL|LOG_ERRNO, "read");
462                 exit (1);
463             }
464             file_bufsize = 0;
465             file_offset = 0;
466             return r;
467         }
468         file_bufsize = r = read (fd, file_buf, 4096);
469         if (r == -1)
470         {
471             logf (LOG_FATAL|LOG_ERRNO, "read");
472             exit (1);
473         }
474         else if (r <= count)
475         {
476             file_offset = r;
477             memcpy (buf + l, file_buf, r);
478             return l + r;
479         }
480         else
481         {
482             file_offset = count;
483             memcpy (buf + l, file_buf, count - l);
484             return count;
485         }
486     }
487     memcpy (buf, file_buf + file_offset, count);
488     file_offset += count;
489     return count;
490 }
491
492 static int atois (const char **s)
493 {
494     int val = 0, c;
495     while ( (c=**s) >= '0' && c <= '9')
496     {
497         val = val*10 + c - '0';
498         ++(*s);
499     }
500     return val;
501 }
502
503 static char *fileMatchStr (struct recKeys *reckeys, struct recordGroup *rGroup,
504                            const char *fname,
505                            const char *recordType,
506                            const char *spec)
507 {
508     static char dstBuf[2048];
509     char *dst = dstBuf;
510     const char *s = spec;
511     static const char **w;
512     int i;
513
514     while (1)
515     {
516         while (*s == ' ' || *s == '\t')
517             s++;
518         if (!*s)
519             break;
520         if (*s == '(')
521         {
522             char matchFlag[32];
523             int attrSet, attrUse;
524             int first = 1;
525
526             s++;
527             attrSet = atois (&s);
528             if (*s != ',')
529             {
530                 logf (LOG_WARN, "Missing , in match criteria %s in group %s",
531                       spec, rGroup->groupName ? rGroup->groupName : "none");
532                 return NULL;
533             }
534             s++;
535             attrUse = atois (&s);
536             w = searchRecordKey (reckeys, attrSet, attrUse);
537             assert (w);
538
539             if (*s == ')')
540             {
541                 for (i = 0; i<32; i++)
542                     matchFlag[i] = 1;
543             }
544             else
545             {
546                 logf (LOG_WARN, "Missing ) in match criteria %s in group %s",
547                       spec, rGroup->groupName ? rGroup->groupName : "none");
548                 return NULL;
549             }
550             s++;
551
552             for (i = 0; i<32; i++)
553                 if (matchFlag[i] && w[i])
554                 {
555                     if (first)
556                     {
557                         *dst++ = ' ';
558                         first = 0;
559                     }
560                     strcpy (dst, w[i]);
561                     dst += strlen(w[i]);
562                 }
563             if (first)
564             {
565                 logf (LOG_WARN, "Record in file %s didn't contain match"
566                       " fields in (%d,%d)", fname, attrSet, attrUse);
567                 return NULL;
568             }
569         }
570         else if (*s == '$')
571         {
572             int spec_len;
573             char special[32];
574             const char *spec_src = NULL;
575             const char *s1 = ++s;
576             while (*s1 && *s1 != ' ' && *s1 != '\t')
577                 s1++;
578
579             spec_len = s1 - s;
580             if (spec_len > 31)
581                 spec_len = 31;
582             memcpy (special, s, spec_len);
583             special[spec_len] = '\0';
584             s = s1;
585
586             if (strcmp (special, "group"))
587                 spec_src = rGroup->groupName;
588             else if (strcmp (special, "database"))
589                 spec_src = rGroup->databaseName;
590             else if (strcmp (special, "filename"))
591                 spec_src = fname;
592             else if (strcmp (special, "type"))
593                 spec_src = recordType;
594             else 
595                 spec_src = NULL;
596             if (spec_src)
597             {
598                 strcpy (dst, spec_src);
599                 dst += strlen(spec_src);
600             }
601         }
602         else
603         {
604             logf (LOG_WARN, "Syntax error in match criteria %s in group %s",
605                   spec, rGroup->groupName ? rGroup->groupName : "none");
606             return NULL;
607         }
608         *dst++ = 1;
609     }
610     if (dst == dstBuf)
611     {
612         logf (LOG_WARN, "No match criteria for record %s in group %s",
613               fname, rGroup->groupName ? rGroup->groupName : "none");
614         return NULL;
615     }
616     return dstBuf;
617 }
618
619 int fileExtract (SYSNO *sysno, const char *fname, struct recordGroup *rGroup,
620                  int deleteFlag)
621 {
622     SYSNO sysnotmp;
623     int i, r;
624     char gprefix[128];
625     char ext[128];
626     char ext_res[128];
627     const char *file_type;
628     const char *file_match;
629     struct recExtractCtrl extractCtrl;
630     RecType recType;
631     Record rec;
632     char *matchStr;
633
634     if (!rGroup->groupName || !*rGroup->groupName)
635         *gprefix = '\0';
636     else
637         sprintf (gprefix, "%s.", rGroup->groupName);
638
639     logf (LOG_DEBUG, "fileExtractAdd %s", fname);
640
641     /* determine file extension */
642     for (i = strlen(fname); --i >= 0; )
643         if (fname[i] == '/')
644         {
645             strcpy (ext, "");
646             break;
647         }
648         else if (fname[i] == '.')
649         {
650             strcpy (ext, fname+i+1);
651             break;
652         }
653     /* determine file type - depending on extension */
654     sprintf (ext_res, "%sfileExtension.%s", gprefix, ext);
655     if (!(file_type = res_get (common_resource, ext_res)))
656         return 0;
657     if (!(recType = recType_byName (file_type)))
658         return 0;
659
660     /* determine match criteria */
661     sprintf (ext_res, "%sfileMatch.%s", gprefix, ext);
662     file_match = res_get (common_resource, ext_res);
663     if (!file_match)
664     {
665         sprintf (ext_res, "%sfileMatch", gprefix);
666         file_match = res_get (common_resource, ext_res);
667     }
668
669     /* determine database name */
670     if (!rGroup->databaseName)
671     {
672         sprintf (ext_res, "%sdatabase.%s", gprefix, ext);
673         if (!(rGroup->databaseName = res_get (common_resource, ext_res)))
674         {
675             sprintf (ext_res, "%sdatabase", gprefix);
676             rGroup->databaseName = res_get (common_resource, ext_res);
677         }
678     }
679     if (!rGroup->databaseName)
680         rGroup->databaseName = "Default";
681
682     /* open input file */
683     if ((extractCtrl.fd = open (fname, O_RDONLY)) == -1)
684     {
685         logf (LOG_WARN|LOG_ERRNO, "open %s", fname);
686         return 0;
687     }
688
689     /* extract keys */
690     extractCtrl.subType = "";
691     extractCtrl.init = wordInit;
692     extractCtrl.add = addRecordKeyAny;
693
694     reckeys.buf_used = 0;
695     file_read_start (extractCtrl.fd);
696     extractCtrl.readf = file_read;
697     r = (*recType->extract)(&extractCtrl);
698     file_read_stop (extractCtrl.fd);
699     close (extractCtrl.fd);
700   
701     if (r)      
702     {
703         logf (LOG_WARN, "Couldn't extract file %s, code %d", fname, r);
704         return 0;
705     }
706
707     /* perform match if sysno not known and if match criteria is specified */
708        
709     matchStr = NULL;
710     if (!sysno && file_match)
711     {
712         char *rinfo;
713         
714         sysno = &sysnotmp;
715         matchStr = fileMatchStr(&reckeys, rGroup, fname, file_type,
716                                 file_match);
717         if (matchStr)
718         {
719             rinfo = dict_lookup (matchDict, matchStr);
720             if (rinfo)
721                 memcpy (sysno, rinfo+1, sizeof(*sysno));
722             else
723                 *sysno = 0;
724         }
725         else
726         {
727             logf (LOG_WARN, "Record not inserted");
728             return 0;
729         }
730     }
731
732     /* new record ? */
733     if (! *sysno)
734     {
735         logf (LOG_LOG, "add record %s", fname);
736         rec = rec_new (records);
737         *sysno = rec->sysno;
738
739         if (matchStr)
740             dict_insert (matchDict, matchStr, sizeof(*sysno), sysno);
741         flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName);
742
743         records_inserted++;
744     }
745     else
746     {
747         struct recKeys delkeys;
748         
749         rec = rec_get (records, *sysno);
750
751         delkeys.buf_used = rec->size[2];
752         delkeys.buf = rec->info[2];
753         flushRecordKeys (*sysno, 0, &delkeys, rec->info[3]);
754         flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName); 
755
756         records_updated++;
757     }
758     free (rec->info[0]);
759     rec->info[0] = rec_strdup (file_type, &rec->size[0]);
760
761     free (rec->info[1]);
762     rec->info[1] = rec_strdup (fname, &rec->size[1]);
763
764     free (rec->info[2]);
765     if (reckeys.buf_used > 0)
766     {
767         rec->info[2] = malloc (reckeys.buf_used);
768         rec->size[2] = reckeys.buf_used;
769         memcpy (rec->info[2], reckeys.buf, rec->size[2]);
770     }
771     else
772     {
773         rec->info[2] = NULL;
774         rec->size[2] = 0;
775     }
776     free (rec->info[3]);
777     rec->info[3] = rec_strdup (rGroup->databaseName, &rec->size[3]); 
778
779     rec_put (records, &rec);
780     return 1;
781 }