Merge branch 'master' of ssh://git.indexdata.com/home/git/pub/pazpar2
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /** \file client.c
21     \brief Z39.50 client 
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 #include <pthread.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #if HAVE_SYS_TIME_H
32 #include <sys/time.h>
33 #endif
34 #if HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include <signal.h>
38 #include <assert.h>
39
40 #include <yaz/marcdisp.h>
41 #include <yaz/comstack.h>
42 #include <yaz/tcpip.h>
43 #include <yaz/proto.h>
44 #include <yaz/readconf.h>
45 #include <yaz/pquery.h>
46 #include <yaz/otherinfo.h>
47 #include <yaz/yaz-util.h>
48 #include <yaz/nmem.h>
49 #include <yaz/query-charset.h>
50 #include <yaz/querytowrbuf.h>
51 #include <yaz/oid_db.h>
52 #include <yaz/diagbib1.h>
53 #include <yaz/snprintf.h>
54 #include <yaz/rpn2cql.h>
55
56 #define USE_TIMING 0
57 #if USE_TIMING
58 #include <yaz/timing.h>
59 #endif
60
61 #include "ppmutex.h"
62 #include "session.h"
63 #include "parameters.h"
64 #include "client.h"
65 #include "connection.h"
66 #include "settings.h"
67 #include "relevance.h"
68 #include "incref.h"
69
70 /** \brief Represents client state for a connection to one search target */
71 struct client {
72     struct session_database *database;
73     struct connection *connection;
74     struct session *session;
75     char *pquery; // Current search
76     char *cqlquery; // used for SRU targets only
77     Odr_int hits;
78     int record_offset;
79     int maxrecs;
80     int startrecs;
81     int diagnostic;
82     enum client_state state;
83     struct show_raw *show_raw;
84     struct client *next;     // next client in session or next in free list
85     ZOOM_resultset resultset;
86     YAZ_MUTEX mutex;
87     int ref_count;
88 };
89
90 struct show_raw {
91     int active; // whether this request has been sent to the server
92     int position;
93     int binary;
94     char *syntax;
95     char *esn;
96     void (*error_handler)(void *data, const char *addinfo);
97     void (*record_handler)(void *data, const char *buf, size_t sz);
98     void *data;
99     struct show_raw *next;
100 };
101
102 static const char *client_states[] = {
103     "Client_Connecting",
104     "Client_Idle",
105     "Client_Working",
106     "Client_Error",
107     "Client_Failed",
108     "Client_Disconnected"
109 };
110
111 const char *client_get_state_str(struct client *cl)
112 {
113     return client_states[cl->state];
114 }
115
116 enum client_state client_get_state(struct client *cl)
117 {
118     return cl->state;
119 }
120
121 void client_set_state(struct client *cl, enum client_state st)
122 {
123     cl->state = st;
124     /* no need to check for all client being non-active if this one
125        already is. Note that session_active_clients also LOCKS session */
126 #if 0
127     if (!client_is_active(cl) && cl->session)
128     {
129         int no_active = session_active_clients(cl->session);
130         if (no_active == 0)
131             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
132     }
133 #endif
134 }
135
136 static void client_show_raw_error(struct client *cl, const char *addinfo);
137
138 // Close connection and set state to error
139 void client_fatal(struct client *cl)
140 {
141     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
142     connection_destroy(cl->connection);
143     client_set_state(cl, Client_Error);
144 }
145
146 struct connection *client_get_connection(struct client *cl)
147 {
148     return cl->connection;
149 }
150
151 struct session_database *client_get_database(struct client *cl)
152 {
153     return cl->database;
154 }
155
156 struct session *client_get_session(struct client *cl)
157 {
158     return cl->session;
159 }
160
161 const char *client_get_pquery(struct client *cl)
162 {
163     return cl->pquery;
164 }
165
166 static void client_send_raw_present(struct client *cl);
167 static int nativesyntax_to_type(struct session_database *sdb, char *type,
168                                 ZOOM_record rec);
169
170 static void client_show_immediate(
171     ZOOM_resultset resultset, struct session_database *sdb, int position,
172     void *data,
173     void (*error_handler)(void *data, const char *addinfo),
174     void (*record_handler)(void *data, const char *buf, size_t sz),
175     int binary)
176 {
177     ZOOM_record rec = 0;
178     char type[80];
179     const char *buf;
180     int len;
181
182     if (!resultset)
183     {
184         error_handler(data, "no resultset");
185         return;
186     }
187     rec = ZOOM_resultset_record(resultset, position-1);
188     if (!rec)
189     {
190         error_handler(data, "no record");
191         return;
192     }
193     if (binary)
194         strcpy(type, "raw");
195     else
196         nativesyntax_to_type(sdb, type, rec);
197     buf = ZOOM_record_get(rec, type, &len);
198     if (!buf)
199     {
200         error_handler(data, "no record");
201         return;
202     }
203     record_handler(data, buf, len);
204 }
205
206
207 int client_show_raw_begin(struct client *cl, int position,
208                           const char *syntax, const char *esn,
209                           void *data,
210                           void (*error_handler)(void *data, const char *addinfo),
211                           void (*record_handler)(void *data, const char *buf,
212                                                  size_t sz),
213                           int binary)
214 {
215     if (syntax == 0 && esn == 0)
216         client_show_immediate(cl->resultset, client_get_database(cl),
217                               position, data,
218                               error_handler, record_handler,
219                               binary);
220     else
221     {
222         struct show_raw *rr, **rrp;
223
224         if (!cl->connection)
225             return -1;
226     
227
228         rr = xmalloc(sizeof(*rr));
229         rr->position = position;
230         rr->active = 0;
231         rr->data = data;
232         rr->error_handler = error_handler;
233         rr->record_handler = record_handler;
234         rr->binary = binary;
235         if (syntax)
236             rr->syntax = xstrdup(syntax);
237         else
238             rr->syntax = 0;
239         if (esn)
240             rr->esn = xstrdup(esn);
241         else
242             rr->esn = 0;
243         rr->next = 0;
244         
245         for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
246             ;
247         *rrp = rr;
248         
249         if (cl->state == Client_Failed)
250         {
251             client_show_raw_error(cl, "client failed");
252         }
253         else if (cl->state == Client_Disconnected)
254         {
255             client_show_raw_error(cl, "client disconnected");
256         }
257         else
258         {
259             client_send_raw_present(cl);
260         }
261     }
262     return 0;
263 }
264
265 static void client_show_raw_delete(struct show_raw *r)
266 {
267     xfree(r->syntax);
268     xfree(r->esn);
269     xfree(r);
270 }
271
272 void client_show_raw_remove(struct client *cl, void *data)
273 {
274     struct show_raw *rr = data;
275     struct show_raw **rrp = &cl->show_raw;
276     while (*rrp != rr)
277         rrp = &(*rrp)->next;
278     if (*rrp)
279     {
280         *rrp = rr->next;
281         client_show_raw_delete(rr);
282     }
283 }
284
285 void client_show_raw_dequeue(struct client *cl)
286 {
287     struct show_raw *rr = cl->show_raw;
288
289     cl->show_raw = rr->next;
290     client_show_raw_delete(rr);
291 }
292
293 static void client_show_raw_error(struct client *cl, const char *addinfo)
294 {
295     while (cl->show_raw)
296     {
297         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
298         client_show_raw_dequeue(cl);
299     }
300 }
301
302 static void client_send_raw_present(struct client *cl)
303 {
304     struct session_database *sdb = client_get_database(cl);
305     struct connection *co = client_get_connection(cl);
306     ZOOM_resultset set = cl->resultset;
307
308     int offset = cl->show_raw->position;
309     const char *syntax = 0;
310     const char *elements = 0;
311
312     assert(cl->show_raw);
313     assert(set);
314
315     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
316             client_get_url(cl), 1, offset);
317
318     if (cl->show_raw->syntax)
319         syntax = cl->show_raw->syntax;
320     else
321         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
322     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
323
324     if (cl->show_raw->esn)
325         elements = cl->show_raw->esn;
326     else
327         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
328     if (elements && *elements)
329         ZOOM_resultset_option_set(set, "elementSetName", elements);
330
331     ZOOM_resultset_records(set, 0, offset-1, 1);
332     cl->show_raw->active = 1;
333
334     connection_continue(co);
335 }
336
337 static int nativesyntax_to_type(struct session_database *sdb, char *type,
338                                 ZOOM_record rec)
339 {
340     const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
341
342     if (s && *s)
343     {
344         if (!strncmp(s, "iso2709", 7))
345         {
346             const char *cp = strchr(s, ';');
347             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
348         }
349         else if (!strncmp(s, "xml", 3))
350         {
351             strcpy(type, "xml");
352         }
353         else if (!strncmp(s, "txml", 4))
354         {
355             const char *cp = strchr(s, ';');
356             yaz_snprintf(type, 80, "txml; charset=%s", cp ? cp+1 : "marc-8s");
357         }
358         else
359             return -1;
360         return 0;
361     }
362     else  /* attempt to deduce structure */
363     {
364         const char *syntax = ZOOM_record_get(rec, "syntax", NULL);
365         if (syntax)
366         {
367             if (!strcmp(syntax, "XML"))
368             {
369                 strcpy(type, "xml");
370                 return 0;
371             }
372             else if (!strcmp(syntax, "TXML"))
373                 {
374                     strcpy(type, "txml");
375                     return 0;
376                 }
377             else if (!strcmp(syntax, "USmarc") || !strcmp(syntax, "MARC21"))
378             {
379                 strcpy(type, "xml; charset=marc8-s");
380                 return 0;
381             }
382             else return -1;
383         }
384         else return -1;
385     }
386 }
387
388 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
389 {
390     const char *buf;
391     int len;
392     char type[80];
393
394     if (cl->show_raw->binary)
395         strcpy(type, "raw");
396     else
397     {
398         struct session_database *sdb = client_get_database(cl);
399         nativesyntax_to_type(sdb, type, rec);
400     }
401
402     buf = ZOOM_record_get(rec, type, &len);
403     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
404     client_show_raw_dequeue(cl);
405 }
406
407 void client_search_response(struct client *cl)
408 {
409     struct connection *co = cl->connection;
410     struct session *se = cl->session;
411     ZOOM_connection link = connection_get_link(co);
412     ZOOM_resultset resultset = cl->resultset;
413     const char *error, *addinfo = 0;
414     
415     if (ZOOM_connection_error(link, &error, &addinfo))
416     {
417         cl->hits = 0;
418         client_set_state(cl, Client_Error);
419         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
420                 error, addinfo, client_get_url(cl));
421     }
422     else
423     {
424         cl->record_offset = cl->startrecs;
425         cl->hits = ZOOM_resultset_size(resultset);
426         if (se)
427             se->total_hits += cl->hits;
428     }
429 }
430
431 void client_got_records(struct client *cl)
432 {
433     if (cl->session)
434     {
435         session_alert_watch(cl->session, SESSION_WATCH_SHOW);
436         session_alert_watch(cl->session, SESSION_WATCH_RECORD);
437     }
438 }
439
440 void client_record_response(struct client *cl)
441 {
442     struct connection *co = cl->connection;
443     ZOOM_connection link = connection_get_link(co);
444     ZOOM_resultset resultset = cl->resultset;
445     const char *error, *addinfo;
446
447     if (ZOOM_connection_error(link, &error, &addinfo))
448     {
449         client_set_state(cl, Client_Error);
450         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
451             error, addinfo, client_get_url(cl));
452     }
453     else
454     {
455         ZOOM_record rec = 0;
456         const char *msg, *addinfo;
457         
458         if (cl->show_raw && cl->show_raw->active)
459         {
460             if ((rec = ZOOM_resultset_record(resultset,
461                                              cl->show_raw->position-1)))
462             {
463                 cl->show_raw->active = 0;
464                 ingest_raw_record(cl, rec);
465             }
466             else
467             {
468                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
469                         cl->show_raw->position-1);
470             }
471         }
472         else
473         {
474             int offset = cl->record_offset;
475             if ((rec = ZOOM_resultset_record(resultset, offset)))
476             {
477                 cl->record_offset++;
478                 if (cl->session == 0)
479                     ;
480                 else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
481                 {
482                     yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
483                             msg, addinfo, client_get_url(cl),
484                             cl->record_offset);
485                 }
486                 else
487                 {
488                     struct session_database *sdb = client_get_database(cl);
489                     NMEM nmem = nmem_create();
490                     const char *xmlrec;
491                     char type[80];
492
493                     if (nativesyntax_to_type(sdb, type, rec))
494                         yaz_log(YLOG_WARN, "Failed to determine record type");
495                     xmlrec = ZOOM_record_get(rec, type, NULL);
496                     if (!xmlrec)
497                         yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
498                                 client_get_url(cl));
499                     else
500                     {
501                         if (ingest_record(cl, xmlrec, cl->record_offset, nmem))
502                             yaz_log(YLOG_WARN, "Failed to ingest from %s",
503                                     client_get_url(cl));
504                     }
505                     nmem_destroy(nmem);
506                 }
507             }
508             else
509             {
510                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
511                         offset);
512             }
513         }
514     }
515 }
516
517 void client_start_search(struct client *cl)
518 {
519     struct session_database *sdb = client_get_database(cl);
520     struct connection *co = client_get_connection(cl);
521     ZOOM_connection link = connection_get_link(co);
522     ZOOM_resultset rs;
523     char *databaseName = sdb->database->databases[0];
524     const char *opt_piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
525     const char *opt_queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
526     const char *opt_elements = session_setting_oneval(sdb, PZ_ELEMENTS);
527     const char *opt_requestsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
528     const char *opt_maxrecs = session_setting_oneval(sdb, PZ_MAXRECS);
529     const char *opt_sru = session_setting_oneval(sdb, PZ_SRU);
530     const char *opt_sort = session_setting_oneval(sdb, PZ_SORT);
531     char maxrecs_str[24], startrecs_str[24];
532
533     assert(link);
534
535     cl->hits = -1;
536     cl->record_offset = 0;
537     cl->diagnostic = 0;
538     client_set_state(cl, Client_Working);
539
540     if (*opt_piggyback)
541         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
542     else
543         ZOOM_connection_option_set(link, "piggyback", "1");
544     if (*opt_queryenc)
545         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
546     if (*opt_sru && *opt_elements)
547         ZOOM_connection_option_set(link, "schema", opt_elements);
548     else if (*opt_elements)
549         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
550     if (*opt_requestsyn)
551         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
552
553     if (!*opt_maxrecs)
554     {
555         sprintf(maxrecs_str, "%d", cl->maxrecs);
556         opt_maxrecs = maxrecs_str;
557     }
558     ZOOM_connection_option_set(link, "count", opt_maxrecs);
559
560
561     if (atoi(opt_maxrecs) > 20)
562         ZOOM_connection_option_set(link, "presentChunk", "20");
563     else
564         ZOOM_connection_option_set(link, "presentChunk", opt_maxrecs);
565
566     sprintf(startrecs_str, "%d", cl->startrecs);
567     ZOOM_connection_option_set(link, "start", startrecs_str);
568
569     if (databaseName)
570         ZOOM_connection_option_set(link, "databaseName", databaseName);
571
572     if (cl->cqlquery)
573     {
574         ZOOM_query q = ZOOM_query_create();
575         yaz_log(YLOG_LOG, "Search %s CQL: %s", sdb->database->url, cl->cqlquery);
576         ZOOM_query_cql(q, cl->cqlquery);
577         if (*opt_sort)
578             ZOOM_query_sortby(q, opt_sort);
579         rs = ZOOM_connection_search(link, q);
580         ZOOM_query_destroy(q);
581     }
582     else
583     {
584         yaz_log(YLOG_LOG, "Search %s PQF: %s", sdb->database->url, cl->pquery);
585         rs = ZOOM_connection_search_pqf(link, cl->pquery);
586     }
587     ZOOM_resultset_destroy(cl->resultset);
588     cl->resultset = rs;
589     connection_continue(co);
590 }
591
592 struct client *client_create(void)
593 {
594     struct client *r = xmalloc(sizeof(*r));
595     r->maxrecs = 100;
596     r->startrecs = 0;
597     r->pquery = 0;
598     r->cqlquery = 0;
599     r->database = 0;
600     r->connection = 0;
601     r->session = 0;
602     r->hits = 0;
603     r->record_offset = 0;
604     r->diagnostic = 0;
605     r->state = Client_Disconnected;
606     r->show_raw = 0;
607     r->resultset = 0;
608     r->next = 0;
609     r->mutex = 0;
610     pazpar2_mutex_create(&r->mutex, "client");
611
612     r->ref_count = 1;
613     
614     return r;
615 }
616
617 void client_incref(struct client *c)
618 {
619     pazpar2_incref(&c->ref_count, c->mutex);
620     yaz_log(YLOG_LOG, "client_incref c=%p %s cnt=%d",
621             c, client_get_url(c), c->ref_count);
622 }
623
624 int client_destroy(struct client *c)
625 {
626     if (c)
627     {
628         yaz_log(YLOG_LOG, "client_destroy c=%p %s cnt=%d",
629                 c, client_get_url(c), c->ref_count);
630         if (!pazpar2_decref(&c->ref_count, c->mutex))
631         {
632             c->next = 0;
633             xfree(c->pquery);
634             c->pquery = 0;
635             xfree(c->cqlquery);
636             c->cqlquery = 0;
637             assert(!c->connection);
638             assert(!c->resultset);
639             
640             yaz_mutex_destroy(&c->mutex);
641             xfree(c);
642             return 1;
643         }
644     }
645     return 0;
646 }
647
648 void client_set_connection(struct client *cl, struct connection *con)
649 {
650     if (cl->resultset)
651     {
652         ZOOM_resultset_destroy(cl->resultset);
653         cl->resultset = 0;
654     }
655     if (con)
656     {
657         assert(cl->connection == 0);
658         cl->connection = con;
659         client_incref(cl);
660     }
661     else
662     {
663         cl->connection = con;
664         client_destroy(cl);
665     }
666 }
667
668 void client_disconnect(struct client *cl)
669 {
670     if (cl->state != Client_Idle)
671         client_set_state(cl, Client_Disconnected);
672     client_set_connection(cl, 0);
673 }
674
675 // Extract terms from query into null-terminated termlist
676 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
677 {
678     int num = 0;
679
680     pull_terms(nmem, query, termlist, &num);
681     termlist[num] = 0;
682 }
683
684 // Initialize CCL map for a target
685 static CCL_bibset prepare_cclmap(struct client *cl)
686 {
687     struct session_database *sdb = client_get_database(cl);
688     struct setting *s;
689     CCL_bibset res;
690
691     if (!sdb->settings)
692         return 0;
693     res = ccl_qual_mk();
694     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
695     {
696         char *p = strchr(s->name + 3, ':');
697         if (!p)
698         {
699             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
700             ccl_qual_rm(&res);
701             return 0;
702         }
703         p++;
704         ccl_qual_fitem(res, s->value, p);
705     }
706     return res;
707 }
708
709 // returns a xmalloced CQL query corresponding to the pquery in client
710 static char *make_cqlquery(struct client *cl)
711 {
712     cql_transform_t cqlt = cql_transform_create();
713     Z_RPNQuery *zquery;
714     char *r;
715     WRBUF wrb = wrbuf_alloc();
716     int status;
717     ODR odr_out = odr_createmem(ODR_ENCODE);
718
719     zquery = p_query_rpn(odr_out, cl->pquery);
720     yaz_log(YLOG_LOG, "PQF: %s", cl->pquery);
721     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
722     {
723         yaz_log(YLOG_WARN, "Failed to generate CQL query, code=%d", status);
724         r = 0;
725     }
726     else
727     {
728         r = xstrdup(wrbuf_cstr(wrb));
729     }     
730     wrbuf_destroy(wrb);
731     odr_destroy(odr_out);
732     cql_transform_close(cqlt);
733     return r;
734 }
735
736 // Parse the query given the settings specific to this client
737 int client_parse_query(struct client *cl, const char *query)
738 {
739     struct session *se = client_get_session(cl);
740     struct session_database *sdb = client_get_database(cl);
741     struct ccl_rpn_node *cn;
742     int cerror, cpos;
743     CCL_bibset ccl_map = prepare_cclmap(cl);
744     const char *sru = session_setting_oneval(sdb, PZ_SRU);
745     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
746     const char *pqf_strftime = session_setting_oneval(sdb, PZ_PQF_STRFTIME);
747
748     if (!ccl_map)
749         return -1;
750
751     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
752     ccl_qual_rm(&ccl_map);
753     if (!cn)
754     {
755         client_set_state(cl, Client_Error);
756         yaz_log(YLOG_WARN, "Failed to parse CCL query %s for %s",
757                 query,
758                 client_get_database(cl)->database->url);
759         return -1;
760     }
761     wrbuf_rewind(se->wrbuf);
762     if (*pqf_prefix)
763     {
764         wrbuf_puts(se->wrbuf, pqf_prefix);
765         wrbuf_puts(se->wrbuf, " ");
766     }
767     if (!pqf_strftime || !*pqf_strftime)
768         ccl_pquery(se->wrbuf, cn);
769     else
770     {
771         time_t cur_time = time(0);
772         struct tm *tm =  localtime(&cur_time);
773         char tmp_str[300];
774         const char *cp = tmp_str;
775
776         /* see man strftime(3) for things .. In particular %% gets converted
777          to %.. And That's our original query .. */
778         strftime(tmp_str, sizeof(tmp_str)-1, pqf_strftime, tm);
779         for (; *cp; cp++)
780         {
781             if (cp[0] == '%')
782                 ccl_pquery(se->wrbuf, cn);
783             else
784                 wrbuf_putc(se->wrbuf, cp[0]);
785         }
786     }
787     xfree(cl->pquery);
788     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
789
790     xfree(cl->cqlquery);
791     if (*sru)
792     {
793         if (!(cl->cqlquery = make_cqlquery(cl)))
794             return -1;
795     }
796     else
797         cl->cqlquery = 0;
798
799     if (!se->relevance)
800     {
801         // Initialize relevance structure with query terms
802         char *p[512];
803         extract_terms(se->nmem, cn, p);
804         se->relevance = relevance_create(
805             se->service->relevance_pct,
806             se->nmem, (const char **) p);
807     }
808
809     ccl_rpn_delete(cn);
810     return 0;
811 }
812
813
814 void client_remove_from_session(struct client *c)
815 {
816     struct session *se;
817
818     se = c->session;
819     assert(se);
820     if (se)
821     {
822         struct client **ccp = &se->clients;
823         
824         while (*ccp && *ccp != c)
825             ccp = &(*ccp)->next;
826         assert(*ccp == c);
827         *ccp = c->next;
828         
829         c->database = 0;
830         c->session = 0;
831         c->next = 0;
832     }
833 }
834
835 void client_set_session(struct client *cl, struct session *se)
836 {
837     cl->session = se;
838     cl->next = se->clients;
839     se->clients = cl;
840 }
841
842 int client_is_active(struct client *cl)
843 {
844     if (cl->connection && (cl->state == Client_Connecting ||
845                            cl->state == Client_Working))
846         return 1;
847     return 0;
848 }
849
850 struct client *client_next_in_session(struct client *cl)
851 {
852     if (cl)
853         return cl->next;
854     return 0;
855
856 }
857
858 Odr_int client_get_hits(struct client *cl)
859 {
860     return cl->hits;
861 }
862
863 int client_get_num_records(struct client *cl)
864 {
865     return cl->record_offset;
866 }
867
868 void client_set_diagnostic(struct client *cl, int diagnostic)
869 {
870     cl->diagnostic = diagnostic;
871 }
872
873 int client_get_diagnostic(struct client *cl)
874 {
875     return cl->diagnostic;
876 }
877
878 void client_set_database(struct client *cl, struct session_database *db)
879 {
880     cl->database = db;
881 }
882
883 struct host *client_get_host(struct client *cl)
884 {
885     return client_get_database(cl)->database->host;
886 }
887
888 const char *client_get_url(struct client *cl)
889 {
890     if (cl->database)
891         return client_get_database(cl)->database->url;
892     else
893         return "NOURL";
894 }
895
896 void client_set_maxrecs(struct client *cl, int v)
897 {
898     cl->maxrecs = v;
899 }
900
901 void client_set_startrecs(struct client *cl, int v)
902 {
903     cl->startrecs = v;
904 }
905
906 /*
907  * Local variables:
908  * c-basic-offset: 4
909  * c-file-style: "Stroustrup"
910  * indent-tabs-mode: nil
911  * End:
912  * vim: shiftwidth=4 tabstop=8 expandtab
913  */
914