/***************************************
  $Revision: 1.26 $

  rollback(), commit(), delete() - rollback, commit update transaction, delete an object

  Status: NOT REVUED, NOT TESTED

 Author(s):       Andrei Robachevsky

  ******************/ /******************
  Modification History:
        andrei (17/01/2000) Created.
  ******************/ /******************
  Copyright (c) 2000                              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 "ud.h"
#include "ud_int.h"
#include "ud_comrol.h"
#include "ud_tr.h"
#include "rp.h"


/************************************************************
* int UD_rollback()                                         *
*                                                           *
* Rolls back the transaction                                *
*                                                           *
* It locks all relevant tables and processes the rollback   *
* General approach is to delete all new records related     *
* to the transaction (thread_id==thread_ins) and clean up   *
* old ones (thread_id==thread_upd)                          *
*                                                           *
************************************************************/
 
int UD_rollback(Transaction_t *tr) {
GString *query;
int i, j;
int sql_err;

 if(ACT_DELETE(tr->action)) return(0);
	
 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error |= ERROR_U_MEM;
   die; 
 }

/* Lock all relevant tables */
   g_string_sprintf(query, "LOCK TABLES ");
   
   /* we need to lock tables for mntner and role differently, otherwise there will be duplicates (names names, etc.)*/
   if((tr->class_type!=C_MT) && (tr->class_type!=C_RO)){
    g_string_sprintfa(query, " %s WRITE,",  DF_get_class_sql_table(tr->class_type));
    
    for (i=0; tables[tr->class_type][i] != NULL; i++) 
      g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
   } else { /* mntner and role are special cases */
      g_string_sprintfa(query, " mntner WRITE, person_role WRITE, ");
   }
   
    for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
      g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
    
    g_string_sprintfa(query, " last WRITE, history WRITE ");
    
    sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);

/* Process AUX and LEAF tables */
    for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
    /* Delete what has been inserted */
    g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld AND thread_id=%d", tables[tr->class_type][i], tr->object_id, tr->thread_ins);
    sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);

    /* Normalize what has been updated/touched */
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld AND thread_id=%d", tables[tr->class_type][i], tr->object_id, tr->thread_upd);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  }

/* Process MAIN tables */
/* Delete if a record was created */
    g_string_sprintf(query, "DELETE FROM %s WHERE  object_id=%ld AND thread_id=%d", 
                             DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_ins);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    
    /* This is needed only for objects with possible dummy type, as they are updated with TR_UPDATE */
    /* We use this tag when committing the update to set dummy==0 */
    /* XXX may be later this should be reconsidered */
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE  object_id=%ld AND thread_id=%d", 
                             DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_upd);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

/* Now tables  that might be affected by dummies */
    for(j=0; j < tr->ndummy; j++) 
    for (i=0; tables[tr->class_type][i] != NULL; i++) {
    	g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", tables[tr->class_type][i], tr->dummy_id[j]);
    	sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    } 

  /* if dummies have been created - get rid of them */
  for(j=0; j < tr->ndummy; j++){
	 g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld ", tr->dummy_id[j]);
	 sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  }
  
/* Rollback last and history tables */

    /* Delete what has been inserted */
    g_string_sprintf(query, "DELETE FROM history WHERE object_id=%ld AND thread_id=%d", tr->object_id, tr->thread_ins);
    sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);

    /* Normalize what has been updated/touched */
    g_string_sprintf(query, "UPDATE history SET thread_id=0 WHERE object_id=%ld AND thread_id=%d", tr->object_id, tr->thread_upd);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

    /* Delete what has been inserted */
    g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld AND thread_id=%d", tr->object_id, tr->thread_ins);
    sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);

    /* Normalize what has been updated/touched */
    g_string_sprintf(query, "UPDATE last SET thread_id=0 WHERE object_id=%ld AND thread_id=%d", tr->object_id, tr->thread_upd);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

  
  /* Unlock all tables */
  g_string_sprintf(query, "UNLOCK TABLES ");
  sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

  
  g_string_free(query, TRUE);
  return(0);
} /* rollback() */

/************************************************************
* int UD_commit_I()                                         *
*                                                           *
* Performs I phase of the commit - deletions                *
*                                                           *
* General approach is to delete untouched rec (thread_id==0)*
*                                                           *
************************************************************/

int UD_commit_I(Transaction_t *tr) {
GString *query;
int err=0;
int i;
int sql_err;


  if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }

/* Commit the transaction for AUX and LEAF tables that may be affected (taken from object template) */
  for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
 /* Delete old records from the tables */  
    g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld AND thread_id=0 ", tables[tr->class_type][i], tr->object_id);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    /*    ER_dbg_va(FAC_UD, ASP_UD_SQL, "%s query (del old): %s\n", UD_TAG, query->str);  */
  }

 /* Delete old record from the last table */  
    g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld AND thread_id=0 ", tr->object_id);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    /*    ER_dbg_va(FAC_UD, ASP_UD_SQL, "%s query (del old): %s\n", UD_TAG, query->str);  */

  
 g_string_free(query, TRUE);
 return(err); 	
}

/************************************************************
* int UD_commit_II()                                        *
*                                                           *
* Performs I phase of the commit - deletions                *
* General approach is to clean up all new and updated       *
* records related to the transaction                        *
* (thread_id==thread_ins) and (thread_id==thread_upd)       *
*                                                           *
************************************************************/
int UD_commit_II(Transaction_t *tr) {
GString *query;
int err=0;
int i,j;
A_Type_t attr_type;
int sql_err;

 
 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }


/* Commit the transaction for AUX and LEAF tables that may be affected (taken from object template) */
  for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
 /* Set thread_id to 0 to commit the transaction */    
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld", tables[tr->class_type][i], tr->object_id);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    /*    ER_dbg_va(FAC_UD, ASP_UD_SQL, "%s query (com new): %s\n", UD_TAG, query->str); */
  }
  
/* Commit changes to the last table */  
   g_string_sprintf(query, "UPDATE last SET thread_id=0 WHERE object_id=%ld ", tr->object_id);
   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

/* Commit changes to the history table */  
   g_string_sprintf(query, "UPDATE history SET thread_id=0 WHERE object_id=%ld ", tr->object_id);
   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
   
/* Commit the transaction for the MAIN tables */

/* Commit the transaction for person_role, mntner, as_set, route_set tables */
/* They require different handling because of dummies */
/* The rule is: Update: dummy->0, Insert: preserve dummy value */
/* These tables do not require deletions since we cannot have such condition (object_id==0 AND thread_id==0) */
 if((tr->class_type==C_PN) || (tr->class_type==C_RO) || 
   (tr->class_type==C_AS) || (tr->class_type==C_RS) ||
   (tr->class_type==C_MT)){

 /* Process the rows updated/touched */
    g_string_sprintf(query, "UPDATE %s SET thread_id=0, dummy=0 WHERE object_id=%ld AND thread_id=%d ",  DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_upd);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
 }
 
 switch (tr->class_type) {
   case C_IR:
   case C_IN:
   case C_I6:
   case C_FS: 
    if((tr->save)){ /* Some special processing for tables with the second attribute */
     /* Update the second field of the table with query like one below */
     /* UPDATE %s SET thread_id=%d, local_as='%s' WHERE object_id=%ld */
     
     switch(tr->class_type) {
      /* Local-as for inet-rtr */
      case C_IR: attr_type=A_LA;
                 break;
      /* netname for inetnum and inet6num */           
      case C_IN: 
      case C_I6: attr_type=A_NA;
                 break;
      /* filter for filter-set */           
      case C_FS: attr_type=A_FI;
                 break;
      default:
		 ER_perror(FAC_UD, UD_BUG, "not valid class type\n");
                 die;
                 break;           
     }
     g_string_sprintf(query, DF_get_update_query(attr_type), DF_get_class_sql_table(tr->class_type), 0, (char *)tr->save, tr->object_id);
     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    }
    else {
     ER_perror(FAC_UD, UD_BUG, "second attribute is not saved\n");
     die;
    }
    break;
   
   default:  
 /* Process all other MAIN tables for updates/inserts and person_role, mntner, as_set, route_set tables for rows inserts */
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld AND thread_id>0", DF_get_class_sql_table(tr->class_type), tr->object_id);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    break;
 }  


/* for tables that might be affected by dummies */
 for(j=0; j < tr->ndummy; j++)/* if dummies have been created */
   for (i=0; tables[tr->class_type][i] != NULL; i++) {
    g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld ", tables[tr->class_type][i], tr->dummy_id[j]);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
 }


   for(j=0; j < tr->ndummy; j++){/* if dummies have been created*/
	 g_string_sprintf(query, "UPDATE last SET thread_id=0 WHERE object_id=%ld ", tr->dummy_id[j]);
	 sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  }
 
 g_string_free(query, TRUE);

 return(err);		
}


/************************************************************
* int UD_commit()                                           *
*                                                           *
* Commits the transaction                                   *
*                                                           *
* It locks all relevant tables and processes the 2 phases of*
* commit. It also performs checkpointing of phases and      * 
* radix tree update                                         * 
*                                                           * 
* We need to split commit into 2 because otherwise it is    *
* hard to distinguish between commited records and untouched*
* ones (both have thread_id==0). Splitting and checkpointing*
* solves this problem                                       *
*                                                           *
************************************************************/

int UD_commit(Transaction_t *tr) {
GString *query;
int err=0;
int i;
int sql_err;

if(ACT_DELETE(tr->action)) return(0);

 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }

/* Lock all relevant tables */
   g_string_sprintf(query, "LOCK TABLES ");
   
   /* we need to lock tables for mntner and role differently, otherwise there will be duplicates (names names, etc.)*/
/*   if((tr->class_type!=C_MT) && (tr->class_type!=C_RO)){ */
   g_string_sprintfa(query, " %s WRITE,",  DF_get_class_sql_table(tr->class_type));
   
   if((tr->class_type==C_RO)) g_string_sprintfa(query, " mntner WRITE, ");
   else if((tr->class_type==C_MT)) g_string_sprintfa(query, " person_role WRITE, names WRITE, "); 
    else
     for (i=0; tables[tr->class_type][i] != NULL; i++) 
        g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
   
   for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
      g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
    
   g_string_sprintfa(query, " last WRITE, history WRITE, transaction_rec WRITE ");
    
   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);


  /* Perform first phase - deletions */
  UD_commit_I(tr);
  /* checkpoint this step */
  CP_COMMIT_I_PASSED(tr->action); TR_update_status(tr);
  /* Perform first phase - updates */
  UD_commit_II(tr);
  /* checkpoint this step */
  CP_COMMIT_II_PASSED(tr->action); TR_update_status(tr);
  
 /* Unlock all tables */
 g_string_sprintf(query, "UNLOCK TABLES ");
 sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);

 /* Update radix tree for route, inetnum and inaddr-arpa domain*/
 err = UD_update_rx(tr, RX_OPER_CRE);
 
 g_string_free(query, TRUE);
 return(err);
} /* commit() */

/************************************************************
* int UD_check_ref()                                        *
*                                                           *
* Checks if the object to be deleted is referenced from     *
* anywhere                                                  *
*                                                           *
* 0 - go ahead                                              *
* -1 - deletion will compromise ref.integrity               *
* Result is also reflected in tr->succeeded                 *
************************************************************/
int UD_check_ref(Transaction_t *tr) 
{
GString *query;
int i;
long ref_id;
long num_rec;

char sobject_id[STR_M];
char *sql_str;

 /* Try to allocate g_string. Return on error */	
 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }


/* Check for referential integrity of deletion */

   sprintf(sobject_id, "%ld", tr->object_id);

   switch(tr->class_type){
    case C_PN:
    case C_RO:
        
       /* Check that this person/role object is not referenced */
        
       for (i=0; t_ipn[i] != NULL; i++) { 
        /* Calculate number of references */
        sql_str= get_field_str(tr->sql_connection, "COUNT(*)", t_ipn[i], "pe_ro_id", sobject_id, NULL);
        if(sql_str) {
         num_rec = atol(sql_str);  free(sql_str);
         ref_id=tr->object_id;
         /* Check if it is a self reference (for role objects) */
         if(num_rec==1) {
          sql_str= get_field_str(tr->sql_connection, "object_id", t_ipn[i], "pe_ro_id", sobject_id, NULL);
          if(sql_str) {
           ref_id = atol(sql_str);  free(sql_str);
          } else {
           tr->succeeded=0; tr->error |= ERROR_U_DBS; break;
          }
         }
         /* If there are references (and not the only self reference) we cannot delete */
         if((num_rec>1) || (ref_id!=tr->object_id)) {
           g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_ipn[i]);
           tr->succeeded=0; tr->error |= ERROR_U_OBJ;
         }
        } else {
        /* SQL error occured */
         tr->succeeded=0; tr->error |= ERROR_U_DBS;
         g_string_sprintfa(tr->error_script,"E[%d][%s]:%s\n", ERROR_U_DBS, t_ipn[i], SQ_error(tr->sql_connection));
        }
       }
       
       /* Check that this person/role object is not referenced by name (legacy stuff) */
       /* But allow overriding this check in NRTM mode and with override_integrity    */
       if(IS_DUMMY_ALLOWED(tr->mode))break;
        
       for (i=0; t_ipn[i] != NULL; i++) { 
        /* Calculate number of references */
        
        g_string_sprintf(query, "SELECT COUNT(*) FROM %s, person_role "
                                "WHERE person_role.object_id=%s.pe_ro_id "
                                "AND person_role.nic_hdl='%s' ", t_ipn[i], t_ipn[i], tr->save);
        
        sql_str= get_qresult_str(tr->sql_connection, query->str);
        if(sql_str) {
         num_rec = atol(sql_str);  free(sql_str);
         /* If there are references (no self reference is possible in this case) we cannot delete */
         if(num_rec>0) {
           g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_ipn[i]);
           tr->succeeded=0; tr->error |= ERROR_U_OBJ;
         }
        } else {
        /* SQL error occured */
         tr->succeeded=0; tr->error |= ERROR_U_DBS;
         g_string_sprintfa(tr->error_script,"E[%d][%s]:%s\n", ERROR_U_DBS, t_ipn[i], SQ_error(tr->sql_connection));
        }
       }
          
       break;
        
    case C_MT:
    
        /* Check that this mntner object is not referenced */
        
       for (i=0; t_imt[i] != NULL; i++) { 
       /* Calculate number of references */
        sql_str= get_field_str(tr->sql_connection, "COUNT(*)", t_imt[i], "mnt_id", sobject_id, NULL);
        if(sql_str) {
         num_rec = atol(sql_str);  free(sql_str);
         ref_id=tr->object_id;
         /* Check if it is a self reference  */
         if(num_rec==1) { 
            sql_str= get_field_str(tr->sql_connection, "object_id", t_imt[i], "mnt_id", sobject_id, NULL);
            if(sql_str) {
              ref_id = atol(sql_str);  free(sql_str);
            } else {
              tr->succeeded=0; tr->error |= ERROR_U_DBS; break;
            } 
         }
         /* If there are references (and not the only self reference) we cannot delete */ 
         if((num_rec>1) || (ref_id!=tr->object_id)) {
           g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_imt[i]);
           tr->succeeded=0; tr->error |= ERROR_U_OBJ;
         }
        } else {
         tr->succeeded=0; tr->error |= ERROR_U_DBS;
        }
       }   
       break;
        
    case C_RS:
    case C_AS:
        /* Check that this set object is not referenced */
        /* Calculate number of references */
        sql_str= get_field_str(tr->sql_connection, "COUNT(*)", "member_of", "set_id", sobject_id, NULL);
        if(sql_str) {
         num_rec = atol(sql_str);  free(sql_str);
         /* XXX though set may contain other sets as memebers, */
         /* there is no member-of attribute in these objects. */
         /* So no self-reference is possible */
         if(num_rec!=0) {
           g_string_sprintfa(tr->error_script,"I[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, "member_of");
	  /* XXX Do not refuse the transaction but change the object to dummy */
           tr->action |=TA_DUMMY;
         }
        } else {
         tr->succeeded=0; tr->error |= ERROR_U_DBS;
        }
        break;

    default:
        break;    
   } 
   
 g_string_free(query, TRUE);

 /* Check if we have passed referential integrity check */  
 if(tr->succeeded) return(0); else return(-1);
 
} 
	
/************************************************************
* int UD_delete()                                              *
*                                                           *
* Deletes the object                                        *
*                                                           *
* It deletes the object from all relevant tables. 
* Then it updates the radix tree for routes, inetnums 
* and rev.domains           *
*                                                           *
************************************************************/
int UD_delete(Transaction_t *tr) 
{
GString *query;
int err=0;
int i;
long timestamp;
int sql_err;
int ref_set;

/* if we are deliting referenced set, we need to  perform delete a bit differently */
/* no deletions of aux tables */
/* dummy main, instead of del */
/* dummy last instead of empty */
/* So let's determine if we are deliting referenced set */
if ((tr->class_type==C_AS || tr->class_type==C_RS) && ACT_UPD_DUMMY(tr->action)) ref_set = 1; else ref_set = 0;

 /* Try to allocate g_string. Return on error */	
 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }

  
/* Lock all relevant tables */
   g_string_sprintf(query, "LOCK TABLES ");
   
   /* we need to lock tables for mntner and role differently, otherwise there will be duplicates (names names, etc.)*/
   if((tr->class_type!=C_MT) && (tr->class_type!=C_RO)){
    g_string_sprintfa(query, " %s WRITE,",  DF_get_class_sql_table(tr->class_type));
    
    for (i=0; tables[tr->class_type][i] != NULL; i++) 
      g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
   } else { /* mntner and role are special cases */
      g_string_sprintfa(query, " mntner WRITE, person_role WRITE, ");
   }
   
    for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
      g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
    
    g_string_sprintfa(query, " last WRITE, history WRITE ");
    
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    if (sql_err) {
	 ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |=ERROR_U_DBS;
	 die;
    }
/* Update the history table */
/* XXX Crash recovery: */
/* If history was not updated - we will create a record */
/* If history was already updated but last wasn't - we will just replace the record */
/* If history and last were already updated - we will have an empty query - 0 rows should be affected */
    g_string_sprintf(query,     "REPLACE history "
				"SELECT 0, object_id, sequence_id, timestamp, object_type, object, pkey, serial, prev_serial "
       				"FROM last "
       				"WHERE object_id=%ld AND sequence_id=%ld ", tr->object_id, tr->sequence_id);
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    if (sql_err) {
	 ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |=ERROR_U_DBS;
	 die;
    }

/* Delete records from the leaf and aux tables */
    for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
     g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", tables[tr->class_type][i], tr->object_id);
     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    /*    ER_dbg_va(FAC_UD, ASP_UD_SQL, "%s query (delete): %s\n", UD_TAG, query->str);*/
       if (sql_err) {
	 ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |=ERROR_U_DBS;
	 die;
       }
    }  
     

 /* For all object except as-sets and route-sets we need to empty MAIN table */
 /* For referenced sets, however, we transform them to dummy, not delete */
 if (ref_set == 0) {

/* Process the MAIN table  */
    g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", DF_get_class_sql_table(tr->class_type), tr->object_id);
   

    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    if (sql_err) {
	 ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |=ERROR_U_DBS;
	 die;
    }
 
 } else { /* this is the referenced set */
 /* we need to 'dummy' MAIN */
    g_string_sprintf(query, "UPDATE %s SET dummy=1 WHERE object_id=%ld ", DF_get_class_sql_table(tr->class_type), tr->object_id);
               
    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
    if (sql_err) {
         ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |= ERROR_U_DBS;
         die;
    }
 }
       
  /* insert new version into the last */
  timestamp=time(NULL);
  
 if(ref_set == 0) 
 {
 /* empty the contents, but leave in the table to restrict re-use of object_id */ 
 /* XXX change sequence_id=0 so it is easy to say that the object was deleted */
  g_string_sprintf(query, "UPDATE last SET object='', timestamp=%ld, sequence_id=0  WHERE object_id=%ld ", timestamp, tr->object_id);

  sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  if (sql_err) {
	 ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
         tr->succeeded=0;
         tr->error |= ERROR_U_DBS;
	 die;
  }
 } else {/* this is the referenced set */
 /* 'dummy' the contents, but leave in the table to prevent re-use of object_id */ 
 g_string_sprintf(query, "UPDATE last SET object='DUMMY SET', object_type=%d, sequence_id=%ld, timestamp=%ld  WHERE object_id=%ld ", DUMMY_TYPE, tr->sequence_id+1, timestamp, tr->object_id);

 sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
 if (sql_err) {
     ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
     tr->succeeded=0;
     tr->error |= ERROR_U_DBS;
    die;
   }
 }


 /* Unlock all tables */
  g_string_sprintf(query, "UNLOCK TABLES ");
  sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  if (sql_err) {
	ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
        tr->succeeded=0;
        tr->error |= ERROR_U_DBS;
	die;
  }


  g_string_free(query, TRUE);

  return(err);

} /* delete() */ 



     	       /* Do more in the forest
   * Update radix tree for route and inetnum
   */

int UD_update_rx(Transaction_t *tr, rx_oper_mt mode)
{
rp_upd_pack_t *packptr = tr->packptr;
int err=0;

  
  if(!IS_STANDALONE(tr->mode)) { /* only if server */
  

    /* Only for these types of objects and only if we have collected data (tr->save != NULL) */
    if( (   (tr->class_type==C_RT) 
	 || (tr->class_type==C_IN) 
	 || (tr->class_type==C_I6)
	 || (tr->class_type==C_DN))) {
      /* Collect some data for radix tree and NH repository update for deletes*/
      if(mode == RX_OPER_DEL)g_slist_foreach((tr->object)->attributes, get_rx_data, tr);
      
      /* Except for regular domains we need to update radix tree */
      if(ACT_UPD_RX(tr->action)){
       packptr->key = tr->object_id;
       if( RP_pack_node(mode, packptr, tr->source_hdl) == RX_OK ) {
	err = 0;
       } else {
	err = (-1);
	ER_perror(FAC_UD, UD_BUG, "cannot update radix tree\n");
	die;
       }
      } /* update radix tree */
    }
  }
  return(err);
}
   
	       

