Merge branch 'master' into bug_4688
authorAdam Dickmeiss <adam@indexdata.dk>
Thu, 10 Nov 2011 10:05:14 +0000 (11:05 +0100)
committerAdam Dickmeiss <adam@indexdata.dk>
Thu, 10 Nov 2011 10:05:14 +0000 (11:05 +0100)
1  2 
src/client.c
src/client.h
src/session.c
src/session.h

diff --combined src/client.c
@@@ -121,7 -121,6 +121,7 @@@ struct client 
      YAZ_MUTEX mutex;
      int ref_count;
      char *id;
 +    facet_limits_t facet_limits;
  };
  
  struct suggestions {
@@@ -555,61 -554,12 +555,61 @@@ void client_got_records(struct client *
      struct session *se = cl->session;
      if (se)
      {
 -        client_unlock(cl);
 -        session_alert_watch(se, SESSION_WATCH_SHOW);
 -        session_alert_watch(se, SESSION_WATCH_BYTARGET);
 -        session_alert_watch(se, SESSION_WATCH_TERMLIST);
 -        session_alert_watch(se, SESSION_WATCH_RECORD);
 -        client_lock(cl);
 +        if (reclist_get_num_records(se->reclist) > 0)
 +        {
 +            client_unlock(cl);
 +            session_alert_watch(se, SESSION_WATCH_SHOW);
 +            session_alert_watch(se, SESSION_WATCH_BYTARGET);
 +            session_alert_watch(se, SESSION_WATCH_TERMLIST);
 +            session_alert_watch(se, SESSION_WATCH_RECORD);
 +            client_lock(cl);
 +        }
 +    }
 +}
 +
 +static void client_record_ingest(struct client *cl)
 +{
 +    const char *msg, *addinfo;
 +    ZOOM_record rec = 0;
 +    ZOOM_resultset resultset = cl->resultset;
 +    int offset = cl->record_offset;
 +    if ((rec = ZOOM_resultset_record(resultset, offset)))
 +    {
 +        cl->record_offset++;
 +        if (cl->session == 0)
 +            ;
 +        else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
 +        {
 +            yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
 +                    msg, addinfo, client_get_id(cl),
 +                    cl->record_offset);
 +        }
 +        else
 +        {
 +            struct session_database *sdb = client_get_database(cl);
 +            NMEM nmem = nmem_create();
 +            const char *xmlrec;
 +            char type[80];
 +            
 +            if (nativesyntax_to_type(sdb, type, rec))
 +                yaz_log(YLOG_WARN, "Failed to determine record type");
 +            xmlrec = ZOOM_record_get(rec, type, NULL);
 +            if (!xmlrec)
 +                yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
 +                        client_get_id(cl));
 +            else
 +            {
 +                /* OK = 0, -1 = failure, -2 = Filtered */
 +                if (ingest_record(cl, xmlrec, cl->record_offset, nmem) == -1)
 +                    yaz_log(YLOG_WARN, "Failed to ingest from %s", client_get_id(cl));
 +            }
 +            nmem_destroy(nmem);
 +        }
 +    }
 +    else
 +    {
 +        yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
 +                offset);
      }
  }
  
@@@ -628,9 -578,11 +628,9 @@@ void client_record_response(struct clie
      }
      else
      {
 -        ZOOM_record rec = 0;
 -        const char *msg, *addinfo;
 -        
          if (cl->show_raw && cl->show_raw->active)
          {
 +            ZOOM_record rec = 0;
              if ((rec = ZOOM_resultset_record(resultset,
                                               cl->show_raw->position-1)))
              {
          }
          else
          {
 -            int offset = cl->record_offset;
 -            if ((rec = ZOOM_resultset_record(resultset, offset)))
 -            {
 -                cl->record_offset++;
 -                if (cl->session == 0)
 -                    ;
 -                else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
 -                {
 -                    yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
 -                            msg, addinfo, client_get_id(cl),
 -                            cl->record_offset);
 -                }
 -                else
 -                {
 -                    struct session_database *sdb = client_get_database(cl);
 -                    NMEM nmem = nmem_create();
 -                    const char *xmlrec;
 -                    char type[80];
 -
 -                    if (nativesyntax_to_type(sdb, type, rec))
 -                        yaz_log(YLOG_WARN, "Failed to determine record type");
 -                    xmlrec = ZOOM_record_get(rec, type, NULL);
 -                    if (!xmlrec)
 -                        yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
 -                                client_get_id(cl));
 -                    else
 -                    {
 -                        /* OK = 0, -1 = failure, -2 = Filtered */
 -                        if (ingest_record(cl, xmlrec, cl->record_offset, nmem) == -1)
 -                            yaz_log(YLOG_WARN, "Failed to ingest from %s", client_get_id(cl));
 -                    }
 -                    nmem_destroy(nmem);
 -                }
 -            }
 -            else
 -            {
 -                yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
 -                        offset);
 -            }
 +            client_record_ingest(cl);
          }
      }
  }
  
 +void client_reingest(struct client *cl)
 +{
 +    int i = cl->startrecs;
 +    int to = cl->record_offset;
 +
 +    cl->record_offset = i;
 +    for (; i < to; i++)
 +        client_record_ingest(cl);
 +}
 +
  static void client_set_facets_request(struct client *cl, ZOOM_connection link)
  {
      struct session_database *sdb = client_get_database(cl);
@@@ -703,12 -683,37 +703,37 @@@ int client_has_facet(struct client *cl
      return 0;
  }
  
- void client_start_search(struct client *cl, const char *sort_strategy_and_spec,
-                          int increasing)
+ static const char *get_strategy_plus_sort(struct client *l, const char *field)
+ {
+     struct session_database *sdb = client_get_database(l);
+     struct setting *s;
+     const char *strategy_plus_sort = 0;
+     
+     for (s = sdb->settings[PZ_SORTMAP]; s; s = s->next)
+     {
+         char *p = strchr(s->name + 3, ':');
+         if (!p)
+         {
+             yaz_log(YLOG_WARN, "Malformed sortmap name: %s", s->name);
+             continue;
+         }
+         p++;
+         if (!strcmp(p, field))
+         {
+             strategy_plus_sort = s->value;
+             break;
+         }
+     }
+     return strategy_plus_sort;
+ }
+ void client_start_search(struct client *cl)
  {
      struct session_database *sdb = client_get_database(cl);
      struct connection *co = client_get_connection(cl);
      ZOOM_connection link = connection_get_link(co);
+     struct session *se = client_get_session(cl);
      ZOOM_resultset rs;
      const char *opt_piggyback   = session_setting_oneval(sdb, PZ_PIGGYBACK);
      const char *opt_queryenc    = session_setting_oneval(sdb, PZ_QUERYENCODING);
  
      assert(link);
  
 +    cl->hits = 0;
      cl->record_offset = 0;
      cl->diagnostic = 0;
  
          
          ZOOM_query_prefix(q, cl->pquery);
      }
-     if (sort_strategy_and_spec &&
-         strlen(sort_strategy_and_spec) < 40 /* spec below */)
-     {
-         char spec[50], *p;
-         strcpy(spec, sort_strategy_and_spec);
-         p = strchr(spec, ':');
-         if (p)
+     if (se->sorted_results)
+     {   /* first entry is current sorting ! */
+         const char *sort_strategy_and_spec =
+             get_strategy_plus_sort(cl, se->sorted_results->field);
+         int increasing = se->sorted_results->increasing;
+         if (sort_strategy_and_spec && strlen(sort_strategy_and_spec) < 40)
          {
-             *p++ = '\0'; /* cut the string in two */
-             while (*p == ' ')
-                 p++;
-             if (increasing)
-                 strcat(p, " <");
-             else
-                 strcat(p, " >");
-             yaz_log(YLOG_LOG, "applying %s %s", spec, p);
-             ZOOM_query_sortby2(q, spec, p);
+             char spec[50], *p;
+             strcpy(spec, sort_strategy_and_spec);
+             p = strchr(spec, ':');
+             if (p)
+             {
+                 *p++ = '\0'; /* cut the string in two */
+                 while (*p == ' ')
+                     p++;
+                 if (increasing)
+                     strcat(p, " <");
+                 else
+                     strcat(p, " >");
+                 yaz_log(YLOG_LOG, "applying %s %s", spec, p);
+                 ZOOM_query_sortby2(q, spec, p);
+             }
+         }
+         else
+         {
+             /* no native sorting.. If this is not the first search, then
+                skip it entirely */
+             if (se->sorted_results->next)
+             {
+                 ZOOM_query_destroy(q);
+                 client_set_state_nb(cl, Client_Idle);
+                 return;
+             }
          }
      }
      rs = ZOOM_connection_search(link, q);
@@@ -835,7 -855,6 +876,7 @@@ struct client *client_create(const cha
      pazpar2_mutex_create(&cl->mutex, "client");
      cl->preferred = 0;
      cl->ref_count = 1;
 +    cl->facet_limits = 0;
      assert(id);
      cl->id = xstrdup(id);
      client_use(1);
@@@ -874,7 -893,6 +915,7 @@@ int client_destroy(struct client *c
              c->cqlquery = 0;
              xfree(c->id);
              assert(!c->connection);
 +            facet_limits_destroy(c->facet_limits);
  
              if (c->resultset)
              {
@@@ -998,38 -1016,10 +1039,38 @@@ static char *make_solrquery(struct clie
      return r;
  }
  
 -static void apply_limit(struct session_database *sdb,
 -                        facet_limits_t facet_limits,
 -                        WRBUF w_pqf, WRBUF w_ccl)
 +const char *client_get_facet_limit_local(struct client *cl,
 +                                         struct session_database *sdb,
 +                                         int *l,
 +                                         NMEM nmem, int *num, char ***values)
 +{
 +    const char *name = 0;
 +    const char *value = 0;
 +    for (; (name = facet_limits_get(cl->facet_limits, *l, &value)); (*l)++)
 +    {
 +        struct setting *s = 0;
 +        
 +        for (s = sdb->settings[PZ_LIMITMAP]; s; s = s->next)
 +        {
 +            const char *p = strchr(s->name + 3, ':');
 +            if (p && !strcmp(p + 1, name) && s->value &&
 +                !strncmp(s->value, "local:", 6))
 +            {
 +                nmem_strsplit_escape2(nmem, "|", value, values,
 +                                      num, 1, '\\', 1);
 +                (*l)++;
 +                return name;
 +            }
 +        }
 +    }
 +    return 0;
 +}
 +
 +static int apply_limit(struct session_database *sdb,
 +                       facet_limits_t facet_limits,
 +                       WRBUF w_pqf, WRBUF w_ccl)
  {
 +    int ret = 0;
      int i = 0;
      const char *name;
      const char *value;
                      wrbuf_puts(w_ccl, ")");
  
                  }
 +                else if (!strncmp(s->value, "local:", 6))
 +                    ;
 +                else
 +                {
 +                    yaz_log(YLOG_WARN, "Target %s: Bad limitmap '%s'",
 +                            sdb->database->id, s->value);
 +                    ret = -1; /* bad limitmap */
 +                }
                  break;
              }
          }
          }
      }
      nmem_destroy(nmem_tmp);
 +    return ret;
  }
                          
  // Parse the query given the settings specific to this client
  int client_parse_query(struct client *cl, const char *query,
 -                       facet_limits_t facet_limits)
 +                       facet_limits_t facet_limits,
 +                       const char *startrecs, const char *maxrecs)
  {
      struct session *se = client_get_session(cl);
      struct session_database *sdb = client_get_database(cl);
      const char *pqf_strftime = session_setting_oneval(sdb, PZ_PQF_STRFTIME);
      const char *query_syntax = session_setting_oneval(sdb, PZ_QUERY_SYNTAX);
      WRBUF w_ccl, w_pqf;
 +    int ret_value = 1;
 +
      if (!ccl_map)
          return -1;
  
 -    cl->hits = -1;
 +
 +    if (maxrecs && atoi(maxrecs) != cl->maxrecs)
 +    {
 +        ret_value = 0;
 +        cl->maxrecs = atoi(maxrecs);
 +    }
 +
 +    if (startrecs && atoi(startrecs) != cl->startrecs)
 +    {
 +        ret_value = 0;
 +        cl->startrecs = atoi(startrecs);
 +    }
 +
      w_ccl = wrbuf_alloc();
      wrbuf_puts(w_ccl, query);
  
          wrbuf_puts(w_pqf, " ");
      }
  
 -    apply_limit(sdb, facet_limits, w_pqf, w_ccl);
 +    if (apply_limit(sdb, facet_limits, w_pqf, w_ccl))
 +        return -2;
 +
 +    facet_limits_destroy(cl->facet_limits);
 +    cl->facet_limits = facet_limits_dup(facet_limits);
  
      yaz_log(YLOG_LOG, "CCL query: %s", wrbuf_cstr(w_ccl));
      cn = ccl_find_str(ccl_map, wrbuf_cstr(w_ccl), &cerror, &cpos);
                  wrbuf_putc(w_pqf, cp[0]);
          }
      }
 -    xfree(cl->pquery);
 -    cl->pquery = xstrdup(wrbuf_cstr(w_pqf));
 +    if (!cl->pquery || strcmp(cl->pquery, wrbuf_cstr(w_pqf)))
 +    {
 +        xfree(cl->pquery);
 +        cl->pquery = xstrdup(wrbuf_cstr(w_pqf));
 +        ret_value = 0;
 +    }
      wrbuf_destroy(w_pqf);
  
      yaz_log(YLOG_LOG, "PQF query: %s", cl->pquery);
      }
  
      ccl_rpn_delete(cn);
 -    return 0;
 +    return ret_value;
  }
  
  void client_set_session(struct client *cl, struct session *se)
@@@ -1314,11 -1272,21 +1355,11 @@@ const char *client_get_id(struct clien
      return cl->id;
  }
  
 -void client_set_maxrecs(struct client *cl, int v)
 -{
 -    cl->maxrecs = v;
 -}
 -
  int client_get_maxrecs(struct client *cl)
  {
      return cl->maxrecs;
  }
  
 -void client_set_startrecs(struct client *cl, int v)
 -{
 -    cl->startrecs = v;
 -}
 -
  void client_set_preferred(struct client *cl, int v)
  {
      cl->preferred = v;
diff --combined src/client.h
@@@ -77,23 -77,23 +77,22 @@@ int client_prep_connection(struct clien
                             int operation_timeout, int session_timeout,
                             iochan_man_t iochan,
                             const struct timeval *abstime);
- void client_start_search(struct client *cl, const char *sort_strategy_and_spec,
-                          int increasing);
+ void client_start_search(struct client *cl);
  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);
  struct client *client_next_in_session(struct client *cl);
  
  int client_parse_query(struct client *cl, const char *query,
 -                       facet_limits_t facet_limits);
 +                       facet_limits_t facet_limits, const char *startrecs,
 +                       const char *maxrecs);
  Odr_int client_get_hits(struct client *cl);
  int client_get_num_records(struct client *cl);
  int client_get_diagnostic(struct client *cl);
  void client_set_diagnostic(struct client *cl, int diagnostic);
  void client_set_database(struct client *cl, struct session_database *db);
  const char *client_get_id(struct client *cl);
 -void client_set_maxrecs(struct client *cl, int v);
  int  client_get_maxrecs(struct client *cl);
 -void client_set_startrecs(struct client *cl, int v);
  void client_remove_from_session(struct client *c);
  void client_incref(struct client *c);
  void client_got_records(struct client *c);
@@@ -102,11 -102,7 +101,11 @@@ void client_unlock(struct client *c)
  
  int client_has_facet(struct client *cl, const char *name);
  void client_check_preferred_watch(struct client *cl);
 -
 +void client_reingest(struct client *cl);
 +const char *client_get_facet_limit_local(struct client *cl,
 +                                         struct session_database *sdb,
 +                                         int *l,
 +                                         NMEM nmem, int *num, char ***values);
  #endif
  
  /*
diff --combined src/session.c
@@@ -95,12 -95,6 +95,6 @@@ struct client_list 
      struct client_list *next;
  };
  
- struct session_sorted_results {
-     const char *field;
-     int increasing;
-     struct session_sorted_results *next;
- };
  /* session counting (1) , disable client counting (0) */
  static YAZ_MUTEX g_session_mutex = 0;
  static int no_sessions = 0;
@@@ -165,14 -159,13 +159,14 @@@ static void session_leave(struct sessio
      yaz_mutex_leave(s->session_mutex);
  }
  
 -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;
      pp2_charset_token_t prt;
      const char *facet_component;
 -    WRBUF facet_wrbuf = wrbuf_alloc();
 -    WRBUF display_wrbuf = wrbuf_alloc();
      int i;
      const char *icu_chain_id = 0;
  
          }
      }
      pp2_charset_token_destroy(prt);
 +}
 +
 +void add_facet(struct session *s, const char *type, const char *value, int count)
 +{
 +    WRBUF facet_wrbuf = wrbuf_alloc();
 +    WRBUF display_wrbuf = wrbuf_alloc();
 +
 +    session_normalize_facet(s, type, value, display_wrbuf, facet_wrbuf);
   
      if (wrbuf_len(facet_wrbuf))
      {
@@@ -414,8 -399,12 +408,8 @@@ static int prepare_map(struct session *
  {
      const char *s;
  
 -    if (!sdb->settings)
 -    {
 -        session_log(se, YLOG_WARN, "No settings on %s", sdb->database->id);
 -        return -1;
 -    }
 -    if ((s = session_setting_oneval(sdb, PZ_XSLT)))
 +    if (sdb->settings && sdb->settings[PZ_XSLT] && !sdb->map &&
 +        (s = session_setting_oneval(sdb, PZ_XSLT)))        
      {
          char auto_stylesheet[256];
  
      return 0;
  }
  
 -// This analyzes settings and recomputes any supporting data structures
 -// if necessary.
 -static int prepare_session_database(struct session *se, 
 -                                    struct session_database *sdb)
 -{
 -    if (!sdb->settings)
 -    {
 -        session_log(se, YLOG_WARN, 
 -                "No settings associated with %s", sdb->database->id);
 -        return -1;
 -    }
 -    if (sdb->settings[PZ_XSLT] && !sdb->map)
 -    {
 -        if (prepare_map(se, sdb) < 0)
 -            return -1;
 -    }
 -    return 0;
 -}
 -
  // called if watch should be removed because http_channel is to be destroyed
  static void session_watch_cancel(void *data, struct http_channel *c,
                                   void *data2)
@@@ -516,60 -524,26 +510,60 @@@ void session_alert_watch(struct sessio
  static void select_targets_callback(struct session *se,
                                      struct session_database *db)
  {
 -    struct client *cl = client_create(db->database->id);
 +    struct client *cl;
      struct client_list *l;
  
 -    client_set_database(cl, db);
 +    for (l = se->clients_cached; l; l = l->next)
 +        if (client_get_database(l->client) == db)
 +            break;
  
 -    client_set_session(cl, se);
 +    if (l)
 +        cl = l->client;
 +    else
 +    {
 +        cl = client_create(db->database->id);
 +        client_set_database(cl, db);
 +        client_set_session(cl, se);
 +
 +        l = xmalloc(sizeof(*l));
 +        l->client = cl;
 +        l->next = se->clients_cached;
 +        se->clients_cached = l;
 +    }
  
      l = xmalloc(sizeof(*l));
      l->client = cl;
 -    l->next = se->clients;
 -    se->clients = l;
 +    l->next = se->clients_active;
 +    se->clients_active = l;
  }
  
 -static void session_remove_clients(struct session *se)
 +static void session_reset_active_clients(struct session *se,
 +                                         struct client_list *new_list)
  {
      struct client_list *l;
  
      session_enter(se);
 -    l = se->clients;
 -    se->clients = 0;
 +    l = se->clients_active;
 +    se->clients_active = new_list;
 +    session_leave(se);
 +
 +    while (l)
 +    {
 +        struct client_list *l_next = l->next;
 +        xfree(l);
 +        l = l_next;
 +    }
 +}
 +
 +static void session_remove_cached_clients(struct session *se)
 +{
 +    struct client_list *l;
 +
 +    session_reset_active_clients(se, 0);
 +
 +    session_enter(se);
 +    l = se->clients_cached;
 +    se->clients_cached = 0;
      session_leave(se);
  
      while (l)
@@@ -598,7 -572,7 +592,7 @@@ int session_active_clients(struct sessi
      struct client_list *l;
      int res = 0;
  
 -    for (l = s->clients; l; l = l->next)
 +    for (l = s->clients_active; l; l = l->next)
          if (client_is_active(l->client))
              res++;
  
@@@ -610,38 -584,13 +604,13 @@@ int session_is_preferred_clients_ready(
      struct client_list *l;
      int res = 0;
  
 -    for (l = s->clients; l; l = l->next)
 +    for (l = s->clients_active; l; l = l->next)
          if (client_is_active_preferred(l->client))
              res++;
      session_log(s, YLOG_DEBUG, "Has %d active preferred clients.", res);
      return res == 0;
  }
  
- static const char *get_strategy_plus_sort(struct client *l, const char *field)
- {
-     struct session_database *sdb = client_get_database(l);
-     struct setting *s;
-     const char *strategy_plus_sort = 0;
-     
-     for (s = sdb->settings[PZ_SORTMAP]; s; s = s->next)
-     {
-         char *p = strchr(s->name + 3, ':');
-         if (!p)
-         {
-             yaz_log(YLOG_WARN, "Malformed sortmap name: %s", s->name);
-             continue;
-         }
-         p++;
-         if (!strcmp(p, field))
-         {
-             strategy_plus_sort = s->value;
-             break;
-         }
-     }
-     return strategy_plus_sort;
- }
  void session_sort(struct session *se, const char *field, int increasing)
  {
      struct session_sorted_results *sr;
      sr->next = se->sorted_results;
      se->sorted_results = sr;
      
 -    for (l = se->clients; l; l = l->next)
 +    for (l = se->clients_active; l; l = l->next)
      {
          struct client *cl = l->client;
-         const char *strategy_plus_sort = get_strategy_plus_sort(cl, field);
-         if (strategy_plus_sort)
-         {
-             struct timeval tval;
-             if (client_prep_connection(cl, se->service->z3950_operation_timeout,
-                                        se->service->z3950_session_timeout,
-                                        se->service->server->iochan_man,
-                                        &tval))
-                 client_start_search(cl, strategy_plus_sort, increasing);
-         }
+         struct timeval tval;
+         if (client_prep_connection(cl, se->service->z3950_operation_timeout,
+                                    se->service->z3950_session_timeout,
+                                    se->service->server->iochan_man,
+                                    &tval))
+             client_start_search(cl);
      }
      session_leave(se);
  }
@@@ -698,9 -643,8 +663,9 @@@ enum pazpar2_error_code session_search(
  {
      int live_channels = 0;
      int no_working = 0;
 -    int no_failed = 0;
 -    struct client_list *l;
 +    int no_failed_query = 0;
 +    int no_failed_limit = 0;
 +    struct client_list *l, *l0;
      struct timeval tval;
      facet_limits_t facet_limits;
  
  
      *addinfo = 0;
  
 -    session_remove_clients(se);
 +    if (se->settings_modified)
 +        session_remove_cached_clients(se);
 +    else
 +        session_reset_active_clients(se, 0);
      
      session_enter(se);
      reclist_destroy(se->reclist);
      se->reclist = 0;
 +    se->settings_modified = 0;
      relevance_destroy(&se->relevance);
      nmem_reset(se->nmem);
      se->total_records = se->total_merged = 0;
          session_leave(se);
          return PAZPAR2_MALFORMED_PARAMETER_VALUE;
      }
 -    for (l = se->clients; l; l = l->next)
 +
 +    l0 = se->clients_active;
 +    se->clients_active = 0;
 +    session_leave(se);
 +
 +    for (l = l0; l; l = l->next)
      {
 +        int parse_ret;
          struct client *cl = l->client;
-         const char *strategy_plus_sort = get_strategy_plus_sort(cl, sort_field);
  
 -        if (maxrecs)
 -            client_set_maxrecs(cl, atoi(maxrecs));
 -        if (startrecs)
 -            client_set_startrecs(cl, atoi(startrecs));
 -        if (prepare_session_database(se, client_get_database(cl)) < 0)
 -            ;
 -        else if (client_parse_query(cl, query, facet_limits) < 0)
 -            no_failed++;
 -        else
 +        if (prepare_map(se, client_get_database(cl)) < 0)
 +            continue;
 +
 +        parse_ret = client_parse_query(cl, query, facet_limits, startrecs,
 +            maxrecs);
 +        if (parse_ret == -1)
 +            no_failed_query++;
 +        else if (parse_ret == -2)
 +            no_failed_limit++;
 +        else if (parse_ret == 0)
          {
 +            session_log(se, YLOG_LOG, "client NEW %s", client_get_id(cl));
              no_working++;
              if (client_prep_connection(cl, se->service->z3950_operation_timeout,
                                         se->service->z3950_session_timeout,
                                         se->service->server->iochan_man,
                                         &tval))
-                 client_start_search(cl, strategy_plus_sort, increasing);
+                 client_start_search(cl);
          }
 +        else
 +        {
 +            session_log(se, YLOG_LOG, "client REUSE %s", client_get_id(cl));
 +            no_working++;
 +            if (client_prep_connection(cl, se->service->z3950_operation_timeout,
 +                                       se->service->z3950_session_timeout,
 +                                       se->service->server->iochan_man,
 +                                       &tval))
 +            {
 +                client_reingest(cl);
 +            }
 +        }
      }
      facet_limits_destroy(facet_limits);
 -    session_leave(se);
 +    session_reset_active_clients(se, l0);
 +
      if (no_working == 0)
      {
 -        if (no_failed > 0)
 +        if (no_failed_query > 0)
          {
              *addinfo = "query";
              return PAZPAR2_MALFORMED_PARAMETER_VALUE;
          }
 +        else if (no_failed_limit > 0)
 +        {
 +            *addinfo = "limit";
 +            return PAZPAR2_MALFORMED_PARAMETER_VALUE;
 +        }
          else
              return PAZPAR2_NO_TARGETS;
      }
 +    yaz_log(YLOG_LOG, "session_start_search done");
      return PAZPAR2_NO_ERROR;
  }
  
@@@ -892,8 -804,6 +856,8 @@@ void session_apply_setting(struct sessi
      new->next = sdb->settings[offset];
      sdb->settings[offset] = new;
  
 +    se->settings_modified = 1;
 +
      // Force later recompute of settings-driven data structures
      // (happens when a search starts and client connections are prepared)
      switch (offset)
      }
  }
  
 -void session_destroy(struct session *se) {
 +void session_destroy(struct session *se)
 +{
      struct session_database *sdb;
      session_log(se, YLOG_DEBUG, "Destroying");
      session_use(-1);
 -    session_remove_clients(se);
 +    session_remove_cached_clients(se);
  
      for (sdb = se->databases; sdb; sdb = sdb->next)
          session_database_destroy(sdb);
@@@ -954,9 -863,7 +918,9 @@@ struct session *new_session(NMEM nmem, 
      session->number_of_warnings_unknown_metadata = 0;
      session->num_termlists = 0;
      session->reclist = 0;
 -    session->clients = 0;
 +    session->clients_active = 0;
 +    session->clients_cached = 0;
 +    session->settings_modified = 0;
      session->session_nmem = nmem;
      session->nmem = nmem_create();
      session->databases = 0;
@@@ -981,12 -888,12 +945,12 @@@ static struct hitsbytarget *hitsbytarge
      struct client_list *l;
      size_t sz = 0;
  
 -    for (l = se->clients; l; l = l->next)
 +    for (l = se->clients_active; l; l = l->next)
          sz++;
  
      res = nmem_malloc(nmem, sizeof(*res) * sz);
      *count = 0;
 -    for (l = se->clients; l; l = l->next)
 +    for (l = se->clients_active; l; l = l->next)
      {
          struct client *cl = l->client;
          WRBUF w = wrbuf_alloc();
@@@ -1228,7 -1135,7 +1192,7 @@@ struct record_cluster **show_range_star
          *total = reclist_get_num_records(se->reclist);
  
          *sumhits = 0;
 -        for (l = se->clients; l; l = l->next)
 +        for (l = se->clients_active; l; l = l->next)
              *sumhits += client_get_hits(l->client);
          
          for (i = 0; i < start; i++)
@@@ -1273,7 -1180,7 +1237,7 @@@ void statistics(struct session *se, str
  
      memset(stat, 0, sizeof(*stat));
      stat->num_hits = 0;
 -    for (l = se->clients; l; l = l->next)
 +    for (l = se->clients_active; l; l = l->next)
      {
          struct client *cl = l->client;
          if (!client_get_connection(cl))
@@@ -1573,85 -1480,13 +1537,85 @@@ int ingest_record(struct client *cl, co
      }
      session_enter(se);
      if (client_get_session(cl) == se)
 -        ingest_to_cluster(cl, xdoc, root, record_no, mergekey_norm);
 +        ret = ingest_to_cluster(cl, xdoc, root, record_no, mergekey_norm);
      session_leave(se);
      
      xmlFreeDoc(xdoc);
      return ret;
  }
  
 +static int check_limit_local(struct client *cl,
 +                             struct record *record,
 +                             int record_no)
 +{
 +    int skip_record = 0;
 +    struct session *se = client_get_session(cl);
 +    struct conf_service *service = se->service;
 +    NMEM nmem_tmp = nmem_create();
 +    struct session_database *sdb = client_get_database(cl);
 +    int l = 0;
 +    while (!skip_record)
 +    {
 +        struct conf_metadata *ser_md = 0;
 +        struct record_metadata *rec_md = 0;
 +        int md_field_id;
 +        char **values = 0;
 +        int i, num_v = 0;
 +        
 +        const char *name =
 +            client_get_facet_limit_local(cl, sdb, &l, nmem_tmp, &num_v,
 +                                         &values);
 +        if (!name)
 +            break;
 +        
 +        md_field_id = conf_service_metadata_field_id(service, name);
 +        if (md_field_id < 0)
 +        {
 +            skip_record = 1;
 +            break;
 +        }
 +        ser_md = &service->metadata[md_field_id];
 +        rec_md = record->metadata[md_field_id];
 +        yaz_log(YLOG_LOG, "check limit local %s", name);
 +        for (i = 0; i < num_v; )
 +        {
 +            if (rec_md)
 +            {
 +                if (ser_md->type == Metadata_type_year 
 +                    || ser_md->type == Metadata_type_date)
 +                {
 +                    int y = atoi(values[i]);
 +                    if (y >= rec_md->data.number.min 
 +                        && y <= rec_md->data.number.max)
 +                        break;
 +                }
 +                else
 +                {
 +                    yaz_log(YLOG_LOG, "cmp: '%s' '%s'",
 +                            rec_md->data.text.disp, values[i]);
 +                    if (!strcmp(rec_md->data.text.disp, values[i]))
 +                    {
 +                        break;
 +                    }
 +                }
 +                rec_md = rec_md->next;
 +            }
 +            else
 +            {
 +                rec_md = record->metadata[md_field_id];
 +                i++;
 +            }
 +        }
 +        if (i == num_v)
 +        {
 +            skip_record = 1;
 +            break;
 +        }
 +    }
 +    nmem_destroy(nmem_tmp);
 +    return skip_record;
 +}
 +                             
  static int ingest_to_cluster(struct client *cl,
                               xmlDoc *xdoc,
                               xmlNode *root,
          }
      }
  
 +    if (check_limit_local(cl, record, record_no))
 +    {
 +        session_log(se, YLOG_LOG, "Facet filtered out record no %d from %s",
 +                    record_no, sdb->database->id);
 +        if (type)
 +            xmlFree(type);
 +        if (value)
 +            xmlFree(value);
 +        return -2;
 +    }
      cluster = reclist_insert(se->reclist, service, record,
                               mergekey_norm, &se->total_merged);
      if (!cluster)
diff --combined src/session.h
@@@ -102,8 -102,7 +102,8 @@@ struct client_list
  struct session {
      struct conf_service *service; /* service in use for this session */
      struct session_database *databases;  // All databases, settings overriden
 -    struct client_list *clients;   // Clients connected for current search
 +    struct client_list *clients_active; // Clients connected for current search
 +    struct client_list *clients_cached; // Clients in cache
      NMEM session_nmem;  // Nmem for session-permanent storage
      NMEM nmem;          // Nmem for each operation (i.e. search, result set, etc)
      int num_termlists;
      normalize_cache_t normalize_cache;
      YAZ_MUTEX session_mutex;
      unsigned session_id;
 +    int settings_modified;
      struct session_sorted_results *sorted_results;
  };
  
@@@ -183,6 -181,7 +183,7 @@@ int ingest_record(struct client *cl, co
  void session_alert_watch(struct session *s, int what);
  void add_facet(struct session *s, const char *type, const char *value, int count);
  
  void perform_termlist(struct http_channel *c, struct session *se,
                        const char *name, int num);
  void session_log(struct session *s, int level, const char *fmt, ...)
      ;
  #endif
  
+ struct session_sorted_results {
+     const char *field;
+     int increasing;
+     struct session_sorted_results *next;
+ };
  /*
   * Local variables:
   * c-basic-offset: 4