* Copyright (C) 1995-2005, Index Data ApS
* See the file LICENSE for details.
*
- * $Id: zoom-c.c,v 1.46 2005-08-09 18:29:35 adam Exp $
+ * $Id: zoom-c.c,v 1.62 2005-12-21 16:41:36 mike Exp $
*/
/**
* \file zoom-c.c
#include <assert.h>
#include <string.h>
+#include <errno.h>
#include "zoom-p.h"
#include <yaz/yaz-util.h>
#include <yaz/charneg.h>
#include <yaz/ill.h>
#include <yaz/srw.h>
+#include <yaz/cql.h>
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
static zoom_ret ZOOM_connection_send_init (ZOOM_connection c);
static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out);
+static char *cql2pqf(ZOOM_connection c, const char *cql);
+
static void initlog()
{
static void clear_error (ZOOM_connection c)
{
-
+ /*
+ * If an error is tied to an operation then it's ok to clear: for
+ * example, a diagnostic returned from a search is cleared by a
+ * subsequent search. However, problems such as Connection Lost
+ * or Init Refused are not cleared, because they are not
+ * recoverable: doing another search doesn't help.
+ */
switch (c->error)
{
case ZOOM_ERROR_CONNECT:
else
c->host_port = xstrdup(host);
+ {
+ /*
+ * If the "<scheme>:" part of the host string is preceded by one
+ * or more comma-separated <name>=<value> pairs, these are taken
+ * to be options to be set on the connection object. Among other
+ * applications, this facility can be used to embed authentication
+ * in a host string:
+ * user=admin,password=secret,tcp:localhost:9999
+ */
+ char *remainder = c->host_port;
+ char *pcolon = strchr(remainder, ':');
+ char *pcomma;
+ char *pequals;
+ while ((pcomma = strchr(remainder, ',')) != 0 &&
+ (pcolon == 0 || pcomma < pcolon)) {
+ *pcomma = '\0';
+ if ((pequals = strchr(remainder, '=')) != 0) {
+ *pequals = '\0';
+ /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
+ ZOOM_connection_option_set(c, remainder, pequals+1);
+ }
+ remainder = pcomma+1;
+ }
+
+ if (remainder != c->host_port) {
+ xfree(c->host_port);
+ c->host_port = xstrdup(remainder);
+ /*printf("# reset hp='%s'\n", remainder);*/
+ }
+ }
+
ZOOM_options_set(c->options, "host", c->host_port);
val = ZOOM_options_get (c->options, "cookie");
return 0;
}
+/*
+ * Translate the CQL string client-side into RPN which is passed to
+ * the server. This is useful for server's that don't themselves
+ * support CQL, for which ZOOM_query_cql() is useless. `conn' is used
+ * only as a place to stash diagnostics if compilation fails; if this
+ * information is not needed, a null pointer may be used.
+ */
+ZOOM_API(int)
+ZOOM_query_cql2rpn(ZOOM_query s, const char *str, ZOOM_connection conn)
+{
+ char *rpn;
+ int ret;
+
+ yaz_log(log_details, "%p ZOOM_query_cql2rpn str=%s conn=%p", s, str, conn);
+ if (conn == 0)
+ conn = ZOOM_connection_create(0);
+
+ if ((rpn = cql2pqf(conn, str)) == 0)
+ return -1;
+
+ ret = ZOOM_query_prefix(s, rpn);
+ xfree(rpn);
+ return ret;
+}
+
ZOOM_API(int)
ZOOM_query_sortby(ZOOM_query s, const char *criteria)
{
return r;
}
+/*
+ * This is the old result-set sorting API, which is maintained only
+ * for the sake of binary compatibility. There is no reason ever to
+ * use this rather than ZOOM_resultset_sort1().
+ */
ZOOM_API(void)
- ZOOM_resultset_sort(ZOOM_resultset r,
- const char *sort_type, const char *sort_spec)
+ZOOM_resultset_sort(ZOOM_resultset r,
+ const char *sort_type, const char *sort_spec)
+{
+ (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
+}
+
+ZOOM_API(int)
+ZOOM_resultset_sort1(ZOOM_resultset r,
+ const char *sort_type, const char *sort_spec)
{
ZOOM_connection c = r->connection;
ZOOM_task task;
+ ZOOM_query newq;
+
+ newq = ZOOM_query_create();
+ if (ZOOM_query_sortby(newq, sort_spec) < 0)
+ return -1;
yaz_log(log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
r, r, sort_type, sort_spec);
if (!c)
- return;
+ return 0;
if (c->host_port && c->proto == PROTO_HTTP)
{
ZOOM_resultset_cache_reset(r);
task = ZOOM_connection_add_task (c, ZOOM_TASK_SORT);
task->u.sort.resultset = r;
- task->u.sort.q = ZOOM_query_create();
- ZOOM_query_sortby(task->u.sort.q, sort_spec);
+ task->u.sort.q = newq;
ZOOM_resultset_addref (r);
while (ZOOM_event (1, &c))
;
}
+
+ return 0;
}
ZOOM_API(void)
}
}
c->state = STATE_IDLE;
- set_ZOOM_error(c, ZOOM_ERROR_CONNECT, effective_host);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
return zoom_complete;
}
ZOOM_options_get(c->options, "implementationName"),
odr_prepend(c->odr_out, "ZOOM-C", ireq->implementationName));
- version = odr_strdup(c->odr_out, "$Revision: 1.46 $");
+ version = odr_strdup(c->odr_out, "$Revision: 1.62 $");
if (strlen(version) > 10) /* check for unexpanded CVS strings */
version[strlen(version)-2] = '\0';
ireq->implementationVersion = odr_prepend(c->odr_out,
handle_records (c, pr->records, 1);
}
+static void handle_queryExpressionTerm(ZOOM_options opt, const char *name,
+ Z_Term *term)
+{
+ switch (term->which)
+ {
+ case Z_Term_general:
+ ZOOM_options_setl(opt, name,
+ (const char *)(term->u.general->buf),
+ term->u.general->len);
+ break;
+ case Z_Term_characterString:
+ ZOOM_options_set(opt, name, term->u.characterString);
+ break;
+ case Z_Term_numeric:
+ ZOOM_options_set_int(opt, name, *term->u.numeric);
+ break;
+ }
+}
+
+static void handle_queryExpression(ZOOM_options opt, const char *name,
+ Z_QueryExpression *exp)
+{
+ char opt_name[80];
+
+ switch (exp->which)
+ {
+ case Z_QueryExpression_term:
+ if (exp->u.term && exp->u.term->queryTerm)
+ {
+ sprintf(opt_name, "%s.term", name);
+ handle_queryExpressionTerm(opt, opt_name, exp->u.term->queryTerm);
+ }
+ break;
+ case Z_QueryExpression_query:
+ break;
+ }
+}
+
+static void handle_searchResult(ZOOM_connection c, ZOOM_resultset resultset,
+ Z_OtherInformation *o)
+{
+ int i;
+ for (i = 0; o && i < o->num_elements; i++)
+ {
+ if (o->list[i]->which == Z_OtherInfo_externallyDefinedInfo)
+ {
+ Z_External *ext = o->list[i]->information.externallyDefinedInfo;
+
+ if (ext->which == Z_External_searchResult1)
+ {
+ int j;
+ Z_SearchInfoReport *sr = ext->u.searchResult1;
+
+ if (sr->num)
+ ZOOM_options_set_int(
+ resultset->options, "searchresult.size", sr->num);
+
+ for (j = 0; j < sr->num; j++)
+ {
+ Z_SearchInfoReport_s *ent =
+ ext->u.searchResult1->elements[j];
+ char pref[80];
+
+ sprintf(pref, "searchresult.%d", j);
+
+ if (ent->subqueryId)
+ {
+ char opt_name[80];
+ sprintf(opt_name, "%s.id", pref);
+ ZOOM_options_set(resultset->options, opt_name,
+ ent->subqueryId);
+ }
+ if (ent->subqueryExpression)
+ {
+ char opt_name[80];
+ sprintf(opt_name, "%s.subquery", pref);
+ handle_queryExpression(resultset->options, opt_name,
+ ent->subqueryExpression);
+ }
+ if (ent->subqueryInterpretation)
+ {
+ char opt_name[80];
+ sprintf(opt_name, "%s.interpretation", pref);
+ handle_queryExpression(resultset->options, opt_name,
+ ent->subqueryInterpretation);
+ }
+ if (ent->subqueryRecommendation)
+ {
+ char opt_name[80];
+ sprintf(opt_name, "%s.recommendation", pref);
+ handle_queryExpression(resultset->options, opt_name,
+ ent->subqueryRecommendation);
+ }
+ if (ent->subqueryCount)
+ {
+ char opt_name[80];
+ sprintf(opt_name, "%s.count", pref);
+ ZOOM_options_set_int(resultset->options, opt_name,
+ *ent->subqueryCount);
+ }
+ }
+ }
+ }
+ }
+}
+
static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
{
ZOOM_resultset resultset;
if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
return ;
-
+
event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
ZOOM_connection_put_event(c, event);
resultset = c->tasks->u.search.resultset;
+ handle_searchResult(c, resultset, sr->additionalSearchInfo);
+
resultset->size = *sr->resultCount;
handle_records (c, sr->records, 0);
}
ZOOM_API(ZOOM_scanset)
ZOOM_connection_scan (ZOOM_connection c, const char *start)
{
+ ZOOM_scanset s;
+ ZOOM_query q = ZOOM_query_create();
+
+ ZOOM_query_prefix (q, start);
+
+ s = ZOOM_connection_scan1(c, q);
+ ZOOM_query_destroy (q);
+ return s;
+
+}
+
+ZOOM_API(ZOOM_scanset)
+ZOOM_connection_scan1 (ZOOM_connection c, ZOOM_query q)
+{
ZOOM_scanset scan = (ZOOM_scanset) xmalloc (sizeof(*scan));
+ char *start;
+ char *freeme = 0;
scan->connection = c;
scan->odr = odr_createmem (ODR_DECODE);
scan->refcount = 1;
scan->scan_response = 0;
- if ((scan->termListAndStartPoint =
- p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet,
- start)))
+ /*
+ * We need to check the query-type, so we can recognise CQL and
+ * compile it into a form that we can use here. The ZOOM_query
+ * structure has no explicit `type' member, but inspection of the
+ * ZOOM_query_prefix() and ZOOM_query_cql() functions shows how
+ * the structure is set up in each case.
+ */
+
+ if (q->z_query->which == Z_Query_type_1) {
+ yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p PQF '%s'",
+ c, q, q->query_string);
+ start = q->query_string;
+ } else if (q->z_query->which == Z_Query_type_104) {
+ yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p CQL '%s'",
+ c, q, q->query_string);
+ start = freeme = cql2pqf(c, q->query_string);
+ if (start == 0)
+ return 0;
+ } else {
+ yaz_log(YLOG_FATAL, "%p ZOOM_connection_scan1 q=%p unknown type '%s'",
+ c, q, q->query_string);
+ abort();
+ }
+
+ scan->termListAndStartPoint =
+ p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet, start);
+ xfree (freeme);
+ if (scan->termListAndStartPoint != 0)
{
ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
task->u.scan.scan = scan;
}
else
{
- set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
do_close(c);
}
break;
}
else
{
- set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
do_close (c);
}
}
return zoom_pending;
}
if (c->state == STATE_CONNECTING)
- set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
else
- set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
do_close (c);
return zoom_complete;
}
return ZOOM_options_get (c->options, key);
}
+ZOOM_API(const char *)
+ZOOM_connection_option_getl (ZOOM_connection c, const char *key, int *lenp)
+{
+ return ZOOM_options_getl (c->options, key, lenp);
+}
+
ZOOM_API(void)
ZOOM_connection_option_set (ZOOM_connection c, const char *key,
const char *val)
}
ZOOM_API(const char *)
+ZOOM_connection_diagset (ZOOM_connection c)
+{
+ const char *diagset;
+ ZOOM_connection_error_x (c, 0, 0, &diagset);
+ return diagset;
+}
+
+ZOOM_API(const char *)
ZOOM_diag_str (int error)
{
switch (error)
return "Unsupported query type";
case ZOOM_ERROR_INVALID_QUERY:
return "Invalid query";
+ case ZOOM_ERROR_CQL_PARSE:
+ return "CQL parsing error";
+ case ZOOM_ERROR_CQL_TRANSFORM:
+ return "CQL transformation error";
default:
return diagbib1_str (error);
}
if (r == CS_NONE)
{
event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
- set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
do_close (c);
ZOOM_connection_put_event (c, event);
}
}
else
{
- set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
+ set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
do_close (c);
ZOOM_connection_put_event (c, event);
}
}
return 0;
}
+
+
+/*
+ * Returns an xmalloc()d string containing RPN that corresponds to the
+ * CQL passed in. On error, sets the Connection object's error state
+ * and returns a null pointer.
+ * ### We could cache CQL parser and/or transformer in Connection.
+ */
+static char *cql2pqf(ZOOM_connection c, const char *cql)
+{
+ CQL_parser parser;
+ int error;
+ struct cql_node *node;
+ const char *cqlfile;
+ static cql_transform_t trans;
+ char pqfbuf[512];
+
+ parser = cql_parser_create();
+ if ((error = cql_parser_string(parser, cql)) != 0) {
+ cql_parser_destroy(parser);
+ set_ZOOM_error(c, ZOOM_ERROR_CQL_PARSE, cql);
+ return 0;
+ }
+
+ node = cql_parser_result(parser);
+ /* ### Do not call cql_parser_destroy() yet: it destroys `node'! */
+
+ cqlfile = ZOOM_connection_option_get(c, "cqlfile");
+ if (cqlfile == 0) {
+ cql_parser_destroy(parser);
+ cql_node_destroy(node);
+ set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, "no CQL transform file");
+ return 0;
+ }
+
+ if ((trans = cql_transform_open_fname(cqlfile)) == 0) {
+ char buf[512];
+ cql_parser_destroy(parser);
+ cql_node_destroy(node);
+ sprintf(buf, "can't open CQL transform file '%.200s': %.200s",
+ cqlfile, strerror(errno));
+ set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
+ return 0;
+ }
+
+ error = cql_transform_buf(trans, node, pqfbuf, sizeof pqfbuf);
+ cql_parser_destroy(parser);
+ cql_node_destroy(node);
+ if (error != 0) {
+ char buf[512];
+ const char *addinfo;
+ error = cql_transform_error(trans, &addinfo);
+ cql_transform_close(trans);
+ sprintf(buf, "%.200s (addinfo=%.200s)", cql_strerror(error), addinfo);
+ set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
+ return 0;
+ }
+
+ cql_transform_close(trans);
+ return xstrdup(pqfbuf);
+}
+
+
/*
* Local variables:
* c-basic-offset: 4