Work on cluster merging; part of PAZ-901
[pazpar2-moved-to-github.git] / src / reclists.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2013 Index Data
3
4 Pazpar2 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 Pazpar2 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 <assert.h>
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <yaz/yaz-util.h>
27
28 #include "ppmutex.h"
29 #include "session.h"
30 #include "reclists.h"
31 #include "jenkins_hash.h"
32
33 struct reclist
34 {
35     struct reclist_bucket **hashtable;
36     unsigned hash_size;
37
38     int num_records;
39     struct reclist_bucket *sorted_list;
40     struct reclist_bucket *sorted_ptr;
41     NMEM nmem;
42     YAZ_MUTEX mutex;
43 };
44
45 struct reclist_bucket
46 {
47     struct record_cluster *record;
48     struct reclist_bucket *hash_next;
49     struct reclist_bucket *sorted_next;
50     struct reclist_sortparms *sort_parms;
51 };
52
53 static void append_merge_keys(struct record_metadata_attr **p,
54                               struct record_metadata_attr *a,
55                               NMEM nmem)
56 {
57     while (*p)
58         p = &(*p)->next;
59     for (; a; a = a->next)
60     {
61         *p = (struct record_metadata_attr *) nmem_malloc(nmem, sizeof(**p));
62         (*p)->name = nmem_strdup_null(nmem, a->name);
63         (*p)->value = nmem_strdup_null(nmem, a->value);
64         p = &(*p)->next;
65     }
66     *p = 0;
67 }
68
69 struct reclist_sortparms *reclist_parse_sortparms(NMEM nmem, const char *parms,
70                                                   struct conf_service *service)
71 {
72     struct reclist_sortparms *res = 0;
73     struct reclist_sortparms **rp = &res;
74
75     if (strlen(parms) > 256)
76         return 0;
77     while (*parms)
78     {
79         char parm[256];
80         char *pp;
81         const char *cpp;
82         int increasing = 0;
83         int i;
84         int offset = 0;
85         enum conf_sortkey_type type = Metadata_sortkey_string;
86         struct reclist_sortparms *new;
87
88         if (!(cpp = strchr(parms, ',')))
89             cpp = parms + strlen(parms);
90         strncpy(parm, parms, cpp - parms);
91         parm[cpp-parms] = '\0';
92
93         if ((pp = strchr(parm, ':')))
94         {
95             if (pp[1] == '1')
96                 increasing = 1;
97             else if (pp[1] == '0')
98                 increasing = 0;
99             else
100             {
101                 yaz_log(YLOG_FATAL, "Bad sortkey modifier: %s", parm);
102                 return 0;
103             }
104
105             if (pp[2])
106             {
107                 if (pp[2] == 'p')
108                     type = Metadata_sortkey_position;
109                 else
110                     yaz_log(YLOG_FATAL, "Bad sortkey modifier: %s", parm);
111             }
112             *pp = '\0';
113         }
114         if (type != Metadata_sortkey_position)
115         {
116             if (!strcmp(parm, "relevance"))
117             {
118                 type = Metadata_sortkey_relevance;
119             }
120             else if (!strcmp(parm, "position"))
121             {
122                 type = Metadata_sortkey_position;
123             }
124             else
125             {
126                 for (i = 0; i < service->num_sortkeys; i++)
127                 {
128                     struct conf_sortkey *sk = &service->sortkeys[i];
129                     if (!strcmp(sk->name, parm))
130                     {
131                         type = sk->type;
132                         if (type == Metadata_sortkey_skiparticle)
133                             type = Metadata_sortkey_string;
134                         break;
135                     }
136                 }
137                 if (i >= service->num_sortkeys)
138                 {
139                     yaz_log(YLOG_FATAL, "Sortkey not defined in service: %s",
140                             parm);
141                     return 0;
142                 }
143                 offset = i;
144             }
145         }
146         new = *rp = nmem_malloc(nmem, sizeof(struct reclist_sortparms));
147         new->next = 0;
148         new->offset = offset;
149         new->type = type;
150         new->increasing = increasing;
151         new->name = nmem_strdup(nmem, parm);
152         rp = &new->next;
153         if (*(parms = cpp))
154             parms++;
155     }
156     return res;
157 }
158
159 static int reclist_cmp(const void *p1, const void *p2)
160 {
161     struct reclist_sortparms *sortparms =
162         (*(struct reclist_bucket **) p1)->sort_parms;
163     struct record_cluster *r1 = (*(struct reclist_bucket**) p1)->record;
164     struct record_cluster *r2 = (*(struct reclist_bucket**) p2)->record;
165     struct reclist_sortparms *s;
166     int res = 0;
167
168     for (s = sortparms; s && res == 0; s = s->next)
169     {
170         union data_types *ut1 = r1->sortkeys[s->offset];
171         union data_types *ut2 = r2->sortkeys[s->offset];
172         const char *s1, *s2;
173         switch (s->type)
174         {
175         case Metadata_sortkey_relevance:
176             res = r2->relevance_score - r1->relevance_score;
177             break;
178         case Metadata_sortkey_string:
179             s1 = ut1 ? ut1->text.sort : "";
180             s2 = ut2 ? ut2->text.sort : "";
181             res = strcmp(s2, s1);
182             if (res)
183             {
184                 if (s->increasing)
185                     res *= -1;
186             }
187             break;
188         case Metadata_sortkey_numeric:
189             if (ut1 && ut2)
190             {
191                 if (s->increasing)
192                     res = ut1->number.min  - ut2->number.min;
193                 else
194                     res = ut2->number.max  - ut1->number.max;
195             }
196             else if (ut1 && !ut2)
197                 res = -1;
198             else if (!ut1 && ut2)
199                 res = 1;
200             else
201                 res = 0;
202             break;
203         case Metadata_sortkey_position:
204             if (r1->records && r2->records)
205             {
206                 int pos1 = 0, pos2 = 0;
207                 struct record *rec;
208                 for (rec = r1->records; rec; rec = rec->next)
209                     if (pos1 == 0 || rec->position < pos1)
210                         pos1 = rec->position;
211                 for (rec = r2->records; rec; rec = rec->next)
212                     if (pos2 == 0 || rec->position < pos2)
213                         pos2 = rec->position;
214                 res = pos1 - pos2;
215             }
216             break;
217         default:
218             yaz_log(YLOG_WARN, "Bad sort type: %d", s->type);
219             res = 0;
220             break;
221         }
222     }
223     if (res == 0)
224         res = strcmp(r1->recid, r2->recid);
225     return res;
226 }
227
228 void reclist_limit(struct reclist *l, struct session *se, int lazy)
229 {
230     unsigned i;
231     int num = 0;
232     struct reclist_bucket **pp = &l->sorted_list;
233
234     reclist_enter(l);
235
236     if (!lazy || !*pp)
237     {
238         for (i = 0; i < l->hash_size; i++)
239         {
240             struct reclist_bucket *p;
241             for (p = l->hashtable[i]; p; p = p->hash_next)
242             {
243                 if (session_check_cluster_limit(se, p->record))
244                 {
245                     *pp = p;
246                     pp = &p->sorted_next;
247                     num++;
248                 }
249             }
250         }
251         *pp = 0;
252     }
253     l->num_records = num;
254     reclist_leave(l);
255 }
256
257 void reclist_sort(struct reclist *l, struct reclist_sortparms *parms)
258 {
259     struct reclist_bucket **flatlist = xmalloc(sizeof(*flatlist) * l->num_records);
260     struct reclist_bucket *ptr;
261     struct reclist_bucket **prev;
262     int i = 0;
263
264     reclist_enter(l);
265
266     ptr = l->sorted_list;
267     prev = &l->sorted_list;
268     while (ptr)
269     {
270         ptr->sort_parms = parms;
271         flatlist[i] = ptr;
272         ptr = ptr->sorted_next;
273         i++;
274     }
275     assert(i == l->num_records);
276
277     qsort(flatlist, l->num_records, sizeof(*flatlist), reclist_cmp);
278     for (i = 0; i < l->num_records; i++)
279     {
280         *prev = flatlist[i];
281         prev = &flatlist[i]->sorted_next;
282     }
283     *prev = 0;
284
285     xfree(flatlist);
286
287     reclist_leave(l);
288 }
289
290 struct record_cluster *reclist_read_record(struct reclist *l)
291 {
292     if (l && l->sorted_ptr)
293     {
294         struct record_cluster *t = l->sorted_ptr->record;
295         l->sorted_ptr = l->sorted_ptr->sorted_next;
296         return t;
297     }
298     else
299         return 0;
300 }
301
302 void reclist_enter(struct reclist *l)
303 {
304     yaz_mutex_enter(l->mutex);
305     if (l)
306         l->sorted_ptr = l->sorted_list;
307 }
308
309
310 void reclist_leave(struct reclist *l)
311 {
312     yaz_mutex_leave(l->mutex);
313     if (l)
314         l->sorted_ptr = l->sorted_list;
315 }
316
317
318 struct reclist *reclist_create(NMEM nmem)
319 {
320     struct reclist *res = nmem_malloc(nmem, sizeof(struct reclist));
321     res->hash_size = 399;
322     res->hashtable
323         = nmem_malloc(nmem, res->hash_size * sizeof(struct reclist_bucket*));
324     memset(res->hashtable, 0, res->hash_size * sizeof(struct reclist_bucket*));
325     res->nmem = nmem;
326
327     res->sorted_ptr = 0;
328     res->sorted_list = 0;
329
330     res->num_records = 0;
331     res->mutex = 0;
332     pazpar2_mutex_create(&res->mutex, "reclist");
333     return res;
334 }
335
336 void reclist_destroy(struct reclist *l)
337 {
338     if (l)
339     {
340         unsigned i;
341         for (i = 0; i < l->hash_size; i++)
342         {
343             struct reclist_bucket *p;
344             for (p = l->hashtable[i]; p; p = p->hash_next)
345             {
346                 wrbuf_destroy(p->record->relevance_explain1);
347                 wrbuf_destroy(p->record->relevance_explain2);
348             }
349         }
350         yaz_mutex_destroy(&l->mutex);
351     }
352 }
353
354 int reclist_get_num_records(struct reclist *l)
355 {
356     if (l)
357         return l->num_records;
358     return 0;
359 }
360
361 static void merge_cluster(struct reclist *l,
362                           struct relevance *r,
363                           struct record_cluster *dst,
364                           struct record_cluster **src)
365 {
366 #if 0
367     dst->metadata = (*src)->metadata;
368     dst->sortkeys = (*src)->sortkeys;
369     int relevance_score;
370     int *term_frequency_vec;
371     float *term_frequency_vecf;
372     // Set-specific ID for this record
373     char *recid;
374     WRBUF relevance_explain1;
375     WRBUF relevance_explain2;
376     struct record *records;
377 #endif
378 }
379
380 // Insert a record. Return record cluster (newly formed or pre-existing)
381 struct record_cluster *reclist_insert(struct reclist *l,
382                                       struct relevance *r,
383                                       struct conf_service *service,
384                                       struct record *record,
385                                       struct record_metadata_attr *merge_keys,
386                                       int *total)
387 {
388     struct record_cluster *cluster = 0;
389     struct record_metadata_attr *mkl = merge_keys;
390     struct reclist_bucket **p;
391
392     assert(service);
393     assert(l);
394     assert(record);
395     assert(merge_keys);
396     assert(total);
397
398     yaz_mutex_enter(l->mutex);
399
400     for (; mkl; mkl = mkl->next)
401     {
402         const char *merge_key = mkl->value;
403         unsigned int bucket =
404             jenkins_hash((unsigned char*) merge_key) % l->hash_size;
405
406         for (p = &l->hashtable[bucket]; *p; p = &(*p)->hash_next)
407         {
408             struct record_metadata_attr *mkr = (*p)->record->merge_keys;
409             for (; mkr; mkr = mkr->next)
410             {
411                 // We found a matching record. Merge them
412                 if (!strcmp(merge_key, mkr->value))
413                 {
414                     struct record **re;
415
416                     for (re = &(*p)->record->records; *re; re = &(*re)->next)
417                     {
418                         if ((*re)->client == record->client &&
419                             record_compare(record, *re, service))
420                         {
421                             yaz_mutex_leave(l->mutex);
422                             return 0;
423                         }
424                     }
425
426                     if (!cluster)
427                     {
428                         cluster = (*p)->record;
429                         *re = record;
430                         record->next = 0;
431                     }
432                     else
433                         merge_cluster(l, r, cluster, &(*p)->record);
434                 }
435             }
436         }
437     }
438     if (!cluster)
439     {
440         struct reclist_bucket *new =
441             nmem_malloc(l->nmem, sizeof(*new));
442
443         cluster = nmem_malloc(l->nmem, sizeof(*cluster));
444
445         record->next = 0;
446         new->record = cluster;
447         new->hash_next = 0;
448         cluster->records = record;
449
450         cluster->merge_keys = 0;
451         append_merge_keys(&cluster->merge_keys, merge_keys, l->nmem);
452
453         cluster->relevance_score = 0;
454         cluster->recid = cluster->merge_keys->value;
455         (*total)++;
456         cluster->metadata =
457             nmem_malloc(l->nmem,
458                         sizeof(struct record_metadata*) * service->num_metadata);
459         memset(cluster->metadata, 0,
460                sizeof(struct record_metadata*) * service->num_metadata);
461         cluster->sortkeys =
462             nmem_malloc(l->nmem, sizeof(struct record_metadata*) * service->num_sortkeys);
463         memset(cluster->sortkeys, 0,
464                sizeof(union data_types*) * service->num_sortkeys);
465
466         relevance_newrec(r, cluster);
467         cluster->relevance_explain1 = wrbuf_alloc();
468         cluster->relevance_explain2 = wrbuf_alloc();
469         /* attach to hash list */
470         *p = new;
471         l->num_records++;
472         l->sorted_list = l->sorted_ptr = 0;
473     }
474     yaz_mutex_leave(l->mutex);
475     return cluster;
476 }
477
478 int reclist_sortparms_cmp(struct reclist_sortparms *sort1, struct reclist_sortparms *sort2)
479 {
480     int rc;
481     if (sort1 == sort2)
482         return 0;
483     if (sort1 == 0 || sort2 == 0)
484         return 1;
485     rc = strcmp(sort1->name, sort2->name) || sort1->increasing != sort2->increasing || sort1->type != sort2->type;
486     return rc;
487 }
488 /*
489  * Local variables:
490  * c-basic-offset: 4
491  * c-file-style: "Stroustrup"
492  * indent-tabs-mode: nil
493  * End:
494  * vim: shiftwidth=4 tabstop=8 expandtab
495  */
496