Merge branch 'master' into sru_2_0
[yaz-moved-to-github.git] / src / rpn2solr.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2013 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file
7  * \brief Implements RPN to SOLR conversion
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <assert.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <yaz/rpn2solr.h>
17 #include <yaz/xmalloc.h>
18 #include <yaz/diagbib1.h>
19 #include <yaz/z-core.h>
20 #include <yaz/wrbuf.h>
21
22 static void wrbuf_vputs(const char *buf, void *client_data)
23 {
24     wrbuf_write((WRBUF) client_data, buf, strlen(buf));
25 }
26
27 static const char *lookup_index_from_string_attr(Z_AttributeList *attributes)
28 {
29     int j;
30     int server_choice = 1;
31     for (j = 0; j < attributes->num_attributes; j++)
32     {
33         Z_AttributeElement *ae = attributes->attributes[j];
34         if (*ae->attributeType == 1) /* use attribute */
35         {
36             if (ae->which == Z_AttributeValue_complex)
37             {
38                 Z_ComplexAttribute *ca = ae->value.complex;
39                 int i;
40                 for (i = 0; i < ca->num_list; i++)
41                 {
42                     Z_StringOrNumeric *son = ca->list[i];
43                     if (son->which == Z_StringOrNumeric_string)
44                         return son->u.string;
45                 }
46             }
47             server_choice = 0; /* not serverChoice because we have use attr */
48         }
49     }
50     if (server_choice)
51         return "cql.serverChoice";
52     return 0;
53 }
54
55 static const char *lookup_relation_index_from_attr(Z_AttributeList *attributes)
56 {
57     int j;
58     for (j = 0; j < attributes->num_attributes; j++)
59     {
60         Z_AttributeElement *ae = attributes->attributes[j];
61         if (*ae->attributeType == 2) /* relation attribute */
62         {
63             if (ae->which == Z_AttributeValue_numeric)
64             {
65                 /* Only support for numeric relation */
66                 Odr_int *relation = ae->value.numeric;
67                 /* map this numeric to representation in SOLR */
68                 switch (*relation)
69                 {
70                     /* Unsure on whether this is the relation attribute constants? */
71                 case Z_ProximityOperator_Prox_lessThan:
72                     return "<";
73                 case Z_ProximityOperator_Prox_lessThanOrEqual:
74                     return "le";
75                 case Z_ProximityOperator_Prox_equal:
76                     return ":";
77                 case Z_ProximityOperator_Prox_greaterThanOrEqual:
78                     return "ge";
79                 case Z_ProximityOperator_Prox_greaterThan:
80                     return ">";
81                 case Z_ProximityOperator_Prox_notEqual:
82                     return 0;
83                 case 100:
84                     /* phonetic is not implemented */
85                     return 0;
86                 case 101:
87                     /* stem is not not implemented */
88                     return 0;
89                 case 102:
90                     /* relevance is supported in SOLR, but not implemented yet */
91                     return 0;
92                 default:
93                     /* Invalid relation */
94                     return 0;
95                 }
96             }
97             else {
98                 /*  Can we have a complex relation value?
99                     Should we implement something?
100                 */
101             }
102         }
103     }
104     return ":";
105 }
106
107 struct solr_attr {
108     const char *index; 
109     const char *relation;
110     const char *term;
111     int  is_range;
112     const char *begin;
113     const char *close;
114 };
115
116 static int rpn2solr_attr(solr_transform_t ct,
117                          Z_AttributeList *attributes, WRBUF w, struct solr_attr *solr_attr)
118 {
119     const char *relation  = solr_lookup_reverse(ct, "relation.", attributes);
120     const char *index     = solr_lookup_reverse(ct, "index.", attributes);
121     const char *structure = solr_lookup_reverse(ct, "structure.", attributes);
122     /* Assume this is not a range */
123     solr_attr->is_range = 0;
124
125     /* if transform (properties) do not match, we'll just use a USE string attribute (bug #2978) */
126     if (!index)
127         index = lookup_index_from_string_attr(attributes);
128
129     /* Attempt to fix bug #2978: Look for a relation attribute */
130     if (!relation)
131         relation = lookup_relation_index_from_attr(attributes);
132
133     if (!index)
134     {
135         solr_transform_set_error(ct, YAZ_BIB1_UNSUPP_USE_ATTRIBUTE, 0);
136         return -1;
137     }
138     /* for serverChoice we omit index+relation+structure */
139     if (strcmp(index, "cql.serverChoice"))
140     {
141         solr_attr->index = index;
142         if (relation)
143         {
144             if (!strcmp(relation, "exact")) {
145                 /* TODO Exact match does not exists in SOLR. Need to use specific field type  */
146                 relation = ":";
147             }
148             else if (!strcmp(relation, "eq")) {
149                 relation = ":";
150             }
151             else if (!strcmp(relation, "<")) {
152                 solr_attr->is_range = 1;
153                 solr_attr->begin = "[* TO ";
154                 solr_attr->close = "}";
155             }
156             else if (!strcmp(relation, "le")) {
157                 solr_attr->is_range = 2;
158                 solr_attr->begin = "[* TO ";
159                 solr_attr->close = "]";
160             }
161             else if (!strcmp(relation, "ge")) {
162                 solr_attr->is_range = 4;
163                 solr_attr->begin = "[";
164                 solr_attr->close = " TO *]";
165             }
166             else if (!strcmp(relation, ">")) {
167                 solr_attr->is_range = 5;
168                 solr_attr->begin = "{";
169                 solr_attr->close = " TO *]";
170             }
171             solr_attr->relation = relation;
172         }
173         // TODO is this valid for Solr? 
174         solr_attr->term = 0;
175         if (structure)
176         {
177             if (strcmp(structure, "*"))
178             {
179                wrbuf_puts(w, "/");
180                wrbuf_puts(w, structure);
181                wrbuf_puts(w, " ");
182                solr_attr->index = 0;  
183             }
184
185         }
186     }
187     else 
188         solr_attr->index = 0;
189     return 0;
190 }
191
192 static Odr_int get_truncation(Z_AttributesPlusTerm *apt)
193 {
194     int j;
195     Z_AttributeList *attributes = apt->attributes;
196     for (j = 0; j < attributes->num_attributes; j++)
197     {
198         Z_AttributeElement *ae = attributes->attributes[j];
199         if (*ae->attributeType == 5) /* truncation attribute */
200         {
201             if (ae->which == Z_AttributeValue_numeric)
202             {
203                 return *(ae->value.numeric);
204             }
205             else if (ae->which == Z_AttributeValue_complex) {
206                 ;
207                 //yaz_log(YLOG_DEBUG, "Z_Attribute_complex");
208                 /* Complex: Shouldn't happen */
209             }
210         }
211     }
212     /* No truncation given */
213     return 0;
214 }
215
216 #define SOLR_SPECIAL "+-&|!(){}[]^\"~*?:\\"
217
218 static int rpn2solr_simple(solr_transform_t ct,
219                            Z_Operand *q, WRBUF w, struct solr_attr *solr_attr)
220 {
221     int ret = 0;
222     if (q->which != Z_Operand_APT)
223     {
224         ret = -1;
225         solr_transform_set_error(ct, YAZ_BIB1_RESULT_SET_UNSUPP_AS_A_SEARCH_TERM, 0);
226     }
227     else
228     {
229         Z_AttributesPlusTerm *apt = q->u.attributesPlusTerm;
230         Z_Term *term = apt->term;
231         const char *sterm = 0;
232         size_t lterm = 0;
233         Odr_int trunc = get_truncation(apt);
234
235         wrbuf_rewind(w);
236         ret = rpn2solr_attr(ct, apt->attributes, w, solr_attr);
237
238         if (trunc == 0 || trunc == 1 || trunc == 100 || trunc == 104)
239             ;
240         else
241         {
242             solr_transform_set_error(ct, YAZ_BIB1_UNSUPP_TRUNCATION_ATTRIBUTE, 0);
243             return -1;
244         }
245         switch (term->which)
246         {
247         case Z_Term_general:
248             lterm = term->u.general->len;
249             sterm = (const char *) term->u.general->buf;
250             break;
251         case Z_Term_numeric:
252             wrbuf_printf(w, ODR_INT_PRINTF, *term->u.numeric);
253             break;
254         case Z_Term_characterString:
255             sterm = term->u.characterString;
256             lterm = strlen(sterm);
257             break;
258         default:
259             ret = -1;
260             solr_transform_set_error(ct, YAZ_BIB1_TERM_TYPE_UNSUPP, 0);
261         }
262
263         if (sterm)
264         {
265             size_t i;
266             int must_quote = 0;
267
268             for (i = 0 ; i < lterm; i++)
269                 if (sterm[i] == ' ')
270                     must_quote = 1;
271             if (must_quote)
272                 wrbuf_puts(w, "\"");
273             for (i = 0 ; i < lterm; i++)
274             {
275                 if (sterm[i] == '\\' && i < lterm - 1)
276                 {
277                     i++;
278                     if (strchr(SOLR_SPECIAL, sterm[i]))
279                         wrbuf_putc(w, '\\');
280                     wrbuf_putc(w, sterm[i]);
281                 }
282                 else if (sterm[i] == '?' && trunc == 104)
283                 {
284                     wrbuf_putc(w, '*');
285                 }
286                 else if (sterm[i] == '#' && trunc == 104)
287                 {
288                     wrbuf_putc(w, '?');
289                 }
290                 else if (strchr(SOLR_SPECIAL, sterm[i]))
291                 {
292                     wrbuf_putc(w, '\\');
293                     wrbuf_putc(w, sterm[i]);
294                 }
295                 else
296                     wrbuf_putc(w, sterm[i]);
297             }
298             if (trunc == 1)
299                 wrbuf_puts(w, "*");
300             if (must_quote)
301                 wrbuf_puts(w, "\"");
302         }
303         if (ret == 0) { 
304             solr_attr->term = wrbuf_cstr(w);
305         }
306         
307     }
308     return ret;
309 };
310
311 static int solr_write_range(void (*pr)(const char *buf, void *client_data),
312                             void *client_data,
313                             struct solr_attr *solr_attr_left, 
314                             struct solr_attr *solr_attr_right)
315 {
316     pr(solr_attr_left->index, client_data);
317     pr(":", client_data);
318     pr(solr_attr_left->begin, client_data);
319     pr(solr_attr_left->term,  client_data);
320     pr(" TO ", client_data);
321     pr(solr_attr_right->term,  client_data);
322     pr(solr_attr_right->close, client_data);
323     return 0;
324 }; 
325
326 static int solr_write_structure(void (*pr)(const char *buf, void *client_data),
327                             void *client_data,
328                             struct solr_attr *solr_attr)
329 {
330     if (solr_attr->index) {
331         pr(solr_attr->index, client_data);
332         pr(":", client_data);
333     }
334     if (solr_attr->is_range) {
335         pr(solr_attr->begin, client_data);
336         pr(solr_attr->term,  client_data);
337         pr(solr_attr->close, client_data);
338     }
339     else if (solr_attr->term) 
340         pr(solr_attr->term,  client_data);
341     return 0;
342 }; 
343
344
345
346 static int solr_write_and_or_range(void (*pr)(const char *buf, void *client_data),
347                              void *client_data,
348                              struct solr_attr *solr_attr_left, 
349                              struct solr_attr *solr_attr_right)
350 {
351     if (solr_attr_left->is_range && 
352         solr_attr_right->is_range && 
353         !strcmp(solr_attr_left->index, solr_attr_right->index)) 
354     {
355         if (solr_attr_left->is_range > 3 && solr_attr_right->is_range < 3)
356             return solr_write_range(pr, client_data, solr_attr_left, solr_attr_right); 
357         else if (solr_attr_left->is_range < 3 && solr_attr_right->is_range > 3)
358             return solr_write_range(pr, client_data, solr_attr_right, solr_attr_left); 
359     }
360     solr_write_structure(pr, client_data, solr_attr_left);
361     pr(" AND ", client_data);
362     solr_write_structure(pr, client_data, solr_attr_right);
363     return 0;
364 }
365
366 static void solr_attr_init(struct solr_attr *solr_attr) {
367     solr_attr->index = 0; 
368     solr_attr->relation = 0;
369     solr_attr->is_range = 0; 
370     solr_attr->term = 0; 
371 }
372
373
374 static int rpn2solr_structure(solr_transform_t ct,
375                               void (*pr)(const char *buf, void *client_data),
376                               void *client_data,
377                               Z_RPNStructure *q, int nested,
378                               WRBUF wa, struct solr_attr *solr_attr)
379 {
380     if (q->which == Z_RPNStructure_simple) {
381         solr_attr_init(solr_attr);
382         return rpn2solr_simple(ct, q->u.simple, wa, solr_attr);
383     }
384     else
385     {
386         Z_Operator *op = q->u.complex->roperator;
387         int r;
388         struct solr_attr solr_attr_left, solr_attr_right;
389         WRBUF w_left = wrbuf_alloc();
390         WRBUF w_right = wrbuf_alloc();
391
392         if (nested)
393             pr("(", client_data);
394
395         solr_attr_init(&solr_attr_left);
396         r = rpn2solr_structure(ct, pr, client_data, q->u.complex->s1, 1, w_left, &solr_attr_left);
397
398
399         if (r) {
400             wrbuf_destroy(w_left);
401             return r;
402         }
403         solr_attr_init(&solr_attr_right);
404
405         r = rpn2solr_structure(ct, pr, client_data, q->u.complex->s2, 1, w_right, &solr_attr_right);
406         if (r) {
407             wrbuf_destroy(w_left);
408             wrbuf_destroy(w_right);
409             return r;
410         }
411
412         switch(op->which)
413         {
414         case  Z_Operator_and:
415             solr_write_and_or_range(pr, client_data, &solr_attr_left, &solr_attr_right);
416             break;
417         case  Z_Operator_or:
418             solr_write_structure(pr, client_data, &solr_attr_left);
419             pr(" OR ", client_data);
420             solr_write_structure(pr, client_data, &solr_attr_right);
421             break;
422         case  Z_Operator_and_not:
423             solr_write_structure(pr, client_data, &solr_attr_left);
424             pr(" AND NOT ", client_data);
425             solr_write_structure(pr, client_data, &solr_attr_right);
426             break;
427         case  Z_Operator_prox:
428             solr_transform_set_error(ct, YAZ_BIB1_UNSUPP_SEARCH, 0);
429             wrbuf_destroy(w_left);
430             wrbuf_destroy(w_right);
431             return -1;
432         }
433
434         if (nested)
435             pr(")", client_data);
436         
437         solr_attr_init(solr_attr);
438         wrbuf_destroy(w_left);
439         wrbuf_destroy(w_right);
440         return r;
441     }
442 }
443
444 int solr_transform_rpn2solr_stream(solr_transform_t ct,
445                                    void (*pr)(const char *buf, void *client_data),
446                                    void *client_data,
447                                    Z_RPNQuery *q)
448 {
449     int r;
450     WRBUF w = wrbuf_alloc();
451     struct solr_attr solr_attr;
452     solr_transform_set_error(ct, 0, 0);
453     solr_attr_init(&solr_attr);
454     r = rpn2solr_structure(ct, pr, client_data, q->RPNStructure, 0, w, &solr_attr);
455     solr_write_structure(pr, client_data, &solr_attr);
456     wrbuf_destroy(w);
457     return r;
458 }
459
460
461 int solr_transform_rpn2solr_wrbuf(solr_transform_t ct,
462                                   WRBUF w,
463                                   Z_RPNQuery *q)
464 {
465     return solr_transform_rpn2solr_stream(ct, wrbuf_vputs, w, q);
466 }
467
468 /*
469  * Local variables:
470  * c-basic-offset: 4
471  * c-file-style: "Stroustrup"
472  * indent-tabs-mode: nil
473  * End:
474  * vim: shiftwidth=4 tabstop=8 expandtab
475  */
476