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