/***************************************
  $Revision: 1.20 $

  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 "rp.h"

/************************************************************
* int 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 rollback(Transaction_t *tr) {
GString *query;
long sequence_id;
int i, j;
int sql_err;

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

/* Lock all relevant tables */
   g_string_sprintf(query, "LOCK TABLES %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]);
    
    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);

    /*fprintf(stderr,"%s\n", query->str);*/


/* 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 */
    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 dummies, as they are updated with TR_UPDATE */
    /* We use this tag when commiting 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 */
    if(ACT_UPDATE(tr->action)) { /* so we are updating an object */
   g_string_sprintf(query, "DELETE FROM history WHERE object_id=%ld AND sequence_id=%ld", tr->object_id, tr->sequence_id-1);
   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
   /* we do not need to delete a row in the last for updates    */
  }
    else { /* we failed to create an object */
      sequence_id=1; /* sequence start == 1 */
   g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld AND sequence_id=%ld", tr->object_id, sequence_id);
   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 commit()                                              *
*                                                           *
* Commits the transaction                                   *
*                                                           *
* It locks all relevant tables and processes the rollback   *
* General approach is to clean up all new and updated       *
* records related to the transaction                        *
* (thread_id==thread_ins) and (thread_id==thread_upd),      *
* and delete untouched ones (thread_id==0)                  *
*                                                           *
************************************************************/

int commit(Transaction_t *tr) {
GString *query;
int err=0;
int i,j;
A_Type_t attr_type;
int sql_err;

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

 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   fprintf(stderr, "E: cannot allocate gstring\n"); 
   tr->succeeded=0;
   tr->error|=ERROR_U_MEM;
   die; 
 }

/* Lock all relevant tables */
    g_string_sprintf(query, "LOCK TABLES %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]);
    
    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);

/* fprintf(stderr,"%s\n", query->str); */

/* 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);
    /*    fprintf(stderr, "D: query (del old): %s\n", query->str);  */

 /* 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);
    /*    fprintf(stderr, "D: query (com new): %s\n", query->str); */
  }
  
/* 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:
                 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 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);
  }
  
 /* 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*/
 if(tr->standalone==0) { /* only if server*/
 
 /* Create a radix node for the object */
   if( (   (tr->class_type==C_RT) 
	|| (tr->class_type==C_IN) 
	|| (tr->class_type==C_I6)
	|| (tr->class_type==C_DN))
       && (ACT_UPD_RX(tr->action))) {
     rp_upd_pack_t *packptr = tr->packptr;
     
     packptr->key = tr->object_id;
     
     if( RP_pack_node(RX_OPER_CRE, packptr, tr->source_hdl) == RX_OK ) {
       err = 0;
     } else {
       err = (-1) ;
     }
   }   
   /* XXX Check for errors */
 } 

  g_string_free(query, TRUE);
  return(err);
} /* commit() */


/************************************************************
* int delete()                                              *
*                                                           *
* Deletes the object                                        *
*                                                           *
* It checks for referential integrity and then deletes the  *
* object from all relevant tables. Then it updates the      *
* radix tree for routes, inetnums and rev.domains           *
*                                                           *
************************************************************/
int delete(Transaction_t *tr) 
{
GString *query;
int err=0;
int i;
int num;
long ref_id;
long num_rec;
long timestamp;

char sobject_id[STR_M];
char *sql_str;
int sql_err;


 /* Try to allocate g_string. Return on error */	
 if ((query = g_string_sized_new(STR_XXL)) == NULL){ 
   fprintf(stderr, "E: 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(tr->dummy==1)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,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, "member_of");
           /*tr->succeeded=0; tr->error |= ERROR_U_OBJ;*/
	  /* XXX Do not refuse the transaction but change the object to dummy */
	  /* Update the history table */
               g_string_sprintf(query,  "INSERT history "
	                                "SELECT 0, object_id, sequence_id, timestamp, object_type, object "
                                        "FROM last "
                                        "WHERE object_id=%ld ", tr->object_id);

      
               sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
               if (sql_err) {
                fprintf(stderr, "E ERROR!<perform_update>: INSERT history failed:[%d][%s]\n", num, query->str);
                tr->succeeded=0;
                tr->error |=ERROR_U_DBS;
               }

               /* get sequence number */
               tr->sequence_id = get_sequence_id(tr);
               tr->sequence_id++;
       
               /* insert new version into the last */
               timestamp=time(NULL);
              
	      /* update the main table */
	      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) {
               fprintf(stderr, "E ERROR!<perform_update>: UPDATE last failed: [%d][%s]\n", num, query->str);
               tr->succeeded=0;
               tr->error |= ERROR_U_DBS;
              }
 
              /* empty 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, timestamp, tr->object_id);

              sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
              if (sql_err) {
               fprintf(stderr, "E ERROR!<perform_update>: UPDATE last failed: [%d][%s]\n", num, query->str);
               tr->succeeded=0;
               tr->error |= ERROR_U_DBS;
              }
              return(0);

         }
        } else {
         tr->succeeded=0; tr->error |= ERROR_U_DBS;
        }
        break;

    default:
        break;    
   } 
   
 /* Check if we have passed referential integrity check */  
 if(tr->succeeded==0){
       return(-1);
 }
          

/* Lock all relevant tables */
    g_string_sprintf(query, "LOCK TABLES %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]);
    
    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);

/* Update the history table */
    g_string_sprintf(query,     "INSERT history "
				"SELECT 0, object_id, sequence_id, timestamp, object_type, object "
       				"FROM last "
       				"WHERE object_id=%ld ", tr->object_id);

/* 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);
    /*    fprintf(stderr, "D: query (delete): %s\n", query->str);*/
  }

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


  sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
  if (sql_err) {
         fprintf(stderr, "E ERROR!<perform_update>: INSERT history failed:[%d][%s]\n", num, query->str);
         tr->succeeded=0;
         tr->error |=ERROR_U_DBS;
  }

  /* get sequence number */
  tr->sequence_id = get_sequence_id(tr);
  tr->sequence_id++;
       
  /* insert new version into the last */
  timestamp=time(NULL);
  
 /* empty the contents, but leave in the table to restrict re-use of object_id */ 
  g_string_sprintf(query, "UPDATE last SET object='', timestamp=%ld  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) {
  	fprintf(stderr, "E ERROR!<perform_update>: UPDATE last failed: [%d][%s]\n", num, query->str);
         tr->succeeded=0;
         tr->error |= ERROR_U_DBS;
  }


  /* Do more in the forest
   * Update radix tree for route and inetnum
   */
  if(tr->standalone==0) { /* only if server */
  /* Collect some data for radix tree and NH repository update */
    g_slist_foreach((tr->object)->attributes, get_rx_data, tr);

    /* 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))
	&& (ACT_UPD_RX(tr->action))) {
      rp_upd_pack_t *packptr = tr->packptr;
      
      packptr->key = tr->object_id;
      if( RP_pack_node(RX_OPER_DEL, packptr, tr->source_hdl) == RX_OK ) {
	err = 0;
      } else {
	err = (-1) ;
      }
    }
  }
  
 /* 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(err);

} /* delete() */              
