77e2406feef41ca4bbaacd092bacb9b11c4eabc2
[yaz-moved-to-github.git] / ztest / ztest.c
1 /*
2  * Copyright (c) 1995-2004, Index Data.
3  * See the file LICENSE for details.
4  *
5  * $Id: ztest.c,v 1.65 2004-09-30 21:54:22 adam Exp $
6  */
7
8 /*
9  * Demonstration of simple server
10  */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <ctype.h>
15
16 #include <yaz/yaz-util.h>
17 #include <yaz/backend.h>
18 #include <yaz/ill.h>
19
20 Z_GenericRecord *dummy_grs_record (int num, ODR o);
21 char *dummy_marc_record (int num, ODR odr);
22 char *dummy_xml_record (int num, ODR odr);
23
24 int ztest_search (void *handle, bend_search_rr *rr);
25 int ztest_sort (void *handle, bend_sort_rr *rr);
26 int ztest_present (void *handle, bend_present_rr *rr);
27 int ztest_esrequest (void *handle, bend_esrequest_rr *rr);
28 int ztest_delete (void *handle, bend_delete_rr *rr);
29
30 int ztest_search (void *handle, bend_search_rr *rr)
31 {
32     if (rr->num_bases != 1)
33     {
34         rr->errcode = 23;
35         return 0;
36     }
37 #if NMEM_DEBUG
38     /* if database is stop, stop this process.. For debugging only. */
39     if (!yaz_matchstr (rr->basenames[0], "stop"))
40     {
41         nmem_print_list_l(LOG_LOG);
42         exit(0);
43     }
44 #endif
45     /* Throw Database unavailable if other than Default */
46     if (yaz_matchstr (rr->basenames[0], "Default"))
47     {
48         rr->errcode = 109;
49         rr->errstring = rr->basenames[0];
50         return 0;
51     }
52     rr->hits = rand() % 24;
53     return 0;
54 }
55
56
57 /* this huge function handles extended services */
58 int ztest_esrequest (void *handle, bend_esrequest_rr *rr)
59 {
60     /* user-defined handle - created in bend_init */
61     int *counter = (int*) handle;  
62
63     yaz_log(LOG_LOG, "ESRequest no %d", *counter);
64
65     (*counter)++;
66
67     if (rr->esr->packageName)
68         yaz_log(LOG_LOG, "packagename: %s", rr->esr->packageName);
69     yaz_log(LOG_LOG, "Waitaction: %d", *rr->esr->waitAction);
70
71
72     yaz_log(LOG_LOG, "function: %d", *rr->esr->function);
73
74     if (!rr->esr->taskSpecificParameters)
75     {
76         yaz_log (LOG_WARN, "No task specific parameters");
77     }
78     else if (rr->esr->taskSpecificParameters->which == Z_External_itemOrder)
79     {
80         Z_ItemOrder *it = rr->esr->taskSpecificParameters->u.itemOrder;
81         yaz_log (LOG_LOG, "Received ItemOrder");
82         if (it->which == Z_IOItemOrder_esRequest)
83         {
84             Z_IORequest *ir = it->u.esRequest;
85             Z_IOOriginPartToKeep *k = ir->toKeep;
86             Z_IOOriginPartNotToKeep *n = ir->notToKeep;
87             
88             if (k && k->contact)
89             {
90                 if (k->contact->name)
91                     yaz_log(LOG_LOG, "contact name %s", k->contact->name);
92                 if (k->contact->phone)
93                     yaz_log(LOG_LOG, "contact phone %s", k->contact->phone);
94                 if (k->contact->email)
95                     yaz_log(LOG_LOG, "contact email %s", k->contact->email);
96             }
97             if (k->addlBilling)
98             {
99                 yaz_log(LOG_LOG, "Billing info (not shown)");
100             }
101             
102             if (n->resultSetItem)
103             {
104                 yaz_log(LOG_LOG, "resultsetItem");
105                 yaz_log(LOG_LOG, "setId: %s", n->resultSetItem->resultSetId);
106                 yaz_log(LOG_LOG, "item: %d", *n->resultSetItem->item);
107             }
108             if (n->itemRequest)
109             {
110                 Z_External *r = (Z_External*) n->itemRequest;
111                 ILL_ItemRequest *item_req = 0;
112                 ILL_APDU *ill_apdu = 0;
113                 if (r->direct_reference)
114                 {
115                     oident *ent = oid_getentbyoid(r->direct_reference);
116                     if (ent)
117                         yaz_log(LOG_LOG, "OID %s", ent->desc);
118                     if (ent && ent->value == VAL_TEXT_XML)
119                     {
120                         yaz_log (LOG_LOG, "ILL XML request");
121                         if (r->which == Z_External_octet)
122                             yaz_log (LOG_LOG, "%.*s", r->u.octet_aligned->len,
123                                      r->u.octet_aligned->buf); 
124                     }
125                     if (ent && ent->value == VAL_ISO_ILL_1)
126                     {
127                         yaz_log (LOG_LOG, "Decode ItemRequest begin");
128                         if (r->which == ODR_EXTERNAL_single)
129                         {
130                             odr_setbuf(rr->decode,
131                                        (char *) r->u.single_ASN1_type->buf,
132                                        r->u.single_ASN1_type->len, 0);
133                             
134                             if (!ill_ItemRequest (rr->decode, &item_req, 0, 0))
135                             {
136                                 yaz_log (LOG_LOG,
137                                     "Couldn't decode ItemRequest %s near %d",
138                                        odr_errmsg(odr_geterror(rr->decode)),
139                                        odr_offset(rr->decode));
140                             }
141                             else
142                                 yaz_log(LOG_LOG, "Decode ItemRequest OK");
143                             if (rr->print)
144                             {
145                                 ill_ItemRequest (rr->print, &item_req, 0,
146                                     "ItemRequest");
147                                 odr_reset (rr->print);
148                             }
149                         }
150                         if (!item_req && r->which == ODR_EXTERNAL_single)
151                         {
152                             yaz_log (LOG_LOG, "Decode ILL APDU begin");
153                             odr_setbuf(rr->decode,
154                                        (char*) r->u.single_ASN1_type->buf,
155                                        r->u.single_ASN1_type->len, 0);
156                             
157                             if (!ill_APDU (rr->decode, &ill_apdu, 0, 0))
158                             {
159                                 yaz_log (LOG_LOG,
160                                     "Couldn't decode ILL APDU %s near %d",
161                                        odr_errmsg(odr_geterror(rr->decode)),
162                                        odr_offset(rr->decode));
163                                 yaz_log(LOG_LOG, "PDU dump:");
164                                 odr_dumpBER(yaz_log_file(),
165                                      (char *) r->u.single_ASN1_type->buf,
166                                      r->u.single_ASN1_type->len);
167                             }
168                             else
169                                 yaz_log(LOG_LOG, "Decode ILL APDU OK");
170                             if (rr->print)
171                             {
172                                 ill_APDU (rr->print, &ill_apdu, 0,
173                                     "ILL APDU");
174                                 odr_reset (rr->print);
175                             }
176                         }
177                     }
178                 }
179                 if (item_req)
180                 {
181                     yaz_log (LOG_LOG, "ILL protocol version = %d",
182                              *item_req->protocol_version_num);
183                 }
184             }
185             if (k)
186             {
187
188                 Z_External *ext = (Z_External *)
189                     odr_malloc (rr->stream, sizeof(*ext));
190                 Z_IUOriginPartToKeep *keep = (Z_IUOriginPartToKeep *)
191                     odr_malloc (rr->stream, sizeof(*keep));
192                 Z_IOTargetPart *targetPart = (Z_IOTargetPart *)
193                     odr_malloc (rr->stream, sizeof(*targetPart));
194
195                 rr->taskPackage = (Z_TaskPackage *)
196                     odr_malloc (rr->stream, sizeof(*rr->taskPackage));
197                 rr->taskPackage->packageType =
198                     odr_oiddup (rr->stream, rr->esr->packageType);
199                 rr->taskPackage->packageName = 0;
200                 rr->taskPackage->userId = 0;
201                 rr->taskPackage->retentionTime = 0;
202                 rr->taskPackage->permissions = 0;
203                 rr->taskPackage->description = 0;
204                 rr->taskPackage->targetReference = (Odr_oct *)
205                     odr_malloc (rr->stream, sizeof(Odr_oct));
206                 rr->taskPackage->targetReference->buf =
207                     (unsigned char *) odr_strdup (rr->stream, "911");
208                 rr->taskPackage->targetReference->len =
209                     rr->taskPackage->targetReference->size =
210                     strlen((char *) (rr->taskPackage->targetReference->buf));
211                 rr->taskPackage->creationDateTime = 0;
212                 rr->taskPackage->taskStatus = odr_intdup(rr->stream, 0);
213                 rr->taskPackage->packageDiagnostics = 0;
214                 rr->taskPackage->taskSpecificParameters = ext;
215
216                 ext->direct_reference =
217                     odr_oiddup (rr->stream, rr->esr->packageType);
218                 ext->indirect_reference = 0;
219                 ext->descriptor = 0;
220                 ext->which = Z_External_itemOrder;
221                 ext->u.itemOrder = (Z_ItemOrder *)
222                     odr_malloc (rr->stream, sizeof(*ext->u.update));
223                 ext->u.itemOrder->which = Z_IOItemOrder_taskPackage;
224                 ext->u.itemOrder->u.taskPackage =  (Z_IOTaskPackage *)
225                     odr_malloc (rr->stream, sizeof(Z_IOTaskPackage));
226                 ext->u.itemOrder->u.taskPackage->originPart = k;
227                 ext->u.itemOrder->u.taskPackage->targetPart = targetPart;
228
229                 targetPart->itemRequest = 0;
230                 targetPart->statusOrErrorReport = 0;
231                 targetPart->auxiliaryStatus = 0;
232             }
233         }
234     }
235     else if (rr->esr->taskSpecificParameters->which == Z_External_update)
236     {
237         Z_IUUpdate *up = rr->esr->taskSpecificParameters->u.update;
238         yaz_log (LOG_LOG, "Received DB Update");
239         if (up->which == Z_IUUpdate_esRequest)
240         {
241             Z_IUUpdateEsRequest *esRequest = up->u.esRequest;
242             Z_IUOriginPartToKeep *toKeep = esRequest->toKeep;
243             Z_IUSuppliedRecords *notToKeep = esRequest->notToKeep;
244             
245             yaz_log (LOG_LOG, "action");
246             if (toKeep->action)
247             {
248                 switch (*toKeep->action)
249                 {
250                 case Z_IUOriginPartToKeep_recordInsert:
251                     yaz_log (LOG_LOG, " recordInsert");
252                     break;
253                 case Z_IUOriginPartToKeep_recordReplace:
254                     yaz_log (LOG_LOG, " recordReplace");
255                     break;
256                 case Z_IUOriginPartToKeep_recordDelete:
257                     yaz_log (LOG_LOG, " recordDelete");
258                     break;
259                 case Z_IUOriginPartToKeep_elementUpdate:
260                     yaz_log (LOG_LOG, " elementUpdate");
261                     break;
262                 case Z_IUOriginPartToKeep_specialUpdate:
263                     yaz_log (LOG_LOG, " specialUpdate");
264                     break;
265                 default:
266                     yaz_log (LOG_LOG, " unknown (%d)", *toKeep->action);
267                 }
268             }
269             if (toKeep->databaseName)
270             {
271                 yaz_log (LOG_LOG, "database: %s", toKeep->databaseName);
272                 if (!strcmp(toKeep->databaseName, "fault"))
273                 {
274                     rr->errcode = 109;
275                     rr->errstring = toKeep->databaseName;
276                 }
277                 if (!strcmp(toKeep->databaseName, "accept"))
278                     rr->errcode = -1;
279             }
280             if (toKeep)
281             {
282                 Z_External *ext = (Z_External *)
283                     odr_malloc (rr->stream, sizeof(*ext));
284                 Z_IUOriginPartToKeep *keep = (Z_IUOriginPartToKeep *)
285                     odr_malloc (rr->stream, sizeof(*keep));
286                 Z_IUTargetPart *targetPart = (Z_IUTargetPart *)
287                     odr_malloc (rr->stream, sizeof(*targetPart));
288
289                 rr->taskPackage = (Z_TaskPackage *)
290                     odr_malloc (rr->stream, sizeof(*rr->taskPackage));
291                 rr->taskPackage->packageType =
292                     odr_oiddup (rr->stream, rr->esr->packageType);
293                 rr->taskPackage->packageName = 0;
294                 rr->taskPackage->userId = 0;
295                 rr->taskPackage->retentionTime = 0;
296                 rr->taskPackage->permissions = 0;
297                 rr->taskPackage->description = 0;
298                 rr->taskPackage->targetReference = (Odr_oct *)
299                     odr_malloc (rr->stream, sizeof(Odr_oct));
300                 rr->taskPackage->targetReference->buf =
301                     (unsigned char *) odr_strdup (rr->stream, "123");
302                 rr->taskPackage->targetReference->len =
303                     rr->taskPackage->targetReference->size =
304                     strlen((char *) (rr->taskPackage->targetReference->buf));
305                 rr->taskPackage->creationDateTime = 0;
306                 rr->taskPackage->taskStatus = odr_intdup(rr->stream, 0);
307                 rr->taskPackage->packageDiagnostics = 0;
308                 rr->taskPackage->taskSpecificParameters = ext;
309
310                 ext->direct_reference =
311                     odr_oiddup (rr->stream, rr->esr->packageType);
312                 ext->indirect_reference = 0;
313                 ext->descriptor = 0;
314                 ext->which = Z_External_update;
315                 ext->u.update = (Z_IUUpdate *)
316                     odr_malloc (rr->stream, sizeof(*ext->u.update));
317                 ext->u.update->which = Z_IUUpdate_taskPackage;
318                 ext->u.update->u.taskPackage =  (Z_IUUpdateTaskPackage *)
319                     odr_malloc (rr->stream, sizeof(Z_IUUpdateTaskPackage));
320                 ext->u.update->u.taskPackage->originPart = keep;
321                 ext->u.update->u.taskPackage->targetPart = targetPart;
322
323                 keep->action = (int *) odr_malloc (rr->stream, sizeof(int));
324                 *keep->action = *toKeep->action;
325                 keep->databaseName =
326                     odr_strdup (rr->stream, toKeep->databaseName);
327                 keep->schema = 0;
328                 keep->elementSetName = 0;
329                 keep->actionQualifier = 0;
330
331                 targetPart->updateStatus = odr_intdup (rr->stream, 1);
332                 targetPart->num_globalDiagnostics = 0;
333                 targetPart->globalDiagnostics = (Z_DiagRec **) odr_nullval();
334                 targetPart->num_taskPackageRecords = 1;
335                 targetPart->taskPackageRecords = 
336                     (Z_IUTaskPackageRecordStructure **)
337                     odr_malloc (rr->stream,
338                                 sizeof(Z_IUTaskPackageRecordStructure *));
339                 targetPart->taskPackageRecords[0] =
340                     (Z_IUTaskPackageRecordStructure *)
341                     odr_malloc (rr->stream,
342                                 sizeof(Z_IUTaskPackageRecordStructure));
343                 
344                 targetPart->taskPackageRecords[0]->which =
345                     Z_IUTaskPackageRecordStructure_record;
346                 targetPart->taskPackageRecords[0]->u.record = 
347                     z_ext_record (rr->stream, VAL_SUTRS, "test", 4);
348                 targetPart->taskPackageRecords[0]->correlationInfo = 0; 
349                 targetPart->taskPackageRecords[0]->recordStatus =
350                     odr_intdup (rr->stream,
351                                 Z_IUTaskPackageRecordStructure_success);  
352                 targetPart->taskPackageRecords[0]->num_supplementalDiagnostics
353                     = 0;
354
355                 targetPart->taskPackageRecords[0]->supplementalDiagnostics = 0;
356             }
357             if (notToKeep)
358             {
359                 int i;
360                 for (i = 0; i < notToKeep->num; i++)
361                 {
362                     Z_External *rec = notToKeep->elements[i]->record;
363
364                     if (rec->direct_reference)
365                     {
366                         struct oident *oident;
367                         oident = oid_getentbyoid(rec->direct_reference);
368                         if (oident)
369                             yaz_log (LOG_LOG, "record %d type %s", i,
370                                      oident->desc);
371                     }
372                     switch (rec->which)
373                     {
374                     case Z_External_sutrs:
375                         if (rec->u.octet_aligned->len > 170)
376                             yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
377                                      rec->u.sutrs->len,
378                                      rec->u.sutrs->buf);
379                         else
380                             yaz_log (LOG_LOG, "%d bytes:\n%s",
381                                      rec->u.sutrs->len,
382                                      rec->u.sutrs->buf);
383                         break;
384                     case Z_External_octet        :
385                         if (rec->u.octet_aligned->len > 170)
386                             yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
387                                      rec->u.octet_aligned->len,
388                                      rec->u.octet_aligned->buf);
389                         else
390                             yaz_log (LOG_LOG, "%d bytes\n%s",
391                                      rec->u.octet_aligned->len,
392                                      rec->u.octet_aligned->buf);
393                     }
394                 }
395             }
396         }
397     }
398     else if (rr->esr->taskSpecificParameters->which == Z_External_update0)
399     {
400         yaz_log(LOG_LOG, "Received DB Update (version 0)");
401     }
402     else
403     {
404         yaz_log (LOG_WARN, "Unknown Extended Service(%d)",
405                  rr->esr->taskSpecificParameters->which);
406         
407     }
408     return 0;
409 }
410
411 /* result set delete */
412 int ztest_delete (void *handle, bend_delete_rr *rr)
413 {
414     if (rr->num_setnames == 1 && !strcmp (rr->setnames[0], "1"))
415         rr->delete_status = Z_DeleteStatus_success;
416     else
417         rr->delete_status = Z_DeleteStatus_resultSetDidNotExist;
418     return 0;
419 }
420
421 /* Our sort handler really doesn't sort... */
422 int ztest_sort (void *handle, bend_sort_rr *rr)
423 {
424     rr->errcode = 0;
425     rr->sort_status = Z_SortResponse_success;
426     return 0;
427 }
428
429
430 /* present request handler */
431 int ztest_present (void *handle, bend_present_rr *rr)
432 {
433     return 0;
434 }
435
436 /* retrieval of a single record (present, and piggy back search) */
437 int ztest_fetch(void *handle, bend_fetch_rr *r)
438 {
439     char *cp;
440
441     r->last_in_set = 0;
442     r->basename = "Default";
443     r->output_format = r->request_format;  
444     if (r->request_format == VAL_SUTRS)
445     {
446         /* this section returns a small record */
447         char buf[100];
448         
449         sprintf(buf, "This is dummy SUTRS record number %d\n", r->number);
450
451         r->len = strlen(buf);
452         r->record = (char *) odr_malloc (r->stream, r->len+1);
453         strcpy(r->record, buf);
454     }
455     else if (r->request_format == VAL_GRS1)
456     {
457         r->len = -1;
458         r->record = (char*) dummy_grs_record(r->number, r->stream);
459         if (!r->record)
460         {
461             r->errcode = 13;
462             return 0;
463         }
464     }
465     else if (r->request_format == VAL_POSTSCRIPT)
466     {
467         char fname[20];
468         FILE *f;
469         long size;
470
471         sprintf (fname, "part.%d.ps", r->number);
472         f = fopen(fname, "rb");
473         if (!f)
474         {
475             r->errcode = 13;
476             return 0;
477         }
478         fseek (f, 0L, SEEK_END);
479         size = ftell (f);
480         if (size <= 0 || size >= 5000000)
481         {
482             r->errcode = 14;
483             return 0;
484         }
485         fseek (f, 0L, SEEK_SET);
486         r->record = (char*) odr_malloc (r->stream, size);
487         r->len = size;
488         r->output_format = VAL_POSTSCRIPT;
489         fread (r->record, size, 1, f);
490         fclose (f);
491     }
492     else if (r->request_format == VAL_TEXT_XML)
493     {
494         if ((cp = dummy_xml_record (r->number, r->stream)))
495         {
496             r->len = strlen(cp);
497             r->record = cp;
498             r->output_format = VAL_TEXT_XML;
499         }
500         else 
501         {
502             r->errcode = 14;
503             r->surrogate_flag = 1;
504             return 0;
505         }
506     }
507     else if ((cp = dummy_marc_record(r->number, r->stream)))
508     {
509         r->len = strlen(cp);
510         r->record = cp;
511         r->output_format = VAL_USMARC;
512     }
513     else
514     {
515         r->errcode = 13;
516         return 0;
517     }
518     r->errcode = 0;
519     return 0;
520 }
521
522 /*
523  * silly dummy-scan what reads words from a file.
524  */
525 int ztest_scan(void *handle, bend_scan_rr *q)
526 {
527     static FILE *f = 0;
528     static struct scan_entry list[200];
529     static char entries[200][80];
530     int hits[200];
531     char term[80], *p;
532     int i, pos;
533     int term_position_req = q->term_position;
534     int num_entries_req = q->num_entries;
535
536     q->errcode = 0;
537     q->errstring = 0;
538     q->entries = list;
539     q->status = BEND_SCAN_SUCCESS;
540     if (!f && !(f = fopen("dummy-words", "r")))
541     {
542         perror("dummy-words");
543         exit(1);
544     }
545     if (q->term->term->which != Z_Term_general)
546     {
547         q->errcode = 229; /* unsupported term type */
548         return 0;
549     }
550     if (*q->step_size != 0)
551     {
552         q->errcode = 205; /*Only zero step size supported for Scan */
553         return 0;
554     }
555     if (q->term->term->u.general->len >= 80)
556     {
557         q->errcode = 11; /* term too long */
558         return 0;
559     }
560     if (q->num_entries > 200)
561     {
562         q->errcode = 31;
563         return 0;
564     }
565     memcpy(term, q->term->term->u.general->buf, q->term->term->u.general->len);
566     term[q->term->term->u.general->len] = '\0';
567     for (p = term; *p; p++)
568         if (islower(*p))
569             *p = toupper(*p);
570
571     fseek(f, 0, SEEK_SET);
572     q->num_entries = 0;
573
574     for (i = 0, pos = 0; fscanf(f, " %79[^:]:%d", entries[pos], &hits[pos]) == 2;
575         i++, pos < 199 ? pos++ : (pos = 0))
576     {
577         if (!q->num_entries && strcmp(entries[pos], term) >= 0) /* s-point fnd */
578         {
579             if ((q->term_position = term_position_req) > i + 1)
580             {
581                 q->term_position = i + 1;
582                 q->status = BEND_SCAN_PARTIAL;
583             }
584             for (; q->num_entries < q->term_position; q->num_entries++)
585             {
586                 int po;
587
588                 po = pos - q->term_position + q->num_entries+1; /* find pos */
589                 if (po < 0)
590                     po += 200;
591
592                 if (!strcmp (term, "SD") && q->num_entries == 2)
593                 {
594                     list[q->num_entries].term = entries[pos];
595                     list[q->num_entries].occurrences = -1;
596                     list[q->num_entries].errcode = 233;
597                     list[q->num_entries].errstring = "SD for Scan Term";
598                 }
599                 else
600                 {
601                     list[q->num_entries].term = entries[po];
602                     list[q->num_entries].occurrences = hits[po];
603                 }
604             }
605         }
606         else if (q->num_entries)
607         {
608             list[q->num_entries].term = entries[pos];
609             list[q->num_entries].occurrences = hits[pos];
610             q->num_entries++;
611         }
612         if (q->num_entries >= num_entries_req)
613             break;
614     }
615     if (feof(f))
616         q->status = BEND_SCAN_PARTIAL;
617     return 0;
618 }
619
620 static int ztest_explain(void *handle, bend_explain_rr *rr)
621 {
622     if (rr->database && !strcmp(rr->database, "Default"))
623     {
624         rr->explain_buf = "<explain>\n"
625             "\t<serverInfo>\n"
626             "\t\t<host>localhost</host>\n"
627             "\t\t<port>210</port>\n"
628             "\t</serverInfo>\n"
629             "</explain>\n";
630     }
631     return 0;
632 }
633
634 bend_initresult *bend_init(bend_initrequest *q)
635 {
636     bend_initresult *r = (bend_initresult *)
637         odr_malloc (q->stream, sizeof(*r));
638     int *counter = (int *) xmalloc (sizeof(int));
639
640     *counter = 0;
641     r->errcode = 0;
642     r->errstring = 0;
643     r->handle = counter;         /* user handle, in this case a simple int */
644     q->bend_sort = ztest_sort;              /* register sort handler */
645     q->bend_search = ztest_search;          /* register search handler */
646     q->bend_present = ztest_present;        /* register present handle */
647     q->bend_esrequest = ztest_esrequest;
648     q->bend_delete = ztest_delete;
649     q->bend_fetch = ztest_fetch;
650     q->bend_scan = ztest_scan;
651     q->bend_explain = ztest_explain;
652
653     return r;
654 }
655
656 void bend_close(void *handle)
657 {
658     xfree (handle);              /* release our user-defined handle */
659     return;
660 }
661
662 int main(int argc, char **argv)
663 {
664     return statserv_main(argc, argv, bend_init, bend_close);
665 }