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