/***************************************
  $Revision: 1.49 $


  Sql module (sq).  This is a mysql implementation of an sql module.

  Status: NOT REVUED, NOT TESTED

  Note: this code has been heavily coupled to MySQL, and may need to be changed
  (to improve performance) if a new RDBMS is used.

  ******************/ /******************
  Filename            : query_instructions.c
  Author              : ottrey@ripe.net
  OSs Tested          : Solaris
  Problems            : Moderately linked to MySQL.  Not sure which inverse
                        attributes each option has.  Would like to modify this
                        after re-designing the objects module.
  Comments            : Not sure about the different keytypes.
  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/
#include <stdio.h>
#include <string.h>
#include <glib.h>

#include "which_keytypes.h"
#include "query_instructions.h"
#include "mysql_driver.h"
#include "rp.h"
#include "stubs.h"
#include "constants.h"
#include "memwrap.h"
#include "wh_queries.h"



/*+ String sizes +*/
#define STR_S   63
#define STR_M   255
#define STR_L   1023
#define STR_XL  4095
#define STR_XXL 16383

/* XXX this must be removed from here!!! a .h file must be 
   generated from xml */

#include "defs.h"

/* body of the query thread.

   takes a ptr to structure with all arguments.
   returns an int (result of sq_execute_query) cast to (void*) 

   by marek
*/
static
void *qi_kill_body(void *arg)
{
  SQ_connection_t *sql_connection = arg;
  ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
	      "rtc: killing SQL connection %d", (sql_connection)->thread_id);
  /* abort the running query */
  SQ_abort_query(sql_connection);

  return NULL;
}

/* 
   wrapper around sq_execute_query: starts a query 
   in a separate thread and starts the socket watcher to cancel the query 
   if the socket is closed.

   the execution of the query or watchdog is not guaranteed at all!

   if the rtc was set before, there will be even no attempt to start
   a query or watchdog.

   by marek
*/
int sql_execute_watched(sk_conn_st *condat, SQ_connection_t **sql_connection, 
			const char *query, SQ_result_set_t **result_ptr)
{
  int retval = 0; /* return value of sq_execute_query */
  SQ_connection_t *tempcon;

  /* assert that, if defined, result_ptr is initialised to NULL 
     prior to calling this function */
  if( result_ptr != NULL ) {
    dieif( *result_ptr != NULL );
  }

  /* don't even try to perform the query/fire up watchdog
     if rtc is already set. Do this only if not set yet. */
  if( condat->rtc == 0 ) {
    
    /* make clean */
    SK_watchclear(condat);
    
    /* set watchdog to execute the abort function */
    SK_watchexec(condat, qi_kill_body, *sql_connection);
    
    /* start the watchdog */
    SK_watchstart(condat);
    
    /* start query. An error may be returned if the query is aborted */
    retval = SQ_execute_query(*sql_connection, query, result_ptr);
    
    /* but short queries will complete before the watchdog kills the
       connection */
    
    SK_watchstop(condat);
    

    /* if the watchdog triggered, then it is guaranteed that
       the kill_body function was invoked and therefore the sql-connection
       is now unusable... 
       Close and reopen it for cleanup, use temporary connection
       to keep the login details */
    if( condat->rtc != 0 ) {
      /* can't rely on the error code from mysql!
       */ 
    
      /* one thing: this code must be entered ONLY if the kill_body
	 thing was invoked by the watchdog. 
      */
    
      /* if result is defined, free it here before destroying the 
	 associated connection */
      if( retval == 0 && result_ptr && *result_ptr ) {
	SQ_free_result( *result_ptr );
	*result_ptr = NULL;
      }
    
      tempcon = SQ_duplicate_connection(*sql_connection);
    
      ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
		"rtc: closing SQL thread %d", (*sql_connection)->thread_id);
      SQ_close_connection(*sql_connection);
    
      *sql_connection = tempcon;
      ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
		"rtc: reopened as thread %d", (*sql_connection)->thread_id);
    
      /* make it look as if there was no error and 
	 the result is empty */
      retval = 0;
    } /* if watchdog set rtc */
  
  } /* if rtc not set before */

  return retval; 
}

/* create_name_query() */
/*++++++++++++++++++++++++++++++++++++++
  Create an sql query for the names table. 

  char *query_str

  const char *sql_query

  const char *keys
   
  More:
  +html+ <PRE>
  Authors:
  ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void create_name_query(char *query_str, const char *sql_query, const char *keys) {
  int i;
  /* Allocate stuff */
  GString *from_clause = g_string_sized_new(STR_L);
  GString *where_clause = g_string_sized_new(STR_L);
  gchar **words = g_strsplit(keys, " ", 0);

  /* double quotes " are used in queries to allow querying for 
     names like O'Hara */

  g_string_sprintfa(from_clause, "names N%.2d", 0);
  g_string_sprintfa(where_clause, "N%.2d.name=\"%s\"", 0, words[0]);

  for (i=1; words[i] != NULL; i++) {
    g_string_sprintfa(from_clause, ", names N%.2d", i);
    g_string_sprintfa(where_clause, " AND N%.2d.name=\"%s\" AND N00.object_id = N%.2d.object_id", i, words[i], i);
  }

  sprintf(query_str, sql_query, from_clause->str, where_clause->str);

  /* Free up stuff */
  g_strfreev(words);
  g_string_free(where_clause,/* CONSTCOND */ TRUE);
  g_string_free(from_clause, /* CONSTCOND */ TRUE);

} /* create_name_query() */

/*+ create_asblock_query: 

  given a string like: AS1
                       AS1 - AS10
		       AS1-AS10
  construct a range query for the as_block table
*/
static int create_asblock_query(char *query_str, 
				const char *sql_query, 
				const char *keys) {
  char *keycopy = wr_string(keys);
  char *token, *cursor = keycopy;
  int  asnums[2] = {0,0};
  int index = 0; /* index into the asnums array */


  while( (token = strsep( &cursor, "-" )) != NULL && index < 2) {  
    /* discard the letters (or leading whitespace), take the number */
    if( sscanf(token, "%*[ AS]%d", &asnums[index++]) < 1 ) {
      return -1; /* error */
    }
  }
  /* if only beginning was supplied, copy it as end */
  if( index == 1 ) {
    asnums[1] = asnums[0];
  }
  
  /* now construct the query */
  sprintf(query_str, sql_query, asnums[0], asnums[1]);

  wr_free(keycopy);
  return 0;
}

static void add_filter(char *query_str, const Query_command *qc) {
  int i;
  int qlen;
  char filter_atom[STR_M];

/*
  if (MA_bitcount(qc->object_type_bitmap) > 0) { 
    g_string_sprintfa(query_str, " AND (");
    for (i=0; i < C_END; i++) {
      if (MA_isset(qc->object_type_bitmap, i)) {
        g_string_sprintfa(query_str, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
      }
    }
    g_string_truncate(query_str, query_str->len-3);
    g_string_append_c(query_str, ')');
  }
*/
  if (MA_bitcount(qc->object_type_bitmap) > 0) { 
    strcat(query_str, " AND (");
    for (i=0; i < C_END; i++) {
      if (MA_isset(qc->object_type_bitmap, i)) {
        strcpy(filter_atom, "");
        sprintf(filter_atom, "i.object_type = %d OR ", i);
	                /* XXX class codes should be used instead:
			   DF_get_class_dbase_code(i)) 
			   but currently the tables contain values of enums
			   (C_IN, etc) and not codes
			*/
        strcat(query_str, filter_atom);
      }
    }
    qlen = strlen(query_str);
    query_str[qlen-3] = ')';
    query_str[qlen-2] = '\0';
    query_str[qlen-1] = '\0';
  }
  
} /* add_filter() */

/* create_query() */
/*++++++++++++++++++++++++++++++++++++++
  Create an sql query from the query_command and the matching keytype and the
  selected inverse attributes.
  Note this clears the first inv_attribute it sees, so is called sequentially
  until there are no inv_attributes left.

  WK_Type keytype The matching keytype.

  const Query_command *qc The query command.

  mask_t *inv_attrs_bitmap The selected inverse attributes.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static char *create_query(const Query_t q, const Query_command *qc) {
  char *result=NULL;
  char result_buff[STR_XL];
  Q_Type_t querytype;
  int addquery = 0; /* controls if the query should be added to the list */

  if (MA_bitcount(qc->inv_attrs_bitmap) > 0) {
    querytype = Q_INVERSE;
  }
  else {
    querytype = Q_LOOKUP;
  }

  if ( (q.query != NULL) 
    && (q.querytype == querytype) ) {
    
    addquery = 1; /* if it got here, it should be added, unless.(see asblock)*/
    
    if (q.keytype == WK_NAME) { 
      /* Name queries require special treatment. */
       create_name_query(result_buff, q.query, qc->keys);
    }
    else if( q.keytype == WK_IPADDRESS ) {  /* ifaddr sql lookups */
	ip_range_t myrang;
	unsigned   begin, end;
	ip_keytype_t key_type;

	if (NOERR(IP_smart_range(qc->keys, &myrang, IP_EXPN, &key_type))) {
	    if(IP_rang_b2_space(&myrang) == IP_V4 ) {
		IP_rang_b2v4(&myrang, &begin, &end);
		sprintf(result_buff, q.query, begin, end);
	    }
	    else {
		die;
	    }
	}
    }
    else if( q.keytype == WK_ASRANGE ) {   /* as_block range composition */
      if( create_asblock_query(result_buff, q.query, qc->keys) != 0 ) {
	addquery = 0; /* ... unless it's not correct */
      }
    }
    else {
      sprintf(result_buff, q.query, qc->keys);
    }

    if (q.class == -1 && addquery == 1 ) {
      /* It is class type ANY so add the object filtering */
      add_filter(result_buff, qc);
    }
  }
  
  if( addquery == 1 ) {
    dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);  
    strcpy(result, result_buff);
    return result;
  } 
  else {
    return NULL;
  }
} /* create_query() */

/* fast_output() */
/*++++++++++++++++++++++++++++++++++++++
  This is for the '-F' flag.
  It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.

  Fast isn't fast anymore - it's just there for compatibility reasons.
  This could be speed up if there were breaks out of the loops, once it matched something.
  (Wanna add a goto Marek?  :-) ).

  const char *string The string to be "fast outputed".
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

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

char *fast_output(const char *str) 
{
int i,j;
char *result;
char result_bit[STR_L];
char result_buff[STR_XL];
gchar **lines = g_strsplit(str, "\n", 0);
char * const *attribute_names;
gboolean filtering_an_attribute = FALSE;
char *value;

attribute_names = DF_get_attribute_names();

strcpy(result_buff, "");
 for (j=0; lines[j] != NULL; j++) {
   for(i=0; attribute_names[i] != NULL; i++) {
     if (strncmp(attribute_names[i], lines[j], strlen(attribute_names[i])) == 0) {
       strcpy(result_bit, "");
       /* This is the juicy bit that converts the likes of; "source: RIPE" to "*so: RIPE" */
       value = strchr(lines[j], ':');
       value++;
       /* Now get rid of whitespace. */
       while (*value == ' ' || *value == '\t') {
	 value++;
       }
       sprintf(result_bit, "*%s: %s\n", DF_get_attribute_code(i), value);
       strcat(result_buff, result_bit);
     }
     /* CONSTCOND */
     else if (filtering_an_attribute == TRUE) {
       switch (lines[j][0]) {
       case ' ':
       case '\t':
       case '+':
	 strcpy(result_bit, "");
	 sprintf(result_bit, "%s\n", lines[j]);
	 strcat(result_buff, result_bit);
	 break;
	 
       default:
	 filtering_an_attribute = FALSE;
       }
     }
   }
 }
 

 dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);

 strcpy(result, result_buff);
 
 return result;
} /* fast_output() */

/* filter() */
/*++++++++++++++++++++++++++++++++++++++
  Basically it's for the '-K' flag for non-set (and non-radix) objects.
  It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.

  This could be speed up if there were breaks out of the loops, once it matched something.
  (Wanna add a goto Marek?  :-) ).

  const char *string The string to be filtered.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *filter(const char *str) {
  int i,j, passed=0;
  char *result;
  char result_bit[STR_L];
  char result_buff[STR_XL];
  gchar **lines = g_strsplit(str, "\n", 0);
  char * const *filter_names;
  gboolean filtering_an_attribute = FALSE;
  
  filter_names = DF_get_filter_names();

  strcpy(result_buff, "");
  for (i=0; filter_names[i] != NULL; i++) {
    for (j=0; lines[j] != NULL; j++) {
      if (strncmp(filter_names[i], lines[j], strlen(filter_names[i])) == 0) {
        strcpy(result_bit, "");
        sprintf(result_bit, "%s\n", lines[j]);
        strcat(result_buff, result_bit);
	passed++;
	
	/* CONSTCOND */
        filtering_an_attribute = TRUE;
      }
      /* CONSTCOND */
      else if (filtering_an_attribute == TRUE) {
        switch (lines[j][0]) {
          case ' ':
          case '\t':
          case '+':
            strcpy(result_bit, "");
            sprintf(result_bit, "%s\n", lines[j]);
            strcat(result_buff, result_bit);
          break;

          default:
            filtering_an_attribute = FALSE;
        }
      }
    }
  }

  if(passed) {
    strcat(result_buff, "\n");
  }

  dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);
  strcpy(result, result_buff);

  return result;
} /* filter() */

/* write_results() */
/*++++++++++++++++++++++++++++++++++++++
  Write the results to the client socket.

  SQ_result_set_t *result The result set returned from the sql query.
  unsigned filtered       if the objects should go through a filter (-K)
  sk_conn_st *condat      Connection data for the client    

  XXX NB. this is very dependendant on what rows are returned in the result!!!
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static int write_results(SQ_result_set_t *result, 
			 unsigned filtered,
			 unsigned fast,
			 sk_conn_st *condat,
			 acc_st    *acc_credit,
			 acl_st    *acl
			 ) {
  SQ_row_t *row;
  char *str;
  char *filtrate;
  char *fasted;
  int retrieved_objects=0;
  char *objt;
  int type;

  /* Get all the results - one at a time */
  if (result != NULL) {
    /* here we are making use of the mysql_store_result capability
       of interrupting the cycle of reading rows. mysql_use_result
       would not allow that, would have to be read until end */
    
    while ( condat->rtc == 0 
	    && AC_credit_isdenied( acc_credit ) == 0
	    && (row = SQ_row_next(result)) != NULL ) {
      
      if (  (str = SQ_get_column_string(result, row, 0)) == NULL
	    || (objt = SQ_get_column_string(result, row, 3)) == NULL )  { 
	/* handle it somehow ? */
	die; 
      }
      else  { 
	/* get + add object type */
	type = atoi(objt);
	
	/* ASP_QI_LAST_DET */
	ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
		  "Retrieved serial id = %d , type = %s", atoi(str), objt);
	
	wr_free(str);
	wr_free(objt);
      }
      
      /* decrement credit for accounting purposes */
      AC_count_object( acc_credit, acl, 
		       type == C_PN || type == C_RO ); /* is private? */

      /* break the loop if the credit has just been exceeded and 
	 further results denied */
      if( AC_credit_isdenied( acc_credit ) ) {
	continue; 
      }
      
      if ((str = SQ_get_column_string(result, row, 2)) == NULL) { die; } 
      else {
	
        /* The fast output stage */
        if (fast == 1) {
          fasted = fast_output(str);
          wr_free(str);
          str = fasted;
        }
	
        /* The filtering stage */
        if (filtered == 0) {
          SK_cd_puts(condat, str);
	  SK_cd_puts(condat, "\n");
        }
        else { 
	  
	  /* XXX accounting should be done AFTER that, and not for objects
	     filtered out */

          filtrate = filter(str);
          SK_cd_puts(condat, filtrate);
          wr_free(filtrate);
        }
        retrieved_objects++;
      }
      wr_free(str);
    }
  }
  
  return retrieved_objects;
} /* write_results() */

/* write_objects() */
/*++++++++++++++++++++++++++++++++++++++
  This is linked into MySQL by the fact that MySQL doesn't have sub selects
  (yet).  The queries are done in two stages.  Make some temporary tables and
  insert into them.  Then use them in the next select.

  SQ_connection_t *sql_connection The connection to the database.

  char *id_table The id of the temporary table (This is a result of the hacky
                  way we've tried to get MySQL to do sub-selects.)

  sk_conn_st *condat  Connection data for the client

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  ++++++++++++++++++++++++++++++++++++++*/
static void write_objects(SQ_connection_t **sql_connection, 
			  char *id_table, 
			  unsigned int filtered, 
			  unsigned int fast, 
			  sk_conn_st *condat,
			  acc_st    *acc_credit,
			  acl_st    *acl
			  ) 
{
  /* XXX This should really return a linked list of the objects */

  SQ_result_set_t *result = NULL;
  int retrieved_objects=0;
  char sql_command[STR_XL];  
#if 0
  SQ_result_set_t *order_res;
  SQ_row_t *order_row;

  SQ_execute_query( *sql_connection, "SELECT object_type FROM object_order ORDER BY order_code", &order_res );
  while( (order_row = SQ_row_next(order_res)) != NULL ) {
    char *object_type = SQ_get_column_string(order_res, order_row, 0); 
    sprintf(sql_command, Q_OBJECTS, id_table, object_type);
    
    exec/write
  }
  SQ_free_result(order_res); 
#endif

  sprintf(sql_command, Q_OBJECTS, id_table);

  dieif(sql_execute_watched(condat, sql_connection, sql_command, &result) == -1 );
  
  /* Problem: if the query was aborted, the result structure does not
     refer to any existing connection anymore. So we check rtc here.
  */
  
  if( condat->rtc == 0) {
    retrieved_objects = write_results(result, filtered, fast, condat, 
				      acc_credit, acl);
    SQ_free_result(result); 
  }
} /* write_objects() */

/* insert_radix_serials() */
/*++++++++++++++++++++++++++++++++++++++
  Insert the radix serial numbers into a temporary table in the database.

  mask_t bitmap The bitmap of attribute to be converted.
   
  SQ_connection_t *sql_connection The connection to the database.

  char *id_table The id of the temporary table (This is a result of the hacky
                  way we've tried to get MySQL to do sub-selects.)
  
  GList *datlist The list of data from the radix tree.

  XXX Hmmmmm this isn't really a good place to free things... infact it's quite nasty.  :-(
  
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
             <LI><A HREF="http://www.gtk.org/rdp/glib/glib-doubly-linked-lists.html">Glist</A>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void insert_radix_serials(sk_conn_st *condat,
				 SQ_connection_t *sql_connection, 
				 char *id_table, GList *datlist) {
  GList    *qitem;
  char sql_command[STR_XL];
  int serial;

  for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
    rx_datcpy_t *datcpy = qitem->data;

    serial = datcpy->leafcpy.data_key;

    sprintf(sql_command, "INSERT INTO %s values (%d)", id_table, serial);
    dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);

    wr_free(datcpy->leafcpy.data_ptr);

    if(condat->rtc != 0) {
      break;
    }
  }

  wr_clear_list( &datlist );

} /* insert_radix_serials() */


/* write_radix_immediate() */
/*++++++++++++++++++++++++++++++++++++++
  Display the immediate data carried with the objects returned by the
  radix tree.

  GList *datlist      The linked list of dataleaf copies
  sk_conn_st *condat  Connection data for the client
  acc_st  *acc_credit Accounting struct

More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>
  

  Also free the list of answers.
*/
static void write_radix_immediate(GList *datlist, 
				  sk_conn_st *condat,
				  acc_st    *acc_credit,
				  acl_st    *acl) 
{
  GList    *qitem;
  
  for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
    rx_datcpy_t *datcpy = qitem->data;

    SK_cd_puts(condat, datcpy->leafcpy.data_ptr );
    SK_cd_puts(condat, "\n");
    
    wr_free(datcpy->leafcpy.data_ptr);
    
    AC_count_object(acc_credit, acl, 0 /* public object (private=0) */ );

    if(condat->rtc != 0) {
      break;
    }
  }
  
  wr_clear_list( &datlist );
} /* write_radix_immediate() */


/* map_qc2rx() */
/*++++++++++++++++++++++++++++++++++++++
  The mapping between a query_command and a radix query.

  Query_instruction *qi The Query Instruction to be created from the mapping
                        of the query command.

  const Query_command *qc The query command to be mapped.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static int map_qc2rx(Query_instruction *qi, const Query_command *qc) {
  int result=1;

  qi->rx_keys = qc->keys;

  if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_EXLESS;
      qi->rx_par_a = 0;
  }
  else if ( (qc->L == 1) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_LESS;
    qi->rx_par_a = RX_ALL_DEPTHS;
  }
  else if ( (qc->L == 0) && (qc->M == 1) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_MORE;
      qi->rx_par_a = RX_ALL_DEPTHS;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 1) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_LESS;
    qi->rx_par_a = 1;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 1) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_MORE;
    qi->rx_par_a = 1;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 1) ) {
    qi->rx_srch_mode = RX_SRCH_EXACT;
    qi->rx_par_a = 0;
  }
  else {
      /* user error  ( XXX : this should have been checked before) */
      
      ER_dbg_va(FAC_QI, ASP_QI_SKIP, 
		"ERROR in qc2rx mapping: bad combination of flags");
      result = 0;
  }
  
  return result;
  
} /* map_qc2rx() */


/* run_referral() */
/*
   invoked when no such domain found. Goes through the domain table
   and searches for shorter domains, then if it finds one with referral 
   it performs it, otherwise it just returns nothing.

   to perform referral, it actually composes the referral query 
   for a given host/port/type and calls the whois query function.

   Well, it returns nothing anyway (void). It just prints to the socket.

*/
void run_referral(char *sourcename,
		  SQ_connection_t *sql_connection, 
		  Query_instructions *qis,   
		  Query_environ *qe, 
		  int qi_index,
		  acc_st *acc_credit) {
  char *dot = qis->qc->keys;
  char querystr[STR_L];
  SQ_row_t *row;
  SQ_result_set_t *result;
  char sql_command[STR_XL];
  int stop_loop=0;
  char *ref_host;
  char *ref_type;
  char *ref_port;
  int  ref_port_int;

  strcpy(querystr,"");

  while( !stop_loop && (dot=index(dot,'.')) != NULL ) {
    dot++;

    ER_dbg_va(FAC_QI, ASP_QI_REF_DET, "run_referral: checking %s", dot);

    sprintf(sql_command, "SELECT domain.object_id, domain, type, port, host FROM domain, refer WHERE domain.object_id = refer.object_id AND domain = '%s'", dot);
    dieif( SQ_execute_query(sql_connection, sql_command, &result) == -1);

    switch( SQ_num_rows(result) ) {
      case 0: /* no such domain -> no action, will try next chunk */
      break;

      case 1: /* check for referral host and perform query if present
               in any case end the loop */
      stop_loop=1;
      assert( (row = SQ_row_next(result)) != NULL);
      
      ref_host = SQ_get_column_string(result, row, 4);

      ER_dbg_va(FAC_QI, ASP_QI_REF_GEN, "referral host is %s", ref_host); 

      if( ref_host != NULL && strlen(ref_host) > 0 ) {
        ref_type = SQ_get_column_string(result, row, 2);
        ref_port = SQ_get_column_string(result, row, 3);
        
        /* get the integer value, it should be correct */
        if( sscanf( ref_port, "%d",&ref_port_int) < 1 ) {
          die;
        }
         
        /* compose the query: */

        /* put -r if the reftype is RIPE and -r or -i were used */
        if( strcmp(ref_type,"RIPE") == 0 
            && (   Query[qis->instruction[qi_index]->queryindex]
                   .querytype == Q_INVERSE       
                   || qis->recursive > 0  )   ) {
          strcat(querystr," -r ");
        }

        /* prepend with -Vversion,IP for type CLIENTADDRESS */
        if( strcmp(ref_type,"CLIENTADDRESS") == 0 ) {
          char optv[STR_M];

          snprintf(optv,STR_M," -V%s,%s ","RIP0.88", qe->condat.ip);
          strcat(querystr,optv);
        }

        /* now set the search term - set to the stripped down version 
           for inverse query, full-length otherwise */
        if( Query[qis->instruction[qi_index]->queryindex].querytype == Q_INVERSE ) {
          strcat(querystr,dot);
        }
        else {
          strcat(querystr,qis->qc->keys);
        }
        
	SK_cd_printf(&(qe->condat), 
"%% The object shown below is NOT in the %s database.\n"
"%% It has been obtained by querying a remote server:\n"
"%% (%s) at port %d.\n"
"%% To see the object stored in the %s database\n"
"%% use the -R flag in your query\n"
"%%\n"
"%%%%%% Start of referred query result\n",
		     sourcename, 
		     ref_host, ref_port_int,
		     sourcename );
	SK_cd_puts(&(qe->condat), "\n");

        /* WH_sock(sock, host, port, query, maxlines, timeout)) */
        switch( WH_sock(qe->condat.sock, ref_host, ref_port_int, querystr,  25, 5) ) {
	case WH_TIMEOUT:
	    SK_cd_puts(&(qe->condat),"referral timeout\n");
	    break;
	    
	case WH_MAXLINES:
	    SK_cd_puts(&(qe->condat),"referral maxlines exceeded\n");
	    break;
	    
	case WH_BADHOST:
	    SK_cd_puts(&(qe->condat),"referral host not found\n");
	    break;

	case WH_CONNECT:
	    SK_cd_puts(&(qe->condat),"referral host not responding\n");
	    break;

	case WH_BIND:
	case WH_SOCKET:
	    /* XXX internal server problem... what to do - wait ? */
	default:
          ;
        } /*switch WH_sock */

	SK_cd_puts(&(qe->condat), "\n%%% End of referred query result\n");
	acc_credit->referrals -= 1;
      }
      break;

      default: /* more than one domain in this file: something broken */
      die;
    }
    SQ_free_result(result);
  }
} /*run_referral*/

static
void 
add_ref_name(SQ_connection_t *sql_connection, 
	     char *rectable,
	     char *allnames
	     )
{
  /* construct the query, allow zero-length list */
  if( strlen(allnames) > 0 ) {
    char final_query[STR_XL];
    char select_query[STR_XL];

    create_name_query(select_query, "SELECT N00.object_id FROM %s WHERE %s "
		      "AND N00.object_type != 100 AND N00.thread_id = 0", 
		      allnames);
    
    sprintf(final_query, "INSERT INTO %s %s",
	    rectable,
	    select_query);
    
    dieif(SQ_execute_query(sql_connection, final_query, NULL) == -1 );

    allnames[0]=0;
  }
}

static
void
qi_collect_ids(ca_dbSource_t *dbhdl,
	       char *sourcename,
	       SQ_connection_t **sql_connection,
	       Query_instructions *qis,
	       Query_environ *qe,	
	       char *id_table,
	       GList **datlist,
	       acc_st *acc_credit,
	       acl_st *acl
	       )
{
  Query_instruction **ins=NULL;
  int i;
  int   count, errors=0;
  char sql_command[STR_XL];
  er_ret_t err;
  char sub_table[32];
  int limit ;
             /* a limit on the max number of objects to be returned
		from a single search. For some queries the object types
		are not known at this stage, so the limit must be
		the higher number of the two: private / public,
		or unlimited if any of them is 'unlimited'.
	     */
  char limit_str[32];

  if( (limit = AC_get_higher_limit(acc_credit,acl)) == -1) {
    strcpy(limit_str,"");
  } else {
    sprintf(limit_str," LIMIT %d", limit+1); /* make sure we collect more
						so that the client hits
						the limit */
  }

  sprintf(sub_table, "%s_S ", id_table);
  
  /* see if there was a leftover table from a crashed session 
   * (assume the ID cannot be currently in use)
   */
  sprintf(sql_command, "DROP TABLE IF EXISTS %s", sub_table);
  dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );

  /* create a table for special subqueries (domain only for now) */
  sprintf(sql_command, "CREATE TABLE %s ( id int ) TYPE=HEAP", sub_table);
  dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
  
  /* Iterate through query instructions */
  ins = qis->instruction;
  for (i=0; ins[i] != NULL && errors == 0; i++) {
    Query_instruction *qi = ins[i];
    
    /* check if the client is still there */
    if( qe->condat.rtc ) {
      break;
    }

    switch ( qi->search_type ) {
    case R_SQL:
      if ( qi->query_str != NULL ) {

	/* handle special cases first */
	if( Query[qi->queryindex].class == C_DN ) {

	  /* XXX if any more cases than just domain appear, we will be
	     cleaning the _S table from the previous query here 
	     
	     "DELETE FROM %s_S"
	  */

	  /* now query into the _S table */
	  sprintf(sql_command, "INSERT INTO %s%s", sub_table, qi->query_str);
	  dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1);
	  
	  /* if any results - copy to the id's table. 
	     Otherwise, run referral */
	  count = SQ_get_affected_rows(*sql_connection);

	  ER_dbg_va(FAC_QI, ASP_QI_COLL_DET, 
		    "DN lookup for %s found %d entries", qis->qc->keys, count);
	  	 
	  if( count ) {
	    sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s", 
		    id_table, sub_table);
	    dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1);
	  }

	  if( count == 0 
	      || Query[qi->queryindex].querytype == Q_INVERSE ) {
	    /* now: if the domain was not found, we run referral.
	       unless prohibited by a flag 
	      
	       But for inverse queries we return the things that were
	       or were not found AND also do the referral unless prohibited.
	    */
	    if (qis->qc->R == 0) {
	      run_referral(sourcename, *sql_connection, qis, qe, i, acc_credit);
	    }
	  }
	  
	} /* if class DN */
	else {
	  /* any other class of query */

	  sprintf(sql_command, "INSERT INTO %s %s %s", 
		  id_table, qi->query_str, limit_str);

	  if(sql_execute_watched( &(qe->condat), sql_connection, 
				  sql_command, NULL) == -1 ) {

	    ER_perror(FAC_QI, QI_SQLERR," query='%s' [%d] %s", 
		      sql_command,
		      SQ_errno(*sql_connection), SQ_error(*sql_connection));
	    errors++;
	  }
	  count = SQ_get_affected_rows(*sql_connection);
	} /* not DN */
      } /* if SQL query not NULL */
      
      ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
		"%d entries added in %s query for %s",
		count, Query[qi->queryindex].descr, qis->qc->keys
		);
      break;
      
    case R_RADIX:

      if( ! qis->qc->S ) /* XXX patch: use new search algorithm by default */ {
	err = RP_new_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, 
			    qi->rx_keys, dbhdl,
			    Query[qi->queryindex].attribute, 
			    datlist, limit);

      }
      else {
	err = RP_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, 
			    qi->rx_keys, dbhdl, 
			    Query[qi->queryindex].attribute, 
			    datlist, limit);
      }

      if( NOERR(err)) {
	if( ER_is_traced(FAC_QI, ASP_QI_COLL_DET ) ) {
	  /* prevent unnecessary g_list_length call */
	  
	  ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
		    "%d entries after %s (mode %d par %d reg %d) query for %s",
		    g_list_length(*datlist),
		    Query[qi->queryindex].descr,
		    qi->rx_srch_mode, qi->rx_par_a, 
		    dbhdl,
		    qi->rx_keys);
	}
      }
      else {
	ER_inf_va(FAC_QI, ASP_QI_COLL_DET,
		  "RP_asc_search returned %x ", err);
      }
      break;
      
    default: die;
    } /* switch */
    
  } /* for <every instruction> */

  /* Now drop the _S table */
  sprintf(sql_command, "DROP TABLE %s", sub_table);
  dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );

}

static
void
qi_fetch_references(SQ_connection_t **sql_connection,
		    Query_environ *qe,
		    char *id_table,
		    acc_st *acc_credit,
		    acl_st *acl
		    )
{
char rec_table[32];
    SQ_result_set_t *result = NULL;
    SQ_row_t *row;
    int thisid = 0;
    int oldid = 0;
    char allnames[STR_M];
    char sql_command[STR_XL];
 
    sprintf(rec_table, "%s_R", id_table);
    
    /* see if there was a leftover table from a crashed session 
     * (assume the ID cannot be currently in use)
     */
    sprintf(sql_command, "DROP TABLE IF EXISTS %s ", rec_table);
    dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );

    /* a temporary table for recursive data must be created, because
       a query using the same table as a source and target is illegal
       ( like: INSERT into ID_123 SELECT * FROM ID_123,admin_c WHERE ... )
    */
    sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", rec_table);
    dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
    
    /* find the contacts */      
    sprintf(sql_command, Q_REC, rec_table, id_table, "author");
    dieif(sql_execute_watched( &(qe->condat), sql_connection, sql_command, NULL) == -1 );
    
    sprintf(sql_command, Q_REC, rec_table, id_table, "admin_c");
    dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
    
    sprintf(sql_command, Q_REC, rec_table, id_table, "tech_c" );
    dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
    
    sprintf(sql_command, Q_REC, rec_table, id_table, "zone_c" );
    dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
    
    
    /* replace references to dummies by references by name */
    sprintf(sql_command, 
	    " SELECT id, name    FROM %s IDS STRAIGHT_JOIN names "
	    " WHERE IDS.id = names.object_id "
	    "      AND names.object_type = 100"
	    " ORDER BY id",
	    rec_table);
    
    dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, 
			      &result) == -1 );
    /* well, it might not be -1, but if the watchdog worked then the
       result is NULL */
    if( result != NULL ) {
      
      allnames[0]=0;
      /* now go through the results and collect names */
      while ( (qe->condat.rtc == 0)
	      && (row = SQ_row_next(result)) != NULL ) {
	char *id   = SQ_get_column_string(result, row, 0);
	char *name = SQ_get_column_string(result, row, 1);
	
	thisid = atoi(id);
	
	/* when the id changes, the name is complete */
	if( thisid != oldid && oldid != 0 ) {
	  add_ref_name( *sql_connection, rec_table, allnames);
	}
	
	strcat(allnames, name);
	strcat(allnames, " ");
	oldid = thisid;
	wr_free(id);
	wr_free(name);
      }
      /* also do the last name */
      add_ref_name( *sql_connection, rec_table, allnames);
      
      SQ_free_result(result); /* we can do it only because the watchdog */
      /* has not started between the check for non-NULL result and here */
    }
    
    /* now copy things back to the main temporary table   */
    sprintf(sql_command, "INSERT INTO %s SELECT * FROM %s", 
	    id_table, rec_table);
    dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
    
    /* Now drop the IDS recursive table */
    sprintf(sql_command, "DROP TABLE %s", rec_table);
    dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
}


/* QI_execute() */
/*++++++++++++++++++++++++++++++++++++++
  Execute the query instructions.  This is called for each source.

  void *database_voidptr Pointer to the database name
  
  void *qis_voidptr Pointer to the query_instructions.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
er_ret_t QI_execute(ca_dbSource_t *dbhdl,
		    Query_instructions *qis, 
		    Query_environ *qe,	
		    acc_st *acc_credit,
		    acl_st *acl
		    ) 
{
  /* those things must be freed after use! */
  char *dbhost = ca_get_srcdbmachine(dbhdl);
  char *dbname = ca_get_srcdbname(dbhdl);
  char *dbuser = ca_get_srcdbuser(dbhdl);
  char *dbpass = ca_get_srcdbpassword(dbhdl);
  char *srcnam = ca_get_srcname(dbhdl);
  char id_table[STR_S];
  char sql_command[STR_XL];
  GList *datlist=NULL;
  SQ_connection_t *sql_connection=NULL;

  sql_connection = SQ_get_connection( dbhost, ca_get_srcdbport(dbhdl),
				      dbname, dbuser, dbpass );
  if (sql_connection == NULL) {
    ER_perror(FAC_QI, QI_CANTDB," database='%s' [%d] %s", 
	      dbname, SQ_errno(sql_connection), SQ_error(sql_connection));
    return QI_CANTDB;
  }

  sprintf(id_table, "ID_%ld_%d",   mysql_thread_id(sql_connection),
	  pthread_self());

  /* see if there was a leftover table from a crashed session 
   * (assume the ID cannot be currently in use)
   */
  sprintf(sql_command, "DROP TABLE IF EXISTS %s ", id_table);
  dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
  
  /* create a table for id's of all objects found NOT NULL , UNIQUE(id) */
  sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", id_table);
  dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );

  qi_collect_ids(dbhdl, srcnam, &sql_connection, qis, qe, id_table, 
		 &datlist, acc_credit, acl);

  /* post-processing */
  if( qis->filtered == 0 ) {
    /* start the watchdog just to set the rtc flag */
    SK_watchclear(&(qe->condat));
    SK_watchstart(&(qe->condat));

    /* add radix results (only if -K is not active) */
    insert_radix_serials(&(qe->condat), sql_connection, id_table, datlist);

    SK_watchstop(&(qe->condat));
  }

  /* fetch recursive objects (ac,tc,zc,ah) */
  if ( qis->recursive ) {
    qi_fetch_references( &sql_connection, qe, id_table, acc_credit, acl);
  } /* if recursive */
  
  /* display */
  /* -K filtering: 
   * right now only filtering, no expanding sets like write_set_objects() 
   */
  
  /* display the immediate data from the radix tree */
  if( qis->filtered == 1 ) {
    write_radix_immediate(datlist, &(qe->condat), acc_credit, acl );
  }

  /* display objects from the IDs table */
  write_objects( &sql_connection, id_table, qis->filtered,
		qis->fast, &(qe->condat), acc_credit, acl);

  /* Now drop the IDS table */
  sprintf(sql_command, "DROP TABLE %s", id_table);
  dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
  SQ_close_connection(sql_connection);  

  /* free allocated parameters */
  wr_free(dbhost);
  wr_free(dbname);
  wr_free(dbuser);
  wr_free(dbpass);
  wr_free(srcnam);

  return QI_OK;
} /* QI_execute() */


/* instruction_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the instruction.

  Query_instruction *qi query_instruction to be freed.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
static void instruction_free(Query_instruction *qi) {
  if (qi != NULL) {
    if (qi->query_str != NULL) {
      wr_free(qi->query_str);
    }
    wr_free(qi);
  }
} /* instruction_free() */

/* QI_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the query_instructions.

  Query_instructions *qis Query_instructions to be freed.
   
  XXX This isn't working too well at the moment.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void QI_free(Query_instructions *qis) {
  int i;

  for (i=0; qis->instruction[i] != NULL; i++) {
    instruction_free(qis->instruction[i]);
  } 

  if (qis != NULL) {
    wr_free(qis);
  }

} /* QI_free() */

/*++++++++++++++++++++++++++++++++++++++
  Determine if this query should be conducted or not.

  If it was an inverse query - it the attribute appears in the query command's bitmap.
  If it was a lookup query - if the attribute appears in the object type bitmap or
                             disregard if there is no object_type bitmap (Ie object filter).

  mask_t bitmap The bitmap of attribute to be converted.
   
  const Query_command *qc The query_command that the instructions are created
                          from.
  
  const Query_t q The query being investigated.

  ++++++++++++++++++++++++++++++++++++++*/
static int valid_query(const Query_command *qc, const Query_t q) {
  int result=0;

  if (MA_isset(qc->keytypes_bitmap, q.keytype) == 1) {
    if (q.query != NULL) {
      switch (q.querytype) {
        case Q_INVERSE:
          if (MA_isset(qc->inv_attrs_bitmap, q.attribute) ) {
            result = 1;
          }
        break;

        case Q_LOOKUP:
          if (MA_bitcount(qc->object_type_bitmap) == 0) {
            result=1;
          }
          else if (q.class<0 || MA_isset(qc->object_type_bitmap, q.class)) {
            result=1;
          }
        break;

        default:
          fprintf(stderr, "qi:valid_query() -> Bad querytype\n");
      }
    }
  }

  return result;
} /* valid_query() */

/* QI_new() */
/*++++++++++++++++++++++++++++++++++++++
  Create a new set of query_instructions.

  const Query_command *qc The query_command that the instructions are created
                          from.

  const Query_environ *qe The environmental variables that they query is being
                          performed under.
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
Query_instructions *QI_new(const Query_command *qc, const Query_environ *qe) {
  Query_instructions *qis=NULL;
  Query_instruction *qi=NULL;
  int i_no=0;
  int i;
  char *query_str;

  dieif(wr_calloc( (void **) & qis, 1, sizeof(Query_instructions)) != UT_OK);

  qis->filtered = qc->filtered;
  qis->fast = qc->fast;
  qis->recursive = qc->recursive;
  qis->qc = (qc);

  
  for (i=0; Query[i].query != NULL; i++) {

    /* If a valid query. */
    if ( valid_query(qc, Query[i]) == 1) {

      dieif( wr_calloc((void **) &qi, 1, sizeof(Query_instruction)) != UT_OK);

      qi->queryindex = i;

      /* SQL Query */
      if ( Query[i].refer == R_SQL) {
        qi->search_type = R_SQL;
        query_str = create_query(Query[i], qc);

        if (query_str!= NULL) {
          qi->query_str = query_str;
          qis->instruction[i_no++] = qi;
        }
      }
      /* Radix Query */
      else if (Query[i].refer == R_RADIX) {
        qi->search_type = R_RADIX;
	
        if (map_qc2rx(qi, qc) == 1) {
	  int j;
	  int found=0;
	  
          /* check that there is no such query yet, for example if
	     more than one keytype (wk) matched */
	  for (j=0; j<i_no; j++) {
	    Query_instruction *qij = qis->instruction[j];
	    
	    if(    qij->search_type == R_RADIX
		   && Query[qij->queryindex].attribute 
		   == Query[qi ->queryindex].attribute) {
	      
              found=1;
              break;
            }
          }
	  
          if ( found ) {
            /* Discard the Query Instruction */
            wr_free(qi);
          } 
          else {
            /* Add the query_instruction to the array */
            qis->instruction[i_no++] = qi;
          }
        }
      }
      else {
	  /* ERROR: bad search_type */
	  die;
      }
    }
  }
  qis->instruction[i_no++] = NULL;


  {  /* tracing */
      char *descrstr = QI_queries_to_string(qis);

      ER_dbg_va(FAC_QI, ASP_QI_COLL_GEN, "Queries: %s", descrstr );
      wr_free( descrstr );
  }

  return qis;

} /* QI_new() */

/* QI_queries_to_string() 
   
   returns a list of descriptions for queries that will be performed.
*/

char *QI_queries_to_string(Query_instructions *qis)
{
   Query_instruction *qi;
   int i;
   char *resstr = NULL;

   dieif( wr_realloc((void **)&resstr, 2 ) != UT_OK);
   strcpy(resstr, "{");

   for( i = 0; ( qi=qis->instruction[i] ) != NULL;  i++ ) {
       char *descr = Query[qi->queryindex].descr;
       int oldres = strlen( resstr );
       
       dieif( wr_realloc((void **)&resstr, oldres+strlen(descr)+2) != UT_OK);
       strcat(resstr, descr);
       strcat(resstr, ",");
   }
   if( i>0 ) {
       /* cancel the last comma */
       resstr[strlen(resstr)-1] = 0;
   }

   dieif( wr_realloc((void **)&resstr, strlen( resstr ) + 2 ) 
	  != UT_OK);
   strcat(resstr, "}");
   
   return resstr;
}
