SRU 1.1 sorting for ZOOM_query_sortby2
[yaz-moved-to-github.git] / src / zoom-query.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2011 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file zoom-query.c
7  * \brief Implements ZOOM C query interface.
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <assert.h>
14 #include <string.h>
15 #include <errno.h>
16 #include "zoom-p.h"
17
18 #include <yaz/yaz-util.h>
19 #include <yaz/xmalloc.h>
20 #include <yaz/log.h>
21 #include <yaz/pquery.h>
22 #include <yaz/cql.h>
23 #include <yaz/ccl.h>
24 #include <yaz/sortspec.h>
25
26 #define SORT_STRATEGY_Z3950 0
27 #define SORT_STRATEGY_TYPE7 1
28 #define SORT_STRATEGY_CQL 2
29 #define SORT_STRATEGY_SRU11 3
30 #define SORT_STRATEGY_EMBED 4
31
32 struct ZOOM_query_p {
33     Z_Query *z_query;
34     int sort_strategy;
35     Z_SortKeySpecList *sort_spec;
36     int refcount;
37     ODR odr_sort_spec;
38     ODR odr_query;
39     int query_type;
40     char *query_string;
41     WRBUF full_query;
42     WRBUF sru11_sort_spec;
43 };
44
45 static int generate(ZOOM_query s)
46 {
47     if (s->query_string)
48     {
49         Z_External *ext;
50
51         wrbuf_rewind(s->full_query);
52         wrbuf_puts(s->full_query, s->query_string);
53         odr_reset(s->odr_query);
54
55         if (s->sort_spec && s->sort_strategy == SORT_STRATEGY_SRU11)
56         {
57             int r;
58             wrbuf_rewind(s->sru11_sort_spec);
59
60             r = yaz_sort_spec_to_srw_sortkeys(s->sort_spec,
61                                               s->sru11_sort_spec);
62             if (r)
63                 return r;
64         }
65         switch (s->query_type)
66         {
67         case Z_Query_type_1: /* RPN */
68             if (s->sort_spec &&
69                 (s->sort_strategy == SORT_STRATEGY_TYPE7 ||
70                  s->sort_strategy == SORT_STRATEGY_EMBED))
71             {
72                 int r = yaz_sort_spec_to_type7(s->sort_spec, s->full_query);
73                 if (r)
74                     return r;
75             }
76             s->z_query = (Z_Query *) odr_malloc(s->odr_query,
77                                                 sizeof(*s->z_query));
78             s->z_query->which = Z_Query_type_1;
79             s->z_query->u.type_1 = 
80                 p_query_rpn(s->odr_query, wrbuf_cstr(s->full_query));
81             if (!s->z_query->u.type_1)
82             {
83                 s->z_query = 0;
84                 return -1;
85             }
86             break;
87         case Z_Query_type_104: /* CQL */
88             if (s->sort_spec &&
89                 (s->sort_strategy == SORT_STRATEGY_CQL ||
90                  s->sort_strategy == SORT_STRATEGY_EMBED))
91             {
92                 int r = yaz_sort_spec_to_cql(s->sort_spec, s->full_query);
93                 if (r)
94                     return r;
95             }
96             ext = (Z_External *) odr_malloc(s->odr_query, sizeof(*ext));
97             ext->direct_reference = odr_oiddup(s->odr_query,
98                                                yaz_oid_userinfo_cql);
99             ext->indirect_reference = 0;
100             ext->descriptor = 0;
101             ext->which = Z_External_CQL;
102             ext->u.cql = odr_strdup(s->odr_query, wrbuf_cstr(s->full_query));
103             
104             s->z_query = (Z_Query *) odr_malloc(s->odr_query, sizeof(*s->z_query));
105             s->z_query->which = Z_Query_type_104;
106             s->z_query->u.type_104 =  ext;
107             
108             break;
109         }
110     }
111     return 0;
112 }
113
114 const char *ZOOM_query_get_sru11(ZOOM_query s)
115 {
116     if (wrbuf_len(s->sru11_sort_spec))
117         return wrbuf_cstr(s->sru11_sort_spec);
118     return 0;
119 }
120
121 Z_Query *ZOOM_query_get_Z_Query(ZOOM_query s)
122 {
123     return s->z_query;
124 }
125
126 Z_SortKeySpecList *ZOOM_query_get_sortspec(ZOOM_query s)
127 {
128     return s->sort_strategy == SORT_STRATEGY_Z3950 ? s->sort_spec : 0;
129 }
130
131 static void cql2pqf_wrbuf_puts(const char *buf, void *client_data)
132 {
133     WRBUF wrbuf = (WRBUF) client_data;
134     wrbuf_puts(wrbuf, buf);
135 }
136
137 const char *ZOOM_query_get_query_string(ZOOM_query s)
138 {
139     return wrbuf_cstr(s->full_query);
140 }
141
142 /*
143  * Returns an xmalloc()d string containing RPN that corresponds to the
144  * CQL passed in.  On error, sets the Connection object's error state
145  * and returns a null pointer.
146  * ### We could cache CQL parser and/or transformer in Connection.
147  */
148 static char *cql2pqf(ZOOM_connection c, const char *cql)
149 {
150     CQL_parser parser;
151     int error;
152     const char *cqlfile;
153     cql_transform_t trans;
154     char *result = 0;
155
156     parser = cql_parser_create();
157     if ((error = cql_parser_string(parser, cql)) != 0) {
158         cql_parser_destroy(parser);
159         ZOOM_set_error(c, ZOOM_ERROR_CQL_PARSE, cql);
160         return 0;
161     }
162
163     cqlfile = ZOOM_connection_option_get(c, "cqlfile");
164     if (cqlfile == 0) 
165     {
166         ZOOM_set_error(c, ZOOM_ERROR_CQL_TRANSFORM, "no CQL transform file");
167     }
168     else if ((trans = cql_transform_open_fname(cqlfile)) == 0) 
169     {
170         char buf[512];        
171         sprintf(buf, "can't open CQL transform file '%.200s': %.200s",
172                 cqlfile, strerror(errno));
173         ZOOM_set_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
174     }
175     else 
176     {
177         WRBUF wrbuf_result = wrbuf_alloc();
178         error = cql_transform(trans, cql_parser_result(parser),
179                               cql2pqf_wrbuf_puts, wrbuf_result);
180         if (error != 0) {
181             char buf[512];
182             const char *addinfo;
183             error = cql_transform_error(trans, &addinfo);
184             sprintf(buf, "%.200s (addinfo=%.200s)", 
185                     cql_strerror(error), addinfo);
186             ZOOM_set_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
187         }
188         else
189         {
190             result = xstrdup(wrbuf_cstr(wrbuf_result));
191         }
192         cql_transform_close(trans);
193         wrbuf_destroy(wrbuf_result);
194     }
195     cql_parser_destroy(parser);
196     return result;
197 }
198
199
200 ZOOM_API(ZOOM_query)
201     ZOOM_query_create(void)
202 {
203     ZOOM_query s = (ZOOM_query) xmalloc(sizeof(*s));
204
205     s->refcount = 1;
206     s->z_query = 0;
207     s->sort_spec = 0;
208     s->odr_query = odr_createmem(ODR_ENCODE);
209     s->odr_sort_spec = odr_createmem(ODR_ENCODE);
210     s->query_string = 0;
211     s->full_query = wrbuf_alloc();
212     s->sort_strategy = SORT_STRATEGY_Z3950;
213     s->sru11_sort_spec = wrbuf_alloc();
214     return s;
215 }
216
217 ZOOM_API(void)
218     ZOOM_query_destroy(ZOOM_query s)
219 {
220     if (!s)
221         return;
222
223     (s->refcount)--;
224     if (s->refcount == 0)
225     {
226         odr_destroy(s->odr_query);
227         odr_destroy(s->odr_sort_spec);
228         xfree(s->query_string);
229         wrbuf_destroy(s->full_query);
230         wrbuf_destroy(s->sru11_sort_spec);
231         xfree(s);
232     }
233 }
234
235 ZOOM_API(void)
236     ZOOM_query_addref(ZOOM_query s)
237 {
238     s->refcount++;
239 }
240
241
242 ZOOM_API(int)
243     ZOOM_query_prefix(ZOOM_query s, const char *str)
244 {
245     xfree(s->query_string);
246     s->query_string = xstrdup(str);
247     s->query_type = Z_Query_type_1;
248     return generate(s);
249 }
250
251 ZOOM_API(int)
252     ZOOM_query_cql(ZOOM_query s, const char *str)
253 {
254     xfree(s->query_string);
255     s->query_string = xstrdup(str);
256     s->query_type = Z_Query_type_104;
257     return generate(s);
258 }
259
260 /*
261  * Translate the CQL string client-side into RPN which is passed to
262  * the server.  This is useful for server's that don't themselves
263  * support CQL, for which ZOOM_query_cql() is useless.  `conn' is used
264  * only as a place to stash diagnostics if compilation fails; if this
265  * information is not needed, a null pointer may be used.
266  */
267 ZOOM_API(int)
268     ZOOM_query_cql2rpn(ZOOM_query s, const char *str, ZOOM_connection conn)
269 {
270     char *rpn;
271     int ret;
272     ZOOM_connection freeme = 0;
273
274     if (conn == 0)
275         conn = freeme = ZOOM_connection_create(0);
276
277     rpn = cql2pqf(conn, str);
278     if (freeme != 0)
279         ZOOM_connection_destroy(freeme);
280     if (rpn == 0)
281         return -1;
282
283     ret = ZOOM_query_prefix(s, rpn);
284     xfree(rpn);
285     return ret;
286 }
287
288 /*
289  * Analogous in every way to ZOOM_query_cql2rpn(), except that there
290  * is no analogous ZOOM_query_ccl() that just sends uninterpreted CCL
291  * to the server, as the YAZ GFS doesn't know how to handle this.
292  */
293 ZOOM_API(int)
294     ZOOM_query_ccl2rpn(ZOOM_query s, const char *str, const char *config,
295                        int *ccl_error, const char **error_string,
296                        int *error_pos)
297 {
298     int ret;
299     struct ccl_rpn_node *rpn;
300     CCL_bibset bibset = ccl_qual_mk();
301
302     if (config)
303         ccl_qual_buf(bibset, config);
304
305     rpn = ccl_find_str(bibset, str, ccl_error, error_pos);
306     if (!rpn)
307     {
308         *error_string = ccl_err_msg(*ccl_error);
309         ret = -1;
310     }
311     else
312     {
313         WRBUF wr = wrbuf_alloc();
314         ccl_pquery(wr, rpn);
315         ccl_rpn_delete(rpn);
316         ret = ZOOM_query_prefix(s, wrbuf_cstr(wr));
317         wrbuf_destroy(wr);
318     }
319     ccl_qual_rm(&bibset);
320     return ret;
321 }
322
323 ZOOM_API(int)
324     ZOOM_query_sortby(ZOOM_query s, const char *criteria)
325 {
326     return ZOOM_query_sortby2(s, "z3950", criteria);
327 }
328
329 ZOOM_API(int)
330 ZOOM_query_sortby2(ZOOM_query s, const char *strategy, const char *criteria)
331 {
332     if (!strcmp(strategy, "z3950"))
333     {
334         s->sort_strategy = SORT_STRATEGY_Z3950;
335     }
336     else if (!strcmp(strategy, "type7"))
337     {
338         s->sort_strategy = SORT_STRATEGY_TYPE7;
339     }
340     else if (!strcmp(strategy, "cql"))
341     {
342         s->sort_strategy = SORT_STRATEGY_CQL;
343     }
344     else if (!strcmp(strategy, "sru11"))
345     {
346         s->sort_strategy = SORT_STRATEGY_SRU11;
347     }
348     else if (!strcmp(strategy, "embed"))
349     {
350         s->sort_strategy = SORT_STRATEGY_EMBED;
351     }
352     else
353         return -1;
354
355     odr_reset(s->odr_sort_spec);
356     s->sort_spec = yaz_sort_spec(s->odr_sort_spec, criteria);
357     if (!s->sort_spec)
358         return -1;
359     return generate(s);
360 }
361
362 /*
363  * Local variables:
364  * c-basic-offset: 4
365  * c-file-style: "Stroustrup"
366  * indent-tabs-mode: nil
367  * End:
368  * vim: shiftwidth=4 tabstop=8 expandtab
369  */
370