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