221e64bdfde80377bd59910f9a07838b27ed0949
[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 0;
73                 case Z_ProximityOperator_Prox_lessThanOrEqual:
74                     return 0;
75                 case Z_ProximityOperator_Prox_equal:
76                     return ":";
77                 case Z_ProximityOperator_Prox_greaterThanOrEqual:
78                     return 0;
79                 case Z_ProximityOperator_Prox_greaterThan:
80                     return 0;
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         
237         ret = rpn2solr_attr(ct, apt->attributes, w, solr_attr);
238
239         if (trunc == 0 || trunc == 1 || trunc == 100 || trunc == 104)
240             ;
241         else
242         {
243             solr_transform_set_error(ct, YAZ_BIB1_UNSUPP_TRUNCATION_ATTRIBUTE, 0);
244             return -1;
245         }
246         switch (term->which)
247         {
248         case Z_Term_general:
249             lterm = term->u.general->len;
250             sterm = (const char *) term->u.general->buf;
251             break;
252         case Z_Term_numeric:
253             wrbuf_printf(w, ODR_INT_PRINTF, *term->u.numeric);
254             break;
255         case Z_Term_characterString:
256             sterm = term->u.characterString;
257             lterm = strlen(sterm);
258             break;
259         default:
260             ret = -1;
261             solr_transform_set_error(ct, YAZ_BIB1_TERM_TYPE_UNSUPP, 0);
262         }
263
264         if (sterm)
265         {
266             size_t i;
267             int must_quote = 0;
268
269             for (i = 0 ; i < lterm; i++)
270                 if (sterm[i] == ' ')
271                     must_quote = 1;
272             if (must_quote)
273                 wrbuf_puts(w, "\"");
274             for (i = 0 ; i < lterm; i++)
275             {
276                 if (sterm[i] == '\\' && i < lterm - 1)
277                 {
278                     i++;
279                     if (strchr(SOLR_SPECIAL, sterm[i]))
280                         wrbuf_putc(w, '\\');
281                     wrbuf_putc(w, sterm[i]);
282                 }
283                 else if (sterm[i] == '?' && trunc == 104)
284                 {
285                     wrbuf_putc(w, '*');
286                 }
287                 else if (sterm[i] == '#' && trunc == 104)
288                 {
289                     wrbuf_putc(w, '?');
290                 }
291                 else if (strchr(SOLR_SPECIAL, sterm[i]))
292                 {
293                     wrbuf_putc(w, '\\');
294                     wrbuf_putc(w, sterm[i]);
295                 }
296                 else
297                     wrbuf_putc(w, sterm[i]);
298             }
299             if (trunc == 1)
300                 wrbuf_puts(w, "*");
301             if (must_quote)
302                 wrbuf_puts(w, "\"");
303         }
304         if (ret == 0) { 
305             solr_attr->term = wrbuf_cstr(w);
306         }
307         
308     }
309     return ret;
310 };
311
312 static int solr_write_range(void (*pr)(const char *buf, void *client_data),
313                             void *client_data,
314                             struct solr_attr *solr_attr_left, 
315                             struct solr_attr *solr_attr_right)
316 {
317     pr(solr_attr_left->index, client_data);
318     pr(":", client_data);
319     pr(solr_attr_left->begin, client_data);
320     pr(solr_attr_left->term,  client_data);
321     pr(" TO ", client_data);
322     pr(solr_attr_right->term,  client_data);
323     pr(solr_attr_right->close, client_data);
324     return 0;
325 }; 
326
327 static int solr_write_structure(void (*pr)(const char *buf, void *client_data),
328                             void *client_data,
329                             struct solr_attr *solr_attr)
330 {
331     if (solr_attr->index) {
332         pr(solr_attr->index, client_data);
333         pr(":", client_data);
334     }
335     if (solr_attr->is_range) {
336         pr(solr_attr->begin, client_data);
337         pr(solr_attr->term,  client_data);
338         pr(solr_attr->close, client_data);
339     }
340     else if (solr_attr->term) 
341         pr(solr_attr->term,  client_data);
342     return 0;
343 }; 
344
345
346
347 static int solr_write_and_or_range(void (*pr)(const char *buf, void *client_data),
348                              void *client_data,
349                              struct solr_attr *solr_attr_left, 
350                              struct solr_attr *solr_attr_right)
351 {
352     if (solr_attr_left->is_range && 
353         solr_attr_right->is_range && 
354         !strcmp(solr_attr_left->index, solr_attr_right->index)) 
355     {
356         if (solr_attr_left->is_range > 3 && solr_attr_right->is_range < 3)
357             return solr_write_range(pr, client_data, solr_attr_left, solr_attr_right); 
358         else if (solr_attr_left->is_range < 3 && solr_attr_right->is_range > 3)
359             return solr_write_range(pr, client_data, solr_attr_right, solr_attr_left); 
360     }
361     solr_write_structure(pr, client_data, solr_attr_left);
362     pr(" AND ", client_data);
363     solr_write_structure(pr, client_data, solr_attr_right);
364     return 0;
365 }
366
367 static void solr_attr_init(struct solr_attr *solr_attr) {
368     solr_attr->index = 0; 
369     solr_attr->relation = 0;
370     solr_attr->is_range = 0; 
371     solr_attr->term = 0; 
372 }
373
374
375 static int rpn2solr_structure(solr_transform_t ct,
376                               void (*pr)(const char *buf, void *client_data),
377                               void *client_data,
378                               Z_RPNStructure *q, int nested,
379                               WRBUF wa, struct solr_attr *solr_attr)
380 {
381     if (q->which == Z_RPNStructure_simple) {
382         solr_attr_init(solr_attr);
383         return rpn2solr_simple(ct, q->u.simple, wa, solr_attr);
384     }
385     else
386     {
387         Z_Operator *op = q->u.complex->roperator;
388         int r;
389
390         if (nested)
391             pr("(", client_data);
392
393         struct solr_attr solr_attr_left;
394         solr_attr_init(&solr_attr_left);
395         WRBUF w_left = wrbuf_alloc();
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         struct solr_attr solr_attr_right;
404         solr_attr_init(&solr_attr_right);
405         WRBUF w_right = wrbuf_alloc();
406
407         r = rpn2solr_structure(ct, pr, client_data, q->u.complex->s2, 1, w_right, &solr_attr_right);
408         if (r) {
409             wrbuf_destroy(w_left);
410             wrbuf_destroy(w_right);
411             return r;
412         }
413             
414         switch(op->which)
415         {
416         case  Z_Operator_and:
417             solr_write_and_or_range(pr, client_data, &solr_attr_left, &solr_attr_right);
418             break;
419         case  Z_Operator_or:
420             solr_write_structure(pr, client_data, &solr_attr_left);
421             pr(" OR ", client_data);
422             solr_write_structure(pr, client_data, &solr_attr_right);
423             break;
424         case  Z_Operator_and_not:
425             solr_write_structure(pr, client_data, &solr_attr_left);
426             pr(" AND NOT ", client_data);
427             solr_write_structure(pr, client_data, &solr_attr_right);
428             break;
429         case  Z_Operator_prox:
430             solr_transform_set_error(ct, YAZ_BIB1_UNSUPP_SEARCH, 0);
431             wrbuf_destroy(w_left);
432             wrbuf_destroy(w_right);
433             return -1;
434         }
435
436         if (nested)
437             pr(")", client_data);
438         
439         solr_attr_init(solr_attr);
440         wrbuf_destroy(w_left);
441         wrbuf_destroy(w_right);
442         return r;
443     }
444 }
445
446 int solr_transform_rpn2solr_stream(solr_transform_t ct,
447                                    void (*pr)(const char *buf, void *client_data),
448                                    void *client_data,
449                                    Z_RPNQuery *q)
450 {
451     int r;
452     WRBUF w = wrbuf_alloc();
453     solr_transform_set_error(ct, 0, 0);
454     struct solr_attr solr_attr;
455     solr_attr_init(&solr_attr);
456     r = rpn2solr_structure(ct, pr, client_data, q->RPNStructure, 0, w, &solr_attr);
457     solr_write_structure(pr, client_data, &solr_attr);
458     wrbuf_destroy(w);
459     return r;
460 }
461
462
463 int solr_transform_rpn2solr_wrbuf(solr_transform_t ct,
464                                   WRBUF w,
465                                   Z_RPNQuery *q)
466 {
467     return solr_transform_rpn2solr_stream(ct, wrbuf_vputs, w, q);
468 }
469
470 /*
471  * Local variables:
472  * c-basic-offset: 4
473  * c-file-style: "Stroustrup"
474  * indent-tabs-mode: nil
475  * End:
476  * vim: shiftwidth=4 tabstop=8 expandtab
477  */
478