added initialization of char *recsyn = 0; and char *piggyback = 0;
[pazpar2-moved-to-github.git] / src / pazpar2.c
index 2c95075..99cc6a7 100644 (file)
@@ -1,4 +1,23 @@
-/* $Id: pazpar2.c,v 1.59 2007-03-31 20:24:59 marc Exp $ */
+/* $Id: pazpar2.c,v 1.78 2007-04-13 11:13:08 marc Exp $
+   Copyright (c) 2006-2007, Index Data.
+
+This file is part of Pazpar2.
+
+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
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Pazpar2; see the file LICENSE.  If not, write to the
+Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+02111-1307, USA.
+ */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <yaz/otherinfo.h>
 #include <yaz/yaz-util.h>
 #include <yaz/nmem.h>
+#include <yaz/query-charset.h>
+#include <yaz/querytowrbuf.h>
+#if YAZ_VERSIONL >= 0x020163
+#include <yaz/oid_db.h>
+#endif
 
 #if HAVE_CONFIG_H
 #include "cconfig.h"
@@ -48,7 +72,6 @@ static void client_fatal(struct client *cl);
 static void connection_destroy(struct connection *co);
 static int client_prep_connection(struct client *cl);
 static void ingest_records(struct client *cl, Z_Records *r);
-//static struct conf_retrievalprofile *database_retrieval_profile(struct database *db);
 void session_alert_watch(struct session *s, int what);
 
 IOCHAN channel_list = 0;  // Master list of connections we're handling events to
@@ -87,7 +110,6 @@ struct parameters global_parameters =
     100,
     MAX_CHUNK,
     0,
-    0,
     0
 };
 
@@ -119,6 +141,22 @@ static int send_apdu(struct client *c, Z_APDU *a)
     return 0;
 }
 
+// Set authentication token in init if one is set for the client
+// TODO: Extend this to handle other schemes than open (should be simple)
+static void init_authentication(struct client *cl, Z_InitRequest *req)
+{
+    struct session_database *sdb = cl->database;
+    char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
+
+    if (auth)
+    {
+        Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
+                sizeof(*idAuth));
+        idAuth->which = Z_IdAuthentication_open;
+        idAuth->u.open = auth;
+        req->idAuthentication = idAuth;
+    }
+}
 
 static void send_init(IOCHAN i)
 {
@@ -139,16 +177,25 @@ static void send_init(IOCHAN i)
     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
 
+    init_authentication(cl, a->u.initRequest);
 
     /* add virtual host if tunneling through Z39.50 proxy */
     
     if (0 < strlen(global_parameters.zproxy_override) 
-        && 0 < strlen(cl->database->url))
-        yaz_oi_set_string_oidval(&a->u.initRequest->otherInfo, 
+        && 0 < strlen(cl->database->database->url))
+    {
+#if YAZ_VERSIONL >= 0x020163
+        const int *oid_proxy = yaz_string_to_oid(yaz_oid_std(),
+                                                 CLASS_USERINFO, OID_STR_PROXY);
+        yaz_oi_set_string_oid(&a->u.initRequest->otherInfo,
+                              global_parameters.odr_out, oid_proxy,
+                              1, cl->database->database->url);
+#else
+        yaz_oi_set_string_oidval(&a->u.initRequest->otherInfo,
                                  global_parameters.odr_out, VAL_PROXY,
-                                 1, cl->database->url);
-    
-
+                                 1, cl->database->database->url);
+#endif
+    }
 
     if (send_apdu(cl, a) >= 0)
     {
@@ -193,17 +240,21 @@ static void send_search(IOCHAN i)
     struct connection *co = iochan_getdata(i);
     struct client *cl = co->client; 
     struct session *se = cl->session;
-    struct database *db = cl->database;
+    struct session_database *sdb = cl->database;
     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
     int ndb, cerror, cpos;
     char **databaselist;
     Z_Query *zquery;
     struct ccl_rpn_node *cn;
     int ssub = 0, lslb = 100000, mspn = 10;
+    char *recsyn = 0;
+    char *piggyback = 0;
+    char *queryenc = 0;
+    yaz_iconv_t iconv = 0;
 
-    yaz_log(YLOG_DEBUG, "Sending search");
+    yaz_log(YLOG_DEBUG, "Sending search to %s", cl->database->database->url);
 
-    cn = ccl_find_str(db->ccl_map, se->query, &cerror, &cpos);
+    cn = ccl_find_str(sdb->database->ccl_map, se->query, &cerror, &cpos);
     if (!cn)
         return;
 
@@ -216,36 +267,81 @@ static void send_search(IOCHAN i)
                 se->expected_maxrecs);
     }
 
+    // constructing RPN query
     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
             sizeof(Z_Query));
     zquery->which = Z_Query_type_1;
     zquery->u.type_1 = ccl_rpn_query(global_parameters.odr_out, cn);
     ccl_rpn_delete(cn);
 
-    for (ndb = 0; db->databases[ndb]; ndb++)
+    // converting to target encoding
+    if ((queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING))){
+        iconv = yaz_iconv_open(queryenc, "UTF-8");
+        if (iconv){
+            yaz_query_charset_convert_rpnquery(zquery->u.type_1, 
+                                               global_parameters.odr_out, 
+                                               iconv);
+            yaz_iconv_close(iconv);
+        } else
+            yaz_log(YLOG_WARN, "Query encoding failed %s %s", 
+                    cl->database->database->url, queryenc);
+    }
+
+    for (ndb = 0; sdb->database->databases[ndb]; ndb++)
        ;
     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
-    for (ndb = 0; db->databases[ndb]; ndb++)
-       databaselist[ndb] = db->databases[ndb];
-
-    a->u.searchRequest->preferredRecordSyntax =
-            yaz_oidval_to_z3950oid(global_parameters.odr_out,
-            CLASS_RECSYN, VAL_USMARC);
-    a->u.searchRequest->smallSetUpperBound = &ssub;
-    a->u.searchRequest->largeSetLowerBound = &lslb;
-    a->u.searchRequest->mediumSetPresentNumber = &mspn;
+    for (ndb = 0; sdb->database->databases[ndb]; ndb++)
+       databaselist[ndb] = sdb->database->databases[ndb];
+
+    if (!(piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK)) || *piggyback == '1')
+    {
+        if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
+        {
+#if YAZ_VERSIONL >= 0x020163
+            a->u.searchRequest->preferredRecordSyntax =
+                yaz_string_to_oid_odr(yaz_oid_std(),
+                                      CLASS_RECSYN, recsyn,
+                                      global_parameters.odr_out);
+#else
+            a->u.searchRequest->preferredRecordSyntax =
+                yaz_str_to_z3950oid(global_parameters.odr_out,
+                                    CLASS_RECSYN, recsyn);
+#endif
+        }
+        a->u.searchRequest->smallSetUpperBound = &ssub;
+        a->u.searchRequest->largeSetLowerBound = &lslb;
+        a->u.searchRequest->mediumSetPresentNumber = &mspn;
+    }
     a->u.searchRequest->resultSetName = "Default";
     a->u.searchRequest->databaseNames = databaselist;
     a->u.searchRequest->num_databaseNames = ndb;
 
-    if (send_apdu(cl, a) >= 0)
-    {
-       iochan_setflags(i, EVENT_INPUT);
-       cl->state = Client_Searching;
-        cl->requestid = se->requestid;
-    }
-    else
-        cl->state = Client_Error;
+    
+    {  //scope for sending and logging queries 
+        WRBUF wbquery = wrbuf_alloc();
+        yaz_query_to_wrbuf(wbquery, zquery);
+
+
+        if (send_apdu(cl, a) >= 0)
+            {
+                iochan_setflags(i, EVENT_INPUT);
+                cl->state = Client_Searching;
+                cl->requestid = se->requestid;
+                yaz_log(YLOG_LOG, "SearchRequest %s %s %s", 
+                         cl->database->database->url,
+                        queryenc ? queryenc : "UTF-8",
+                        wrbuf_cstr(wbquery));
+            }
+        else {
+            cl->state = Client_Error;
+                yaz_log(YLOG_WARN, "Failed SearchRequest %s  %s %s", 
+                         cl->database->database->url, 
+                        queryenc ? queryenc : "UTF-8",
+                        wrbuf_cstr(wbquery));
+        }
+        
+        wrbuf_destroy(wbquery);
+    }    
 
     odr_reset(global_parameters.odr_out);
 }
@@ -254,9 +350,11 @@ static void send_present(IOCHAN i)
 {
     struct connection *co = iochan_getdata(i);
     struct client *cl = co->client; 
+    struct session_database *sdb = cl->database;
     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
     int toget;
     int start = cl->records + 1;
+    char *recsyn;
 
     toget = global_parameters.chunk;
     if (toget > global_parameters.toget - cl->records)
@@ -271,9 +369,19 @@ static void send_present(IOCHAN i)
 
     a->u.presentRequest->resultSetId = "Default";
 
-    a->u.presentRequest->preferredRecordSyntax =
-            yaz_oidval_to_z3950oid(global_parameters.odr_out,
-            CLASS_RECSYN, VAL_USMARC);
+    if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
+    {
+#if YAZ_VERSIONL >= 0x020163
+        a->u.presentRequest->preferredRecordSyntax =
+            yaz_string_to_oid_odr(yaz_oid_std(),
+                                  CLASS_RECSYN, recsyn,
+                                  global_parameters.odr_out);
+#else
+        a->u.presentRequest->preferredRecordSyntax =
+            yaz_str_to_z3950oid(global_parameters.odr_out,
+                                CLASS_RECSYN, recsyn);
+#endif
+    }
 
     if (send_apdu(cl, a) >= 0)
     {
@@ -291,7 +399,7 @@ static void do_initResponse(IOCHAN i, Z_APDU *a)
     struct client *cl = co->client;
     Z_InitResponse *r = a->u.initResponse;
 
-    yaz_log(YLOG_DEBUG, "Received init response");
+    yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
 
     if (*r->result)
     {
@@ -308,7 +416,8 @@ static void do_searchResponse(IOCHAN i, Z_APDU *a)
     struct session *se = cl->session;
     Z_SearchResponse *r = a->u.searchResponse;
 
-    yaz_log(YLOG_DEBUG, "Searchresponse (status=%d)", *r->searchStatus);
+    yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
+            cl->database->database->url, *r->searchStatus);
 
     if (*r->searchStatus)
     {
@@ -316,7 +425,8 @@ static void do_searchResponse(IOCHAN i, Z_APDU *a)
         se->total_hits += cl->hits;
         if (r->presentStatus && !*r->presentStatus && r->records)
         {
-            yaz_log(YLOG_DEBUG, "Records in search response");
+            yaz_log(YLOG_DEBUG, "Records in search response %s", 
+                    cl->database->database->url);
             ingest_records(cl, r->records);
         }
         cl->state = Client_Idle;
@@ -329,7 +439,9 @@ static void do_searchResponse(IOCHAN i, Z_APDU *a)
             Z_Records *recs = r->records;
             if (recs->which == Z_Records_NSD)
             {
-                yaz_log(YLOG_WARN, "Non-surrogate diagnostic");
+                yaz_log(YLOG_WARN, 
+                        "Search response: Non-surrogate diagnostic %s",
+                        cl->database->database->url);
                 cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
                 cl->state = Client_Error;
             }
@@ -337,6 +449,19 @@ static void do_searchResponse(IOCHAN i, Z_APDU *a)
     }
 }
 
+static void do_closeResponse(IOCHAN i, Z_APDU *a)
+{
+    struct connection *co = iochan_getdata(i);
+    struct client *cl = co->client;
+    /* Z_Close *r = a->u.close; */
+
+    yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
+
+    cl->state = Client_Failed;
+    connection_destroy(co);
+}
+
+
 char *normalize_mergekey(char *buf, int skiparticle)
 {
     char *p = buf, *pout = buf;
@@ -378,42 +503,6 @@ char *normalize_mergekey(char *buf, int skiparticle)
     return buf;
 }
 
-
-#ifdef GAGA
-// FIXME needs to be generalized. Should flexibly generate X lists per search
-static void extract_subject(struct session *s, const char *rec)
-{
-    const char *field, *subfield;
-
-    while ((field = find_field(rec, "650")))
-    {
-        rec = field; 
-        if ((subfield = find_subfield(field, 'a')))
-        {
-            char *e, *ef;
-            char buf[1024];
-            int len;
-
-            ef = index(subfield, '\n');
-            if (!ef)
-                return;
-            if ((e = index(subfield, '\t')) && e < ef)
-                ef = e;
-            while (ef > subfield && !isalpha(*(ef - 1)) && *(ef - 1) != ')')
-                ef--;
-            len = ef - subfield;
-            assert(len < 1023);
-            memcpy(buf, subfield, len);
-            buf[len] = '\0';
-#ifdef FIXME
-            if (*buf)
-                termlist_insert(s->termlist, buf);
-#endif
-        }
-    }
-}
-#endif
-
 static void add_facet(struct session *s, const char *type, const char *value)
 {
     int i;
@@ -439,46 +528,55 @@ static void add_facet(struct session *s, const char *type, const char *value)
 
 static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
 {
-    struct conf_retrievalprofile *rprofile = cl->database->rprofile;
-    struct conf_retrievalmap *m;
+    struct database_retrievalmap *m;
+    struct database *db = cl->database->database;
     xmlNode *res;
     xmlDoc *rdoc;
 
     // First normalize to XML
-    if (rprofile->native_syntax == Nativesyn_iso2709)
+    if (db->yaz_marc)
     {
         char *buf;
         int len;
         if (rec->which != Z_External_octet)
         {
-            yaz_log(YLOG_WARN, "Unexpected external branch, probably BER");
+            yaz_log(YLOG_WARN, "Unexpected external branch, probably BER %s",
+                    cl->database->database->url);
             return 0;
         }
         buf = (char*) rec->u.octet_aligned->buf;
         len = rec->u.octet_aligned->len;
-        if (yaz_marc_read_iso2709(rprofile->yaz_marc, buf, len) < 0)
+        if (yaz_marc_read_iso2709(db->yaz_marc, buf, len) < 0)
         {
-            yaz_log(YLOG_WARN, "Failed to decode MARC");
+            yaz_log(YLOG_WARN, "Failed to decode MARC %s",
+                    cl->database->database->url);
             return 0;
         }
-        if (yaz_marc_write_xml(rprofile->yaz_marc, &res,
+
+        yaz_marc_write_using_libxml2(db->yaz_marc, 1);
+        if (yaz_marc_write_xml(db->yaz_marc, &res,
                     "http://www.loc.gov/MARC21/slim", 0, 0) < 0)
         {
-            yaz_log(YLOG_WARN, "Failed to encode as XML");
+            yaz_log(YLOG_WARN, "Failed to encode as XML %s",
+                    cl->database->database->url);
             return 0;
         }
         rdoc = xmlNewDoc((xmlChar *) "1.0");
         xmlDocSetRootElement(rdoc, res);
+
     }
     else
     {
-        yaz_log(YLOG_FATAL, "Unknown native_syntax in normalize_record");
+        yaz_log(YLOG_FATAL, 
+                "Unknown native_syntax in normalize_record from %s",
+                cl->database->database->url);
         exit(1);
     }
 
-    if (global_parameters.dump_records)
-    {
-        fprintf(stderr, "Input Record (normalized):\n----------------\n");
+    if (global_parameters.dump_records){
+        fprintf(stderr, 
+                "Input Record (normalized) from %s\n----------------\n",
+                cl->database->database->url);
 #if LIBXML_VERSION >= 20600
         xmlDocFormatDump(stderr, rdoc, 1);
 #else
@@ -486,25 +584,50 @@ static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
 #endif
     }
 
-    for (m = rprofile->maplist; m; m = m->next)
-    {
-        xmlDoc *new;
-        if (m->type != Map_xslt)
+    for (m = db->map; m; m = m->next){
+        xmlDoc *new = 0;
+
+#if 1
+        {
+            xmlNodePtr root = 0;
+            new = xsltApplyStylesheet(m->stylesheet, rdoc, 0);
+            root= xmlDocGetRootElement(new);
+        if (!new || !root || !(root->children))
         {
-            yaz_log(YLOG_WARN, "Unknown map type");
+            yaz_log(YLOG_WARN, "XSLT transformation failed from %s",
+                    cl->database->database->url);
+            xmlFreeDoc(new);
+            xmlFreeDoc(rdoc);
             return 0;
         }
-        if (!(new = xsltApplyStylesheet(m->stylesheet, rdoc, 0)))
+        }
+#endif
+
+#if 0
+        // do it another way to detect transformation errors right now
+        // but does not seem to work either!
         {
-            yaz_log(YLOG_WARN, "XSLT transformation failed");
-            return 0;
+            xsltTransformContextPtr ctxt;
+            ctxt = xsltNewTransformContext(m->stylesheet, rdoc);
+            new = xsltApplyStylesheetUser(m->stylesheet, rdoc, 0, 0, 0, ctxt);
+            if ((ctxt->state == XSLT_STATE_ERROR) ||
+                (ctxt->state == XSLT_STATE_STOPPED)){
+                yaz_log(YLOG_WARN, "XSLT transformation failed from %s",
+                        cl->database->database->url);
+                xmlFreeDoc(new);
+                xmlFreeDoc(rdoc);
+                return 0;
+            }
         }
+#endif      
+   
         xmlFreeDoc(rdoc);
         rdoc = new;
     }
     if (global_parameters.dump_records)
     {
-        fprintf(stderr, "Record:\n----------------\n");
+        fprintf(stderr, "Record from %s\n----------------\n", 
+                cl->database->database->url);
 #if LIBXML_VERSION >= 20600
         xmlDocFormatDump(stderr, rdoc, 1);
 #else
@@ -581,7 +704,7 @@ static struct record *ingest_record(struct client *cl, Z_External *rec)
                              &se->total_merged);
     if (global_parameters.dump_records)
         yaz_log(YLOG_LOG, "Cluster id %d from %s (#%d)", cluster->recid,
-                cl->database->url, cl->records);
+                cl->database->database->url, cl->records);
     if (!cluster)
     {
         /* no room for record */
@@ -767,6 +890,15 @@ static struct record *ingest_record(struct client *cl, Z_External *rec)
     return res;
 }
 
+// Retrieve first defined value for 'name' for given database.
+// Will be extended to take into account user associated with session
+char *session_setting_oneval(struct session_database *db, int offset)
+{
+    if (!db->settings[offset])
+        return "";
+    return db->settings[offset]->value;
+}
+
 static void ingest_records(struct client *cl, Z_Records *r)
 {
 #if USE_TIMING
@@ -787,7 +919,9 @@ static void ingest_records(struct client *cl, Z_Records *r)
         cl->records++;
         if (npr->which != Z_NamePlusRecord_databaseRecord)
         {
-            yaz_log(YLOG_WARN, "Unexpected record type, probably diagnostic");
+            yaz_log(YLOG_WARN, 
+                    "Unexpected record type, probably diagnostic %s",
+                    cl->database->database->url);
             continue;
         }
 
@@ -817,7 +951,8 @@ static void do_presentResponse(IOCHAN i, Z_APDU *a)
         Z_Records *recs = r->records;
         if (recs->which == Z_Records_NSD)
         {
-            yaz_log(YLOG_WARN, "Non-surrogate diagnostic");
+            yaz_log(YLOG_WARN, "Non-surrogate diagnostic %s",
+                    cl->database->database->url);
             cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
             cl->state = Client_Error;
         }
@@ -825,13 +960,15 @@ static void do_presentResponse(IOCHAN i, Z_APDU *a)
 
     if (!*r->presentStatus && cl->state != Client_Error)
     {
-        yaz_log(YLOG_DEBUG, "Good Present response");
+        yaz_log(YLOG_DEBUG, "Good Present response %s",
+                cl->database->database->url);
         ingest_records(cl, r->records);
         cl->state = Client_Idle;
     }
     else if (*r->presentStatus) 
     {
-        yaz_log(YLOG_WARN, "Bad Present response");
+        yaz_log(YLOG_WARN, "Bad Present response %s",
+                cl->database->database->url);
         cl->state = Client_Error;
     }
 }
@@ -877,13 +1014,14 @@ static void handler(IOCHAN i, int event)
 
        if (len < 0)
        {
-            yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from Z server");
+            yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
+                    cl->database->database->url);
             connection_destroy(co);
            return;
        }
         else if (len == 0)
        {
-            yaz_log(YLOG_WARN, "EOF reading from Z server");
+            yaz_log(YLOG_WARN, "EOF reading from %s", cl->database->database->url);
             connection_destroy(co);
            return;
        }
@@ -913,8 +1051,13 @@ static void handler(IOCHAN i, int event)
                     case Z_APDU_presentResponse:
                         do_presentResponse(i, a);
                         break;
+                    case Z_APDU_close:
+                        do_closeResponse(i, a);
+                        break;
                     default:
-                        yaz_log(YLOG_WARN, "Unexpected result from server");
+                        yaz_log(YLOG_WARN, 
+                                "Unexpected Z39.50 response from %s",  
+                                cl->database->database->url);
                         client_fatal(cl);
                         return;
                 }
@@ -1007,24 +1150,21 @@ static struct connection *connection_create(struct client *cl)
     
     if (0 == strlen(global_parameters.zproxy_override)){
         /* no Z39.50 proxy needed - direct connect */
-        yaz_log(YLOG_DEBUG, "Connection create %s", cl->database->url);
+        yaz_log(YLOG_DEBUG, "Connection create %s", cl->database->database->url);
         
-        if (!(addr = cs_straddr(link, cl->database->host->ipport)))
+        if (!(addr = cs_straddr(link, cl->database->database->host->ipport)))
             {
                 yaz_log(YLOG_WARN|YLOG_ERRNO, 
                         "Lookup of IP address %s failed", 
-                        cl->database->host->ipport);
+                        cl->database->database->host->ipport);
                 return 0;
             }
     
     } else {
         /* Z39.50 proxy connect */
         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
-                cl->database->url, global_parameters.zproxy_override);
+                cl->database->database->url, global_parameters.zproxy_override);
 
-        yaz_log(YLOG_LOG, "Connection cs_create_host %s proxy %s", 
-                cl->database->url, global_parameters.zproxy_override);
-        
         if (!(addr = cs_straddr(link, global_parameters.zproxy_override)))
             {
                 yaz_log(YLOG_WARN|YLOG_ERRNO, 
@@ -1037,7 +1177,7 @@ static struct connection *connection_create(struct client *cl)
     res = cs_connect(link, addr);
     if (res < 0)
     {
-        yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s", cl->database->url);
+        yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s", cl->database->database->url);
         return 0;
     }
 
@@ -1050,14 +1190,14 @@ static struct connection *connection_create(struct client *cl)
         new->ibufsize = 0;
     }
     new->state = Conn_Connecting;
-    new->host = cl->database->host;
+    new->host = cl->database->database->host;
     new->next = new->host->connections;
     new->host->connections = new;
     new->client = cl;
     cl->connection = new;
     new->link = link;
 
-    new->iochan = iochan_create(cs_fileno(link), 0, handler, 0);
+    new->iochan = iochan_create(cs_fileno(link), handler, 0);
     iochan_setdata(new->iochan, new);
     new->iochan->next = channel_list;
     channel_list = new->iochan;
@@ -1067,7 +1207,7 @@ static struct connection *connection_create(struct client *cl)
 // Close connection and set state to error
 static void client_fatal(struct client *cl)
 {
-    yaz_log(YLOG_WARN, "Fatal error from %s", cl->database->url);
+    yaz_log(YLOG_WARN, "Fatal error from %s", cl->database->database->url);
     connection_destroy(cl->connection);
     cl->state = Client_Error;
 }
@@ -1077,11 +1217,11 @@ static int client_prep_connection(struct client *cl)
 {
     struct connection *co;
     struct session *se = cl->session;
-    struct host *host = cl->database->host;
+    struct host *host = cl->database->database->host;
 
     co = cl->connection;
 
-    yaz_log(YLOG_DEBUG, "Client prep %s", cl->database->url);
+    yaz_log(YLOG_DEBUG, "Client prep %s", cl->database->database->url);
 
     if (!co)
     {
@@ -1118,114 +1258,6 @@ static int client_prep_connection(struct client *cl)
         return 0;
 }
 
-#ifdef GAGA // Moved to database.c
-
-// This function will most likely vanish when a proper target profile mechanism is
-// introduced.
-void load_simpletargets(const char *fn)
-{
-    FILE *f = fopen(fn, "r");
-    char line[256];
-
-    if (!f)
-    {
-        yaz_log(YLOG_WARN|YLOG_ERRNO, "open %s", fn);
-        exit(1);
-    }
-
-    while (fgets(line, 255, f))
-    {
-        char *url, *db;
-        char *name;
-        struct host *host;
-        struct database *database;
-
-        if (strncmp(line, "target ", 7))
-            continue;
-        line[strlen(line) - 1] = '\0';
-
-        if ((name = strchr(line, ';')))
-            *(name++) = '\0';
-
-        url = line + 7;
-        if ((db = strchr(url, '/')))
-            *(db++) = '\0';
-        else
-            db = "Default";
-
-        yaz_log(YLOG_LOG, "Target: %s, '%s'", url, db);
-        for (host = hosts; host; host = host->next)
-            if (!strcmp((const char *) url, host->hostport))
-                break;
-        if (!host)
-        {
-            struct addrinfo *addrinfo, hints;
-            char *port;
-            char ipport[128];
-            unsigned char addrbuf[4];
-            int res;
-
-            host = xmalloc(sizeof(struct host));
-            host->hostport = xstrdup(url);
-            host->connections = 0;
-
-            if ((port = strchr(url, ':')))
-                *(port++) = '\0';
-            else
-                port = "210";
-
-            hints.ai_flags = 0;
-            hints.ai_family = PF_INET;
-            hints.ai_socktype = SOCK_STREAM;
-            hints.ai_protocol = IPPROTO_TCP;
-            hints.ai_addrlen = 0;
-            hints.ai_addr = 0;
-            hints.ai_canonname = 0;
-            hints.ai_next = 0;
-            // This is not robust code. It assumes that getaddrinfo returns AF_INET
-            // address.
-            if ((res = getaddrinfo(url, port, &hints, &addrinfo)))
-            {
-                yaz_log(YLOG_WARN, "Failed to resolve %s: %s", url, gai_strerror(res));
-                xfree(host->hostport);
-                xfree(host);
-                continue;
-            }
-            assert(addrinfo->ai_family == PF_INET);
-            memcpy(addrbuf, &((struct sockaddr_in*)addrinfo->ai_addr)->sin_addr.s_addr, 4);
-            sprintf(ipport, "%u.%u.%u.%u:%s",
-                    addrbuf[0], addrbuf[1], addrbuf[2], addrbuf[3], port);
-            host->ipport = xstrdup(ipport);
-            freeaddrinfo(addrinfo);
-            host->next = hosts;
-            hosts = host;
-        }
-        database = xmalloc(sizeof(struct database));
-        database->host = host;
-        database->url = xmalloc(strlen(url) + strlen(db) + 2);
-        strcpy(database->url, url);
-        strcat(database->url, "/");
-        strcat(database->url, db);
-        if (name)
-            database->name = xstrdup(name);
-        else
-            database->name = 0;
-        
-        database->databases = xmalloc(2 * sizeof(char *));
-        database->databases[0] = xstrdup(db);
-        database->databases[1] = 0;
-        database->errors = 0;
-        database->qprofile = 0;
-        database->rprofile = database_retrieval_profile(database);
-        database->next = databases;
-        databases = database;
-
-    }
-    fclose(f);
-}
-
-#endif
-
 static struct client *client_create(void)
 {
     struct client *r;
@@ -1284,7 +1316,7 @@ void session_alert_watch(struct session *s, int what)
 }
 
 //callback for grep_databases
-static void select_targets_callback(void *context, struct database *db)
+static void select_targets_callback(void *context, struct session_database *db)
 {
     struct session *se = (struct session*) context;
     struct client *cl = client_create();
@@ -1294,14 +1326,14 @@ static void select_targets_callback(void *context, struct database *db)
     se->clients = cl;
 }
 
-// This should be extended with parameters to control selection criteria
 // Associates a set of clients with a session;
+// Note: Session-databases represent databases with per-session setting overrides
 int select_targets(struct session *se, struct database_criterion *crit)
 {
     while (se->clients)
         client_destroy(se->clients);
 
-    return grep_databases(se, crit, select_targets_callback);
+    return session_grep_databases(se, crit, select_targets_callback);
 }
 
 int session_active_clients(struct session *s)
@@ -1371,7 +1403,6 @@ char *search(struct session *se, char *query, char *filter)
     criteria = parse_filter(se->nmem, filter);
     strcpy(se->query, query);
     se->requestid++;
-    // Release any existing clients
     select_targets(se, criteria);
     for (cl = se->clients; cl; cl = cl->next)
     {
@@ -1394,6 +1425,57 @@ char *search(struct session *se, char *query, char *filter)
     return 0;
 }
 
+// Apply a session override to a database
+void session_apply_setting(struct session *se, char *dbname, char *setting, char *value)
+{
+    struct session_database *sdb;
+
+    for (sdb = se->databases; sdb; sdb = sdb->next)
+        if (!strcmp(dbname, sdb->database->url))
+        {
+            struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
+            int offset = settings_offset(setting);
+
+            if (offset < 0)
+            {
+                yaz_log(YLOG_WARN, "Unknown setting %s", setting);
+                return;
+            }
+            new->precedence = 0;
+            new->target = dbname;
+            new->name = setting;
+            new->value = value;
+            new->next = sdb->settings[offset];
+            sdb->settings[offset] = new;
+            break;
+        }
+    if (!sdb)
+        yaz_log(YLOG_WARN, "Unknown database in setting override: %s", dbname);
+}
+
+void session_init_databases_fun(void *context, struct database *db)
+{
+    struct session *se = (struct session *) context;
+    struct session_database *new = nmem_malloc(se->session_nmem, sizeof(*new));
+    int num = settings_num();
+    int i;
+
+    new->database = db;
+    new->settings = nmem_malloc(se->session_nmem, sizeof(struct settings *) * num);
+    for (i = 0; i < num; i++)
+        new->settings[i] = db->settings[i];
+    new->next = se->databases;
+    se->databases = new;
+}
+
+// Initialize session_database list -- this represents this session's view
+// of the database list -- subject to modification by the settings ws command
+void session_init_databases(struct session *se)
+{
+    se->databases = 0;
+    grep_databases(se, 0, session_init_databases_fun);
+}
+
 void destroy_session(struct session *s)
 {
     yaz_log(YLOG_LOG, "Destroying session");
@@ -1403,12 +1485,12 @@ void destroy_session(struct session *s)
     wrbuf_destroy(s->wrbuf);
 }
 
-struct session *new_session() 
+struct session *new_session(NMEM nmem) 
 {
     int i;
-    struct session *session = xmalloc(sizeof(*session));
+    struct session *session = nmem_malloc(nmem, sizeof(*session));
 
-    yaz_log(YLOG_DEBUG, "New pazpar2 session");
+    yaz_log(YLOG_DEBUG, "New Pazpar2 session");
     
     session->total_hits = 0;
     session->total_records = 0;
@@ -1418,8 +1500,10 @@ struct session *new_session()
     session->clients = 0;
     session->expected_maxrecs = 0;
     session->query[0] = '\0';
+    session->session_nmem = nmem;
     session->nmem = nmem_create();
     session->wrbuf = wrbuf_alloc();
+    session_init_databases(session);
     for (i = 0; i <= SESSION_WATCH_MAX; i++)
     {
         session->watchlist[i].data = 0;
@@ -1437,8 +1521,10 @@ struct hitsbytarget *hitsbytarget(struct session *se, int *count)
     *count = 0;
     for (cl = se->clients; cl; cl = cl->next)
     {
-        res[*count].id = cl->database->url;
-        res[*count].name = cl->database->name;
+        char *name = session_setting_oneval(cl->database, PZ_NAME);
+
+        res[*count].id = cl->database->database->url;
+        res[*count].name = *name ? name : "Unknown";
         res[*count].hits = cl->hits;
         res[*count].records = cl->records;
         res[*count].diagnostic = cl->diagnostic;
@@ -1583,36 +1669,6 @@ static void start_http_listener(void)
     http_init(hp);
 }
 
-// Initialize CCL map for a target
-// Note: This approach ignores user-specific CCL maps, for which I
-// don't presently see any application.
-static void prepare_cclmap(void *context, struct database *db)
-{
-    struct setting *s;
-
-    if (!db->settings)
-        return;
-    db->ccl_map = ccl_qual_mk();
-    for (s = db->settings[PZ_CCLMAP]; s; s = s->next)
-        if (!*s->user)
-        {
-            char *p = strchr(s->name + 3, ':');
-            if (!p)
-            {
-                yaz_log(YLOG_FATAL, "Malformed cclmap name: %s", s->name);
-                exit(1);
-            }
-            p++;
-            ccl_qual_fitem(db->ccl_map, s->value, p);
-        }
-}
-
-// Read settings for each database, and prepare a CCL map for that database
-static void prepare_cclmaps(void)
-{
-    grep_databases(0, 0, prepare_cclmap);
-}
-
 static void start_proxy(void)
 {
     char hp[128] = "";
@@ -1735,10 +1791,8 @@ int main(int argc, char **argv)
     else if (global_parameters.server->settings)
         settings_read(global_parameters.server->settings);
     else
-        yaz_log(YLOG_WARN, "No settings-directory specified. Problems may ensue!");
-    prepare_cclmaps();
-    global_parameters.yaz_marc = yaz_marc_create();
-    yaz_marc_subfield_str(global_parameters.yaz_marc, "\t");
+        yaz_log(YLOG_WARN, "No settings-directory specified. Problems may well ensue!");
+    prepare_databases();
     global_parameters.odr_in = odr_createmem(ODR_DECODE);
     global_parameters.odr_out = odr_createmem(ODR_ENCODE);