/***************************************
  $Revision: 1.29 $


  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 "rxroutines.h"
#include "stubs.h"
#include "constants.h"

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


#include "QI_queries.def"

/* log_inst_print() */
/*++++++++++++++++++++++++++++++++++++++
  Log the instruction.

  char *str instruction to be logged.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
void log_inst_print(char *str) {
  FILE *logf;

  if (CO_get_instr_logging() == 1) {
    if (strcmp(CO_get_instr_logfile(), "stdout") == 0) {
      printf("%s", str);
    }
    else {
      logf = fopen(CO_get_instr_logfile(), "a");
      fprintf(logf, "%s", str);
      fclose(logf);
    }
  }

} /* log_inst_print() */

/* 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_XL);
  GString *where_clause = g_string_sized_new(STR_XL);
  gchar **words = g_strsplit(keys, " ", 0);

  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, TRUE);
  g_string_free(from_clause, TRUE);

} /* create_name_query() */

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 ", DF_get_class_dbase_code(i));
        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 conduct_test = 0;

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

  if ( (q.query != NULL) 
    && (q.querytype == querytype) ) {
    conduct_test=1;
  }

  if (conduct_test == 1) {

    if (q.keytype == WK_NAME) { 
      /* Name queries require special treatment. */
       create_name_query(result_buff, q.query, qc->keys);
    }
    else {
      sprintf(result_buff, q.query, qc->keys);
    }

    if (q.class == -1) {
      /* It is class type ANY so add the object filtering */
      add_filter(result_buff, qc);
    }

    result = (char *)malloc(strlen(result_buff)+1);
    strcpy(result, result_buff);
  }

  return result;
} /* 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 (i=0; attribute_names[i] != NULL; i++) {
    for (j=0; lines[j] != NULL; j++) {
      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);
      }
      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;
        }
      }
    }
  }

  result = (char *)malloc(strlen(result_buff)+1);
  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;
  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);
        filtering_an_attribute = TRUE;
      }
      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;
        }
      }
    }
  }

  result = (char *)malloc(strlen(result_buff)+1);
  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    
  int maxobjects          max # of objects to write

  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,
			 int maxobjects /* -1 == unlimited */
			 ) {
  SQ_row_t *row;
  char *str;
  char *filtrate;
  char *fasted;
  char log_str[STR_L];
  int retrieved_objects=0;

  /* Get all the results - one at a time */
  if (result != NULL) {
    while ( (row = SQ_row_next(result)) != NULL 
	    && (maxobjects == -1 || retrieved_objects < maxobjects) ) {
      str = SQ_get_column_string(result, row, 0);
      if (str != NULL) {
        strcpy(log_str, "");
        sprintf(log_str, "Retrieved serial id = %d\n", atoi(str));
        log_inst_print(log_str);
      }
      free(str);

      str = SQ_get_column_string(result, row, 2);
      if (str != NULL) {

        /* The fast output stage */
        if (fast == 1) {
          fasted = fast_output(str);
          free(str);
          str = fasted;
        }

        /* The filtering stage */
        if (filtered == 0) {
          SK_cd_puts(condat, str);
        }
        else {
          filtrate = filter(str);
          SK_cd_puts(condat, filtrate);
          free(filtrate);
        }
        SK_cd_puts(condat, "\n");
        retrieved_objects++;
      }
      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.)
  
  unsigned int recursive A recursive query.

  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 recursive, 
			  unsigned int filtered, 
			  unsigned int fast, 
			  sk_conn_st *condat,
			  acc_st    *acc_credit
			  ) {
  /* XXX This should really return a linked list of the objects */

  SQ_result_set_t *result;
  int retrieved_objects=0;
  int retrieved_contacts=0;

  char sql_command[STR_XL];
  char log_str[STR_L];
    
  strcpy(sql_command, "");
  /* XXX These may and should change a lot. */
  sprintf(sql_command, Q_OBJECTS, id_table, id_table);
  result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);

  retrieved_objects += write_results(result, filtered, fast, condat, 
				     acc_credit->public_objects);

  if( retrieved_objects < SQ_num_rows(result) ) {
    SK_cd_puts(condat,
     "% You have reached the limit of returned contact information objects.\n"
     "% This connection will be terminated now.\n"
     "% This is a mechanism to prevent abusive use of contact data in the RIPE Database.\n"
     "% You will not be allowed to query for more CONTACT information for a while.\n"
     "% Continued attempts to return excessive amounts of contact\n"
     "% information will result in permanent denial of service.\n"
     "% Please do not try to use CONTACT information information in the\n"
     "% RIPE Database for non-operational purposes.\n"
     "% Refer to http://www.ripe.net/db/dbcopyright.html for more information.\n"
    );
  }
  SQ_free_result(result);

  /* Now for recursive queries */
  if (recursive == 1) {

    /* create a table for recursive data */
    sprintf(sql_command, "CREATE TABLE %s_R ( id int ) TYPE=HEAP", id_table);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

    /* find the contacts */
    sprintf(sql_command, Q_REC, id_table, "admin_c", id_table, id_table);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

    sprintf(sql_command, Q_REC, id_table, "tech_c", id_table, id_table);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
    
    sprintf(sql_command, Q_REC, id_table, "zone_c", id_table, id_table);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

    /* XXX These may and should change a lot. */
    sprintf(sql_command, Q_REC_OBJECTS, id_table, id_table);
    result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);

    retrieved_contacts += write_results(result, filtered, fast, condat, 
					acc_credit->private_objects);
    if( retrieved_contacts < SQ_num_rows(result) ) {
      SK_cd_puts(condat,
       "% The contact data has been truncated due to inadequate privileges\n"
       "% The amount of data you have requested is considered excessive\n"
       "% You will be denied to get any more contact objects for some time\n"
       "% Please do not check if the access has been restored more often\n"
       "% than once every few hours or you will be denied access permanently\n"
      );
    }
    SQ_free_result(result);

    /* Now drop the IDS recursive table */
    sprintf(sql_command, "DROP TABLE %s_R", id_table);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

    acc_credit->private_objects -= retrieved_contacts;
  }

  if (retrieved_objects == 0) {
    /* XXX This is getting called at the wrong time -so don't bother.  :-(  (ottrey 30/12/1999)
    SK_cd_puts(condat, "%%One or more objects were not found\n");
    */
    ;
  }
  else {
    acc_credit->public_objects -= retrieved_objects;
  }
  
  sprintf(log_str, "%d public / %d contact object(s) retrieved\n\n", 
	  retrieved_objects, retrieved_contacts );
  log_inst_print(log_str);

} /* 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(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);
    SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

    wr_free(datcpy->leafcpy.data_ptr);
  }
  
  g_list_foreach(datlist, rx_free_list_element, NULL);
  g_list_free(datlist);

} /* insert_radix_serials() */


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

  GList *datlist
  sk_conn_st *condat  Connection data for the client
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) 
{
  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);
  }
  
  g_list_foreach(datlist, rx_free_list_element, NULL);
  g_list_free(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=0;
  char log_str[STR_XL];

  qi->rx_keys = qc->keys;

  if (MA_bitcount(qc->object_type_bitmap) == 0) {
    /* Ie. there was no object typed specified with the -T flag. */
    result=1;
  }
  else {
    switch(qi->family) {
      case RX_FAM_IN:
        if (MA_isset(qc->object_type_bitmap, C_IN)) {
          result=1;
        }
      break;

      case RX_FAM_RT:
        if (MA_isset(qc->object_type_bitmap, C_RT)) {
          result=1;
        }
      break;
      
      default:
        fprintf(stderr, "ERROR: Bad family type in radix query\n");
    }
  }

  if (result == 1) {
    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 {
      sprintf(log_str, "ERROR in qc2rx mapping: bad combination of flags\n");
      log_inst_print(log_str);
      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(SQ_connection_t *sql_connection, Query_instructions *qis,   Query_environ *qe, int qi_index) {
  char *dot = qis->qc->keys;
  char querystr[STR_L];
  char log_str[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++;

    sprintf(log_str, "run_referral: checking %s\n", dot);
    log_inst_print(log_str);

/* Changed for RIPE4 - ottrey 27/12/1999
    sprintf(sql_command, "SELECT * FROM domain WHERE domain = '%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);
    result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);

    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);
      sprintf(log_str, "referral host is >%s<\n",ref_host);
      log_inst_print(log_str);
      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);
        }
        
        /* 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;

          default:
          ;
        } /*switch WH_sock */
      }
      break;

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

/* QI_execute() */
/*++++++++++++++++++++++++++++++++++++++
  Execute the query instructions.  This is called by a g_list_foreach
  function, so each of the sources in the "database source" list can be passed
  into this function.

  This function has bloated itself.  Can we split it up Marek?  (ottrey 13/12/99)

  void *database_voidptr Pointer to the database.
  
  void *qis_voidptr Pointer to the query_instructions.
   
  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-singly-linked-lists.html#G-SLIST-FOREACH">g_list_foreach</A>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
void QI_execute(void *database_voidptr, 
		Query_instructions *qis, 
		Query_environ *qe,	
		acc_st *acc_credit
		) {
  char *database = (char *)database_voidptr;
  Query_instruction **ins=NULL;
  char id_table[STR_S];
  char sql_command[STR_XL];
  GList *datlist=NULL;
  int i;
  SQ_row_t *row;
  char log_str[STR_L];
  SQ_result_set_t *result;
  SQ_connection_t *sql_connection=NULL;

  sql_connection = SQ_get_connection(CO_get_host(), CO_get_database_port(), database, CO_get_user(), CO_get_password() );

  if (sql_connection == NULL) {
    SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to ");
    SK_cd_puts(&(qe->condat), database);
    SK_cd_puts(&(qe->condat), " database mirror.\n\n");

    /* XXX void prevents us from sending any error code back. It is OK ? */
    return;
  }
  
  /* XXX This is a really bad thing to do.  
     It should'nt _have_ to be called here.
     But unfortunately it does.   -- Sigh. */
  sprintf(id_table, "ID_%d", mysql_thread_id(sql_connection) );

  /* create a table for id's of all objects found */
  sprintf(sql_command, "CREATE TABLE %s ( id int ) TYPE=HEAP", id_table);
  SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);

  /* create a table for individual subqueries (one keytype) */
  sprintf(sql_command, "CREATE TABLE %s_S ( id int ) TYPE=HEAP", id_table);
  SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
    
  /* Iterate through query instructions */
  ins = qis->instruction;
  for (i=0; ins[i] != NULL; i++) {
    Query_instruction *qi = ins[i];

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

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

	  char *countstr;
	  int   count;

	  /* XXX if any more cases than just domain appear, we will be
	     cleaning the _S table from the previous query here */
	  
	  /* now query into the _S table */
	  sprintf(sql_command, "INSERT INTO %s_S %s", id_table, qi->query_str);
	  SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
	  
	  /* if any results - copy to the id's table. 
	     Otherwise, run referral */
	  
	  sprintf(sql_command, "SELECT COUNT(*) FROM %s_S", id_table);
	  result = SQ_execute_query(SQ_STORE,sql_connection, sql_command);
	  row = SQ_row_next(result);
	  countstr = SQ_get_column_string(result, row, 0);
	  sscanf(countstr, "%d", &count);
	  SQ_free_result(result);

	  sprintf(log_str, "DN lookup for %s found %d entries\n",
		  qis->qc->keys, count);
	  log_inst_print(log_str);
	  
	 
	  if( count ) {
	    sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s_S", 
		    id_table, id_table);
	    SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
	  }

	  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(sql_connection, qis, qe, i);
	    }
	  }
	  
	}
	else {
	  sprintf(sql_command, "INSERT INTO %s %s", id_table, qi->query_str);
	  SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
	}
      }
      break;

#define RIPE_REG 17
    case R_RADIX:
      if ( RX_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, qi->rx_keys, RIPE_REG, qi->space, qi->family, &datlist, RX_ANS_ALL) == RX_OK ) {
	sprintf(log_str, "After RX query (mode %d par %d spc %d fam %d reg %d key %s) datlist has %d objects\n",
		qi->rx_srch_mode, qi->rx_par_a, qi->space, qi->family, 
		RIPE_REG, 	qi->rx_keys,    
		g_list_length(datlist) );
	log_inst_print(log_str); 
	    
      }
      else {
	/* Skip query */
	sprintf(log_str, " /* Skip in query */\n");
	log_inst_print(log_str);
      }
      break;

    default: die;
    } /* switch */
  }

  /* post-processing */

  if( qis->filtered == 0 ) {
    /* add radix results to the end */
    insert_radix_serials(sql_connection, id_table, datlist);

    /* display objects */
    write_objects(sql_connection, id_table, qis->recursive, 0 /*nofilter*/, 
		  qis->fast, &(qe->condat), acc_credit);
  }
  else {
    /* XXX TODO display filtered objects, expanding sets */
    /* right now only the ugly filter thing instead */

    /* write_set_objects */

    /* write the remaining (non-set,non-radix) objects through the filter.
       imply no recursion */
    write_objects(sql_connection, id_table, 0, qis->filtered, qis->fast, &(qe->condat), acc_credit);

    /* display the immediate data from the radix tree */
    /* XXX pass+decrease credit here */
    write_radix_immediate(datlist, &(qe->condat));
  }
  /* Now drop the _S table */
  sprintf(sql_command, "DROP TABLE %s_S", id_table);
  SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);

  /* Now drop the IDS table */
  sprintf(sql_command, "DROP TABLE %s", id_table);
  SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
  SQ_close_connection(sql_connection);

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

  Query_instruction *qi query_instruction to be freed.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void instruction_free(Query_instruction *qi) {
  if (qi != NULL) {
    if (qi->query_str != NULL) {
      free(qi->query_str);
    }
    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><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
void QI_free(Query_instructions *qis) {
  /* XXX huh!?H?
  int i;

  for (i=0; qis[i] != NULL; i++) {
    instruction_free(*qis[i]);
  }
  */
  if (qis != NULL) {
    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 (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><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
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;

  char log_str[STR_L];

  qis = (Query_instructions *)calloc(1, sizeof(Query_instructions));

  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) {
      qi = (Query_instruction *)calloc(1, sizeof(Query_instruction));

      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;
        qi->space = Query[i].space;
        qi->family = Query[i].family;

        if (map_qc2rx(qi, qc) == 1) {
        int j;
        int found=0;

          /* check that there is no such query yet */
          for (j=0; j<i_no; j++) {
            Query_instruction *qij = qis->instruction[j];
            
            if( qij->search_type == R_RADIX
                && qij->space       == qi->space
                && qij->family      == qi->family) {
              found=1;
              break;
            }
          }

          if ( found ) {
            /* Discard the Query Instruction */
            free(qi);
          } 
          else {
            /* Add the query_instruction to the array */
            qis->instruction[i_no++] = qi;
          }
        }
      }
      else {
        sprintf(log_str, "ERROR: bad search_type\n");
        log_inst_print(log_str);
      }
    }
  }
  qis->instruction[i_no++] = NULL;

  return qis;

} /* QI_new() */
