Merge branch 'master' into paz-927 paz-927
authorAdam Dickmeiss <adam@indexdata.dk>
Wed, 12 Aug 2015 13:43:51 +0000 (15:43 +0200)
committerAdam Dickmeiss <adam@indexdata.dk>
Wed, 12 Aug 2015 13:43:51 +0000 (15:43 +0200)
1  2 
src/client.c
src/client.h
src/pazpar2_config.c
src/pazpar2_config.h
src/session.c
test/test_limit_limitmap.urls
win/makefile

diff --combined src/client.c
@@@ -521,7 -521,7 +521,7 @@@ static void client_report_facets(struc
                                  ZOOM_facet_field_get_term(facets[facet_idx],
                                                            term_idx, &freq);
                              if (term)
-                                 add_facet(se, p, term, freq);
+                                 add_facet(se, p, term, freq, cl);
                          }
                          break;
                      }
@@@ -778,6 -778,8 +778,8 @@@ int client_has_facet(struct client *cl
      for (s = sdb->settings[PZ_FACETMAP]; s; s = s->next)
      {
          const char *p = strchr(s->name + 3, ':');
+         if ( !strncmp(p, ":split:", 7) )
+             p += 6; // PAZ-1009
          if (p && !strcmp(name, p + 1))
              return 1;
      }
@@@ -888,6 -890,22 +890,22 @@@ int client_parse_range(struct client *c
      return 0;
  }
  
+ const char *client_get_query(struct client *cl, const char **type, NMEM nmem)
+ {
+     if (cl->pquery)
+     {
+         *type = "pqf";
+         return nmem_strdup(nmem, cl->pquery);
+     }
+     if (cl->cqlquery)
+     {
+         *type = "cql";
+         return nmem_strdup(nmem, cl->cqlquery);
+     }
+     *type = 0;
+     return 0;
+ }
  int client_start_search(struct client *cl)
  {
      struct session_database *sdb = client_get_database(cl);
      }
      else if (!rc_prep_connection)
      {
 -        session_log(se, YLOG_LOG, "%s: postponing search: No connection",
 -                    client_get_id(cl));
 -        client_set_state_nb(cl, Client_Working);
 +        client_set_diagnostic(cl, 2,
 +                              ZOOM_diag_str(2),
 +                              "Cannot create connection");
 +        client_set_state_nb(cl, Client_Error);
          return -1;
      }
      co = client_get_connection(cl);
@@@ -1154,12 -1171,6 +1172,12 @@@ void client_disconnect(struct client *c
      client_set_connection(cl, 0);
  }
  
 +void client_mark_dead(struct client *cl)
 +{
 +    if (cl->connection)
 +        connection_mark_dead(cl->connection);
 +}
 +
  void client_stop(struct client *cl)
  {
      client_lock(cl);
@@@ -1315,7 -1326,32 +1333,32 @@@ const char *client_get_facet_limit_loca
      return 0;
  }
  
- static int apply_limit(struct session_database *sdb,
+ static void ccl_quote_map_term(CCL_bibset ccl_map, WRBUF w,
+                                const char *term)
+ {
+     int quote_it = 0;
+     const char *cp;
+     for (cp = term; *cp; cp++)
+         if ((*cp >= '0' && *cp <= '9') || strchr(" +-", *cp))
+             ;
+         else
+             quote_it = 1;
+     if (!quote_it)
+         wrbuf_puts(w, term);
+     else
+     {
+         wrbuf_putc(w, '\"');
+         for (cp = term; *cp; cp++)
+         {
+             if (strchr( "\\\"", *cp))
+                 wrbuf_putc(w, '\\');
+             wrbuf_putc(w, *cp);
+         }
+         wrbuf_putc(w, '\"');
+     }
+ }
+ static int apply_limit(struct client *cl,
                         facet_limits_t facet_limits,
                         WRBUF w_pqf, CCL_bibset ccl_map,
                         struct conf_service *service)
      int i = 0;
      const char *name;
      const char *value;
+     struct session_database *sdb = client_get_database(cl);
  
      NMEM nmem_tmp = nmem_create();
      for (i = 0; (name = facet_limits_get(facet_limits, i, &value)); i++)
                  nmem_strsplit_escape2(nmem_tmp, "|", value, &values,
                                        &num, 1, '\\', 1);
  
+                 for (i = 0; i < num; i++)
+                 {
+                     const char *id = session_lookup_id_facet(cl->session,
+                                                              cl, name,
+                                                              values[i]);
+                     if (id)
+                     {
+                         if ( *id )
+                         {
+                             values[i] = nmem_strdup(nmem_tmp, id);
+                             yaz_log(YLOG_DEBUG,
+                                 "apply_limit: s='%s' found id '%s'",s->name,id );
+                         }
+                         else
+                         {
+                             yaz_log(YLOG_DEBUG,
+                                 "apply_limit: %s: term '%s' not found, failing client",
+                                 s->name, values[i] );
+                             ret = -1;
+                         }
+                     }
+                 }
                  nmem_strsplit_escape2(nmem_tmp, ",", s->value, &cvalues,
                                        &cnum, 1, '\\', 1);
  
                              struct ccl_rpn_node *cn;
                              wrbuf_rewind(ccl_w);
                              wrbuf_puts(ccl_w, ccl);
-                             wrbuf_puts(ccl_w, "=\"");
-                             wrbuf_puts(ccl_w, values[i]);
-                             wrbuf_puts(ccl_w, "\"");
+                             wrbuf_putc(ccl_w, '=');
+                             ccl_quote_map_term(ccl_map, ccl_w, values[i]);
                              cn = ccl_find_str(ccl_map, wrbuf_cstr(ccl_w),
                                                &cerror, &cpos);
                              if (cn)
@@@ -1461,6 -1518,9 +1525,9 @@@ int client_parse_query(struct client *c
      if (!ccl_map)
          return -3;
  
+     xfree(cl->cqlquery);
+     cl->cqlquery = 0;
      w_ccl = wrbuf_alloc();
      wrbuf_puts(w_ccl, query);
  
          wrbuf_puts(w_pqf, " ");
      }
  
-     if (apply_limit(sdb, facet_limits, w_pqf, ccl_map, service))
+     if (apply_limit(cl, facet_limits, w_pqf, ccl_map, service))
      {
+         client_set_state(cl, Client_Error);
          ccl_qual_rm(&ccl_map);
+         wrbuf_destroy(w_ccl);
+         wrbuf_destroy(w_pqf);
+         xfree(cl->pquery);
+         cl->pquery = 0;
          return -2;
      }
  
                      wrbuf_cstr(w_ccl));
          wrbuf_destroy(w_ccl);
          wrbuf_destroy(w_pqf);
+         xfree(cl->pquery);
+         cl->pquery = 0;
          return -1;
      }
      wrbuf_destroy(w_ccl);
      {
          if (cl->pquery)
              session_log(se, YLOG_LOG, "Client %s: "
-                         "Re-search due query/limit change: %s to %s", 
+                         "Re-search due query/limit change: %s to %s",
                          client_get_id(cl), cl->pquery, wrbuf_cstr(w_pqf));
          xfree(cl->pquery);
          cl->pquery = xstrdup(wrbuf_cstr(w_pqf));
      }
      wrbuf_destroy(w_pqf);
  
-     xfree(cl->cqlquery);
-     cl->cqlquery = 0;
      odr_out = odr_createmem(ODR_ENCODE);
      zquery = p_query_rpn(odr_out, cl->pquery);
      if (!zquery)
      {
          session_log(se, YLOG_WARN, "Invalid PQF query for Client %s: %s",
                      client_get_id(cl), cl->pquery);
          ret_value = -1;
      return ret_value;
  }
  
- int client_parse_sort(struct client *cl, struct reclist_sortparms *sp)
+ int client_parse_sort(struct client *cl, struct reclist_sortparms *sp,
+                       int *has_sortmap)
  {
+     if (has_sortmap)
+         *has_sortmap = 0;
      if (sp)
      {
          const char *sort_strategy_and_spec =
                      xfree(cl->sort_criteria);
                      cl->sort_criteria = xstrdup(p);
                  }
+                 if (has_sortmap)
+                     (*has_sortmap)++;
              }
              else {
                  yaz_log(YLOG_LOG, "Client %s: "
@@@ -1726,12 -1799,12 +1806,12 @@@ int client_get_diagnostic(struct clien
      return cl->diagnostic;
  }
  
- const char * client_get_suggestions_xml(struct client *cl, WRBUF wrbuf)
+ const char *client_get_suggestions_xml(struct client *cl, WRBUF wrbuf)
  {
      /* int idx; */
      struct suggestions *suggestions = cl->suggestions;
  
-     if (!suggestions) 
+     if (!suggestions)
          return "";
      if (suggestions->passthrough)
      {
      return wrbuf_cstr(wrbuf);
  }
  
  void client_set_database(struct client *cl, struct session_database *db)
  {
      cl->database = db;
diff --combined src/client.h
@@@ -69,7 -69,6 +69,7 @@@ int client_destroy(struct client *c)
  
  void client_set_connection(struct client *cl, struct connection *con);
  void client_disconnect(struct client *cl);
 +void client_mark_dead(struct client *cl);
  int client_prep_connection(struct client *cl,
                             int operation_timeout, int session_timeout,
                             iochan_man_t iochan,
@@@ -78,7 -77,8 +78,8 @@@ int client_start_search(struct client *
  int client_fetch_more(struct client *cl);
  int client_parse_init(struct client *cl, int same_search);
  int client_parse_range(struct client *cl, const char *startrecs, const char *maxrecs);
- int client_parse_sort(struct client *cl, struct reclist_sortparms *sp);
+ int client_parse_sort(struct client *cl, struct reclist_sortparms *sp,
+                       int *has_sortmap);
  void client_set_session(struct client *cl, struct session *se);
  int client_is_active(struct client *cl);
  int client_is_active_preferred(struct client *cl);
@@@ -111,10 -111,14 +112,14 @@@ const char *client_get_facet_limit_loca
                                           int *l,
                                           NMEM nmem, int *num, char ***values);
  
+ const char *client_get_suggestions_xml(struct client *cl, WRBUF wrbuf);
  void client_update_show_stat(struct client *cl, int cmd);
  
  void client_store_xdoc(struct client *cl, int record_no, xmlDoc *xdoc);
  
+ const char *client_get_query(struct client *cl, const char **type, NMEM nmem);
  #endif
  
  /*
diff --combined src/pazpar2_config.c
@@@ -56,6 -56,7 +56,6 @@@ struct conf_confi
      WRBUF confdir;
      char *path;
      iochan_man_t iochan_man;
 -    database_hosts_t database_hosts;
  };
  
  struct service_xslt
@@@ -169,7 -170,8 +169,8 @@@ static struct conf_metadata* conf_servi
      enum conf_metadata_mergekey mt,
      const char *facetrule,
      const char *limitmap,
-     const char *limitcluster
+     const char *limitcluster,
+     const char *icurule
      )
  {
      struct conf_metadata * md = 0;
      md->facetrule = nmem_strdup_null(nmem, facetrule);
      md->limitmap = nmem_strdup_null(nmem, limitmap);
      md->limitcluster = nmem_strdup_null(nmem, limitcluster);
+     md->icurule = nmem_strdup_null(nmem, icurule);
      return md;
  }
  
@@@ -314,6 -317,7 +316,7 @@@ static int parse_metadata(struct conf_s
      xmlChar *xml_limitmap = 0;
      xmlChar *xml_limitcluster = 0;
      xmlChar *xml_icu_chain = 0;
+     xmlChar *xml_icurule = 0;
  
      struct _xmlAttr *attr;
  
          else if (!xmlStrcmp(attr->name, BAD_CAST "limitcluster") &&
                   attr->children && attr->children->type == XML_TEXT_NODE)
              xml_limitcluster = attr->children->content;
+         else if (!xmlStrcmp(attr->name, BAD_CAST "icurule") &&
+                  attr->children && attr->children->type == XML_TEXT_NODE)
+             xml_icurule = attr->children->content;
          else
          {
              yaz_log(YLOG_FATAL, "Unknown metadata attribute '%s'", attr->name);
                                mergekey_type,
                                (const char *) xml_icu_chain,
                                (const char *) xml_limitmap,
-                               (const char *) xml_limitcluster);
+                               (const char *) xml_limitcluster,
+                               (const char *) xml_icurule
+         );
      (*md_node)++;
      return 0;
  }
@@@ -871,6 -880,7 +879,6 @@@ static struct conf_server *server_creat
      server->charsets = 0;
      server->http_server = 0;
      server->iochan_man = 0;
 -    server->database_hosts = config->database_hosts;
      server->settings_fname = 0;
  
      if (server_id)
@@@ -1073,6 -1083,9 +1081,9 @@@ static void info_service_metadata(struc
                  case Metadata_type_position:
                      wrbuf_puts(w, "position");
                      break;
+                 case Metadata_type_retrieval:
+                     wrbuf_puts(w, "retrieval");
+                     break;
                  default:
                      wrbuf_puts(w, "yes");
                      break;
@@@ -1308,6 -1321,7 +1319,6 @@@ struct conf_config *config_create(cons
      config->path = nmem_strdup(nmem, ".");
      config->no_threads = 0;
      config->iochan_man = 0;
 -    config->database_hosts = database_hosts_create();
  
      config->confdir = wrbuf_alloc();
      if ((p = strrchr(fname,
@@@ -1370,6 -1384,7 +1381,6 @@@ void config_destroy(struct conf_config 
              struct conf_server *s_next = server->next;
              server_destroy(server);
              server = s_next;
 -            database_hosts_destroy(&config->database_hosts);
          }
          wrbuf_destroy(config->confdir);
          nmem_destroy(config->nmem);
diff --combined src/pazpar2_config.h
@@@ -28,6 -28,7 +28,6 @@@ Foundation, Inc., 51 Franklin St, Fift
  #include "charsets.h"
  #include "http.h"
  #include "database.h"
 -#include "host.h"
  
  enum conf_metadata_type {
      Metadata_type_generic,    // Generic text field
@@@ -37,6 -38,7 +37,7 @@@
      Metadata_type_skiparticle,
      Metadata_type_relevance,
      Metadata_type_position,
+     Metadata_type_retrieval,
  };
  
  enum conf_metadata_merge {
@@@ -81,6 -83,7 +82,7 @@@ struct conf_metadat
  
      char *limitmap;  // Should be expanded into service-wide default e.g. pz:limitmap:<name>=value setting
      char *limitcluster;
+     char *icurule;
  };
  
  
@@@ -153,6 -156,7 +155,6 @@@ struct conf_serve
      struct conf_config *config;
      http_server_t http_server;
      iochan_man_t iochan_man;
 -    database_hosts_t database_hosts;
  };
  
  struct conf_config *config_create(const char *fname);
diff --combined src/session.c
@@@ -56,6 -56,7 +56,7 @@@ Foundation, Inc., 51 Franklin St, Fift
  #include <yaz/querytowrbuf.h>
  #include <yaz/oid_db.h>
  #include <yaz/snprintf.h>
+ #include <yaz/xml_get.h>
  
  #define USE_TIMING 0
  #if USE_TIMING
@@@ -149,30 -150,19 +150,19 @@@ static void session_leave(struct sessio
          session_log(s, YLOG_DEBUG, "Session unlock by %s", caller);
  }
  
- static void session_normalize_facet(struct session *s,
-                                     const char *type, const char *value,
-                                     WRBUF display_wrbuf, WRBUF facet_wrbuf)
+ static int run_icu(struct session *s, const char *icu_chain_id,
+                    const char *value,
+                    WRBUF norm_wr, WRBUF disp_wr)
  {
-     struct conf_service *service = s->service;
-     pp2_charset_token_t prt;
      const char *facet_component;
-     int i;
-     const char *icu_chain_id = 0;
-     for (i = 0; i < service->num_metadata; i++)
-         if (!strcmp((service->metadata + i)->name, type))
-             icu_chain_id = (service->metadata + i)->facetrule;
-     if (!icu_chain_id)
-         icu_chain_id = "facet";
-     prt = pp2_charset_token_create(service->charsets, icu_chain_id);
+     struct conf_service *service = s->service;
+     pp2_charset_token_t prt =
+         pp2_charset_token_create(service->charsets, icu_chain_id);
      if (!prt)
      {
          session_log(s, YLOG_FATAL,
-                     "Unknown ICU chain '%s' for facet of type '%s'",
-                 icu_chain_id, type);
-         wrbuf_destroy(facet_wrbuf);
-         wrbuf_destroy(display_wrbuf);
-         return;
+                     "Unknown ICU chain '%s'", icu_chain_id);
+         return 0;
      }
      pp2_charset_token_first(prt, value, 0);
      while ((facet_component = pp2_charset_token_next(prt)))
          const char *display_component;
          if (*facet_component)
          {
-             if (wrbuf_len(facet_wrbuf))
-                 wrbuf_puts(facet_wrbuf, " ");
-             wrbuf_puts(facet_wrbuf, facet_component);
+             if (wrbuf_len(norm_wr))
+                 wrbuf_puts(norm_wr, " ");
+             wrbuf_puts(norm_wr, facet_component);
          }
          display_component = pp2_get_display(prt);
          if (display_component)
          {
-             if (wrbuf_len(display_wrbuf))
-                 wrbuf_puts(display_wrbuf, " ");
-             wrbuf_puts(display_wrbuf, display_component);
+             if (wrbuf_len(disp_wr))
+                 wrbuf_puts(disp_wr, " ");
+             wrbuf_puts(disp_wr, display_component);
          }
      }
      pp2_charset_token_destroy(prt);
+     return 1;
  }
  
- void add_facet(struct session *s, const char *type, const char *value, int count)
+ static void session_normalize_facet(struct session *s,
+                                     const char *type, const char *value,
+                                     WRBUF display_wrbuf, WRBUF facet_wrbuf)
+ {
+     struct conf_service *service = s->service;
+     int i;
+     const char *icu_chain_id = 0;
+     for (i = 0; i < service->num_metadata; i++)
+         if (!strcmp((service->metadata + i)->name, type))
+             icu_chain_id = (service->metadata + i)->facetrule;
+     if (!icu_chain_id)
+         icu_chain_id = "facet";
+     run_icu(s, icu_chain_id, value, facet_wrbuf, display_wrbuf);
+ }
+ struct facet_id {
+     char *client_id;
+     char *type;
+     char *id;
+     char *term;
+     struct facet_id *next;
+ };
+ static void session_add_id_facet(struct session *s, struct client *cl,
+                                  const char *type,
+                                  const char *id,
+                                  size_t id_len,
+                                  const char *term)
+ {
+     struct facet_id *t = nmem_malloc(s->session_nmem, sizeof(*t));
+     t->client_id = nmem_strdup(s->session_nmem, client_get_id(cl));
+     t->type = nmem_strdup(s->session_nmem, type);
+     t->id = nmem_strdupn(s->session_nmem, id, id_len);
+     t->term = nmem_strdup(s->session_nmem, term);
+     t->next = s->facet_id_list;
+     s->facet_id_list = t;
+ }
+ // Look up a facet term, and return matching id
+ // If facet type not found, returns 0
+ // If facet type found, but no matching term, returns ""
+ const char *session_lookup_id_facet(struct session *s, struct client *cl,
+                                     const char *type,
+                                     const char *term)
+ {
+     char *retval = 0;
+     struct facet_id *t = s->facet_id_list;
+     for (; t; t = t->next) 
+     {
+         if (!strcmp(client_get_id(cl), t->client_id) &&  !strcmp(t->type, type) )
+         {
+             retval = "";
+             if ( !strcmp(t->term, term))
+             {
+                 return t->id;
+             }
+         }
+     }
+     return retval;
+ }
+ void add_facet(struct session *s, const char *type, const char *value, int count, struct client *cl)
  {
      WRBUF facet_wrbuf = wrbuf_alloc();
      WRBUF display_wrbuf = wrbuf_alloc();
+     const char *id = 0;
+     size_t id_len = 0;
  
-     session_normalize_facet(s, type, value, display_wrbuf, facet_wrbuf);
+     /* inspect pz:facetmap:split:name ?? */
+     if (!strncmp(type, "split:", 6))
+     {
+         const char *cp = strchr(value, ':');
+         if (cp)
+         {
+             id = value;
+             id_len = cp - value;
+             value = cp + 1;
+         }
+         type += 6;
+     }
  
+     session_normalize_facet(s, type, value, display_wrbuf, facet_wrbuf);
      if (wrbuf_len(facet_wrbuf))
      {
          struct named_termlist **tp = &s->termlists;
              (*tp)->next = 0;
          }
          termlist_insert((*tp)->termlist, wrbuf_cstr(display_wrbuf),
-                         wrbuf_cstr(facet_wrbuf), count);
+                         wrbuf_cstr(facet_wrbuf), id, id_len, count);
+         if (id)
+             session_add_id_facet(s, cl, type, id, id_len,
+                                  wrbuf_cstr(display_wrbuf));
      }
      wrbuf_destroy(facet_wrbuf);
      wrbuf_destroy(display_wrbuf);
@@@ -497,6 -570,7 +570,6 @@@ static void select_targets_callback(str
          l->next = se->clients_cached;
          se->clients_cached = l;
      }
 -    /* set session always. If may be 0 if client is not active */
      client_set_session(cl, se);
  
      l = xmalloc(sizeof(*l));
@@@ -545,7 -619,6 +618,7 @@@ static void session_remove_cached_clien
          client_lock(l->client);
          client_set_session(l->client, 0);
          client_set_database(l->client, 0);
 +        client_mark_dead(l->client);
          client_unlock(l->client);
          client_destroy(l->client);
          xfree(l);
@@@ -650,7 -723,7 +723,7 @@@ void session_sort(struct session *se, s
                  break;
          if (sr)
          {
-             session_log(se, YLOG_DEBUG, "session_sort: field=%s increasing=%d type=%d already fetched",
+             session_log(se, YLOG_LOG, "session_sort: field=%s increasing=%d type=%d already fetched",
                          field, increasing, type);
              session_leave(se, "session_sort");
              return;
          struct client *cl = l->client;
          // Assume no re-search is required.
          client_parse_init(cl, 1);
-         clients_research += client_parse_sort(cl, sp);
+         clients_research += client_parse_sort(cl, sp, 0);
      }
      if (!clients_research || se->clients_starting)
      {
          }
          session_enter(se, "session_sort");
          se->clients_starting = 0;
+         se->force_position = 0;
          session_leave(se, "session_sort");
      }
  }
@@@ -757,6 -831,7 +831,7 @@@ enum pazpar2_error_code session_search(
      int no_working = 0;
      int no_failed_query = 0;
      int no_failed_limit = 0;
+     int no_sortmap = 0;
      struct client_list *l;
  
      session_log(se, YLOG_DEBUG, "Search");
          return PAZPAR2_NO_ERROR;
      }
      se->clients_starting = 1;
+     se->force_position = 0;
      session_leave(se, "session_search0");
  
      if (se->settings_modified) {
          *addinfo = "limit";
          session_leave(se, "session_search");
          se->clients_starting = 0;
+         session_reset_active_clients(se, 0);
          return PAZPAR2_MALFORMED_PARAMETER_VALUE;
      }
  
          else
          {
              client_parse_range(cl, startrecs, maxrecs);
-             client_parse_sort(cl, sp);
+             client_parse_sort(cl, sp, &no_sortmap);
              client_start_search(cl);
              no_working++;
          }
      }
+     yaz_log(YLOG_LOG, "session_search: no_working=%d no_sortmap=%d",
+             no_working, no_sortmap);
      session_enter(se, "session_search2");
+     if (no_working == 1 && no_sortmap == 1)
+     {
+         se->force_position = 1;
+         yaz_log(YLOG_LOG, "force_position=1");
+     }
      se->clients_starting = 0;
      session_leave(se, "session_search2");
      if (no_working == 0)
@@@ -1019,6 -1103,7 +1103,7 @@@ struct session *new_session(NMEM nmem, 
      session->clients_cached = 0;
      session->settings_modified = 0;
      session->session_nmem = nmem;
+     session->facet_id_list = 0;
      session->nmem = nmem_create();
      session->databases = 0;
      session->sorted_results = 0;
      session->mergekey = 0;
      session->rank = 0;
      session->clients_starting = 0;
+     session->force_position = 0;
  
      for (i = 0; i <= SESSION_WATCH_MAX; i++)
      {
      return session;
  }
  
- const char * client_get_suggestions_xml(struct client *cl, WRBUF wrbuf);
  static struct hitsbytarget *hitsbytarget_nb(struct session *se,
                                              int *count, NMEM nmem)
  {
          session_settings_dump(se, client_get_database(cl), w);
          res[*count].settings_xml = nmem_strdup(nmem, wrbuf_cstr(w));
          wrbuf_rewind(w);
-         wrbuf_puts(w, "");
-         res[*count].suggestions_xml = nmem_strdup(nmem, client_get_suggestions_xml(cl, w));
+         res[*count].suggestions_xml =
+             nmem_strdup(nmem, client_get_suggestions_xml(cl, w));
+         res[*count].query_data =
+             client_get_query(cl, &res[*count].query_type, nmem);
          wrbuf_destroy(w);
          (*count)++;
      }
@@@ -1207,7 -1294,6 +1294,6 @@@ void perform_termlist(struct http_chann
                          wrbuf_puts(c->wrbuf, "<name>");
                          wrbuf_xmlputs(c->wrbuf, p[i]->display_term);
                          wrbuf_puts(c->wrbuf, "</name>");
                          wrbuf_printf(c->wrbuf,
                                       "<frequency>%d</frequency>",
                                       p[i]->frequency);
@@@ -1344,6 -1430,7 +1430,7 @@@ struct record_cluster **show_range_star
      struct reclist_sortparms *spp;
      struct client_list *l;
      int i;
+     NMEM nmem_tmp = 0;
  #if USE_TIMING
      yaz_timing_t t = yaz_timing_create();
  #endif
              *approx_hits += client_get_approximation(l->client);
          }
      }
+     if (se->force_position)
+     {
+         nmem_tmp = nmem_create();
+         sp = reclist_parse_sortparms(nmem_tmp, "position:1", 0);
+         assert(sp);
+     }
      reclist_sort(se->reclist, sp);
+     if (nmem_tmp)
+         nmem_destroy(nmem_tmp);
  
      reclist_enter(se->reclist);
      *total = reclist_get_num_records(se->reclist);
@@@ -1470,7 -1565,8 +1565,8 @@@ void statistics(struct session *se, str
  }
  
  static struct record_metadata *record_metadata_init(
-     NMEM nmem, const char *value, enum conf_metadata_type type,
+     NMEM nmem, const char *value, const char *norm,
+     enum conf_metadata_type type,
      struct _xmlAttr *attr)
  {
      struct record_metadata *rec_md = record_metadata_create(nmem);
      {
      case Metadata_type_generic:
      case Metadata_type_skiparticle:
-         if (strstr(value, "://")) /* looks like a URL */
+         if (norm)
+         {
              rec_md->data.text.disp = nmem_strdup(nmem, value);
+             rec_md->data.text.norm = nmem_strdup(nmem, norm);
+         }
          else
-             rec_md->data.text.disp =
-                 normalize7bit_generic(nmem_strdup(nmem, value), " ,/.:([");
+         {
+             if (strstr(value, "://")) /* looks like a URL */
+                 rec_md->data.text.disp = nmem_strdup(nmem, value);
+             else
+                 rec_md->data.text.disp =
+                     normalize7bit_generic(nmem_strdup(nmem, value), " ,/.:([");
+             rec_md->data.text.norm = rec_md->data.text.disp;
+         }
          rec_md->data.text.sort = 0;
          rec_md->data.text.snippet = 0;
          break;
          break;
      case Metadata_type_relevance:
      case Metadata_type_position:
+     case Metadata_type_retrieval:
          return 0;
      }
      return rec_md;
@@@ -1564,7 -1670,7 +1670,7 @@@ static int get_mergekey_from_doc(xmlDo
              continue;
          if (!strcmp((const char *) n->name, "metadata"))
          {
-             xmlChar *type = xmlGetProp(n, (xmlChar *) "type");
+             const char *type = yaz_xml_get_prop(n, "type");
              if (type == NULL) {
                  yaz_log(YLOG_FATAL, "Missing type attribute on metadata element. Skipping!");
              }
                  if (value)
                      xmlFree(value);
              }
-             xmlFree(type);
          }
      }
      return no_found;
@@@ -1596,7 -1701,7 +1701,7 @@@ static const char *get_mergekey(xmlDoc 
  {
      char *mergekey_norm = 0;
      WRBUF norm_wr = wrbuf_alloc();
-     xmlChar *mergekey;
+     const char *mergekey;
  
      if (session_mergekey)
      {
          for (i = 0; i < num; i++)
              get_mergekey_from_doc(doc, root, values[i], service, norm_wr);
      }
-     else if ((mergekey = xmlGetProp(root, (xmlChar *) "mergekey")))
+     else if ((mergekey = yaz_xml_get_prop(root, "mergekey")))
      {
-         mergekey_norm_wr(service->charsets, norm_wr, (const char *) mergekey);
-         xmlFree(mergekey);
+         mergekey_norm_wr(service->charsets, norm_wr, mergekey);
      }
      else
      {
      /* generate unique key if none is not generated already or is empty */
      if (wrbuf_len(norm_wr) == 0)
      {
-         wrbuf_printf(norm_wr, "position: %s-%d",
+         wrbuf_printf(norm_wr, "position: %s-%06d",
                       client_get_id(cl), record_no);
      }
      else
@@@ -1678,7 -1782,7 +1782,7 @@@ static int check_record_filter(xmlNode 
              continue;
          if (!strcmp((const char *) n->name, "metadata"))
          {
-             xmlChar *type = xmlGetProp(n, (xmlChar *) "type");
+             const char *type = yaz_xml_get_prop(n, "type");
              if (type)
              {
                  size_t len;
                      }
                      xmlFree(value);
                  }
-                 xmlFree(type);
              }
          }
      }
  }
  
  static int ingest_to_cluster(struct client *cl,
+                              WRBUF wrbuf_disp,
+                              WRBUF wrbuf_norm,
                               xmlDoc *xdoc,
                               xmlNode *root,
                               int record_no,
@@@ -1726,6 -1831,7 +1831,7 @@@ static int ingest_sub_record(struct cli
  {
      int ret = 0;
      struct session *se = client_get_session(cl);
+     WRBUF wrbuf_disp, wrbuf_norm;
  
      if (!check_record_filter(root, sdb))
      {
                      record_no, sdb->database->id);
          return 0;
      }
+     wrbuf_disp = wrbuf_alloc();
+     wrbuf_norm = wrbuf_alloc();
      session_enter(se, "ingest_sub_record");
      if (client_get_session(cl) == se && se->relevance)
-         ret = ingest_to_cluster(cl, xdoc, root, record_no, mergekeys);
+         ret = ingest_to_cluster(cl, wrbuf_disp, wrbuf_norm,
+                                 xdoc, root, record_no, mergekeys);
      session_leave(se, "ingest_sub_record");
+     wrbuf_destroy(wrbuf_norm);
+     wrbuf_destroy(wrbuf_disp);
      return ret;
  }
  
@@@ -2019,14 -2129,14 +2129,14 @@@ static int check_limit_local(struct cli
  }
  
  static int ingest_to_cluster(struct client *cl,
+                              WRBUF wrbuf_disp,
+                              WRBUF wrbuf_norm,
                               xmlDoc *xdoc,
                               xmlNode *root,
                               int record_no,
                               struct record_metadata_attr *merge_keys)
  {
      xmlNode *n;
-     xmlChar *type = 0;
-     xmlChar *value = 0;
      struct session *se = client_get_session(cl);
      struct conf_service *service = se->service;
      int term_factor = 1;
  
      for (n = root->children; n; n = n->next)
      {
-         if (type)
-             xmlFree(type);
-         if (value)
-             xmlFree(value);
-         type = value = 0;
          if (n->type != XML_ELEMENT_NODE)
              continue;
          if (!strcmp((const char *) n->name, "metadata"))
              struct record_metadata **wheretoput = 0;
              struct record_metadata *rec_md = 0;
              int md_field_id = -1;
+             xmlChar *value0;
+             const char *type = yaz_xml_get_prop(n, "type");
  
-             type = xmlGetProp(n, (xmlChar *) "type");
-             value = xmlNodeListGetString(xdoc, n->children, 1);
              if (!type)
                  continue;
-             if (!value || !*value)
-             {
-                 xmlChar *empty = xmlGetProp(n, (xmlChar *) "empty");
-                 if (!empty)
-                     continue;
-                 if (value)
-                     xmlFree(value);
-                 value = empty;
-             }
              md_field_id
                  = conf_service_metadata_field_id(service, (const char *) type);
              if (md_field_id < 0)
                  continue;
              }
  
+             wrbuf_rewind(wrbuf_disp);
+             value0 = xmlNodeListGetString(xdoc, n->children, 1);
+             if (!value0 || !*value0)
+             {
+                 const char *empty = yaz_xml_get_prop(n, "empty");
+                 if (!empty)
+                     continue;
+                 wrbuf_puts(wrbuf_disp, (const char *) empty);
+             }
+             else
+             {
+                 wrbuf_puts(wrbuf_disp, (const char *) value0);
+             }
+             if (value0)
+                 xmlFree(value0);
              ser_md = &service->metadata[md_field_id];
  
              // non-merged metadata
-             rec_md = record_metadata_init(se->nmem, (const char *) value,
+             rec_md = record_metadata_init(se->nmem, wrbuf_cstr(wrbuf_disp), 0,
                                            ser_md->type, n->properties);
              if (!rec_md)
              {
                  session_log(se, YLOG_WARN, "bad metadata data '%s' "
-                             "for element '%s'", value, type);
+                             "for element '%s'", wrbuf_cstr(wrbuf_disp), type);
                  continue;
              }
  
              {
                  WRBUF w = wrbuf_alloc();
                  if (relevance_snippet(se->relevance,
-                                       (char*) value, ser_md->name, w))
+                                       wrbuf_cstr(wrbuf_disp), ser_md->name, w))
                      rec_md->data.text.snippet = nmem_strdup(se->nmem,
                                                              wrbuf_cstr(w));
                  wrbuf_destroy(w);
  
      if (check_limit_local(cl, record, record_no))
      {
-         if (type)
-             xmlFree(type);
-         if (value)
-             xmlFree(value);
          return -2;
      }
      cluster = reclist_insert(se->reclist, se->relevance, service, record,
                               merge_keys, &se->total_merged);
      if (!cluster)
      {
-         if (type)
-             xmlFree(type);
-         if (value)
-             xmlFree(value);
          return 0; // complete match with existing record
      }
  
      // now parsing XML record and adding data to cluster or record metadata
      for (n = root->children; n; n = n->next)
      {
-         pp2_charset_token_t prt;
-         if (type)
-             xmlFree(type);
-         if (value)
-             xmlFree(value);
-         type = value = 0;
          if (n->type != XML_ELEMENT_NODE)
              continue;
          if (!strcmp((const char *) n->name, "metadata"))
              int md_field_id = -1;
              int sk_field_id = -1;
              const char *rank = 0;
-             xmlChar *xml_rank = 0;
-             type = xmlGetProp(n, (xmlChar *) "type");
-             value = xmlNodeListGetString(xdoc, n->children, 1);
+             const char *xml_rank = 0;
+             const char *type = 0;
+             xmlChar *value0;
  
-             if (!type || !value || !*value)
+             type = yaz_xml_get_prop(n, "type");
+             if (!type)
                  continue;
  
              md_field_id
                  ser_sk = &service->sortkeys[sk_field_id];
              }
  
-             // merged metadata
-             rec_md = record_metadata_init(se->nmem, (const char *) value,
-                                           ser_md->type, 0);
+             wrbuf_rewind(wrbuf_disp);
+             wrbuf_rewind(wrbuf_norm);
  
-             // see if the field was not in cluster already (from beginning)
+             value0 = xmlNodeListGetString(xdoc, n->children, 1);
+             if (!value0 || !*value0)
+             {
+                 if (value0)
+                     xmlFree(value0);
+                 continue;
+             }
+             if (ser_md->icurule)
+             {
+                 run_icu(se, ser_md->icurule, (const char *) value0,
+                         wrbuf_norm, wrbuf_disp);
+                 yaz_log(YLOG_LOG, "run_icu input=%s norm=%s disp=%s",
+                         (const char *) value0,
+                         wrbuf_cstr(wrbuf_norm), wrbuf_cstr(wrbuf_disp));
+                 rec_md = record_metadata_init(se->nmem, wrbuf_cstr(wrbuf_disp),
+                                               wrbuf_cstr(wrbuf_norm),
+                                               ser_md->type, 0);
+             }
+             else
+             {
+                 wrbuf_puts(wrbuf_disp, (const char *) value0);
+                 rec_md = record_metadata_init(se->nmem, wrbuf_cstr(wrbuf_disp),
+                                               0,
+                                               ser_md->type, 0);
+             }
+             xmlFree(value0);
  
+             // see if the field was not in cluster already (from beginning)
              if (!rec_md)
                  continue;
  
              }
              else
              {
-                 xml_rank = xmlGetProp(n, (xmlChar *) "rank");
+                 xml_rank = yaz_xml_get_prop(n, "rank");
                  rank = xml_rank ? (const char *) xml_rank : ser_md->rank;
              }
  
              {
                  while (*wheretoput)
                  {
-                     if (!strcmp((const char *) (*wheretoput)->data.text.disp,
-                                 rec_md->data.text.disp))
+                     if (!strcmp((const char *) (*wheretoput)->data.text.norm,
+                                 rec_md->data.text.norm))
                          break;
                      wheretoput = &(*wheretoput)->next;
                  }
              else if (ser_md->merge == Metadata_merge_longest)
              {
                  if (!*wheretoput
-                     || strlen(rec_md->data.text.disp)
-                     > strlen((*wheretoput)->data.text.disp))
+                     || strlen(rec_md->data.text.norm)
+                     > strlen((*wheretoput)->data.text.norm))
                  {
                      *wheretoput = rec_md;
                      if (ser_sk)
                      {
+                         pp2_charset_token_t prt;
                          const char *sort_str = 0;
                          int skip_article =
                              ser_sk->type == Metadata_type_skiparticle;
              if (rank)
              {
                  relevance_countwords(se->relevance, cluster,
-                                      (char *) value, rank, ser_md->name);
+                                      wrbuf_cstr(wrbuf_disp),
+                                      rank, ser_md->name);
              }
              // construct facets ... unless the client already has reported them
              if (ser_md->termlist && !client_has_facet(cl, (char *) type))
                      char year[64];
                      sprintf(year, "%d", rec_md->data.number.max);
  
-                     add_facet(se, (char *) type, year, term_factor);
+                     add_facet(se, (char *) type, year, term_factor, cl);
                      if (rec_md->data.number.max != rec_md->data.number.min)
                      {
                          sprintf(year, "%d", rec_md->data.number.min);
-                         add_facet(se, (char *) type, year, term_factor);
+                         add_facet(se, (char *) type, year, term_factor, cl);
                      }
                  }
                  else
-                     add_facet(se, (char *) type, (char *) value, term_factor);
+                     add_facet(se, type, wrbuf_cstr(wrbuf_disp), term_factor, cl);
              }
-             // cleaning up
-             if (xml_rank)
-                 xmlFree(xml_rank);
-             xmlFree(type);
-             xmlFree(value);
-             type = value = 0;
          }
          else
          {
              se->number_of_warnings_unknown_elements++;
          }
      }
-     if (type)
-         xmlFree(type);
-     if (value)
-         xmlFree(value);
      nmem_destroy(ingest_nmem);
      xfree(metadata0);
      relevance_donerecord(se->relevance, cluster);
@@@ -1,6 -1,6 +1,6 @@@
  test_limit_limitmap_service.xml http://localhost:9763/search.pz2?command=init&service=limitmap
  test_limit_limitmap_settings_1.xml http://localhost:9763/search.pz2?session=1&command=settings
- http://localhost:9763/search.pz2?session=1&command=search&query=computer
+ http://localhost:9763/search.pz2?session=1&command=search&query=computer&limit=date%3D1976-1978|date%3D%22foo%23%3F
  w http://localhost:9763/search.pz2?session=1&command=show&block=1
  http://localhost:9763/search.pz2?session=1&command=bytarget
  http://localhost:9763/search.pz2?session=1&command=termlist&block=1&name=xtargets%2Cauthor%2Csubject%2Cdate
@@@ -17,7 -17,7 +17,7 @@@ test_limit_limitmap_settings_3.xml http
  w http://localhost:9763/search.pz2?session=1&command=search&query=greece&limit=author%3Dadam\,+james%7Cother_author
  w http://localhost:9763/search.pz2?session=1&command=show&block=1
  http://localhost:9763/search.pz2?session=1&command=search&query=greece&limit=author%3Dadam\,+james%7Cother_author&filter=pz%3Aid%3DTarget-1
 -http://localhost:9763/search.pz2?session=1&command=bytarget
 +1 http://localhost:9763/search.pz2?session=1&command=bytarget
  http://localhost:9763/search.pz2?session=1&command=show
  test_limit_limitmap_settings_4.xml http://localhost:9763/search.pz2?session=1&command=settings
  http://localhost:9763/search.pz2?session=1&command=search&query=computer&limit=Mysubject%3DRailroads
diff --combined win/makefile
@@@ -5,7 -5,7 +5,7 @@@
  DEBUG=0   # 0 for release, 1 for debug
  USE_MANIFEST = 0 # Can be enabled Visual Studio 2005/2008
  PACKAGE_NAME=pazpar2
- PACKAGE_VERSION=1.8.6
+ PACKAGE_VERSION=1.11.3
  
  # YAZ
  YAZ_DIR=..\..\yaz
@@@ -175,6 -175,8 +175,6 @@@ LNKOPT= $(COMMON_LNK_OPTIONS) $(RELEASE
  # Source and object modules
  
  PAZPAR2_OBJS = \
 -   "$(OBJDIR)\getaddrinfo.obj" \
 -   "$(OBJDIR)\host.obj" \
     "$(OBJDIR)\pazpar2.obj" \
     "$(OBJDIR)\pazpar2_config.obj" \
     "$(OBJDIR)\http.obj" \