/*++++++++++++++++++++++
 refdbdnote.c: refdb application server, notes handling functions 
 markus@mhoenicka.de 2003-10-12
 $Id: refdbdnote.c,v 1.25.2.15 2005/11/03 20:40:19 mhoenicka Exp $

   This program 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 of the License, or
   (at your option) any later version.
   
   This program 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 this program; if not, see <http://www.gnu.org/licenses/>

   ++++++++++++++++++++++++*/

#include "stdio.h"
#include "string.h"
#include <syslog.h>
#include <dbi/dbi.h>
#include <expat.h> /* the xml parser header file */
#include <iconv.h>
#include <errno.h>

#include "refdb.h"
#include "connect.h"
#include "linklist.h"
#include "refdbd.h"
#include "noteshandler.h"
#include "strfncs.h"
#include "tokenize.h"
#include "writenote.h"
#include "dbfncs.h"
#include "risdb.h"


#ifndef HAVE_ATOLL
long long atoll(const char *str);
#endif

extern int n_log_level; /* numeric version of log_level */

/* prototypes of local functions */
static int delete_note_by_id(unsigned long long idval, dbi_conn conn, struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  addnote(): implements the client command addnote

  int addnote returns 1 if mem error, 2 if other error, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  char *set_owner the username of the owner of new/updated
                      references if different from *username

  struct ADDRESULT* addresult this structure will be filled in with the number
                      of (un-)successfully added/updated references

  int replace_note if 1, note will be updated according to ID field; if 0
         note will be added without looking at an ID field

  Lilid* ptr_sentinel ptr to linked list that will receive all
                      added ID values

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int addnote(struct CLIENT_REQUEST* ptr_clrequest, char* set_owner, struct ADDRESULT* ptr_addresult, int replace_note, Lilid* ptr_sentinel) {
  dbi_conn conn;
  dbi_conn conn_refdb = NULL;
  XML_Parser p;
  int numbyte; /* number of bytes written */
  int result; /* result value of processing a note set */
  int retval = 0; /* be optimistic */
  int iconv_init_status = 0; /* indicates iconv initialization problem */
  char *the_user; /* name (either set_owner or username) */
  char *return_msg; /* string to hold a return message for the client */
  const char *db_encoding; /* encoding of database */
  struct addnotes_data andata;
  struct lilimem sentinel;
  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  ptr_addresult->success = 0;
  ptr_addresult->failure = 0;
  ptr_addresult->updated = 0;
  ptr_addresult->skipped = 0;

  andata.msgpool = NULL;

  /* if set_owner is set, use it, otherwise use username as owner */
  if (set_owner && *set_owner) {
    the_user = set_owner;
  }
  else {
    the_user = ptr_clrequest->username;
  }

  /* get some memory for a return message */
  return_msg = malloc((size_t)256);
  if (return_msg == NULL || insert_lilimem(&sentinel, (void**)&return_msg, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 2;
  }

  /* get the database encoding */
  db_encoding = dbi_conn_get_encoding(conn);

  /* if we need to convert, create a conversion descriptor for iconv() */
  if (db_encoding && strcmp(db_encoding, "UTF-8")) {
    andata.conv_descriptor = iconv_open(!strcmp(db_encoding, "US-ASCII") ? "ASCII" : db_encoding, "UTF-8");
    if (andata.conv_descriptor == (iconv_t)(-1)) {
      andata.conv_descriptor = NULL;
      LOG_PRINT(LOG_WARNING, "cannot set output encoding (database):");
      iconv_init_status = 701;
    }
    else {
      LOG_PRINT(LOG_DEBUG, "encoding is now:");
    }
    LOG_PRINT(LOG_DEBUG, db_encoding);
  }
  else {
    andata.conv_descriptor = NULL;
    LOG_PRINT(LOG_DEBUG, "encoding is UTF-8, no conversion required");
  }

  while (1) { /* leave with break */
    /* create the parser instance */
    p = XML_ParserCreate(NULL);
    if (!p) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      retval = 1;
      goto Finish;
    }

    /* initialize "global" handler data */
    andata.msgpool_len = 512;
    andata.msgpool = malloc(andata.msgpool_len);
    if (!andata.msgpool || insert_lilimem(&sentinel, (void**)&(andata.msgpool), NULL)) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      retval = 1;
      goto Finish;
    }


    *(andata.msgpool) = '\0';
    *(andata.real_key) = '\0';
    strcpy(andata.user, the_user); /* fallback if dataset doesn't
				      specify user */
    andata.ptr_id_sentinel = ptr_sentinel;
    andata.ptr_clrequest = ptr_clrequest;
    andata.ptr_first = NULL;
    andata.nmem_error = 0;
    andata.ndb_error = 0;
    andata.n_skip = 0;
    andata.depth = 0;
    andata.depth_adjust = 0;
    andata.set_count = 0;
    andata.added_count = 0;
    andata.skipped_count = 0;
    andata.updated_count = 0;
    andata.failed_count = 0;
    andata.replace_note = replace_note;
    andata.create_new = 1;
    andata.n_user_id = 0;
    andata.share = 0;
    andata.conn = conn;
    andata.driver = dbi_conn_get_driver(conn);
    andata.drivername = dbi_driver_get_name(andata.driver);


    /* register our handlers. these handlers will be called whenever expat finds a start- or endtag or character data */
    XML_SetElementHandler(p, notes_start_handler, notes_end_handler);
    XML_SetCharacterDataHandler(p, notes_char_handler);
      
    /* make pointer to "global data" available for handlers */
    XML_SetUserData(p, (void*)&andata);

    send_status(ptr_clrequest->fd, iconv_init_status, TERM_NO);

    result = read_xml(ptr_clrequest->fd, p, ptr_addresult);
    XML_ParserFree(p);

    /* do something intelligent in the case of a parse or mem error */
    if (!result || andata.ndb_error || andata.nmem_error) {
      if (strcmp(my_dbi_conn_get_cap(andata.conn, "transaction"), "t")) {
	/* we have to delete the junk reference manually */
	delete_note_by_id(andata.n_note_id, conn, ptr_clrequest, ptr_addresult);
      }
      else {
	my_dbi_conn_rollback(andata.conn);
      }
      my_dbi_conn_unlock(andata.conn);
    }

    /* send messages to client */
    numbyte = tiwrite(ptr_clrequest->fd, andata.msgpool, TERM_YES);
    if (numbyte == -1) {
      LOG_PRINT(LOG_INFO, get_status_msg(110));
      retval = 1;
      goto Finish;
    }

    ptr_addresult->success += andata.added_count;
    ptr_addresult->failure += andata.failed_count;
    ptr_addresult->updated += andata.updated_count;
    ptr_addresult->skipped += andata.skipped_count;
    if (!result) { /* error */
      retval = 1;
      goto Finish;
    }
    else if (result == -1) { /* we're done */
      break;
    }
  } /* end while */

 Finish:
  if (ptr_addresult->success || ptr_addresult->updated) {
    /* database was changed, update meta info */
    update_meta(conn, ptr_clrequest);
  }

  if (conn_refdb) {
    dbi_conn_close(conn_refdb);
  }

  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);

  if (andata.conv_descriptor) {
    iconv_close(andata.conv_descriptor);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  deletenote(): implements the client command deletenote

  int deletenote returns >0 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed deleterefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int deletenote(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  int numbyte; /* number of bytes written */
  int error;
  int retval = 0;
  int cs_status;
  size_t n_bufsize;
  char* id_list;
  char* new_msg;
  char buffer[64];
  const char* drivername;
  struct lilimem sentinel;
  Lilid id_sentinel;
  Lilid *ptr_curr;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  id_sentinel.ptr_next = NULL;

  n_bufsize = atoi(ptr_clrequest->argument);

  /* try to allocate the amount the client requested */
  id_list = malloc(n_bufsize);
  if (id_list == NULL || insert_lilimem(&sentinel, (void**)&id_list, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 801;
  }

  /* send acknowledgement to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  /* read id list from client */
  if ((cs_status = read_status(ptr_clrequest->fd)) != 0) {
    LOG_PRINT(LOG_INFO, get_status_msg(112));
    delete_all_lilimem(&sentinel);
    return 112;
  }

  numbyte = tread(ptr_clrequest->fd, id_list, n_bufsize);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(109));
    delete_all_lilimem(&sentinel);
    return 109;
  }
  
  /* split id_list into tokens */
  error = string_tokenize_lili(&id_sentinel, id_list);
  if (error == 2) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 801;
  }
  else if (error == 1) {
    send_status(ptr_clrequest->fd, 412, TERM_NO);
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 412;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 204;
  }

  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));


  ptr_addresult->success = 0;
  ptr_addresult->failure = 0;
  ptr_addresult->skipped = 0;

  ptr_curr = &id_sentinel;

  /* loop over arguments */
  while ((ptr_curr = get_next_lilid(ptr_curr)) != NULL) {
    if ((retval = delete_note_by_id(ptr_curr->value, conn, ptr_clrequest, ptr_addresult)) != 0) {
      sprintf(buffer, "420:"ULLSPEC"\n", (unsigned long long)ptr_curr->value);
      if ((new_msg = mstrcat(ptr_addresult->msg, buffer, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }
    }
  } /* end while */

  if (conn && ptr_addresult->success) {
    update_meta(conn, ptr_clrequest);
  }

  dbi_conn_close(conn);
  delete_all_lilid(&id_sentinel);
  delete_all_lilimem(&sentinel);
  
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  numbyte = tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(110));
    retval = 1;
  }

  return retval;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  delete_note_by_id(): deletes a note by its ID value

  static int delete_note_by_id returns 0 if ok, >0 if failed

  unsigned long long idval ID value of the note to be deleted

  dbi_conn conn database connection

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed deleterefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int delete_note_by_id(unsigned long long idval, dbi_conn conn, struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  char sql_command[640]; /* fixed length is ok here since only IDs are appended to the query strings, and the length of an ID is limited by the database */
  char* new_msg;
  const char* owner;
  int result;
  unsigned long long n_user_id = 0;
  dbi_result dbires;

  /* The general procedure to remove a note is as follows: Remove
     the entry in the main table t_note. Query the keyword, user
     crosslink tables for keywords, users used by this reference.
     Find out whether these keywords, users are used by any other
     dataset. If not, delete them from the keywords, users data
     tables. In all cases, delete the entries in the crosslink tables */


  /* If the db server supports it, start a transaction. We want one
     transaction per reference */
  if (my_dbi_conn_begin(conn)) {
    LOG_PRINT(LOG_WARNING, get_status_msg(227));
    return 227;
  }
  
  /* lock the tables we'll write to to prevent concurrent writes
     from different clients */
  if (my_dbi_conn_lock(conn, 0 /* lock regular tables */)) {
    LOG_PRINT(LOG_WARNING, get_status_msg(228));
    return 228;
  }

  sprintf(sql_command, "SELECT note_id FROM t_note WHERE note_id="ULLSPEC, (unsigned long long)idval);

  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_get_numrows(dbires) == 0) {
    /* ID does not exist - nothing to do */
    ptr_addresult->skipped++;
    dbi_result_free(dbires);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_commit(conn);

    /* add message, reuse sql_command */
    sprintf(sql_command, "417:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    return 417;
  }

  dbi_result_free(dbires);

  /* get user id and name */
  owner = NULL;

  sprintf(sql_command, "SELECT note_user_id, user_name FROM t_note,t_user WHERE t_note.note_user_id=t_user.user_id AND note_id="ULLSPEC, (unsigned long long)idval);

  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }
  else if (dbi_result_next_row(dbires) != 0){
    n_user_id = my_dbi_result_get_idval_idx(dbires, 1);
    owner = dbi_result_get_string_idx(dbires, 2);
  }
  else {
    n_user_id = 0;
  }

  if (owner && *owner) {
    if (strcmp(owner, ptr_clrequest->username)) {
      /* only owner can delete this note */
      ptr_addresult->failure++;
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);

      /* send message to client, reuse sql_command */
      sprintf(sql_command, "422:%s:"ULLSPEC"\n", owner, (unsigned long long)idval);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }

      return 422;
    }
  }

  dbi_result_free(dbires);

  /* search orphans in t_keyword */
  result = remove_keyword_entries(idval, conn, 1);

  if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "229:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 229;
  }


  /* search orphans in t_ulink */
  result = remove_ulink_entries(idval, ptr_clrequest->username, conn, 1 /* note */);

  if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "259:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 259;
  }


  /* search orphans in t_xnote */
  result = remove_xnote_entries(idval, 0 /* any link target */, conn, 4);

  if (result == 1 || result == 3) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "245:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 245;
  }

  /* delete entry in main table */
  sprintf(sql_command, "DELETE FROM t_note WHERE note_id="ULLSPEC, (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "246:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(246));
    return 246;
  }
  dbi_result_free(dbires);

  /* search orphans in t_user */
  if (n_user_id) {
    result = remove_user_entries(n_user_id, conn);

    if (result != 0 && result != 4) {
      /* send message to client, reuse sql_command */
      sprintf(sql_command, "234:"ULLSPEC"\n", (unsigned long long)idval);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      return 234;
    }
  } /* end if n_user_id */

  ptr_addresult->success++;

  /* send message to client, reuse sql_command */
  sprintf(sql_command, "419:"ULLSPEC"\n", (unsigned long long)idval);
  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 801;
  }
  else {
    ptr_addresult->msg = new_msg;
  }

  my_dbi_conn_unlock(conn);
  my_dbi_conn_commit(conn);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  getnote(): implements the client command getnote

  int getnote returns 0 = no error, 1 = out of memory or connection error, 2 = subselect failed

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct bibinfo *ptr_biblio_info ptr to structure holding bibliography style

  int ref_format the output format 0 = simple screen rendering,
        4 = HTML 12 = notex

  int n_privatelist if set to 1, limit search to user's private list
                    if set to 0, search all

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  int send_data if set to 1, sends back matching datasets (getref)
                if set to 0, sends back number of matching datasets (scanref)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int getnote(struct CLIENT_REQUEST* ptr_clrequest, struct bibinfo *ptr_biblio_info, int ref_format, int n_privatelist, struct ADDRESULT* ptr_addresult, int send_data) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  char **inargv; /* tokens of the client request */
  int inargc; /* number of tokens of the client request */
  int inargcmax; /* maximum number of tokens of the client request */
  size_t len;
  int counter;
  int nref_counter = 0; /* counter for references */
  int n_isalpha;
  int n_havesome;
  int retval = 0; 
  int render_res;
  int cs_status;
  int numbyte; /* number of bytes written */
  unsigned long long n_id;
  unsigned long long numrows = 0;
  size_t buffer_len; /* these are the allocated lengths of the buffers */
  size_t sql_command_len;
  size_t sql_command1_len;
  size_t stringbuf_len;
  size_t result_len;
  size_t n_bufsize;
  char *sql_command; /* these are ptrs to the buffers and temporary ptrs */
  char *new_sql_command;
  char *sql_command1;
  char *new_sql_command1;
  char *buffer;
  char *new_buffer;
  char *stringbuf;
  char *new_stringbuf;
  char *token;
  char *newtoken;
  char *quoted_token;
  char *query_string;
  char *eostring;
  const char* db_encoding;
  char operator[5];
  char the_id[32];
  char bitsandpieces[256];
  char limitstring[128];
  struct SQLTOKEN sqltoken;
  struct lilimem sentinel;
  struct renderinfo rendinfo;
  iconv_t conv_descriptor;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  /* fill in invariant elements of structure */
  rendinfo.ptr_biblio_info = ptr_biblio_info;
  rendinfo.ref_format = ref_format;
  rendinfo.nuse_citestyle = 0;
  rendinfo.database = ptr_clrequest->current_db;
  rendinfo.dbname = NULL;
  rendinfo.username = ptr_clrequest->username;
  rendinfo.cgi_url = ptr_clrequest->cgi_url;
  rendinfo.ptr_clrequest = ptr_clrequest;
  rendinfo.javascript = 0;

  /* get buffer to analyze the request */
  inargc = 0;
  inargcmax = 10;
  inargv = malloc((size_t)inargcmax*sizeof(char*));
  if (inargv == NULL || insert_lilimem(&sentinel, (void**)&inargv, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* get buffer to assemble the SQL queries */
  sql_command_len = 4096; 
  sql_command = malloc(sql_command_len); 

  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  sql_command[0] = '\0'; /* start with an empty string */

  /* get another buffer to assemble the SQL queries */
  sql_command1_len = 4096;
  sql_command1 = malloc(sql_command1_len); 

  if (sql_command1 == NULL || insert_lilimem(&sentinel, (void**)&sql_command1, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  sql_command1[0] = '\0'; /* start with an empty string */

  /* get a buffer to hold tokens */
  buffer_len = 4096;
  buffer = malloc(buffer_len); 

  if (buffer == NULL || insert_lilimem(&sentinel, (void**)&buffer, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  buffer[0] = '\0'; /* start with an empty string */

  /* get a buffer to hold tokens */
  stringbuf_len = 4096;
  stringbuf = malloc(stringbuf_len); 

  if (stringbuf == NULL || insert_lilimem(&sentinel, (void**)&stringbuf, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  buffer[0] = '\0'; /* start with an empty string */

  /* get length of the input string */
  n_bufsize = atoi(ptr_clrequest->argument);

  /* try to allocate the amount the client requested */
  query_string = malloc(n_bufsize);
  if (query_string == NULL || insert_lilimem(&sentinel, (void**)&query_string, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* send acknowledgement to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  if ((cs_status = read_status(ptr_clrequest->fd)) != 0) {
    LOG_PRINT(LOG_INFO, get_status_msg(112));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* read id list from client */
  numbyte = tread(ptr_clrequest->fd, query_string, n_bufsize);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(109));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  eostring = query_string + strlen(query_string);

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  driver = dbi_conn_get_driver(conn);

  /* get the database encoding */
  db_encoding = dbi_conn_get_encoding(conn);

  /* if we need to convert, create a conversion descriptor for iconv() */
  if (db_encoding && *(ptr_biblio_info->encoding) && strcmp(db_encoding, ptr_biblio_info->encoding)) {
    char to_encoding[64];

    if (!strcmp(ptr_biblio_info->encoding, "US-ASCII")) {
      strcpy(to_encoding, "ASCII//TRANSLIT");
    }
    else {
      snprintf(to_encoding, 64, "%s//TRANSLIT", ptr_biblio_info->encoding);
    }

    conv_descriptor = iconv_open(to_encoding, !strcmp(db_encoding, "US-ASCII") ? "ASCII" : db_encoding);
    if (conv_descriptor == (iconv_t)(-1)) {
      LOG_PRINT(LOG_WARNING, "cannot set conversion descriptor (database/output):");
      LOG_PRINT(LOG_DEBUG, db_encoding);
      LOG_PRINT(LOG_DEBUG, ptr_biblio_info->encoding);
      send_status(ptr_clrequest->fd, 701, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(701));
      delete_all_lilimem(&sentinel);
      return 1;
    }
    else {
      LOG_PRINT(LOG_DEBUG, "database encoding is:");
      LOG_PRINT(LOG_DEBUG, db_encoding);
    }
  }
  else {
    conv_descriptor = NULL;
    LOG_PRINT(LOG_DEBUG, "no character encoding conversion required");
  }

  LOG_PRINT(LOG_DEBUG, "output encoding is:");
  if (*(ptr_biblio_info->encoding)) {
    LOG_PRINT(LOG_DEBUG, ptr_biblio_info->encoding);
  }
  else {
    LOG_PRINT(LOG_DEBUG, db_encoding);
  }



  token = query_string;
  newtoken = token;

  /* general strategy: we extract the tokens one after the other and insert
     brackets, logical operators, and those requests which map to simple
     t_refdb queries directly into the future search string. For those 
     queries that require a relational lookup a surrogate subselect query
     is formulated, as mySQL does not (yet) support true subselects.
     The results of these subselects are lists of refdb_id values which
     are translated into query fragments and get inserted into the future
     search string. The final search string is sent to the db server
     and provides us with the final list of the t_refdb fields we're
     interested in. This list is finally used to extract the additional
     data from the t_author, t_keyword, and t_user fields that we need to
     send back the result. */

  /* loop as long as we find more tokens */
  while (newtoken != NULL) {
    token = sql_tokenize(newtoken, &sqltoken);
    newtoken = sqltoken.next_token;
/*      printf("%s\n", token);  */

    if (token != NULL) {
      /* extract the token and save to a temporary string */
      if (sqltoken.length > buffer_len) {
	new_buffer = (char*)realloc(buffer, (size_t)sqltoken.length);
	if (new_buffer == NULL) { /* out of memory */
	  retval = 1;
	  break;
	}
	else {
	  buffer = new_buffer;
	  buffer_len = sqltoken.length;
	}
      }
      strncpy(buffer, token, sqltoken.length);
      buffer[sqltoken.length] ='\0';
      if (sqltoken.type != 4) { /* we recycle these tokens */
	if ((new_sql_command1 = mstrcat(sql_command1, buffer, &sql_command1_len, 0)) == NULL) {
	  retval = 1;
	  break;
	}
	else {
	  sql_command1 = new_sql_command1;
	}
      }
      else {
	/*----------------------------------------------------------------*/
	/* keywords of note datasets */
	if (strncmp(token, ":NKW:", 5) == 0) { /* keyword query */
	  strncpy(operator, &token[5], sqltoken.length-5); /* save the comparison operator */
	  operator[sqltoken.length-5] = '\0'; /* terminate string */

	  /* assemble a surrogate subselect query */
	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;
	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
	      if (*token != '&' && *token != '|') {
		/* simple keyword query */
		sprintf(sql_command, "SELECT DISTINCT t_xkeyword.xref_id FROM t_xkeyword, t_keyword WHERE t_xkeyword.xkeyword_type=\'NOTE\' AND t_keyword.keyword_id=t_xkeyword.keyword_id AND t_keyword.keyword_name %s ", (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) ? "=" : my_dbi_conn_get_cap(conn, "rlike"));
		quoted_token = mstrdup(token);
		if (!quoted_token) {
		  retval = 1;
		  break;
		}
		if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		  retval = 1;
		  break;
		}
		if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  free(quoted_token);
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		free(quoted_token);
	      }
	      else if (*token == '|') {
		strcpy(sql_command, "SELECT DISTINCT t_xkeyword.xref_id FROM t_xkeyword, t_keyword WHERE t_xkeyword.xkeyword_type=\'NOTE\' AND (");
		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_keyword.keyword_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_keyword.keyword_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
		if ((new_sql_command = mstrcat(sql_command, "AND t_keyword.keyword_id=t_xkeyword.keyword_id", &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
	      }
	      else if (*token == '&') {
		counter = 0;
		strcpy(sql_command, "SELECT DISTINCT t_xkeyword.xref_id, COUNT(*) AS count FROM t_xkeyword, t_keyword WHERE t_xkeyword.xkeyword_type=\'NOTE\' AND (");
		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_keyword.keyword_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_keyword.keyword_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    counter++;
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
		if ((new_sql_command = mstrcat(sql_command, "AND t_keyword.keyword_id=t_xkeyword.keyword_id GROUP BY t_xkeyword.xref_id HAVING count=", &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		sprintf(buffer, "%d", counter);
		if ((new_sql_command = mstrcat(sql_command, buffer, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
	      }
	    }
	  }

	  LOG_PRINT(LOG_DEBUG, sql_command);

	  /* dbires will contain all refdb_ids that match the above
	     keyword requirements in the pseudo-subselect */
	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    retval = 2;
	    break;
	  }

	  if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id IN (", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }

	  n_havesome = 0;
	  while (dbi_result_next_row(dbires) != 0) {
	    n_havesome = 1;
	    n_id = my_dbi_result_get_idval_idx(dbires, 1);
	    sprintf(the_id, ULLSPEC, (unsigned long long)n_id);
	    if ((new_sql_command1 = mstrcat(sql_command1, the_id, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	    if ((new_sql_command1 = mstrcat(sql_command1, ",", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  if (!n_havesome) { /* use a dummy value that does not exist in the database */
	    if ((new_sql_command1 = mstrcat(sql_command1, "-1,", &sql_command1_len, 0)) == NULL) { /* the comma will be removed further down */
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }	    

	  dbi_result_free(dbires);
	  if (retval) {
	    break;
	  }
	  if ((new_sql_command1 = mstrcat(sql_command1, ")", &sql_command1_len, 1)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }
	}


	/*----------------------------------------------------------------*/
	/* queries linking to keywords */
	else if (strncmp(token, ":KW:", 4) == 0) { /* keyword query */
	  strncpy(operator, &token[4], sqltoken.length-4); /* save the comparison operator */
	  operator[sqltoken.length-4] = '\0'; /* terminate string */

	  /* assemble a surrogate subselect query */
	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;
	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
	      if (*token != '&' && *token != '|') {
		/* simple keyword query */
		sprintf(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_keyword WHERE t_keyword.keyword_id=t_xnote.xref_id AND t_xnote.xnote_type=\'KEYWORD\' AND t_keyword.keyword_name %s ", (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) ? "=" : my_dbi_conn_get_cap(conn, "rlike"));
		quoted_token = mstrdup(token);
		if (!quoted_token) {
		  retval = 1;
		  break;
		}
		if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		  retval = 1;
		  break;
		}
		if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  free(quoted_token);
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		free(quoted_token);
	      }
	      else if (*token == '|') {
		strcpy(sql_command, "SELECT DISTINCT t_xnote.note_id FROM t_xnote, t_keyword WHERE t_keyword.keyword_id=t_xnote.xref_id AND t_xnote.xnote_type=\'KEYWORD\' AND (");
		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_keyword.keyword_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_keyword.keyword_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
	      }
	      else if (*token == '&') {
		counter = 0;
		strcpy(sql_command, "SELECT DISTINCT t_xnote.note_id, COUNT(*) FROM t_xnote, t_keyword WHERE t_keyword.keyword_id=t_xnote.xref_id AND t_xnote.xnote_type=\'KEYWORD\' AND (");

		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_keyword.keyword_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_keyword.keyword_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    counter++;
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
		if ((new_sql_command = mstrcat(sql_command, " GROUP BY t_xnote.note_id HAVING count=", &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		sprintf(buffer, "%d", counter);
		if ((new_sql_command = mstrcat(sql_command, buffer, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
	      }
	    }
	  }

	  LOG_PRINT(LOG_DEBUG, sql_command);

	  /* dbires will contain all note_ids that match the above
	     keyword requirements in the pseudo-subselect */
	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    retval = 2;
	    break;
	  }

	  if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id IN (", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }

	  n_havesome = 0;
	  while (dbi_result_next_row(dbires) != 0) {
	    n_havesome = 1;
	    n_id = my_dbi_result_get_idval_idx(dbires, 1);
	    sprintf(the_id, ULLSPEC, (unsigned long long)n_id);
	    if ((new_sql_command1 = mstrcat(sql_command1, the_id, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	    if ((new_sql_command1 = mstrcat(sql_command1, ",", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  if (!n_havesome) { /* use a dummy value that does not exist in the database */
	    if ((new_sql_command1 = mstrcat(sql_command1, "-1,", &sql_command1_len, 0)) == NULL) { /* the comma will be removed further down */
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }	    

	  dbi_result_free(dbires);
	  if (retval) {
	    break;
	  }
	  if ((new_sql_command1 = mstrcat(sql_command1, ")", &sql_command1_len, 1)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }
	}

	/*----------------------------------------------------------------*/
	/* queries linking to author names */
	else if (strncmp(token, ":AU:", 4) == 0) { /* author query */
	  strncpy(operator, &token[4], sqltoken.length-4); /* save the comparison operator */
	  operator[sqltoken.length-4] = '\0'; /* terminate string */

	  /* assemble a surrogate subselect query */
	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;
	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
	      if (*token != '&' && *token != '|') {
		/* simple keyword query */
		sprintf(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_author WHERE t_author.author_id=t_xnote.xref_id AND t_xnote.xnote_type=\'AUTHOR\' AND t_author.author_name %s ", (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) ? "=" : my_dbi_conn_get_cap(conn, "rlike"));
		quoted_token = mstrdup(token);
		if (!quoted_token) {
		  retval = 1;
		  break;
		}
		if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		  retval = 1;
		  break;
		}
		if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  free(quoted_token);
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		free(quoted_token);
	      }
	      else if (*token == '|') {
		strcpy(sql_command, "SELECT DISTINCT t_xnote.note_id FROM t_xnote, t_author WHERE t_author.author_id=t_xnote.xref_id AND t_xnote.xnote_type=\'AUTHOR\' AND (");
		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_author.author_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_author.author_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
	      }
	      else if (*token == '&') {
		counter = 0;
		strcpy(sql_command, "SELECT DISTINCT t_xnote.note_id, COUNT(*) FROM t_xnote, t_author WHERE t_author.author_id=t_xnote.xref_id AND t_xnote.xnote_type=\'AUTHOR\' AND (");

		while (token != NULL) {
		  token = nstrtok(token+len, &len, " ");
		  if (token != NULL) {
		    if (*operator == '=' || (*operator == '!' && *(operator+1) == '=')) {
		      strcpy(bitsandpieces, "t_author.author_name = ");
		    }
		    else {
		      sprintf(bitsandpieces, "t_author.author_name %s ", my_dbi_conn_get_cap(conn, "rlike"));
		    }

		    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    if ((new_stringbuf = mstrncpy(stringbuf, token, len, &stringbuf_len)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      stringbuf = new_stringbuf;
		    }
		    stringbuf[len] = '\0';
		    quoted_token = mstrdup(stringbuf);
		    if (!quoted_token) {
		      retval = 1;
		      break;
		    }
		    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		      retval = 1;
		      break;
		    }
		    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      free(quoted_token);
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    free(quoted_token);

		    if ((new_sql_command = mstrcat(sql_command, " OR ", &sql_command_len, 0)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		    counter++;
		  }
		  else {
		    if ((new_sql_command = mstrcat(sql_command, ") ", &sql_command_len, 4)) == NULL) {
		      retval = 1;
		      break;
		    }
		    else {
		      sql_command = new_sql_command;
		    }
		  }
		}
		if ((new_sql_command = mstrcat(sql_command, " GROUP BY t_xnote.note_id HAVING count=", &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
		sprintf(buffer, "%d", counter);
		if ((new_sql_command = mstrcat(sql_command, buffer, &sql_command_len, 0)) == NULL) {
		  retval = 1;
		  break;
		}
		else {
		  sql_command = new_sql_command;
		}
	      }
	    }
	  }

	  LOG_PRINT(LOG_DEBUG, sql_command);

	  /* dbires will contain all note_ids that match the above
	     keyword requirements in the pseudo-subselect */
	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    retval = 2;
	    break;
	  }

	  if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id IN (", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }

	  n_havesome = 0;
	  while (dbi_result_next_row(dbires) != 0) {
	    n_havesome = 1;
	    n_id = my_dbi_result_get_idval_idx(dbires, 1);
	    sprintf(the_id, ULLSPEC, (unsigned long long)n_id);
	    if ((new_sql_command1 = mstrcat(sql_command1, the_id, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	    if ((new_sql_command1 = mstrcat(sql_command1, ",", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  if (!n_havesome) { /* use a dummy value that does not exist in the database */
	    if ((new_sql_command1 = mstrcat(sql_command1, "-1,", &sql_command1_len, 0)) == NULL) { /* the comma will be removed further down */
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }	    

	  dbi_result_free(dbires);
	  if (retval) {
	    break;
	  }
	  if ((new_sql_command1 = mstrcat(sql_command1, ")", &sql_command1_len, 1)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }
	}

	/*----------------------------------------------------------------*/
	/* queries linking to journal names */
	else if (strncmp(token, ":JO:", 4) == 0 || strncmp(token, ":JF:", 4) == 0 || strncmp(token, ":J1:", 4) == 0 || strncmp(token, ":J2:", 4) == 0) { /* periodical query */
	  strncpy(operator, &token[4], sqltoken.length-4); /* save the comparison operator */
	  operator[sqltoken.length-4] = '\0'; /* terminate string */

	  /* assemble a surrogate subselect query */
	  if (strncmp(token, ":JF:", 4) == 0) {
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_periodical WHERE t_periodical.periodical_id=t_xnote.xref_id AND t_periodical.periodical_name");
	  }
	  else if (strncmp(token, ":JO:", 4) == 0) {
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_periodical WHERE t_periodical.periodical_id=t_xnote.xref_id AND t_periodical.periodical_abbrev");
	  }
	  else if (strncmp(token, ":J1:", 4) == 0) {
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_periodical WHERE t_periodical.periodical_id=t_xnote.xref_id AND t_periodical.periodical_custabbrev1");
	  }
	  else { /* :J2: */
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_periodical WHERE t_periodical.periodical_id=t_xnote.xref_id AND t_periodical.periodical_custabbrev2");
	  }

	  /* translate operator to regexp operator */
	  if (*operator == '=') {
/* 	    sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "rlike")); */
	    if ((new_sql_command = mstrcat(sql_command, " = ", &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	  }
	  else if (*operator == '~') {
	    sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "rlike"));
	    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	  }
	  else if (*(operator+1) == '=') { 
/* 	    sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "not_rlike")); */
	    if ((new_sql_command = mstrcat(sql_command, " != ", &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	  }
	  else { /* treat all other operators as non-equal regexp*/
	    sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "not_rlike"));
	    if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	  }

	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    if ((new_buffer = mstrncpy(buffer, token, sqltoken.length, &buffer_len)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      buffer = new_buffer;
	    }
	    buffer[sqltoken.length] ='\0';
	    quoted_token = mstrdup(buffer);
	    if (!quoted_token) {
	      retval = 1;
	      break;
	    }
	    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
	      retval = 1;
	      break;
	    }
	    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      free(quoted_token);
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	    free(quoted_token);
	  }
	  LOG_PRINT(LOG_DEBUG, sql_command);

	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    retval = 2;
	    break;
	  }

	  if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id IN (", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }

	  if (dbi_result_get_numrows(dbires)) {
	    while (dbi_result_next_row(dbires) != 0) {
	      n_id = my_dbi_result_get_idval_idx(dbires, 1);
	      sprintf(the_id, ULLSPEC, (unsigned long long)n_id);
	      if ((new_sql_command1 = mstrcat(sql_command1, the_id, &sql_command1_len, 0)) == NULL) {
		retval = 1;
		dbi_result_free(dbires);
		break;
	      }
	      else {
		sql_command1 = new_sql_command1;
	      }
	      if ((new_sql_command1 = mstrcat(sql_command1, ",", &sql_command1_len, 0)) == NULL) {
		retval = 1;
		dbi_result_free(dbires);
		break;
	      }
	      else {
		sql_command1 = new_sql_command1;
	      }
	    }
	    /* remove trailing comma */
	    sql_command1[strlen(sql_command1)-1] = '\0';
	  }
	  else { /* use a dummy value that does not exist in the database */
	    if ((new_sql_command1 = mstrcat(sql_command1, "-1", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }	    
	  
	  if ((new_sql_command1 = mstrcat(sql_command1, ")", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    dbi_result_free(dbires);
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }
	  dbi_result_free(dbires);
	}

	/*----------------------------------------------------------------*/
	/* queries linking to references */
	else if (strncmp(token, ":ID:", 4) == 0 || strncmp(token, ":CK:", 4) == 0) { /* reference by ID or by citation key */
	  strncpy(operator, &token[4], sqltoken.length-4); /* save the comparison operator */
	  operator[sqltoken.length-4] = '\0'; /* terminate string */

	  /* assemble a surrogate subselect query */
	  if (strncmp(token, ":ID:", 4) == 0) {
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote WHERE t_xnote.xnote_type=\'REFERENCE\' AND t_xnote.xref_id");
	  }
	  else { /* :CK: */
	    strcpy(sql_command, "SELECT t_xnote.note_id FROM t_xnote, t_refdb WHERE t_xnote.xnote_type=\'REFERENCE\' AND t_refdb.refdb_id=t_xnote.xref_id AND t_refdb.refdb_citekey");
	  }

	  /* append operator */
	  if (strncmp(token, ":ID:", 4) == 0 && *operator == '~') {
	    strcpy(bitsandpieces, "=");
	  }
	  else if (strncmp(token, ":ID:", 4) == 0
		   && *operator == '!'
		   && *(operator+1) == '~') {
	    strcpy(bitsandpieces, "!=");
	  }
	  else {
	    strcpy(bitsandpieces, operator);
	  }

	  if ((new_sql_command = mstrcat(sql_command, bitsandpieces, &sql_command_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command = new_sql_command;
	  }

	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    if ((new_buffer = mstrncpy(buffer, token, sqltoken.length, &buffer_len)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      buffer = new_buffer;
	    }
	    buffer[sqltoken.length] ='\0';
	    quoted_token = mstrdup(buffer);
	    if (!quoted_token) {
	      retval = 1;
	      break;
	    }
	    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
	      retval = 1;
	      break;
	    }
	    if ((new_sql_command = mstrcat(sql_command, quoted_token, &sql_command_len, 0)) == NULL) {
	      retval = 1;
	      free(quoted_token);
	      break;
	    }
	    else {
	      sql_command = new_sql_command;
	    }
	    free(quoted_token);
	  }
	  LOG_PRINT(LOG_DEBUG, sql_command);

	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    retval = 2;
	    break;
	  }

	  if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id IN (", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }

	  if (dbi_result_get_numrows(dbires)) {
	    while (dbi_result_next_row(dbires) != 0) {
	      n_id = my_dbi_result_get_idval_idx(dbires, 1);
	      sprintf(the_id, ULLSPEC, (unsigned long long)n_id);
	      if ((new_sql_command1 = mstrcat(sql_command1, the_id, &sql_command1_len, 0)) == NULL) {
		retval = 1;
		dbi_result_free(dbires);
		break;
	      }
	      else {
		sql_command1 = new_sql_command1;
	      }
	      if ((new_sql_command1 = mstrcat(sql_command1, ",", &sql_command1_len, 0)) == NULL) {
		retval = 1;
		dbi_result_free(dbires);
		break;
	      }
	      else {
		sql_command1 = new_sql_command1;
	      }
	    }
	    /* remove trailing comma */
	    sql_command1[strlen(sql_command1)-1] = '\0';
	  }
	  else { /* use a dummy value that does not exist in the database */
	    if ((new_sql_command1 = mstrcat(sql_command1, "-1", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      dbi_result_free(dbires);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }	    
	  
	  if ((new_sql_command1 = mstrcat(sql_command1, ")", &sql_command1_len, 0)) == NULL) {
	    retval = 1;
	    dbi_result_free(dbires);
	    break;
	  }
	  else {
	    sql_command1 = new_sql_command1;
	  }
	  dbi_result_free(dbires);
	}

	/*----------------------------------------------------------------*/
	/* simple queries */
	else { /* all simple t_note queries */
	  strncpy(operator, &token[5], sqltoken.length-5); /* save the comparison operator */
	  operator[sqltoken.length-5] = '\0'; /* terminate string */
	  
	  /* initialize alphanumeric indicator */
	  n_isalpha = 1;

	  if (strncmp(token, ":NPY:", 5) == 0) {
	    if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_date", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	      n_isalpha = 0;
	    }
	  }
	  else if (strncmp(token, ":NID:", 5) == 0) {
	    if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_id", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	      n_isalpha = 0;
	    }
	  }
	  else if (strncmp(token, ":NCK:", 5) == 0) {
	    if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_key", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  else if (strncmp(token, ":NTI:", 5) == 0) {
	    if ((new_sql_command1 = mstrcat(sql_command1, "t_note.note_title", &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }

	  /* we have to treat two types of columns: numerical columns
	     can reuse the operator directly. Alphanumeric columns need
	     some intelligent translation into a regexp-based query */
	  if (n_isalpha) {
	    if (*operator == '=') {
 	      strcpy(bitsandpieces, " = ");
	    }
	    else if (*operator == '~') {
	      sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "rlike"));
	    }
	    else if (*operator == '!' && *(operator+1) == '=') {
 	      sprintf(bitsandpieces, " != ");
	    }
	    else { /* treat all other operators as non-equal */
	      sprintf(bitsandpieces, " %s ", my_dbi_conn_get_cap(conn, "not_rlike"));
	    }

	    if ((new_sql_command1 = mstrcat(sql_command1, bitsandpieces, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  else {
	    if ((new_sql_command1 = mstrcat(sql_command1, operator, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	  }
	  token = sql_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
/*  	    printf("%s:%d\n", token, error); */
/*  	    printf("%s\n", newtoken); */
	    if ((new_buffer = mstrncpy(buffer, token, sqltoken.length, &buffer_len)) == NULL) {
	      retval = 1;
	      break;
	    }
	    else {
	      buffer = new_buffer;
	    }
	    buffer[sqltoken.length] ='\0';
	    quoted_token = mstrdup(buffer);
	    if (!quoted_token) {
	      retval = 1;
	      break;
	    }
	    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
	      retval = 1;
	      break;
	    }
	    if ((new_sql_command1 = mstrcat(sql_command1, quoted_token, &sql_command1_len, 0)) == NULL) {
	      retval = 1;
	      free(quoted_token);
	      break;
	    }
	    else {
	      sql_command1 = new_sql_command1;
	    }
	    free(quoted_token);
	  }
	}
      } /* if (sqltoken.type != 4) / else */
    } /* if (token != NULL) */
  } /* while (newtoken != NULL) */

  if (retval) {
    if (retval == 1) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
    }
    else if (retval == 2) {
      send_status(ptr_clrequest->fd, 234, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
    }
/*      printf("error detected\n"); */
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  /* now assemble the SQL query string proper */
  if (n_privatelist) {
    /* this list contains only the notes that belong to the user. We need not care about the share setting of the server or of extended notes */
    sprintf(sql_command, "SELECT DISTINCT t_note.note_id, t_note.note_key, t_note.note_title, t_note.note_user_id, t_note.note_date, t_note.note_content, t_note.note_content_type, t_note.note_content_xmllang, t_note.note_share, t_user.user_name FROM t_note,t_user WHERE t_note.note_user_id=t_user.user_id AND t_user.user_name=\'%s\' AND ", ptr_clrequest->username);
  }
  else {
    /* need to check the server and note share settings */
    if (ptr_clrequest->share_default) {
    /* if the server shares by default, we need to make sure the notes
       of other users are not private */
      sprintf(sql_command, "SELECT DISTINCT t_note.note_id, t_note.note_key, t_note.note_title, t_note.note_user_id, t_note.note_date, t_note.note_content, t_note.note_content_type, t_note.note_content_xmllang, t_note.note_share, t_user.user_name FROM t_note,t_user WHERE t_note.note_user_id=t_user.user_id AND (t_user.user_name=\'%s\' OR (t_user.user_name!=\'%s\' AND t_note.note_share!=0)) AND ", ptr_clrequest->username, ptr_clrequest->username);
    }
    else {
    /* if the server is paranoid by default, we need to make sure the
       notes of other users are positively public */
      sprintf(sql_command, "SELECT DISTINCT t_note.note_id, t_note.note_key, t_note.note_title, t_note.note_user_id, t_note.note_date, t_note.note_content, t_note.note_content_type, t_note.note_content_xmllang, t_note.note_share, t_user.user_name FROM t_note,t_user WHERE t_note.note_user_id=t_user.user_id AND (t_user.user_name=\'%s\' OR (t_user.user_name!=\'%s\' AND t_note.note_share=1)) AND ", ptr_clrequest->username, ptr_clrequest->username);
    }
  }


  if ((new_sql_command = mstrcat(sql_command, sql_command1, &sql_command_len, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }
  else {
    sql_command = new_sql_command;
  }

  /* sort the output */
  if (strncmp(ptr_biblio_info->sort_string, "PY", 2) == 0) {
    if (retval || ((new_sql_command = mstrcat(sql_command, " ORDER BY t_note.note_date", &sql_command_len, 0)) == NULL)) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }
  else {
    if (retval || ((new_sql_command = mstrcat(sql_command, " ORDER BY t_note.note_id", &sql_command_len, 0)) == NULL)) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }

  /* see whether the query should be limited to a range */
  if (*(ptr_clrequest->limit)) {
    char *colon;
    
    colon = strchr(ptr_clrequest->limit, (int)':');
      
    if (!colon) {
      snprintf(limitstring, 128, " LIMIT %s ", ptr_clrequest->limit);
    }
    else {
      *colon = '\0';
      snprintf(limitstring, 128, " LIMIT %s OFFSET %s ", ptr_clrequest->limit, colon+1);
    }

    if ((new_sql_command = mstrcat(sql_command, limitstring, &sql_command_len, 0)) == NULL) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }

  LOG_PRINT(LOG_DEBUG, sql_command);

  /* actually run the query */
  rendinfo.dbires = dbi_conn_query(conn, sql_command);
  if (!rendinfo.dbires) {
    send_status(ptr_clrequest->fd, 234, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  numrows = dbi_result_get_numrows(rendinfo.dbires);

  if (!send_data) { /* countref */
    ptr_addresult->success = numrows;
    dbi_result_free(rendinfo.dbires);
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 402, TERM_YES);
    
    return retval;
  }
  /* else: getnote */


  /* create a "header" if necessary */
  /* prepare_render resets sql_command */

  /* fill in variable elements of structure */
  rendinfo.ptr_ref = &sql_command;
  rendinfo.ptr_ref_len = &sql_command_len;
  if (!*(rendinfo.ptr_biblio_info->encoding)) {
    rendinfo.ptr_biblio_info->encoding = dbi_conn_get_encoding(conn);
  }

  /* real output starts here */
  if (prepare_render_note(&rendinfo)) {
    tiwrite(ptr_clrequest->fd, "", TERM_YES);
    read_status(ptr_clrequest->fd);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_result_free(rendinfo.dbires);
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  if (numrows) {
    /* fetch all notes we're interested in */
    while (dbi_result_next_row(rendinfo.dbires) != 0) {
      nref_counter++;

      /* fill in variable elements of structure */
      rendinfo.ptr_ref = &sql_command;
      rendinfo.ptr_ref_len = &sql_command_len;
      rendinfo.nref_counter = nref_counter;
      
      /* recycle sql_command */
      if ((render_res = render_note(&rendinfo)) != 0) {
	if (render_res == 801) {
	  retval = 1;
	  tiwrite(ptr_clrequest->fd, "", TERM_YES);
	  read_status(ptr_clrequest->fd);
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}
	else {
	  ptr_addresult->failure++;
	}
      }

      /* run a character encoding conversion if required */
      if (conv_descriptor && *sql_command) {
	size_t inlength;
	size_t outlength;
	char* my_sql_command = NULL; /* this ptr will be modified by iconv() */
	char* my_sql_command_start = NULL; /* records initial state of my_elvalue */
	const char* my_instring = NULL; /* this ptr will be modified by iconv() */
	inlength = strlen(sql_command)/*  + 1 */;
	/* with the encodings supported by our database engines, the converted
	   string can't be longer than six times the input string */
	outlength = 6*inlength;
	
	if ((my_sql_command = malloc(outlength)) == NULL) {
	  retval = 1;
	  tiwrite(ptr_clrequest->fd, "", TERM_YES);
	  read_status(ptr_clrequest->fd);
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}

	/* keep start of the converted string */
	my_sql_command_start = my_sql_command;

	/* variable will be modified by iconv, so don't use original */
	my_instring = (const char*)sql_command;
	
	/* now actually do the conversion */
	if (iconv(conv_descriptor, &my_instring, &inlength, &my_sql_command, &outlength) == (size_t)(-1)) {
	  if (errno == EILSEQ) {
	    sprintf(sql_command, "iconv: invalid input character sequence\n");
	    LOG_PRINT(LOG_WARNING, "iconv: invalid input character sequence");
	  }
	  else if (errno == E2BIG) {
	    sprintf(sql_command, "iconv: output buffer too small\n");
	    LOG_PRINT(LOG_WARNING, "iconv: output buffer too small");
	  }
	  else if (errno == EINVAL) {
	    sprintf(sql_command, "iconv: incomplete input character\n");
	    LOG_PRINT(LOG_WARNING, "iconv: incomplete input character");
	  }
	  
	  tiwrite(ptr_clrequest->fd, "", TERM_YES);
	  read_status(ptr_clrequest->fd);
	  send_status(ptr_clrequest->fd, 702, TERM_NO);
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}
	/* else: conversion went ok. We free the original string and replace
	   it with the converted copy */
	if (sql_command) {
	  free(sql_command);
	}
	sql_command = my_sql_command_start;
	sql_command_len = outlength;
	result_len = (size_t)(my_sql_command - my_sql_command_start);
      }
      else { /* no conversion required */
	result_len = strlen(sql_command);
      }

      /* send ok status, then the terminated result string */
      send_status(ptr_clrequest->fd, 404, TERM_NO);
      iwrite(ptr_clrequest->fd, sql_command, result_len); 
      tiwrite(ptr_clrequest->fd, "", TERM_YES);
      ptr_addresult->success++;

      /* reset buffer string */
      sql_command[0] = '\0';
      
      /* read client response */
      cs_status = read_status(ptr_clrequest->fd);
      
      if (cs_status) {
	break;
      }
    } /* end while */
  }
  else {
    /* no notes found, send an empty body */
    send_status(ptr_clrequest->fd, 402, TERM_NO);
    tiwrite(ptr_clrequest->fd, "", TERM_YES);
  }

  if (ptr_addresult->success) {
    /* create a "footer" if necessary */
    /* fill in variable elements of structure */
    if ((render_res = finish_render_note(&rendinfo)) != 0) {
      retval = 1;
      tiwrite(ptr_clrequest->fd, "", TERM_YES);
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      read_status(ptr_clrequest->fd);
      dbi_result_free(rendinfo.dbires);
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 0;
    }
    send_status(ptr_clrequest->fd, 402, TERM_NO);
    tiwrite(ptr_clrequest->fd, sql_command, TERM_YES); 
  }

  dbi_result_free(rendinfo.dbires);
  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);

  if (conv_descriptor) {
    iconv_close(conv_descriptor);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  addlink(): implements the client command addlink

  int addlink returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  char *set_owner the username of the owner of new/updated
                      references if different from *username

  struct ADDRESULT* addresult this structure will be filled in with the number
                      of (un-)successfully added/updated references

  int n_remove if 0, links will be added, if 1, links will be removed

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int addlink(struct CLIENT_REQUEST* ptr_clrequest, char* set_owner, struct ADDRESULT* ptr_addresult, int n_remove) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  char **inargv; /* tokens of the client request */
  int inargc; /* number of tokens of the client request */
  int inargcmax; /* maximum number of tokens of the client request */
  size_t len;
  int result;
  int mode = 0;
  int retval = 0;
  int error = 0; /* codes: 0 = no error, 1 = out of memory, 2 = subselect failed */
  unsigned long long n_id;
  unsigned long long n_target_id;
  size_t buffer_len; /* these are the allocated lengths of the buffers */
  size_t sql_command_len;
  size_t return_msg_len;
  char *return_msg;
  char *sql_command; /* these are ptrs to the buffers and temporary ptrs */
  char *buffer;
  char *new_buffer;
  char *token;
  char *newtoken;
  char *quoted_token;
  char *stripped_token;
  char *eostring;
  char note_val[256] = ""; /* note id or citation key value*/
  char note_type[7] = ""; /* type: either numerical or key */
  char field_buffer[10];
  char my_type[20];
  char assemble_buffer[2048];
  struct SQLTOKEN sqltoken;
  struct lilimem sentinel;
  Liliform *ptr_curr;
  Liliform link_sentinel;

  ptr_addresult->success = 0;
  ptr_addresult->failure = 0;
  ptr_addresult->updated = 0;
  ptr_addresult->skipped = 0;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  link_sentinel.ptr_next = NULL;
  link_sentinel.name[0] = '\0';
  link_sentinel.value = NULL;

  /* get buffer to analyze the request */
  inargc = 0;
  inargcmax = 10;
  inargv = malloc((size_t)inargcmax*sizeof(char*));
  if (inargv == NULL || insert_lilimem(&sentinel, (void**)&inargv, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* get buffer to assemble the SQL queries */
  sql_command_len = 4096; 
  sql_command = malloc(sql_command_len); 

  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *sql_command = '\0'; /* start with an empty string */

  /* get a buffer to hold tokens */
  buffer_len = 4096;
  buffer = malloc(buffer_len); 

  if (buffer == NULL || insert_lilimem(&sentinel, (void**)&buffer, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *buffer = '\0'; /* start with an empty string */

  /* yet another buffer */
  return_msg_len = 4096;
  return_msg = malloc(return_msg_len); 

  if (return_msg == NULL || insert_lilimem(&sentinel, (void**)&return_msg, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *return_msg = '\0'; /* start with an empty string */

  eostring = ptr_clrequest->argument + strlen(ptr_clrequest->argument);

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  driver = dbi_conn_get_driver(conn);

  token = ptr_clrequest->argument;
  newtoken = token;

/*   printf("%s\n", token); */

  /* general strategy: scan the command string for items and add all of
     them except the first :NID: or :NCK: to a linked list. Then walk through
     the linked list to add or remove the links */


  /* loop as long as we find more tokens */
  while (newtoken != NULL) {
    token = link_tokenize(newtoken, &sqltoken);
    newtoken = sqltoken.next_token;
/*     printf("token:%s<<newtoken:%s<<\n", token, newtoken); */

    if (token != NULL) {
      /* extract the token and save to a temporary string */
      if (sqltoken.length > buffer_len) {
	new_buffer = (char*)realloc(buffer, (size_t)sqltoken.length);
	if (new_buffer == NULL) { /* out of memory */
	  error = 1;
	  break;
	}
	else {
	  buffer = new_buffer;
	  buffer_len = sqltoken.length;
	}
      }
      strncpy(buffer, token, sqltoken.length);
      buffer[sqltoken.length] ='\0';
      if (sqltoken.type == 4) { 
	/*----------------------------------------------------------------*/
	/* target fields */
	if (strncmp(token, ":ID:", 4) == 0 || /* ID of reference */
	    strncmp(token, ":KW:", 4) == 0 || /* keyword */
	    strncmp(token, ":AU:", 4) == 0 || /* author */
	    strncmp(token, ":JF:", 4) == 0 || /* journal full */
	    strncmp(token, ":JO:", 4) == 0 || /* journal abbrev */
	    strncmp(token, ":J1:", 4) == 0 || /* journal custabbrev1 */
	    strncmp(token, ":J2:", 4) == 0 || /* journal custabbrev2 */
	    strncmp(token, ":CK:", 4) == 0) { /* citation key of reference */

	  strcpy(field_buffer, buffer);
	  field_buffer[4] = '\0'; /* terminate string */

	  token = link_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;
	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
/*  	      printf("key:%s<<value:%s<<\n", field_buffer, token); */
	      quoted_token = malloc(strlen(token)+1);
	      if (!quoted_token) {
		error = 1;
		break;
	      }

	      stripped_token = strip_quote(token);

	      unescape_chars(quoted_token, stripped_token, strlen(stripped_token));
/*  	      printf("key:%s<<value:%s<<\n", field_buffer, quoted_token); */
	      if (insert_liliform(&link_sentinel, field_buffer, quoted_token)) {
		error = 1;
		break;
	      }
	      free(quoted_token);
	    }
	  }
	}
	else if (strncmp(token, ":NID:", 5) == 0 ||
		 strncmp(token, ":NCK:", 5) == 0) {

	  /* there is no need to unescape/quote these */
	  strcpy(field_buffer, buffer);
	  field_buffer[5] = '\0'; /* terminate string */

	  token = link_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;
	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
	      strncpy(note_type, field_buffer, 5);
	      note_type[5] = '\0';
	      strncpy(note_val, token, 255);
	      note_val[255] = '\0';
/* 	      printf("key:%s<<value:%s<<\n", note_type, note_val); */
	    }
	  }
	}
	else {
	  /* ignore unknown specifier */
	  ptr_addresult->skipped++;
	}
      } /* if (sqltoken.type != 4) / else */
    } /* if (token != NULL) */
  } /* while (newtoken != NULL) */

  if (error || !*note_type || !*note_val) {
    if (error == 1) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
    }
    else {
      send_status(ptr_clrequest->fd, 417, TERM_NO);
      LOG_PRINT(LOG_INFO, get_status_msg(417));
    }
    retval = 1;
    goto Finish;
  }

  if (!strcmp(note_type, ":NCK:")) {
    if ((quoted_token = strdup(note_val)) == NULL
	|| dbi_conn_quote_string(conn, &quoted_token) == 0) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      retval = 1;
      error = 1;
      goto Finish;
    }

    sprintf(sql_command, "SELECT note_id FROM t_note WHERE note_key=%s", quoted_token);
    free(quoted_token);

    LOG_PRINT(LOG_DEBUG, sql_command);

    dbires = dbi_conn_query(conn, sql_command);
    if (!dbires) {
      send_status(ptr_clrequest->fd, 417, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(417));
      retval = 1;
      error = 1;
      goto Finish;
    }

    if (dbi_result_next_row(dbires)) {
      n_id = my_dbi_result_get_idval_idx(dbires, 1);
    }
    else {
      dbi_result_free(dbires);
      send_status(ptr_clrequest->fd, 417, TERM_NO);
      LOG_PRINT(LOG_INFO, get_status_msg(417));
      retval = 1;
      error = 1;
      goto Finish;
    }
    dbi_result_free(dbires);
  }
  else {
    n_id = (unsigned long long)atoll(note_val);
  }

  /* loop over all targets */
  ptr_curr = &link_sentinel;

  while ((ptr_curr = get_next_liliform(ptr_curr)) != NULL) {

    if (!strcmp(ptr_curr->name, ":ID:")) {
      sprintf(my_type, "REFID");
    }
    else if (!strcmp(ptr_curr->name, ":CK:")) {
      sprintf(my_type, "REFERENCE");
    }
    else if (!strcmp(ptr_curr->name, ":KW:")) {
      sprintf(my_type, "KEYWORD");
    }
    else if (!strcmp(ptr_curr->name, ":AU:")) {
      sprintf(my_type, "AUTHOR");
    }
    else if (!strcmp(ptr_curr->name, ":JF:")) {
      sprintf(my_type, "JOURNALFULL");
    }
    else if (!strcmp(ptr_curr->name, ":JO:")) {
      sprintf(my_type, "JOURNALABBREV");
    }
    else if (!strcmp(ptr_curr->name, ":J1:")) {
      sprintf(my_type, "JOURNALCUSTABBREV1");
    }
    else if (!strcmp(ptr_curr->name, ":J2:")) {
      sprintf(my_type, "JOURNALCUSTABBREV2");
    }

    /* add links */
    if (!n_remove) {
      /* function will quote ptr_curr->value */
      result = insert_link(my_type, ptr_curr->value, conn, n_id);
/*       printf("my_type: %s<< ptr_curr->value: %s<<\n", my_type, ptr_curr->value); */

      if (!result) { /* ok */
	sprintf(assemble_buffer, "421:%s -> %s:%s\n", note_val, my_type, ptr_curr->value);
	ptr_addresult->success++;
      }
      else if (result == 7) { /* link already exists */
	sprintf(assemble_buffer, "418:%s -> %s:%s\n", note_val, my_type, ptr_curr->value);
	ptr_addresult->skipped++;
      }
      else { /* other errors */
	sprintf(assemble_buffer, "414:%s -> %s:%s\n", note_val, my_type, ptr_curr->value);
	ptr_addresult->failure++;
      }

/*       free(ptr_curr->value); */

      if ((new_buffer = mstrcat(return_msg, assemble_buffer, &return_msg_len, 0)) == NULL) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	error = 1;
	retval = 1;
	goto Finish;
      }
      else {
	return_msg = new_buffer;
      }
    }
    else { /* if n_remove */

      /* remove links */
      if (!strcmp(ptr_curr->name, ":ID:")) {
	/* we already have the xref ID */
	n_target_id = (unsigned long long)atoll(ptr_curr->value);
      }
      else {
	if ((quoted_token = strdup(ptr_curr->value)) == NULL
	    || dbi_conn_quote_string(conn, &quoted_token) == 0) {
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  error = 1;
	  ptr_addresult->failure++;
	  retval = 1;
	  goto Finish;
	}

	if (!strcmp(ptr_curr->name, ":CK:")) {
	  sprintf(sql_command, "SELECT refdb_id FROM t_refdb WHERE refdb_citekey=%s", quoted_token);
	  mode = 0;
	}
	else if (!strcmp(ptr_curr->name, ":KW:")) {
	  sprintf(sql_command, "SELECT keyword_id FROM t_keyword WHERE keyword_name=%s", quoted_token);
	  mode = 2;
	}
	else if (!strcmp(ptr_curr->name, ":AU:")) {
	  sprintf(sql_command, "SELECT author_id FROM t_author WHERE author_name=%s", quoted_token);
	  mode = 1;
	}
	else if (!strcmp(ptr_curr->name, ":JF:")) {
	  sprintf(sql_command, "SELECT periodical_id FROM t_periodical WHERE periodical_name=%s", quoted_token);
	  mode = 3;
	}
	else if (!strcmp(ptr_curr->name, ":JO:")) {
	  sprintf(sql_command, "SELECT periodical_id FROM t_periodical WHERE periodical_abbrev=%s", quoted_token);
	  mode = 3;
	}
	else if (!strcmp(ptr_curr->name, ":J1:")) {
	  sprintf(sql_command, "SELECT periodical_id FROM t_periodical WHERE periodical_custabbrev1=%s", quoted_token);
	  mode = 3;
	}
	else if (!strcmp(ptr_curr->name, ":J2:")) {
	  sprintf(sql_command, "SELECT periodical_id FROM t_periodical WHERE periodical_custabbrev2=%s", quoted_token);
	  mode = 3;
	}

	free(quoted_token);

	LOG_PRINT(LOG_DEBUG, sql_command);

	dbires = dbi_conn_query(conn, sql_command);
	if (!dbires) {
	  send_status(ptr_clrequest->fd, 233, TERM_NO);
	  LOG_PRINT(LOG_INFO, get_status_msg(233));
	  error = 2;
	  ptr_addresult->failure++;
	  retval = 1;
	  goto Finish;
	}

	if (dbi_result_next_row(dbires)) {
	  n_target_id = my_dbi_result_get_idval_idx(dbires, 1);
	}
	else {
	  /* target missing */
	  ptr_addresult->failure++;
	  dbi_result_free(dbires);
	  /* todo: check for buffer overflow */
	  sprintf(assemble_buffer, "415:%s -> %s:%s\n", note_val, my_type, ptr_curr->value);
	  if ((new_buffer = mstrcat(return_msg, assemble_buffer, &return_msg_len, 0)) == NULL) {
	    send_status(ptr_clrequest->fd, 801, TERM_NO);
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    error = 1;
	    retval = 1;
	    goto Finish;
	  }
	  else {
	    return_msg = new_buffer;
	  }
	  dbi_result_free(dbires);
	  continue;
	}
	dbi_result_free(dbires);
      } /* end if :ID: */

      result = remove_xnote_entries(n_id, n_target_id, conn, mode);

      /* todo: fix output */

      if (!result) { /* ok */
	sprintf(assemble_buffer, "419:%s -> %s:%s\n", note_val, my_type, ptr_curr->value);
	ptr_addresult->success++;
      }
      else if (result == 4) { /* link not found */
	sprintf(assemble_buffer, "417:%s -> %s:%s ", note_val, my_type, ptr_curr->value);
	ptr_addresult->skipped++;
      }
      else { /* other errors */
	sprintf(assemble_buffer, "414:%s -> %s:%s ", note_val, my_type, ptr_curr->value);
	ptr_addresult->failure++;
      }

      if ((new_buffer = mstrcat(return_msg, assemble_buffer, &return_msg_len, 0)) == NULL) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	error = 1;
	retval = 1;
	goto Finish;
      }
      else {
	return_msg = new_buffer;
      }
    } /* end if add */
  } /* end while */

 Finish:
  if (ptr_addresult->success) {
    /* database was changed, update meta info */
    update_meta(conn, ptr_clrequest);
  }

  /* send back result message */
  if (!error) {
    send_status(ptr_clrequest->fd, 0, TERM_NO);
  }

  tiwrite(ptr_clrequest->fd, return_msg, TERM_YES); 

  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);
  delete_all_liliform(&link_sentinel);
  
  return retval;
  }

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  create_personal_list(): creates an almost empty note for the purposes
                          of a personal reference list

  unsigned long long create_personal_list returns 0 if failed,
                          ID of new list if successful

  dbi_conn conn connection structure

  char *listname name of the personal list, escaped and quoted

  unsigned long long n_user_id ID of the current user

  const char *username name of the current user

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
unsigned long long create_personal_list(dbi_conn conn, char *listname, unsigned long long n_user_id, const char *username) {
  unsigned long long n_list_id;
  char sql_command[256];
  char date_buffer[12];
  const char *citekey;
  time_t the_time;
  dbi_result dbires;

  citekey = get_unique_citekey(conn, listname, 0, 1 /* note */, 0 /* regular tables */);
  
  if (!citekey) {
    return 0;
  }
  
  time(&the_time);
  strftime(date_buffer, 12, "%Y-%m-%d", gmtime(&the_time));

  sprintf(sql_command, "INSERT INTO t_note (note_key, note_title, note_user_id, note_date, note_share) VALUES ('%s', '%s', "ULLSPEC", '%s', 0)", citekey, citekey, (unsigned long long)n_user_id, date_buffer);

  free((char*)citekey);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 0;
  }

  dbi_result_free(dbires);

  /* retrieve ID of new note */
  if (!strcmp(my_dbi_conn_get_cap(conn, "named_seq"), "f")) {
    n_list_id = dbi_conn_sequence_last(conn, NULL);
  }
  else {
    n_list_id = dbi_conn_sequence_last(conn, "t_note_note_id_seq");
  }

  return n_list_id;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  remove_personal_list(): removes a personal reference list

  int remove_personal_list returns > 0 if failed, 0 if successful

  dbi_conn conn connection structure

  char *quoted_listname name of the personal list, escaped and quoted

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed deleterefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int remove_personal_list(dbi_conn conn, char *quoted_listname, struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  unsigned long long n_list_id;
  char sql_command[256];
  dbi_result dbires;

  sprintf(sql_command, "SELECT note_id FROM t_note WHERE note_key=%s", quoted_listname);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 1;
  }

  if (!dbi_result_next_row(dbires)) {
    return 1;
  }

  n_list_id = my_dbi_result_get_idval(dbires, "note_id");

  delete_note_by_id(n_list_id, conn, ptr_clrequest, ptr_addresult);
  dbi_result_free(dbires);

  return 0;
}
