Support for scanning by CQL query (not yet debugged).
[yaz-moved-to-github.git] / src / zoom-c.c
index 9fa1e49..7131b41 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 1995-2005, Index Data ApS
  * See the file LICENSE for details.
  *
- * $Id: zoom-c.c,v 1.47 2005-10-17 12:29:44 mike Exp $
+ * $Id: zoom-c.c,v 1.58 2005-12-20 22:24:05 mike Exp $
  */
 /**
  * \file zoom-c.c
@@ -11,6 +11,7 @@
 
 #include <assert.h>
 #include <string.h>
+#include <errno.h>
 #include "zoom-p.h"
 
 #include <yaz/yaz-util.h>
@@ -23,6 +24,7 @@
 #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>
@@ -50,6 +52,8 @@ typedef enum {
 
 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()
 {
@@ -388,6 +392,37 @@ ZOOM_connection_connect(ZOOM_connection c,
     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");
@@ -644,17 +679,34 @@ ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
     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)
     {
@@ -674,8 +726,7 @@ ZOOM_API(void)
     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);  
 
@@ -684,6 +735,8 @@ ZOOM_API(void)
         while (ZOOM_event (1, &c))
             ;
     }
+
+    return 0;
 }
 
 ZOOM_API(void)
@@ -905,7 +958,7 @@ static zoom_ret do_connect (ZOOM_connection c)
         }
     }
     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;
 }
 
@@ -1045,7 +1098,7 @@ static zoom_ret ZOOM_connection_send_init (ZOOM_connection c)
         ZOOM_options_get(c->options, "implementationName"),
         odr_prepend(c->odr_out, "ZOOM-C", ireq->implementationName));
 
-    version = odr_strdup(c->odr_out, "$Revision: 1.47 $");
+    version = odr_strdup(c->odr_out, "$Revision: 1.58 $");
     if (strlen(version) > 10)   /* check for unexpanded CVS strings */
         version[strlen(version)-2] = '\0';
     ireq->implementationVersion = odr_prepend(c->odr_out,
@@ -2028,6 +2081,112 @@ static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
     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;
@@ -2035,12 +2194,14 @@ static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
 
     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);
 }
@@ -2241,7 +2402,23 @@ static zoom_ret send_present(ZOOM_connection c)
 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);
@@ -2249,9 +2426,34 @@ ZOOM_connection_scan (ZOOM_connection c, const char *start)
     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;
@@ -3130,7 +3332,7 @@ static void recv_apdu (ZOOM_connection c, Z_APDU *apdu)
         }
         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;
@@ -3310,7 +3512,7 @@ static int do_read (ZOOM_connection c)
         }
         else
         {
-            set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
+            set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
             do_close (c);
         }
     }
@@ -3373,9 +3575,9 @@ static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
             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;
     }
@@ -3467,6 +3669,14 @@ ZOOM_connection_addinfo (ZOOM_connection c)
 }
 
 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)
@@ -3495,6 +3705,10 @@ ZOOM_diag_str (int 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);
     }
@@ -3542,7 +3756,7 @@ static int ZOOM_connection_do_io(ZOOM_connection c, int mask)
     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);
     }
@@ -3581,7 +3795,7 @@ static int ZOOM_connection_do_io(ZOOM_connection c, int mask)
         }
         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);
         }
@@ -3788,6 +4002,74 @@ ZOOM_event (int no, ZOOM_connection *cs)
     }
     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();
+    printf("*** got CQL parser %p\n", parser);
+    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);
+    printf("*** got CQL node %p\n", node);
+    cql_parser_destroy(parser);
+    printf("*** destroyed parser\n");
+
+    cqlfile = ZOOM_connection_option_get(c, "cqlfile");
+    printf("*** cqlfile is %p\n", cqlfile);
+    if (cqlfile == 0) {
+        printf("*** cqlfile is null\n");
+        cql_node_destroy(node);
+        printf("*** destroyed node\n");
+        set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, "no CQL transform file");
+        printf("*** set ZOOM_error\n");
+        return 0;
+    }
+    printf("*** got CQL file %s\n", cqlfile);
+
+    if ((trans = cql_transform_open_fname(cqlfile)) == 0) {
+        char buf[512];        
+        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_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