/***************************************
  $Revision: 1.33 $

  History/Serial Cleanup (hs_cleanup). This utility archives serials
  and history entries, and deletes them from the live database.

  Status: COMPLETE, NOT REVUED, NOT FULLY TESTED

  ******************/ /******************
  Filename            : hs_cleanup.c
  Authors             : Daniele Arena
  OSs Tested          : Solaris 7
  ******************/ /******************
  Copyright (c) 2000, 2001                        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.
  ***************************************/


/********** INCLUDES **********/

/* Standard includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h> /* for time() */
#include <math.h> /* for floor() */
#include <unistd.h> /* for sleep() */

/* RIP includes */
#include "mysql_driver.h"
#include "stubs.h"


/********** DEFINES **********/

/* Default settings for the SQL DB */
#define MYSQL_HOST "myhost.mydb.net"
#define MYSQL_PORT 3306
#define MYSQL_USER "sqluser"
#define MYSQL_PSWD "sqlpswd"
#define MYSQL_DB "sqldb"

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

/* Maximum allowed length of a SQL command */
#define MAXCMDLEN 8192


/* 
 * Standard time constants:
 * 1min = 60s
 * 1hr = 3600s
 * 1day = 86400s
 * 1wk = 604800s
 */

#define MINUTE 60
#define HOUR 3600
#define DAY 86400
#define WEEK 604800

/* For debugging purposes */
#define DEBUG 0

/* If this flag is set, older-and-already-archived history object won't get deleted.
   XXX: currently this *must* be set, otherwise weird things happen. To be fixed. */
#define ARCHIVE_ONLY 1

/* Object types from RIP */
#define OBJ_TYPE_DUMMY 100

/* For crash-recovery tests */

/* Activate this flag if you want to test crash-recovery */
#define CRASHTEST 0

/* Helpers for blackbox crashtest */
#define IS_LAST_STEP 1
#define MAX_STEPS 8

/********** ENUMS **********/

/* List of possible "atlast" values */
enum {
  IN_HISTORY_TABLE = 0,
  IN_LAST_TABLE,
  IN_FAILED_TRANSACTION_TABLE,
  IN_UNDEF_TABLE
};

const char *atlast_table[] = { "history",
			       "last",
			       "failed_transaction" };

/* List of operations for a serial in RIP */
enum {
  OP_NULL = 0,
  OP_ADD,
  OP_DELETE,
  OP_UPDATE
};

/* For checkpointing/crash-recovery:
 * we mark the actions to be taken if recovering
 */
enum {
  CHKP_NOOP = 0,
  CHKP_DELETE_FROM_ARCHIVE,
  CHKP_DELETE_FROM_LIVE,
  CHKP_DELETE_FROM_LIVE_ONLY_SERIAL,
  CHKP_DONE
};



/********** TYPEDEFS **********/

/* A structure to hold an object from last or history table */
typedef struct table_object *tblobjPtr;

typedef struct table_object {
  long objid;
  long seqid;
  tblobjPtr next;
} tblObjList;



/********** GLOBAL VARIABLES **********/

SQ_connection_t *connection;
int debug = DEBUG;
int archive_only = ARCHIVE_ONLY;
long highest_objid;
/* For crash recovery test */
long crashing_serial;
int crash_position;
int code_location;
int case_branch;


/********** FUNCTION DEFINITIONS **********/


/*** Main functions ***/
int main (int argc, char *argv[]);
void usage(char *argv[]);

/*** Archiving algorithm functions ***/
static int get_highest_serial_before_date(long *highest_serial_ptr, long date);
static int archive_serials_and_history(long highest_serial);
static long fetch_timestamp_from_last(long obj_id, long ser_id);
static long get_highest_object_id();
static int check_if_next_is_deletion (long obj_id, long seq_id);

/*** Table creation ***/
static int create_archive_tables();
static int create_table(const char *cmd);

/*** Object update ***/
static int update_history_archive_sequence_id_and_timestamp(long ser_id, long new_seq_id, long new_timestamp);
static int update_history_archive_object_id_and_sequence_id (long ser_id, long new_obj_id, long new_seq_id);
static int update_prev_serial_of_object (long obj_id, long seq_id, long prev_ser, const char *tablename);
static int update_serial_of_object (long obj_id, long seq_id, long ser_id, const char *tablename);
#define update_prev_serial_of_next_object(obj_id, seq_id, prev_ser, tablename) update_prev_serial_of_object(obj_id, seq_id+1, prev_ser, tablename)
#define update_serial_in_last(obj_id, seq_id, ser_id) update_serial_of_object(obj_id, seq_id, ser_id, "last")
#define update_serial_in_history(obj_id, seq_id, ser_id) update_serial_of_object(obj_id, seq_id, ser_id, "history")

/*** Object archiving ***/
static int archive_serial(long ser_id, long obj_id, long seq_id, int op);
static int archive_failed_transaction(long ser_id);
static int copy_into_history_archive(long ser_id, long obj_id, long seq_id, const char *tablename);
static int copy_deleted_object_into_history_archive (long ser_id, long obj_id, long seq_id, const char *tablename);
#define archive_history(ser_id, obj_id, seq_id) copy_into_history_archive(ser_id, obj_id, seq_id, "history")
#define archive_last(ser_id, obj_id, seq_id) copy_into_history_archive(ser_id, obj_id, seq_id, "last")

/*** Object deletion ***/
static int delete_serial_entry(long ser_id);
static int delete_failed_transaction_entry (long ser_id);
static int delete_entry_from_object_table(long obj_id, long seq_id, const char* tablename);
static int delete_archived_objects(long ser_id);
static int delete_serial_archive_entry(long ser_id);
static int delete_history_archive_entry(long ser_id);
#define delete_history_entry(obj_id, seq_id) delete_entry_from_object_table(obj_id, seq_id, "history")
#define delete_last_entry(obj_id, seq_id) delete_entry_from_object_table(obj_id, seq_id, "last")

/*** Handling of older, unreferenced history entries ***/
static tblObjList *find_unreferenced_history_entries(long date);
static int delete_unreferenced_history_entries(tblObjList *objectsToDelete);

/*** Handling of dummy objects ***/
static int delete_dummy_history_objects();

/*** Interactions with SQL DB ***/
static int lock_last_history_serial_tables();
static int unlock_all_tables();
static int optimize_sql_table(const char* tablename);

/*** SQL interfaces ***/
static int execute_sql_command(const char *cmd);
static int execute_sql_query(const char *query, SQ_result_set_t **result_ptr);

/*** Checkpointing ***/
static int create_auxiliary_table();
static int reset_auxiliary_table();
static int drop_auxiliary_table();
static int update_hs_auxiliary_checkpoint(long ser_id, long obj_id, long seq_id, int atlast, int checkpoint);
static int crash_recovery();
static int exists_checkpointing_table();
/* Checkpointing test */
static long get_smallest_serial();
static int get_random_number_in_range(int num1, int num2, int seed);
static void do_crash(long crashserial, int is_last_step);


/********** KNOWN BUGS AND LIMITATIONS **********/

/* XXX Fixme:
   - Subroutine (+option) to warn that the size is bigger than a watermark
     (needed to burn CDs)
   */




/********** THE CODE **********/


/*** Main functions ***/

/****
 *
 * main()
 *
 ****/

int main (int argc, char *argv[])
{

  int ch, rc;
  long highest_serial;
  short errflg = 1;
  short tflg = 0;
  extern char *optarg;
  extern int optind;
  time_t now = time(0);
  time_t date;
  time_t lapse;

  char sqlhost[STR_M] = MYSQL_HOST;
  int sqlport = MYSQL_PORT;
  char sqluser[STR_M] = MYSQL_USER;
  char sqlpswd[STR_M] = MYSQL_PSWD;
  char sqldb[STR_M] = MYSQL_DB;

  tblObjList *objectsToDelete;
  long serialsArchived;
  /* tblObjList *tmpObj; */

  /* Get options */

  while ((ch = getopt(argc, argv, "?T:S:M:H:D:W:h:P:u:p:d:")) != EOF )
    switch((char)ch)
      {
      case 'T':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    date = atol (optarg);
	  }
	break;
      case 'S':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    lapse = atol (optarg);
	    date = now - lapse;
	  }
	break;
      case 'M':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    lapse = atol (optarg);
	    date = now - lapse * MINUTE;
	  }
	break;
      case 'H':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    lapse = atol (optarg);
	    date = now - lapse * HOUR;
	  }
	break;
      case 'D':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    lapse = atol (optarg);
	    date = now - lapse * DAY;
	  }
	break;
      case 'W':
	if (tflg)
	  errflg++;
	else
	  {
	    tflg++;
	    errflg = 0;
	    lapse = atol (optarg);
	    date = now - lapse * WEEK;
	  }
	break;
      case 'h':
	sprintf (sqlhost,"%s",optarg);
	break;
      case 'P':
	sqlport = atoi(optarg);
	break;
      case 'u':
	sprintf (sqluser,"%s",optarg);
	break;
      case 'p':
	sprintf (sqlpswd,"%s",optarg);
	break;
      case 'd':
	sprintf (sqldb,"%s",optarg);
	break;
      case '?':
      default:
	errflg++;
      }

if (errflg)
  usage(argv);


  /* Initialize connection */
  connection = SQ_get_connection(sqlhost, sqlport,sqldb,sqluser,sqlpswd);

  /* Create tables for history and serials archives
   * if they do not exist */
  if ((rc = create_archive_tables()) != 0)
    { return(rc); }

  /* XXX Call remadmin interface and stop updates 
   * (currently done externally via a wrapping script) */
  /* XXX If retcode is successful, go on */

  /* Crash recovery handling. */
  /* fprintf (stderr, "Starting crash recovery...\n"); */
  crash_recovery();
  /* fprintf (stderr, "Crash recovery done.\n"); */

  /* Deal with very old history entries, those that do not even have a corresponding
   * serial. These are entries which had been archived when they were in the "last" table,
   * and have in the meanwhile been updated. We have to:
   *    - Update prev_serial of their next object
   *    - Delete them!
   */

  objectsToDelete = find_unreferenced_history_entries((long) date);

  /* printf ("Elements to be deleted:\n");
     for (tmpObj = objectsToDelete; tmpObj != NULL; tmpObj = tmpObj->next)
     {
     printf ("objid: %ld, seqid: %ld\n", tmpObj->objid, tmpObj->seqid);
     } */ 


  /* Get the biggest serial for which the history or last timestamp is lower than
   * the defined timestamp
   */

  if ((rc = get_highest_serial_before_date(&highest_serial, (long) date)) != 0)
    { return(rc); }
  printf ("Highest serial ID: %ld\n",highest_serial);

  highest_objid = get_highest_object_id();
  printf ("Highest object_id: %ld\n",highest_objid);

  /* Execute the archiving commands */

  serialsArchived = archive_serials_and_history(highest_serial);
  if (serialsArchived>0)printf ("Serials archived: %ld [%ld - %ld]\n",serialsArchived, highest_serial-serialsArchived, highest_serial-1);
  else printf ("Serials archived: 0 \n");

  /* Optimize history serial and last tables: there might have been many deletions */
  /* XXX: optimizing the 'last' table takes about 300 seconds now, during
          which time queries are blocked - we need another way to do 
	  this - SK 2001-03-29 */
  /*optimize_sql_table("serials");*/
  /*optimize_sql_table("history");*/
  /*optimize_sql_table("last");*/

  /* XXX Call remadmin interface and restart updates 
   * (currently done externally via a wrapping script) */
  /* XXX If retcode is not successful, go on, but issue a warning */

  /* Delete the unreferenced history entries. Must be done at the end. */
  /* XXX Bug here. The older entries cannot be deleted or they wreak havoc. 
   * archive_only must be 1. */
  if (! archive_only)
    delete_unreferenced_history_entries(objectsToDelete);

  /* Delete dummy history objects */
  delete_dummy_history_objects();

  /* OK, it's over. */

  drop_auxiliary_table();

  SQ_close_connection(connection);
  printf ("\nProgram done.\n");
  return(0);

} /* main() */



/****
 *
 * usage(): help for command usage
 * Needs argv[] for the command path supplied
 *
 ****/

void usage(char *argv[])
{

  printf ("Usage: \n\n");
  printf ("  %s [-?] [-h host] [-P port] [-u user] [-p password] [-d database]\n", argv[0]);
  printf ("     [-T date|-S seconds|-M minutes|-H hours|-D days|-W weeks] \n");

  printf ("\nGeneral options:\n");
  printf ("   -?: This text\n");

  printf ("\nSQL Options:\n");
  printf ("   -h: host \t\t(default: %s)\n",MYSQL_HOST);
  printf ("   -P: port \t\t(default: %d)\n",MYSQL_PORT);
  printf ("   -u: user \t\t(default: %s)\n",MYSQL_USER);
  printf ("   -p: password \t(default: %s)\n",MYSQL_PSWD);
  printf ("   -d: database name \t(default: %s)\n",MYSQL_DB);

  printf ("\nTime-related options: (one and only one must be specified)\n");
  printf ("   -T date: Date before which to archive (secs from the Epoch)\n");
  printf ("   -S seconds: Seconds elapsed between the date to archive and now\n");
  printf ("   -M minutes: Minutes elapsed between the date to archive and now\n");
  printf ("   -H hours: Hours elapsed between the date to archive and now\n");
  printf ("   -D days: Days elapsed between the date to archive and now\n");
  printf ("   -W weeks: Weeks elapsed between the date to archive and now\n");
  exit(1);

} /* usage() */





/*** Archiving algorithm functions ***/

/****
 * 
 * get_highest_serial_before_date()
 * We get the biggest serial for which the history or last timestamp is lower than
 * the defined timestamp 
 *
 ****/

int get_highest_serial_before_date (long *highest_serial_ptr, long date)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long highest_serial_last, highest_serial_history;

  /* sprintf (query, "SELECT MAX(serials.serial_id) FROM history,serials 
			WHERE history.timestamp < %ld 
			AND history.object_id = serials.object_id 
			AND history.sequence_id = serials.sequence_id ", date); */

  
/*    sprintf (query, "SELECT MAX(serials.serial_id) 
			FROM serials NATURAL LEFT JOIN last NATURAL LEFT JOIN history
			WHERE ((last.timestamp < %ld 
				AND last.object_id = serials.object_id 
				AND last.sequence_id = last.sequence_id)
			OR (history.timestamp < %ld 
				AND history.object_id = serials.object_id 
				AND history.sequence_id = serials.sequence_id))",
	   date, date);

printf(query);

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      *highest_serial_ptr = row[0] ? atol((const char *)row[0]) : 0;
    }

  SQ_free_result(result);

*/
    sprintf (query, "SELECT MAX(serials.serial_id) 
                     FROM serials, last
                     WHERE (last.object_id = serials.object_id 
		     AND last.sequence_id = serials.sequence_id 
		     AND last.timestamp < %ld)", date);
  
  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      highest_serial_last = row[0] ? atol((const char *)row[0]) : 0;
    }

  SQ_free_result(result);

    sprintf (query, "SELECT MAX(serials.serial_id) 
                     FROM serials, history
                     WHERE (history.object_id = serials.object_id 
		     AND history.sequence_id = serials.sequence_id 
		     AND history.timestamp < %ld)", date);
  
  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      highest_serial_history = row[0] ? atol((const char *)row[0]) : 0;
    }

  SQ_free_result(result);
  
  if(highest_serial_history>highest_serial_last)
	  *highest_serial_ptr=highest_serial_history; 
  else *highest_serial_ptr=highest_serial_last;

  return(0);

} /* get_highest_serial_before_date() */



/****
 *
 * archive_serials_and_history():
 * This function contains the core algorithm that manipulates the last,
 * history and serials tables and archives them into serials_archive
 * and history_archive tables.
 *
 * Returns number of serials archived
 * -1 if error occured
 *
 ****/

int archive_serials_and_history (long highest_serial)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long serial, objid, seqid;
  int atlast, op;
  int nserials=0;
  char *tablename;
  long timestamp;
  /* For crash-recovery test */
  int crashtest = CRASHTEST;
  long smallest_serial;
  time_t now = time(0);


  if (crashtest)
    {

      /* 

	 If you want to run blackbox crash-recovery testing, all you need to do is add
	 the random-crashing function between the archiving functions that modify
	 the SQL DB:

	 if ((crashtest) && (serial == crashing_serial)) do_crash(serial, 0);

	 and activate the CRASHTEST flag at the top of this file. 

       */
	 

      smallest_serial = get_smallest_serial();
      crashing_serial = get_random_number_in_range(smallest_serial, highest_serial, (long)now);
      /* crashing_serial = 0; */
      if (debug) fprintf (stderr, "Crashing serial: %ld\n",crashing_serial);
      code_location = 1;
      crash_position = get_random_number_in_range(code_location, MAX_STEPS, (long)now);
    }


  /* Get the entries for each serial */
  /* One word about the "<": Don't use "<=" because if highest_serial
     is the CURRENTSERIAL, it causes big problems to UD! 
     (at least in mirror mode...) */
  sprintf (query, "SELECT serials.serial_id, serials.atlast, 
			ELT(serials.atlast+1,'history','last','failed_transaction'), 
			serials.object_id, serials.sequence_id, serials.operation 
			FROM serials 
			WHERE serials.serial_id < %ld
			ORDER BY serials.serial_id", highest_serial);
  execute_sql_query(query, &result);

  /* Loop on every serial */
  while ( (row = SQ_row_next(result)) != NULL )
    {
       nserials++;
      /* The lock is inserted here, inside the loop, because it is
       * a write lock, which disallows the reading of the table.
       * Since one concerned table is "last", the queries would
       * be blocked.
       * By freeing the lock at the end of every loop, we are assured
       * that the reads in queue are executed before a new lock is set.
       */

      /* Lock (write lock!) relevant tables */
      lock_last_history_serial_tables();

      /* XXX Add stronger error checking: NULL rows should never happen */
      serial = row[0] ? atol((const char *)row[0]) : 0;
      atlast = row[1] ? atoi((const char *)row[1]) : IN_UNDEF_TABLE;

      if (row[2] == NULL)
	{
	  /* That should never happen! */
	  fprintf (stderr, "Fatal: No pointer to table\n");
	  return (-1);
	}
      else
	{
	  tablename = strdup((const char *)row[2]);
	}

      objid = atol((const char *)row[3]);
      seqid = atol((const char *)row[4]);
      op = atol((const char *)row[5]);

      /* printf ("Serial: %ld; Atlast: %d; Objid: %ld; Seqid: %ld; Op: %d; Tablename: %s\n",serial, atlast, objid, seqid, op, tablename); */

      free(tablename);

      /* For crashtests */
      code_location = 1;
      case_branch = 0;

      if (atlast == IN_FAILED_TRANSACTION_TABLE)
	{

	  /* The serial points to a failed transaction */

	  /* Checkpointing: if recovering, delete from archive */
	  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_ARCHIVE);

	  /* Support for deletion of dummy objects (that happens when a DEL+ADD
	     updates a dummy object in the DB): the deletion goes to the
	     failed_transaction table, but we want to put it into the history_archive
	     with the correct object_id and sequence_id */
	  if (objid != 0)
	    {

	      /* Archive serial with sequence_id = 1 instead of 0 */
	      archive_serial(serial, objid, seqid+1, op);

	      /* Archive the object from the failed transaction table */
	      archive_failed_transaction(serial);

	      /* Update the object in history_archive with the correct objid
		 and seqid = 1 */
	      update_history_archive_object_id_and_sequence_id (serial, objid, seqid+1);

	      /* Update prev_serial of the corresponding ADD entry */
	      if (!update_prev_serial_of_object(objid, seqid+2, serial, "history"))
		{ update_prev_serial_of_object(objid, seqid+2, serial, "last"); }

	    }
	  else
	    {

	      /* Archive serial */
	      archive_serial(serial, objid, seqid, op);
	      
	      /* Archive failed transaction */
	      archive_failed_transaction(serial);

	    }

	  /* Checkpointing: if recovering, delete from the live DB - all has been archived */
	  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_LIVE);

	  /* Delete serial */
	  delete_serial_entry(serial);

	  /* Delete failed transaction */
	  delete_failed_transaction_entry(serial);

	  /* Done */
	  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DONE);

	}
      else /* atlast == (IN_LAST_TABLE || IN_HISTORY_TABLE) */
	{

	  if (op == OP_DELETE)
	    {

	      /* Then it must be in the history */

	      if (debug) printf ("Deleted serial. Objid: %ld, seqid: %ld, serial: %ld\n",objid, seqid, serial);

	      /* We need to update the prev_serial of the next element, if there is one...
	       * This compensates for UPD = DEL + ADD; the ADD is treated with the same
	       * object_id and sequence_id++ */
	      if (!update_prev_serial_of_next_object(objid, seqid, serial, "history")) 
		update_prev_serial_of_next_object(objid, seqid, serial, "last");

	      /* Checkpointing: if recovering, delete from archive */
	      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_ARCHIVE);
	      
	      /* Archive serial */
	      archive_serial(serial, objid, seqid, op);

	      /* XXX Fixme: no timestamp is archived if this DEL is part of a DEL+ADD .
	       * This could be solved by fetching the timestamp from the next
	       * sequence_id (which is the corresponding UPD), if existent.
	       */

	      /* Fetch timestamp from the corresponding empty last entry */
	      /* We need that for deleted objects; the actual entry is in the history,
	       * but the timestamp of the deletion is in the (otherwise empty) last entry */
	      timestamp = fetch_timestamp_from_last(objid, seqid);

	      /* printf ("Timestamp for serial %ld: %ld\n",serial, timestamp); */

	      /* Archive history:
	       * we need a special function here because we need to archive
	       * history.serial as history_archive.prev_serial .
	       */
	      copy_deleted_object_into_history_archive(serial, objid, seqid, "history");

	      /* Update history archive with correct timestamp */
	      /* XXX We don't really need a function which also updates the seq_id */
	      update_history_archive_sequence_id_and_timestamp(serial, seqid, timestamp);

	      /* Checkpointing: if recovering, delete from the live DB - all has been archived */
	      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_LIVE);

	      /* Delete serial */
	      delete_serial_entry(serial);
	      
	      /* Delete corresponding empty last entry: it has a seq_id of 0 */
	      /* It must only do so if the entry to be deleted is not the
		 highest object_id */
	      /* This will have no effect for DEL+ADD operations,
		 but no harm is done if left so (sequence_id = 0 only for the empty entries) */
	      if (objid < highest_objid)
		{
		  if (debug) printf ("Deleting empty entry in last table: object_id = %ld\n",objid);
		  delete_last_entry(objid, 0);
		}

	      /* Delete history entry */
	      delete_history_entry(objid, seqid);

	      /* Done */
	      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DONE);

	      /*	      if ((crashtest) && (serial == crashing_serial)) die;*/

	    }
	  else /* It is an update */
	    {

	      if (atlast == IN_LAST_TABLE )
		{

		  /* Checkpointing: if recovering, delete from archive */
		  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_ARCHIVE);
	      
		  /* Archive serial */
		  archive_serial(serial, objid, seqid, op);

		  /* Archive last */
		  archive_last(serial, objid, seqid);

		  /* Update serial element of the entry in last table */
		  update_serial_in_last(objid, seqid, serial);

		  /* Checkpointing: if recovering, delete from the live DB - all has been archived */
		  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_LIVE_ONLY_SERIAL);

		  /* Delete serial */
		  delete_serial_entry(serial);

		  /* !!!Do not delete the "last" entry!!! */

		  /* Done */
		  update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DONE);

		}
	      else /* atlast == IN_HISTORY_TABLE */
		{

		  /* We check for the next object, in order to update
		   * its prev_serial. We first look in the history table,
		   * then in the last table, otherwise there is no such object
		   * => the following update is in fact a deletion...
		   */

		  if (check_if_next_is_deletion(objid, seqid) == 0)
		    {

		      /* We are dealing with a last-update-before-deletion */

		      /* update_prev_serial_of_next_object() returns the number of
		       * affected rows: this shows us if the operation has been successful
		       * or not */
		      if (!update_prev_serial_of_next_object(objid, seqid, serial, "history")) 
			update_prev_serial_of_next_object(objid, seqid, serial, "last");

		      /* Checkpointing: if recovering, delete from archive */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_ARCHIVE);
	      
		      /* Archive serial */
		      archive_serial(serial, objid, seqid, op);

		      /* Archive history */
		      archive_history(serial, objid, seqid);

		      /* Checkpointing: if recovering, delete from the live DB - all has been archived */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_LIVE);

		      /* Delete serial */
		      delete_serial_entry(serial);

		      /* Delete history */
		      delete_history_entry(objid, seqid);
		      
		      /* Done */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DONE);

		    }
		  else /* This is the one always executed */
		    {

		      case_branch = 6;

		      /* Checkpointing: if recovering, delete from archive */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_ARCHIVE);
	      
		      /* Archive serial */
		      archive_serial(serial, objid, seqid, op);

		      /* Archive history */
		      archive_history(serial, objid, seqid);

		      /* Update serial in -current- history entry */
		      update_serial_in_history(objid, seqid, serial);

		      /* Checkpointing: if recovering, delete from the live DB - all has been archived */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DELETE_FROM_LIVE_ONLY_SERIAL);

		      /* Delete serial */
		      delete_serial_entry(serial);

		      /* Do not delete current history entry! It will be needed
			 by the deleting serial */

		      /* Done */
		      update_hs_auxiliary_checkpoint(serial, objid, seqid, atlast, CHKP_DONE);

		    }

		}

	    }

	}

      /* Unlock relevant tables */
      unlock_all_tables();

    }

  SQ_free_result(result);

  return(nserials);

} /* archive_serials_and_history() */



/****
 *
 * fetch_timestamp_from_last()
 * Get the timestamp of a specific (object_id, sequence_id)
 * from the last table
 *
 ****/

long fetch_timestamp_from_last (long obj_id, long seq_id)
{

  long timestamp = 0;
  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;

  sprintf (query, "SELECT timestamp FROM last WHERE object_id = %ld AND sequence_id = %ld", 
	   obj_id, seq_id);

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL)
    timestamp = atol((const char *)row[0]);

  SQ_free_result(result);

  return(timestamp);

} /* fetch_timestamp_from_last() */



/****
 *
 * get_highest_object_id()
 * Get the highest object_id in the last table.
 *
 ****/

long get_highest_object_id()
{

  long highest_objid = 0;
  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;

  sprintf (query, "SELECT max(object_id) FROM last"); 

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL)
    highest_objid = atol((const char *)row[0]);

  SQ_free_result(result);

  return(highest_objid);
  

} /* get_highest_object_id() */


/****
 *
 * check_if_next_is_deletion()
 * This functions checks if there is a row in the serials
 * table with same obj_id and seq_id, but a delete operation.
 * This would mean that we are dealing with a last-update-before-deletion.
 *
 ****/

int check_if_next_is_deletion (long obj_id, long seq_id)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long serial = 0;

  sprintf (query, "SELECT serial_id, atlast FROM serials 
			WHERE object_id = %ld AND sequence_id = %ld AND operation = %d", 
	   obj_id, seq_id, OP_DELETE);

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL)
    serial = atol((const char *)row[0]);

  SQ_free_result(result);

  return(serial);

} /* check_if_next_is_deletion() */





/*** Table creation ***/

/****
 * 
 * create_archive_tables():
 * Create tables for history and serials archives
 * if they do not exist 
 *
 ****/

int create_archive_tables()
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, 
	   "CREATE TABLE history_archive (
		object_id int(10) unsigned DEFAULT '0' NOT NULL,
		sequence_id int(10) unsigned DEFAULT '0' NOT NULL,
		serial int(11) DEFAULT '0' NOT NULL,
		prev_serial int(11) DEFAULT '0' NOT NULL,
		timestamp int(10) unsigned DEFAULT '0' NOT NULL,
		object_type tinyint(3) unsigned DEFAULT '0' NOT NULL,
		object longblob NOT NULL,
		pkey varchar(64) default '' NOT NULL,
		PRIMARY KEY (object_id,sequence_id,serial),
		INDEX (serial)
		);");
  create_table(cmd);


  sprintf (cmd, 
	   "CREATE TABLE serials_archive (
		serial_id int(11) DEFAULT '0' NOT NULL,
		object_id int(10) unsigned DEFAULT '0' NOT NULL,
		sequence_id int(10) unsigned DEFAULT '0' NOT NULL,
		operation tinyint(4) unsigned DEFAULT '0' NOT NULL,
		PRIMARY KEY (serial_id)
		);");
  create_table(cmd);

  return(0);

} /* create_archive_tables() */



/**** 
 * 
 * create_table()
 * This function wraps a table creation (which must be already
 * specified in cmd), and only issues a warning if the table
 * already exists.
 *
 ****/

int create_table (const char *cmd)
{

  int state;

  state = SQ_execute_query(connection, cmd, NULL);
  if (state != 0)
    {
      /* XXX is ER_TABLE_EXISTS_ERROR mysql-bounded? */
      if (SQ_errno(connection) == ER_TABLE_EXISTS_ERROR)
	{ 
	  /* Don't die if a table already exists */
	  fprintf (stderr,"Warning: %s\n",SQ_error(connection));
	  return (state);
	}
      else
	{
	  fprintf (stderr,"Fatal: %s\n",SQ_error(connection));
	  die;
	}
    }

  return(0);

} /* create_table() */





/*** Object update ***/

/****
 *
 * update_history_archive_sequence_id_and_timestamp()
 *
 ****/

int update_history_archive_sequence_id_and_timestamp (long ser_id, long new_seq_id, long new_timestamp)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE history_archive
		SET timestamp = %ld, sequence_id = %ld
		WHERE serial = %ld",
	   new_timestamp, new_seq_id, ser_id);

  return (execute_sql_command(cmd));

} /* update_history_archive_sequence_id_and_timestamp() */



/****
 *
 * update_history_archive_object_id_and_sequence_id()
 *
 ****/

int update_history_archive_object_id_and_sequence_id (long ser_id, long new_obj_id, long new_seq_id)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE history_archive
		SET object_id = %ld, sequence_id = %ld
		WHERE serial = %ld",
	   new_obj_id, new_seq_id, ser_id);

  return (execute_sql_command(cmd));

} /* update_history_archive_object_id_and_sequence_id() */



/****
 *
 * update_prev_serial_of_object()
 *
 ****/

int update_prev_serial_of_object (long obj_id, long seq_id, long prev_ser, const char *tablename)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE %s 
		SET prev_serial = %ld 
		WHERE object_id = %ld
		AND sequence_id = %ld",
	   tablename, prev_ser, obj_id, seq_id);

  return(execute_sql_command(cmd));

} /* update_prev_serial_of_object() */



/****
 *
 * update_serial_of_object()
 *
 ****/

int update_serial_of_object (long obj_id, long seq_id, long ser_id, const char *tablename)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE %s 
		SET serial = %ld 
		WHERE object_id = %ld
		AND sequence_id = %ld",
	   tablename, ser_id, obj_id, seq_id);

  return(execute_sql_command(cmd));

} /* update_serial_of_object() */





/*** Object archiving ***/

/****
 *
 * archive_serial()
 *
 ****/

int archive_serial (long ser_id, long obj_id, long seq_id, int op)
{

  /* Put the given values into the serials_archive table */

  char cmd[MAXCMDLEN];

  sprintf (cmd, "INSERT INTO serials_archive (serial_id, object_id, sequence_id, operation)
		 VALUES (%ld, %ld, %ld, %d)",
	   ser_id, obj_id, seq_id, op);

  return (execute_sql_command(cmd));

} /* archive_serial() */



/****
 *
 * archive_failed_transaction()
 *
 ****/

int archive_failed_transaction (long ser_id)
{

  char cmd[MAXCMDLEN];
  
  sprintf (cmd,"INSERT INTO history_archive (serial, timestamp, object)
		SELECT failed_transaction.serial_id, failed_transaction.timestamp, failed_transaction.object
		FROM failed_transaction
		WHERE failed_transaction.serial_id = %ld",
	   ser_id);

  return (execute_sql_command(cmd));

} /* archive_failed_transaction() */



/****
 *
 * copy_into_history_archive()
 * Generic function that works both for last and history tables
 *
 ****/

int copy_into_history_archive (long ser_id, long obj_id, long seq_id, const char *tablename)
{

  char cmd[MAXCMDLEN];
  
  sprintf (cmd,"INSERT INTO history_archive (object_id, sequence_id, serial, prev_serial, timestamp, object_type, object, pkey)
		SELECT serials.object_id, serials.sequence_id, serials.serial_id, %s.prev_serial, %s.timestamp, %s.object_type, %s.object, %s.pkey
		FROM serials,%s 
		WHERE serials.serial_id = %ld 
		AND %s.object_id = %ld 
		AND %s.sequence_id = %ld",
	   tablename, tablename, tablename, tablename, tablename, tablename, ser_id, tablename, obj_id, tablename, seq_id);

  return (execute_sql_command(cmd));

} /* copy_into_history_archive() */



/****
 *
 * copy_deleted_object_into_history_archive()
 * The difference here is that we archive the history.serial as history_archive.prev_serial .
 * This is only needed for history objects corresponding to OP_DELETE,
 * where the row actually corresponds to the last update before the deletion.
 *
 ****/

int copy_deleted_object_into_history_archive (long ser_id, long obj_id, long seq_id, const char *tablename)
{

  char cmd[MAXCMDLEN];
  int affected_rows;

  sprintf (cmd,"INSERT INTO history_archive (object_id, sequence_id, serial, prev_serial, timestamp, object_type, object, pkey)
		SELECT serials.object_id, serials.sequence_id, serials.serial_id, %s.serial, %s.timestamp, %s.object_type, %s.object, %s.pkey
		FROM serials,%s 
		WHERE serials.serial_id = %ld 
		AND %s.object_id = %ld 
		AND %s.sequence_id = %ld",
	   tablename, tablename, tablename, tablename, tablename, tablename, ser_id, tablename, obj_id, tablename, seq_id);

  affected_rows = execute_sql_command(cmd);
  if (debug) printf ("copy_deleted_object_into_history_archive (%ld, %ld, %ld, %s): affected rows %d\n",ser_id,obj_id,seq_id,tablename,affected_rows);
  return (affected_rows);

} /* copy_deleted_object_into_history_archive() */





/*** Object deletion ***/

/****
 *
 * delete_serial_entry()
 *
 ****/

int delete_serial_entry (long ser_id)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "DELETE FROM serials WHERE serial_id = %ld", ser_id);

  return (execute_sql_command(cmd));

} /* delete_serial_entry() */



/****
 *
 * delete_failed_transaction_entry()
 *
 ****/

int delete_failed_transaction_entry (long ser_id)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "DELETE FROM failed_transaction WHERE serial_id = %ld",ser_id);

  return (execute_sql_command(cmd));

} /* delete_failed_transaction_entry() */



/****
 *
 * delete_entry_from_object_table()
 * Works both for last and history tables
 *
 ****/

int delete_entry_from_object_table (long obj_id, long seq_id, const char* tablename)
{

  char cmd[MAXCMDLEN];
  int affected_rows;

  if (debug) printf ("Deleting %s entry. Objid: %ld, seqid: %ld\n",tablename, obj_id, seq_id);

  sprintf (cmd, "DELETE FROM %s WHERE object_id = %ld AND sequence_id = %ld",
	   tablename, obj_id, seq_id);

  affected_rows = execute_sql_command(cmd);
  if (debug) printf ("delete_entry_from_object_table (%ld, %ld, %s): affected rows %d\n",obj_id,seq_id,tablename,affected_rows);
  return (affected_rows);

} /* delete_entry_from_object_table() */



/****
 *
 * delete_archived_objects()
 *
 ****/

int delete_archived_objects(long ser_id)
{

  delete_serial_archive_entry(ser_id);
  delete_history_archive_entry(ser_id);

  return(0);

} /* delete_archived_object() */


/****
 *
 * delete_serial_archive_entry()
 *
 ****/

int delete_serial_archive_entry (long ser_id)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "DELETE FROM serials_archive WHERE serial_id = %ld", ser_id);

  return (execute_sql_command(cmd));

} /* delete_serial_archive_entry() */



/****
 *
 * delete_history_archive_entry()
 *
 ****/

int delete_history_archive_entry (long ser_id)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "DELETE FROM history_archive WHERE serial = %ld", ser_id);

  return (execute_sql_command(cmd));

} /* delete_history_archive_entry() */






/*** Handling of older, unreferenced history entries ***/

/****
 *
 * find_unreferenced_history_entries()
 * Deal with very old history entries, those that are not referenced by any serial,
 * due to a previous history/serial cleanup.
 * These are entries which had been archived when they were in the "last" table,
 * and have in the meanwhile been updated. We have to:
 *    - Update prev_serial of their next object
 *    - Delete them: this is done by delete_unreferenced_history_entries()
 *
 ****/

tblObjList *find_unreferenced_history_entries(long date)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long objid, seqid, serid;

  tblObjList *curListElmt = NULL;
  tblObjList *firstListElmt = NULL;
  tblObjList *tmpList = NULL;

  /* Find object_id, sequence_id of unreferenced history entries
   * This query returns all history entries which do not have a corresponding
   * (object_id, serial_id) in the serials table
   */
  /* XXX Bug! This will find (and then remove) objects that would be
   * needed in the future by deletions. */

  sprintf (query, "SELECT history.object_id, history.sequence_id, history.serial
			FROM history LEFT JOIN serials
			ON history.serial = serials.serial_id
			WHERE serials.serial_id is NULL
			AND history.serial != 0
			AND history.timestamp < %ld", date);
  execute_sql_query(query, &result);

  /* Foreach entry: */
  while ( (row = SQ_row_next(result)) != NULL )
    {

      /* Lock tables in writing... */
      /* XXX We don't need to do it, we are not deleting the objects here! */
      /* lock_last_history_serial_tables(); */

      /* XXX Error checking missing... */
      objid = atol((const char *)row[0]);
      seqid = atol((const char *)row[1]);
      serid = atol((const char *)row[2]);

      /* Update prev_serial of the same object with next sequence_id */
      if (!update_prev_serial_of_next_object(objid, seqid, serid, "history"))
	{ update_prev_serial_of_next_object(objid, seqid, serid, "last"); }

      /* Delete the entry */
      if (debug) printf ("I am deleting this entry: %ld, %ld\n",objid, seqid);

      /* Don't delete the history entries directly! This will cause problems
	 if the next serial is a deletion */
      /* Instead, add it in a list that will be batch-deleted at the end */
      /* PushTblObjList (objid, seqid, curListElmt); */
      
      tmpList = (tblObjList *)malloc(sizeof(tblObjList));
      tmpList->objid = objid;
      tmpList->seqid = seqid;
      tmpList->next = NULL;

      if (firstListElmt == NULL)
	{
	  firstListElmt = tmpList;
	  curListElmt = tmpList;
	}
      else
	{
	  curListElmt->next = tmpList;
	  curListElmt = curListElmt->next;
	}

      /* Unlock tables... */
      /* unlock_all_tables(); */

    }

  /* printf ("Elements to be deleted:\n");
     for (curListElmt = firstListElmt; curListElmt != NULL; curListElmt = curListElmt->next)
     {
     printf ("objid: %ld, seqid: %ld\n", curListElmt->objid, curListElmt->seqid);
     } */ 

  SQ_free_result(result);

  return (firstListElmt);

} /* find_unreferenced_history_entries() */


/****
 *
 * delete_unreferenced_history_entries()
 *
 ****/

int delete_unreferenced_history_entries(tblObjList *objectsToDelete)
{

  tblObjList *tmpObj;

  if (debug) printf ("Elements to be deleted:\n");
  for (tmpObj = objectsToDelete; tmpObj != NULL; tmpObj = tmpObj->next)
    {
      lock_last_history_serial_tables();
      if (debug) printf ("objid: %ld, seqid: %ld\n", tmpObj->objid, tmpObj->seqid);
      delete_history_entry(tmpObj->objid, tmpObj->seqid);
      unlock_all_tables();
    } 

  return(0);

} /* delete_unreferenced_history_entries() */



/****
 *
 * PushTblObjList() 
 *
 ****/

/* XXX Fix this function! It is currently not used. */

int PushTblObjList(long objid, long seqid, tblObjList *curListElmt)
{

  tblObjList *tmpList;

  tmpList = (tblObjList *)malloc(sizeof(tblObjList));
  tmpList->objid = objid;
  tmpList->seqid = seqid;
  tmpList->next = NULL;

  if (curListElmt == NULL)
    {
      curListElmt = tmpList;
    }
  else
    {
      curListElmt->next = tmpList;
      /* curListElmt = tmpList; */
    }

  if (debug) printf ("Inside PushTblObjList: %ld, %ld\n", curListElmt->objid, curListElmt->seqid);

  return(0);

} /* PushTblObjList() */





/*** Handling of dummy objects ***/

/****
 *
 * delete_dummy_history_objects()
 * Deletes from history all "dummy" objects. They should not be
 * archived, being only a tweak of the software to allow mirroring
 *
 ****/

int delete_dummy_history_objects()
{

  char cmd[MAXCMDLEN];
  int rc;

  sprintf (cmd,"DELETE FROM history WHERE object_type = %d",OBJ_TYPE_DUMMY);

  lock_last_history_serial_tables();
  rc = execute_sql_command(cmd);
  unlock_all_tables();

  printf ("%d dummy history rows deleted.\n", rc);

  return(rc);

} /* delete_dummy_history_objects() */





/*** Interactions with SQL DB ***/

/****
 *
 * lock_last_history_serial_tables()
 *
 ****/

int lock_last_history_serial_tables()
{

  char cmd[MAXCMDLEN];

  /* No real choice - we must lock the tables in write mode */

  sprintf (cmd, "LOCK TABLES last WRITE, history WRITE, failed_transaction WRITE, serials WRITE, serials_archive WRITE, history_archive WRITE, hs_auxiliary WRITE");

  return (execute_sql_command(cmd));

} /* lock_last_history_serial_tables() */



/****
 *
 * unlock_all_tables()
 *
 ****/

int unlock_all_tables()
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "UNLOCK TABLES");

  return (execute_sql_command(cmd));

} /* unlock_all_tables() */



/****
 *
 * optimize_sql_table()
 *
 ****/

int optimize_sql_table(const char* tablename)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "OPTIMIZE TABLE %s", tablename);

  return (execute_sql_command(cmd));

} /* optimize_sql_table() */





/*** SQL interfaces ***/

/****
 *
 * execute_sql_query()
 * Returns a result in result_ptr;
 * the return code is the state.
 *
 ****/

int execute_sql_query (const char *query, SQ_result_set_t **result_ptr)
{

  int state;

  state = SQ_execute_query(connection, query, result_ptr);
  if (state != 0)
    {
      fprintf (stderr, "Fatal:\n Offending query: %s\n Error: %s\n",query,SQ_error(connection));
      die;
    }

  return(state);

} /* execute_sql_query() */



/****
 *
 * execute_sql_command()
 * Does not return any result;
 * the return code is the number of affected rows.
 *
 ****/

int execute_sql_command (const char *cmd)
{

  int state;

  state = SQ_execute_query(connection, cmd, NULL);
  if (state != 0)
    {
      fprintf (stderr, "Fatal:\n Offending command: %s\n Error: %s\n",cmd,SQ_error(connection));
      die;
    }

  return(SQ_get_affected_rows(connection));

} /* execute_sql_command() */





/*** Checkpointing ***/

/****
 * 
 * create_auxiliary_table()
 * This auxiliary table will record some checkpointing
 * data, in order to recover from crashes
 * and to help with the clenup of the older history tables
 *
 ****/

int create_auxiliary_table()
{

  int state;
  char cmd[MAXCMDLEN];

  sprintf (cmd,"CREATE TABLE hs_auxiliary (
		serial int(11) DEFAULT '0' NOT NULL,
		object_id int(10) unsigned DEFAULT '0' NOT NULL,
		sequence_id int(10) unsigned DEFAULT '0' NOT NULL,
		atlast tinyint(4) unsigned DEFAULT '0' NOT NULL,
		checkpoint tinyint(4) unsigned DEFAULT '0' NOT NULL,
		PRIMARY KEY (serial)
		);");
  state = create_table(cmd);
  if (state == 0) /* state != 0 only if the table already exists - other errors make the program die */
    {
      fprintf (stderr,"Table created. Inserting dummy objects.\n");
      /* We also need to create a dummy row if the table had not been created */
      sprintf (cmd,"INSERT INTO hs_auxiliary VALUES (0, 0, 0, 0, 0)");
      execute_sql_command(cmd);
    }

  return(0);

} /* create_auxiliary_table() */



/****
 * 
 * reset_auxiliary_table()
 *
 ****/

int reset_auxiliary_table()
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE hs_auxiliary SET
		serial = 0, object_id = 0, sequence_id = 0, atlast = 0, checkpoint = 0");

  return(execute_sql_command(cmd));

} /* reset_auxiliary_table() */



/****
 * 
 * drop_auxiliary_table()
 *
 ****/

int drop_auxiliary_table()
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"DROP TABLE IF EXISTS hs_auxiliary");
  return(execute_sql_command(cmd));
 
} /* drop_auxiliary_table() */



/****
 *
 * update_hs_auxiliary_checkpoint()
 *
 ****/

int update_hs_auxiliary_checkpoint(long ser_id, long obj_id, long seq_id, int atlast, int checkpoint)
{

  char cmd[MAXCMDLEN];

  sprintf (cmd,"UPDATE hs_auxiliary 
		SET serial = %ld,
		object_id = %ld,
		sequence_id = %ld,
		atlast = %d,
		checkpoint = %d",
	   ser_id, obj_id, seq_id, atlast, checkpoint);

  return (execute_sql_command(cmd));

} /* update_hs_auxiliary_checkpoint() */



/****
 *
 * crash_recovery()
 * Check if last time we crashed; if so, try to recover.
 *
 ****/

int crash_recovery()
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long serial, objid, seqid;
  int atlast, chkp;

 
  if (!exists_checkpointing_table())
    {
      /* The checkpointing table does not exist, there was no crash!
	 Create the table and back to work. */
      fprintf (stderr, "No auxiliary table found. Creating it...\n");
      create_auxiliary_table();
      return(0);
    }

  /* Otherwise, let's start recovering... */
  fprintf (stderr, "The auxiliary table exists! Start recovering...\n");

  sprintf(query, "SELECT serial, object_id, sequence_id, atlast, checkpoint FROM hs_auxiliary");

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      serial = atol((const char *)row[0]);
      objid = atol((const char *)row[1]);
      seqid = atol((const char *)row[2]);
      atlast = atoi((const char *)row[3]);
      chkp = atoi((const char *)row[4]);
      if (debug) fprintf (stderr,"DEBUG: Recovering from crash.\n It happened on serial %ld, object_id %ld, sequence_id %ld, table %s, checkpoint %d\n",
	      serial, objid, seqid, atlast_table[atlast], chkp);
    }
  else
    {
      /* The table is empty! Weird, but return */
      fprintf (stderr, "The checkpointing table exists but is empty!\n");
      drop_auxiliary_table();
      return(0);
    }

  /* Recover depending on what the checkpointing is */
  switch(chkp)
    {
    case CHKP_DELETE_FROM_ARCHIVE:
      /* Delete all the archived objects corresponding to that serial */
      if (debug) fprintf (stderr, "DEBUG: Deleting archived objects for serial %ld\n",serial);
      delete_archived_objects(serial);
      break;
    case CHKP_DELETE_FROM_LIVE:
      /* In this case, we have to delete the corresponding objects in the live DB */
      if (debug) fprintf (stderr, "DEBUG: Deleting serial entry %ld\n",serial);
      delete_serial_entry(serial);
      if (atlast == IN_FAILED_TRANSACTION_TABLE)
	{
	  if (debug) fprintf (stderr, "DEBUG: Deleting failed transaction entry for serial %ld\n",serial);
	  delete_failed_transaction_entry(serial);
	}
      else if (atlast != IN_LAST_TABLE) /* Should never happen, double-check */
	/* (It can actually only be in the history table) */
	{
	  if (debug) fprintf (stderr, "DEBUG: Deleting history entry for serial %ld\n",serial);
	  delete_entry_from_object_table(objid, seqid, atlast_table[atlast]);
	}
      else
	fprintf (stderr,"WARNING! Attempt to delete object from last table in crash-recovery\n");
      break;
    case CHKP_DELETE_FROM_LIVE_ONLY_SERIAL:
      if (debug) fprintf (stderr, "DEBUG: Deleting serial entry %ld\n",serial);
      delete_serial_entry(serial);
      break;
    case CHKP_NOOP:
    case CHKP_DONE:
    default:
      /* Do nothing */
      break;
    }

  reset_auxiliary_table();

  SQ_free_result(result);

  return(0);

} /* crash_recovery() */



/****
 *
 * exists_checkpointing_table()
 * Check if the checkpointing table exists.
 *
 ****/

int exists_checkpointing_table()
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
 
  sprintf(query, "SHOW tables LIKE 'hs_auxiliary'");

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      SQ_free_result(result);
      return(1);
    }
  else
    {
      SQ_free_result(result);
      return(0);
    }


} /*  exists_checkpointing_table() */



/****** Checkpointing - crash-recovery test functions ******/


/****
 * 
 * get_smallest_serial()
 *
 ****/

long get_smallest_serial()
{

  long smallest_serial = 0;

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;

  sprintf (query, "SELECT MIN(serial_id) FROM serials");

  execute_sql_query(query, &result);
  if ( (row = SQ_row_next(result)) != NULL )
    {
      smallest_serial = row[0] ? atol((const char *)row[0]) : 0;
      /* printf ("Smallest serial ID: %ld\n", smallest_serial); */
    }

  SQ_free_result(result);

  return(smallest_serial);
  

} /* get_smallest_serial() */



/****
 * 
 * get_random_number_in_range(int num1, int num2, int seed)
 * This function gets a random number in a specific range of numbers
 *
 ****/

int get_random_number_in_range(int num1, int num2, int seed)
{

  int randnum;
  int lonum, hinum, diff;
  double gauge;

  if (num1 < num2)
    {
      lonum = num1; hinum = num2;
    }
  else
    {
      lonum = num2; hinum = num1;
    }

  if (lonum == hinum)
    return(lonum);

  diff = hinum - lonum;

  if (debug) printf ("Diff: %d\n",diff);

  /* You need that - otherwise the same number is always cast */
  srand(seed);

  gauge = (double)rand() / RAND_MAX;

  if (debug) printf ("Gauge: %f\n",gauge);

  randnum = lonum + (int)floor((double)diff * gauge);
  
  if (debug) printf ("Randnum: %d\n",randnum);

  return(randnum);


} /* get_random_number_in_range() */



/****
 * 
 * do_crash()
 * Crash the program in a random part of the code
 *
 ****/

void do_crash(long crashserial, int is_last_step)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  long serial, objid, seqid;
  int atlast, chkp;

  /* crash_position has been defined randomly at the beginning of the crashtest */
  if ((code_location++ >= crash_position) || (is_last_step == IS_LAST_STEP))
    /* Crash - no mercy! */
    {
      if (debug) fprintf (stderr, "***Crashing***\nSerial: %ld, Code location: %d; crash position: %d; case branch: %d\n",crashserial, code_location, crash_position, case_branch);
      /* debug stuff, to check if what was written in the checkpointing table is OK */
      sprintf(query, "SELECT serial, object_id, sequence_id, atlast, checkpoint FROM hs_auxiliary");
      
      execute_sql_query(query, &result);
      if ( (row = SQ_row_next(result)) != NULL )
	{
	  serial = atol((const char *)row[0]);
	  objid = atol((const char *)row[1]);
	  seqid = atol((const char *)row[2]);
	  atlast = atoi((const char *)row[3]);
	  chkp = atoi((const char *)row[4]);
	  if (debug) fprintf (stderr,"In auxiliary table: serial %ld, object_id %ld, sequence_id %ld, table %d, checkpoint %d\n",
			      serial, objid, seqid, atlast, chkp);
	}
      else
	{
	  /* The table is empty! Weird, but return */
	  fprintf (stderr, "The checkpointing table exists but is empty!\n");
	}
      
      SQ_free_result(result);

      fflush(stdout);
      sleep(3);
      fflush(stderr);
      sleep(3);
      die;
    }

} /* do_crash() */
