New service setting: ccldirective
[pazpar2-moved-to-github.git] / src / session.c
index f2d7e2d..8edaa8d 100644 (file)
@@ -1,5 +1,5 @@
 /* This file is part of Pazpar2.
-   Copyright (C) 2006-2011 Index Data
+   Copyright (C) 2006-2012 Index Data
 
 Pazpar2 is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free
@@ -76,6 +76,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #include "settings.h"
 #include "normalize7bit.h"
 
+#include <libxml/tree.h>
+
 #define TERMLIST_HIGH_SCORE 25
 
 #define MAX_CHUNK 15
@@ -159,13 +161,14 @@ static void session_leave(struct session *s)
     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;
 
@@ -202,6 +205,14 @@ void add_facet(struct session *s, const char *type, const char *value, int count
         }
     }
     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))
     {
@@ -397,63 +408,43 @@ const char *session_setting_oneval(struct session_database *db, int offset)
 // setting. However, this is not a realistic use scenario.
 static int prepare_map(struct session *se, struct session_database *sdb)
 {
-    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->map)
     {
-        char auto_stylesheet[256];
+        const char *s;
 
-        if (!strcmp(s, "auto"))
+        if (sdb->settings[PZ_XSLT] &&
+            (s = session_setting_oneval(sdb, PZ_XSLT)))        
         {
-            const char *request_syntax = session_setting_oneval(
-                sdb, PZ_REQUESTSYNTAX);
-            if (request_syntax)
+            char auto_stylesheet[256];
+            
+            if (!strcmp(s, "auto"))
             {
-                char *cp;
-                yaz_snprintf(auto_stylesheet, sizeof(auto_stylesheet),
-                             "%s.xsl", request_syntax);
-                for (cp = auto_stylesheet; *cp; cp++)
+                const char *request_syntax = session_setting_oneval(
+                    sdb, PZ_REQUESTSYNTAX);
+                if (request_syntax)
                 {
-                    /* deliberately only consider ASCII */
-                    if (*cp > 32 && *cp < 127)
-                        *cp = tolower(*cp);
+                    char *cp;
+                    yaz_snprintf(auto_stylesheet, sizeof(auto_stylesheet),
+                                 "%s.xsl", request_syntax);
+                    for (cp = auto_stylesheet; *cp; cp++)
+                    {
+                        /* deliberately only consider ASCII */
+                        if (*cp > 32 && *cp < 127)
+                            *cp = tolower(*cp);
+                    }
+                    s = auto_stylesheet;
+                }
+                else
+                {
+                    session_log(se, YLOG_WARN,
+                                "No pz:requestsyntax for auto stylesheet");
                 }
-                s = auto_stylesheet;
-            }
-            else
-            {
-                session_log(se, YLOG_WARN,
-                            "No pz:requestsyntax for auto stylesheet");
             }
+            sdb->map = normalize_cache_get(se->normalize_cache,
+                                           se->service, s);
+            if (!sdb->map)
+                return -1;
         }
-        sdb->map = normalize_cache_get(se->normalize_cache,
-                                       se->service, s);
-        if (!sdb->map)
-            return -1;
-    }
-    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;
 }
@@ -524,26 +515,66 @@ void session_alert_watch(struct session *s, int what)
 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;
 
+    if (l)
+        cl = l->client;
+    else
+    {
+        cl = client_create(db->database->id);
+        client_set_database(cl, db);
+
+        l = xmalloc(sizeof(*l));
+        l->client = cl;
+        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));
     l->client = cl;
-    l->next = se->clients;
-    se->clients = l;
+    l->next = se->clients_active;
+    se->clients_active = l;
+}
+
+static void session_reset_active_clients(struct session *se,
+                                         struct client_list *new_list)
+{
+    struct client_list *l;
+
+    session_enter(se);
+    l = se->clients_active;
+    se->clients_active = new_list;
+    session_leave(se);
+
+    while (l)
+    {
+        struct client_list *l_next = l->next;
+
+        client_lock(l->client); 
+        client_set_session(l->client, 0); /* mark client inactive */
+        client_unlock(l->client);
+
+        xfree(l);
+        l = l_next;
+    }
 }
 
-static void session_remove_clients(struct session *se)
+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;
-    se->clients = 0;
+    l = se->clients_cached;
+    se->clients_cached = 0;
     session_leave(se);
 
     while (l)
@@ -572,7 +603,7 @@ int session_active_clients(struct session *s)
     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++;
 
@@ -584,49 +615,77 @@ int session_is_preferred_clients_ready(struct session *s)
     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;
 }
 
-void session_sort(struct session *se, const char *field, int increasing)
+static void session_clear_set(struct session *se,
+                              const char *sort_field, int increasing)
+{
+    reclist_destroy(se->reclist);
+    se->reclist = 0;
+    if (nmem_total(se->nmem))
+        session_log(se, YLOG_DEBUG, "NMEN operation usage %zd",
+                    nmem_total(se->nmem));
+    nmem_reset(se->nmem);
+    se->total_records = se->total_merged = 0;
+    se->num_termlists = 0;
+    
+    /* reset list of sorted results and clear to relevance search */
+    se->sorted_results = nmem_malloc(se->nmem, sizeof(*se->sorted_results));
+    se->sorted_results->field = nmem_strdup(se->nmem, sort_field);
+    se->sorted_results->increasing = increasing;
+    se->sorted_results->next = 0;
+    
+    se->reclist = reclist_create(se->nmem);
+}
+
+void session_sort(struct session *se, const char *field, int increasing,
+                  int clear_set)
 {
     struct session_sorted_results *sr;
     struct client_list *l;
 
     session_enter(se);
 
-    /* see if we already have sorted for this critieria */
-    for (sr = se->sorted_results; sr; sr = sr->next)
+    yaz_log(YLOG_LOG, "session_sort field=%s", field);
+    if (clear_set)
     {
-        if (!strcmp(field, sr->field) && increasing == sr->increasing)
-            break;
+        session_clear_set(se, field, increasing);
     }
-    if (sr)
+    else
     {
-        yaz_log(YLOG_LOG, "search_sort: field=%s increasing=%d already fetched",
-                field, increasing);
-        session_leave(se);
-        return;
+        /* see if we already have sorted for this critieria */
+        for (sr = se->sorted_results; sr; sr = sr->next)
+        {
+            if (!strcmp(field, sr->field) && increasing == sr->increasing)
+                break;
+        }
+        if (sr)
+        {
+            session_log(se, YLOG_DEBUG, "search_sort: field=%s increasing=%d already fetched",
+                        field, increasing);
+            session_leave(se);
+            return;
+        }
+        session_log(se, YLOG_DEBUG, "search_sort: field=%s increasing=%d must fetch",
+                    field, increasing);
+        sr = nmem_malloc(se->nmem, sizeof(*sr));
+        sr->field = nmem_strdup(se->nmem, field);
+        sr->increasing = increasing;
+        sr->next = se->sorted_results;
+        se->sorted_results = sr;
     }
-    yaz_log(YLOG_LOG, "search_sort: field=%s increasing=%d must fetch",
-            field, increasing);
-    sr = nmem_malloc(se->nmem, sizeof(*sr));
-    sr->field = nmem_strdup(se->nmem, field);
-    sr->increasing = increasing;
-    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;
-        struct timeval tval;
-        if (client_prep_connection(cl, se->service->z3950_operation_timeout,
-                                   se->service->z3950_session_timeout,
-                                   se->service->server->iochan_man,
-                                   &tval))
+        if (client_get_state(cl) == Client_Connecting ||
+            client_get_state(cl) == Client_Idle ||
+            client_get_state(cl) == Client_Working)
             client_start_search(cl);
     }
     session_leave(se);
@@ -643,8 +702,9 @@ enum pazpar2_error_code session_search(struct session *se,
 {
     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;
 
@@ -652,29 +712,22 @@ enum pazpar2_error_code session_search(struct session *se,
 
     *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;
+    session_clear_set(se, sort_field, increasing);
     relevance_destroy(&se->relevance);
-    nmem_reset(se->nmem);
-    se->total_records = se->total_merged = 0;
-    se->num_termlists = 0;
 
-    /* reset list of sorted results and clear to relevance search */
-    se->sorted_results = nmem_malloc(se->nmem, sizeof(*se->sorted_results));
-    se->sorted_results->field = nmem_strdup(se->nmem, sort_field);
-    se->sorted_results->increasing = increasing;
-    se->sorted_results->next = 0;
-    
     live_channels = select_targets(se, filter);
     if (!live_channels)
     {
         session_leave(se);
         return PAZPAR2_NO_TARGETS;
     }
-    se->reclist = reclist_create(se->nmem);
 
     yaz_gettimeofday(&tval);
     
@@ -687,40 +740,64 @@ enum pazpar2_error_code session_search(struct session *se,
         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;
 
-        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++;
+        if (prepare_map(se, client_get_database(cl)) < 0)
+            continue;
+
+        parse_ret = client_parse_query(cl, query, facet_limits, startrecs,
+                                       maxrecs, se->service->ccl_bibset);
+        if (parse_ret == -1)
+            no_failed_query++;
+        else if (parse_ret == -2)
+            no_failed_limit++;
         else
         {
-            no_working++;
-            if (client_prep_connection(cl, se->service->z3950_operation_timeout,
+            int r =
+                client_prep_connection(cl, se->service->z3950_operation_timeout,
                                        se->service->z3950_session_timeout,
                                        se->service->server->iochan_man,
-                                       &tval))
+                                       &tval);
+            if (parse_ret == 1 && r == 2)
+            {
+                session_log(se, YLOG_LOG, "client %s REUSE result", client_get_id(cl));
+                client_reingest(cl);
+            }
+            else if (r)
+            {
+                session_log(se, YLOG_LOG, "client %s NEW search", client_get_id(cl));
                 client_start_search(cl);
+            }
+            no_working++;
         }
     }
     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;
     }
+    session_log(se, YLOG_LOG, "session_start_search done");
     return PAZPAR2_NO_ERROR;
 }
 
@@ -766,8 +843,7 @@ void session_init_databases(struct session *se)
 static struct session_database *load_session_database(struct session *se, 
                                                       char *id)
 {
-    struct database *db = new_database(id, se->session_nmem);
-
+    struct database *db = new_database_inherit_settings(id, se->session_nmem, se->service->settings);
     session_init_databases_fun((void*) se, db);
 
     // New sdb is head of se->databases list
@@ -804,6 +880,8 @@ void session_apply_setting(struct session *se, char *dbname, char *setting,
     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)
@@ -817,17 +895,22 @@ void session_apply_setting(struct session *se, char *dbname, char *setting,
     }
 }
 
-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);
     normalize_cache_destroy(se->normalize_cache);
     relevance_destroy(&se->relevance);
     reclist_destroy(se->reclist);
+    if (nmem_total(se->nmem))
+        session_log(se, YLOG_DEBUG, "NMEN operation usage %zd", nmem_total(se->nmem));
+    if (nmem_total(se->session_nmem))
+        session_log(se, YLOG_DEBUG, "NMEN session usage %zd", nmem_total(se->session_nmem));
     nmem_destroy(se->nmem);
     service_destroy(se->service);
     yaz_mutex_destroy(&se->session_mutex);
@@ -863,7 +946,9 @@ struct session *new_session(NMEM nmem, struct conf_service *service,
     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;
@@ -888,12 +973,12 @@ static struct hitsbytarget *hitsbytarget_nb(struct session *se,
     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();
@@ -903,8 +988,11 @@ static struct hitsbytarget *hitsbytarget_nb(struct session *se,
         res[*count].id = client_get_id(cl);
         res[*count].name = *name ? name : "Unknown";
         res[*count].hits = client_get_hits(cl);
+        res[*count].approximation = client_get_approximation(cl);
         res[*count].records = client_get_num_records(cl);
-        res[*count].diagnostic = client_get_diagnostic(cl);
+        res[*count].filtered = client_get_num_records_filtered(cl);
+        res[*count].diagnostic =
+            client_get_diagnostic(cl, &res[*count].addinfo);
         res[*count].state = client_get_state_str(cl);
         res[*count].connected  = client_get_connection(cl) ? 1 : 0;
         session_settings_dump(se, client_get_database(cl), w);
@@ -952,14 +1040,25 @@ static int cmp_ht(const void *p1, const void *p2)
     return h2->hits - h1->hits;
 }
 
+// Compares two hitsbytarget nodes by hitcount
+static int cmp_ht_approx(const void *p1, const void *p2)
+{
+    const struct hitsbytarget *h1 = p1;
+    const struct hitsbytarget *h2 = p2;
+    return h2->approximation - h1->approximation;
+}
+
 static int targets_termlist_nb(WRBUF wrbuf, struct session *se, int num,
-                               NMEM nmem)
+                               NMEM nmem, int version)
 {
     struct hitsbytarget *ht;
     int count, i;
 
     ht = hitsbytarget_nb(se, &count, nmem);
-    qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht);
+    if (version >= 2)
+        qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht_approx);
+    else
+        qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht);
     for (i = 0; i < count && i < num && ht[i].hits > 0; i++)
     {
 
@@ -980,7 +1079,14 @@ static int targets_termlist_nb(WRBUF wrbuf, struct session *se, int num,
         
         wrbuf_printf(wrbuf, "<frequency>" ODR_INT_PRINTF "</frequency>\n",
                      ht[i].hits);
-        
+
+        if (version >= 2) {
+            // Should not print if we know it isn't a approximation.
+            wrbuf_printf(wrbuf, "<approximation>" ODR_INT_PRINTF "</approximation>\n", ht[i].approximation);
+            wrbuf_printf(wrbuf, "<records>%d</records>\n", ht[i].records - ht[i].filtered);
+            wrbuf_printf(wrbuf, "<filtered>%d</filtered>\n", ht[i].filtered);
+        }
+
         wrbuf_puts(wrbuf, "<state>");
         wrbuf_xmlputs(wrbuf, ht[i].state);
         wrbuf_puts(wrbuf, "</state>\n");
@@ -993,33 +1099,38 @@ static int targets_termlist_nb(WRBUF wrbuf, struct session *se, int num,
 }
 
 void perform_termlist(struct http_channel *c, struct session *se,
-                      const char *name, int num)
+                      const char *name, int num, int version)
 {
     int i, j;
     NMEM nmem_tmp = nmem_create();
     char **names;
     int num_names = 0;
 
-    if (name)
-        nmem_strsplit(nmem_tmp, ",", name, &names, &num_names);
+    if (!name)
+        name = "*";
+
+    nmem_strsplit(nmem_tmp, ",", name, &names, &num_names);
 
     session_enter(se);
 
     for (j = 0; j < num_names; j++)
     {
         const char *tname;
-        
-        wrbuf_puts(c->wrbuf, "<list name=\"");
-        wrbuf_xmlputs(c->wrbuf, names[j]);
-        wrbuf_puts(c->wrbuf, "\">\n");
+        int must_generate_empty = 1; /* bug 5350 */
 
         for (i = 0; i < se->num_termlists; i++)
         {
             tname = se->termlists[i].name;
-            if (num_names > 0 && !strcmp(names[j], tname))
+            if (!strcmp(names[j], tname) || !strcmp(names[j], "*"))
             {
                 struct termlist_score **p = 0;
                 int len;
+
+                wrbuf_puts(c->wrbuf, "<list name=\"");
+                wrbuf_xmlputs(c->wrbuf, tname);
+                wrbuf_puts(c->wrbuf, "\">\n");
+                must_generate_empty = 0;
+
                 p = termlist_highscore(se->termlists[i].termlist, &len);
                 if (p)
                 {
@@ -1041,14 +1152,26 @@ void perform_termlist(struct http_channel *c, struct session *se,
                         wrbuf_puts(c->wrbuf, "</term>\n");
                     }
                 }
+                wrbuf_puts(c->wrbuf, "</list>\n");
             }
         }
         tname = "xtargets";
-        if (num_names > 0 && !strcmp(names[j], tname))
+        if (!strcmp(names[j], tname) || !strcmp(names[j], "*"))
+        {
+            wrbuf_puts(c->wrbuf, "<list name=\"");
+            wrbuf_xmlputs(c->wrbuf, tname);
+            wrbuf_puts(c->wrbuf, "\">\n");
+
+            targets_termlist_nb(c->wrbuf, se, num, c->nmem, version);
+            wrbuf_puts(c->wrbuf, "</list>\n");
+            must_generate_empty = 0;
+        }
+        if (must_generate_empty)
         {
-            targets_termlist_nb(c->wrbuf, se, num, c->nmem);
+            wrbuf_puts(c->wrbuf, "<list name=\"");
+            wrbuf_xmlputs(c->wrbuf, names[j]);
+            wrbuf_puts(c->wrbuf, "\"/>\n");
         }
-        wrbuf_puts(c->wrbuf, "</list>\n");
     }
     session_leave(se);
     nmem_destroy(nmem_tmp);
@@ -1102,7 +1225,7 @@ void show_single_stop(struct session *se, struct record_cluster *rec)
 
 struct record_cluster **show_range_start(struct session *se,
                                          struct reclist_sortparms *sp, 
-                                         int start, int *num, int *total, Odr_int *sumhits)
+                                         int start, int *num, int *total, Odr_int *sumhits, Odr_int *approx_hits)
 {
     struct record_cluster **recs;
     struct reclist_sortparms *spp;
@@ -1116,7 +1239,8 @@ struct record_cluster **show_range_start(struct session *se,
     {
         *num = 0;
         *total = 0;
-        *sumhits = 0;
+        *sumhits = 0;        
+        *approx_hits = 0;
         recs = 0;
     }
     else
@@ -1135,9 +1259,11 @@ struct record_cluster **show_range_start(struct session *se,
         *total = reclist_get_num_records(se->reclist);
 
         *sumhits = 0;
-        for (l = se->clients; l; l = l->next)
+        *approx_hits = 0;
+        for (l = se->clients_active; l; l = l->next) {
             *sumhits += client_get_hits(l->client);
-        
+            *approx_hits += client_get_approximation(l->client);
+        }
         for (i = 0; i < start; i++)
             if (!reclist_read_record(se->reclist))
             {
@@ -1180,7 +1306,7 @@ void statistics(struct session *se, struct statistics *stat)
 
     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))
@@ -1465,8 +1591,7 @@ int ingest_record(struct client *cl, const char *rec,
     
     if (!check_record_filter(root, sdb))
     {
-        session_log(se, YLOG_LOG, "Filtered out record no %d from %s",
-                    record_no, sdb->database->id);
+        session_log(se, YLOG_LOG, "Filtered out record no %d from %s", record_no, sdb->database->id);
         xmlFreeDoc(xdoc);
         return -2;
     }
@@ -1480,13 +1605,85 @@ int ingest_record(struct client *cl, const char *rec,
     }
     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;
 }
 
+// Skip record on non-zero
+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_DEBUG, "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_DEBUG, "cmp: '%s' '%s'", rec_md->data.text.disp, values[i]);
+                    if (!strcmp(rec_md->data.text.disp, values[i]))
+                    {
+                        // Value equals, should not be filtered.
+                        break;
+                    }
+                }
+                rec_md = rec_md->next;
+            }
+            else
+            {
+                rec_md = record->metadata[md_field_id];
+                i++;
+            }
+        }
+        // At end , not match
+        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,
@@ -1560,6 +1757,16 @@ static int ingest_to_cluster(struct client *cl,
         }
     }
 
+    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)
@@ -1605,6 +1812,8 @@ static int ingest_to_cluster(struct client *cl,
             struct record_metadata *rec_md = 0;
             int md_field_id = -1;
             int sk_field_id = -1;
+            const char *rank;
+            xmlChar *xml_rank;
             
             type = xmlGetProp(n, (xmlChar *) "type");
             value = xmlNodeListGetString(xdoc, n->children, 1);
@@ -1618,7 +1827,7 @@ static int ingest_to_cluster(struct client *cl,
                 continue;
             
             ser_md = &service->metadata[md_field_id];
-            
+
             if (ser_md->sortkey_offset >= 0)
             {
                 sk_field_id = ser_md->sortkey_offset;
@@ -1631,6 +1840,9 @@ static int ingest_to_cluster(struct client *cl,
             if (!rec_md)
                 continue;
 
+            xml_rank = xmlGetProp(n, (xmlChar *) "rank");
+            rank = xml_rank ? (const char *) xml_rank : ser_md->rank;
+
             wheretoput = &cluster->metadata[md_field_id];
 
             // and polulate with data:
@@ -1715,12 +1927,12 @@ static int ingest_to_cluster(struct client *cl,
                 }
             }
 
-
             // ranking of _all_ fields enabled ... 
-            if (ser_md->rank)
+            if (rank)
+            {
                 relevance_countwords(se->relevance, cluster, 
-                                     (char *) value, ser_md->rank,
-                                     ser_md->name);
+                                     (char *) value, rank, ser_md->name);
+            }
 
             // construct facets ... unless the client already has reported them
             if (ser_md->termlist && !client_has_facet(cl, (char *) type))
@@ -1742,6 +1954,8 @@ static int ingest_to_cluster(struct client *cl,
             }
 
             // cleaning up
+            if (xml_rank)
+                xmlFree(xml_rank);
             xmlFree(type);
             xmlFree(value);
             type = value = 0;
@@ -1772,7 +1986,7 @@ void session_log(struct session *s, int level, const char *fmt, ...)
     va_start(ap, fmt);
 
     yaz_vsnprintf(buf, sizeof(buf)-30, fmt, ap);
-    yaz_log(level, "Session (%u): %s", s->session_id, buf);
+    yaz_log(level, "Session %u: %s", s->session_id, buf);
 
     va_end(ap);
 }