Start work on multiple mergekeys - tests do not pass
[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 // Insert a record. Return record cluster (newly formed or pre-existing)
362 struct record_cluster *reclist_insert(struct reclist *l,
363                                       struct conf_service *service,
364                                       struct record *record,
365                                       struct record_metadata_attr *merge_keys,
366                                       int *total)
367 {
368     struct record_cluster *cluster = 0;
369     struct record_metadata_attr *mkl = merge_keys;
370     struct reclist_bucket **p;
371
372     assert(service);
373     assert(l);
374     assert(record);
375     assert(merge_keys);
376     assert(total);
377
378     yaz_mutex_enter(l->mutex);
379
380     for (; mkl; mkl = mkl->next)
381     {
382         const char *merge_key = mkl->value;
383         unsigned int bucket =
384             jenkins_hash((unsigned char*) merge_key) % l->hash_size;
385
386         for (p = &l->hashtable[bucket]; *p; p = &(*p)->hash_next)
387         {
388             struct record_metadata_attr *mkr = (*p)->record->merge_keys;
389             for (; mkr; mkr = mkr->next)
390             {
391                 // We found a matching record. Merge them
392                 if (!strcmp(merge_key, mkr->value))
393                 {
394                     struct record **re;
395
396                     cluster = (*p)->record;
397                     for (re = &cluster->records; *re; re = &(*re)->next)
398                     {
399                         if ((*re)->client == record->client &&
400                             record_compare(record, *re, service))
401                         {
402                             yaz_mutex_leave(l->mutex);
403                             return 0;
404                         }
405                     }
406                     *re = record;
407                     record->next = 0;
408                     goto out;
409                 }
410             }
411         }
412     }
413 out:
414     if (!cluster)
415     {
416         struct reclist_bucket *new =
417             nmem_malloc(l->nmem, sizeof(*new));
418
419         cluster = nmem_malloc(l->nmem, sizeof(*cluster));
420
421         record->next = 0;
422         new->record = cluster;
423         new->hash_next = 0;
424         cluster->records = record;
425
426         cluster->merge_keys = 0;
427         append_merge_keys(&cluster->merge_keys, merge_keys, l->nmem);
428
429         cluster->relevance_score = 0;
430         cluster->term_frequency_vec = 0;
431         cluster->recid = merge_keys->value;
432         (*total)++;
433         cluster->metadata =
434             nmem_malloc(l->nmem,
435                         sizeof(struct record_metadata*) * service->num_metadata);
436         memset(cluster->metadata, 0,
437                sizeof(struct record_metadata*) * service->num_metadata);
438         cluster->sortkeys =
439             nmem_malloc(l->nmem, sizeof(struct record_metadata*) * service->num_sortkeys);
440         memset(cluster->sortkeys, 0,
441                sizeof(union data_types*) * service->num_sortkeys);
442
443         cluster->relevance_explain1 = wrbuf_alloc();
444         cluster->relevance_explain2 = wrbuf_alloc();
445         /* attach to hash list */
446         *p = new;
447         l->num_records++;
448         l->sorted_list = l->sorted_ptr = 0;
449     }
450     yaz_mutex_leave(l->mutex);
451     return cluster;
452 }
453
454 int reclist_sortparms_cmp(struct reclist_sortparms *sort1, struct reclist_sortparms *sort2)
455 {
456     int rc;
457     if (sort1 == sort2)
458         return 0;
459     if (sort1 == 0 || sort2 == 0)
460         return 1;
461     rc = strcmp(sort1->name, sort2->name) || sort1->increasing != sort2->increasing || sort1->type != sort2->type;
462     return rc;
463 }
464 /*
465  * Local variables:
466  * c-basic-offset: 4
467  * c-file-style: "Stroustrup"
468  * indent-tabs-mode: nil
469  * End:
470  * vim: shiftwidth=4 tabstop=8 expandtab
471  */
472