/***************************************
  $Revision: 1.22 $

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

  Status: NOT COMPLETE, NOT REVUED, NOT FULLY TESTED

  ******************/ /******************
  Filename            : hs_cleanup.c
  Authors             : daniele@ripe.net
  OSs Tested          : Solaris 7
  ******************/ /******************
  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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "mysql_driver.h"
#include "stubs.h"



#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

#define MAXCMDLEN 8192
#define MAXNUMCMDS 65536

#define DATE 966050936

/* Set the default lapse before which we want to archive:
 * it must be in seconds.
 * 1min = 60s
 * 1hr = 3600s
 * 1day = 86400s
 * 1wk = 604800s
 */

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

#define DEBUG 1
#define ARCHIVE_ONLY 1


enum {
  IN_HISTORY_TABLE = 0,
  IN_LAST_TABLE,
  IN_FAILED_TRANSACTION_TABLE,
  IN_UNDEF_TABLE
};

enum {
  OP_NULL = 0,
  OP_ADD,
  OP_DELETE,
  OP_UPDATE
};


enum {
  CHKP_NOOP = 1,
  CHKP_ARCHIVED_SERIAL,
  CHKP_ARCHIVED_HISTORY,
  CHKP_DELETED_HISTORY,
  CHKP_DELETED_SERIAL,
  CHKP_DONE
};

typedef struct table_object *tblobjPtr;

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


/* Global variables */

SQ_connection_t *connection;
int debug = DEBUG;
int archive_only = ARCHIVE_ONLY;

/* Function definitions */

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

static tblObjList *find_unreferenced_history_entries(int date);
static int delete_unreferenced_history_entries(tblObjList *objectsToDelete);
static int create_auxiliary_table();
static int create_archive_tables();
static int create_table(const char *cmd);
static int get_highest_serial_before_date(int *highest_serial_ptr, int date);
static int archive_serials_and_history(int highest_serial);
static int archive_serial(int ser_id, int obj_id, int seq_id, int op);
static int archive_failed_transaction(int ser_id);
static int copy_into_history_archive(int ser_id, int obj_id, int seq_id, const char *tablename);
static int copy_deleted_object_into_history_archive (int ser_id, int obj_id, int seq_id, const char *tablename);
static int update_history_sequence_id_and_timestamp(int ser_id, int new_seq_id, int new_timestamp);
static int delete_serial_entry(int ser_id);
static int delete_failed_transaction_entry (int ser_id);
static int delete_entry_from_object_table(int obj_id, int seq_id, const char* tablename);
static int fetch_timestamp_from_last(int obj_id, int ser_id);
static int execute_sql_command(const char *cmd);
static int execute_sql_query(const char *query, SQ_result_set_t **result_ptr);
static int check_if_next_is_deletion (int obj_id, int seq_id);
static int update_prev_serial_of_object (int obj_id, int seq_id, int prev_ser, const char *tablename);
static int update_serial_of_object (int obj_id, int seq_id, int ser_id, const char *tablename);
static int lock_last_history_serial_tables();
static int unlock_all_tables();
static int optimize_sql_table(const char* tablename);
static int update_hs_auxiliary_checkpoint(int ser_id, int obj_id, int seq_id, int timestamp, int checkpoint);



#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")
#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")
#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")



/* XXX Fixme:
   - No more hardcoded variables
   - Subroutine (+option) to warn that the size is bigger than a watermark
   - Checkpointing for crash recovery
   */



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

  int ch, rc;
  int 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;
  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); }

  /* Create auxiliary table if it does not exist */
  if ((rc = create_auxiliary_table()) != 0)
    { return(rc); }

  /* XXX Call remadmin interface and stop updates */


  /* XXX If retcode is successful, go on */

  /* 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!
   */

  /* XXX Not fully tested: some code must be updated in UD to support the extra columns
     serial and prev_serial */
  objectsToDelete = find_unreferenced_history_entries((int) date);

  /* printf ("Elements to be deleted:\n");
     for (tmpObj = objectsToDelete; tmpObj != NULL; tmpObj = tmpObj->next)
     {
     printf ("objid: %d, seqid: %d\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, (int) date)) != 0)
    { return(rc); }
  printf ("Highest serial ID: %d\n",highest_serial);
  

  /* Execute the archiving commands */

  archive_serials_and_history(highest_serial);

  /* Optimize history serial and last tables: there might have been many deletions */
  optimize_sql_table("serials");
  optimize_sql_table("history");
  optimize_sql_table("last");

  /* XXX Call remadmin interface and restart updates */


  /* XXX If retcode is not successful, go on, 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. */
  if (! archive_only)
    delete_unreferenced_history_entries(objectsToDelete);

  /* OK, it's over. */

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

} /* main() */





/* Functions */


/* 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,
		PRIMARY KEY (object_id,sequence_id,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_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,
		timestamp int(10) 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 */
    {
      /* 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() */




/* 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));
	  dieif (state != 0);
	}
    }

  return(0);

} /* create_table() */




/* 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!
 */

tblObjList *find_unreferenced_history_entries(int date)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  int 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 < %d", 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 = atoi((const char *)row[0]);
      seqid = atoi((const char *)row[1]);
      serid = atoi((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 */
      printf ("I am deleting this entry: %d, %d\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: %d, seqid: %d\n", curListElmt->objid, curListElmt->seqid);
     } */ 

  return (firstListElmt);

  return (0);

} /* find_unreferenced_history_entries() */


int delete_unreferenced_history_entries(tblObjList *objectsToDelete)
{

  tblObjList *tmpObj;

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

  return(0);

}


/* PushTblObjList() */

int PushTblObjList(int objid, int 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; */
    }

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

  return(0);

} /* PushTblObjList() */


/* 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 (int *highest_serial_ptr, int date)
{

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

  /* sprintf (query, "SELECT MAX(serials.serial_id) FROM history,serials 
			WHERE history.timestamp < %d 
			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 < %d 
				AND last.object_id = serials.object_id 
				AND last.sequence_id = last.sequence_id)
			OR (history.timestamp < %d 
				AND history.object_id = serials.object_id 
				AND history.sequence_id = serials.sequence_id))",
	   date, date);

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

  SQ_free_result(result);

  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.
 */

int archive_serials_and_history (int highest_serial)
{

  char query[MAXCMDLEN];
  SQ_result_set_t *result;
  SQ_row_t *row;
  int serial, atlast, objid, seqid, op;
  char *tablename;
  int timestamp;
  int rc;


  /* XXX Missing: crash recovery handling. */


  /* Get the entries for each serial */
  /* One word about the "<": I had "<=" but 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 < %d
			ORDER BY serials.serial_id", highest_serial);
  execute_sql_query(query, &result);

  /* Loop on every serial */
  while ( (row = SQ_row_next(result)) != NULL )
    {

      /* 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 */
      if ((rc = lock_last_history_serial_tables()) != 0)
	{ return rc; }

      /* XXX Add stronger error checking: NULL rows should never happen */
      serial = row[0] ? atoi((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 = atoi((const char *)row[3]);
      seqid = atoi((const char *)row[4]);
      op = atoi((const char *)row[5]);

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

      update_hs_auxiliary_checkpoint(serial, objid, seqid, 0, CHKP_NOOP);

      if (atlast == IN_FAILED_TRANSACTION_TABLE)
	{

	  /* The serial points to a failed transaction */

	  /* Archive serial */
	  archive_serial(serial, objid, seqid, op);
	  update_hs_auxiliary_checkpoint(serial, objid, seqid, 0, CHKP_ARCHIVED_SERIAL);

	  /* Archive failed transaction */
	  archive_failed_transaction(serial);

	  /* Delete serial */
	  delete_serial_entry(serial);

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


	}
      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: %d, seqid: %d, serial: %d\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");

	      /* 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 */
	      timestamp = fetch_timestamp_from_last(objid, seqid);

	      /* printf ("Timestamp for serial %d: %d\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_sequence_id_and_timestamp(serial, seqid, timestamp);


	      /* Delete serial */
	      delete_serial_entry(serial);
	      
	      /* Delete corresponding empty last entry: it has a seq_id of 0 */
	      /* XXX It must only do so if the entry to be deleted is not the
		 highest object_id */
	      /* To be fixed */
	      /* delete_last_entry(objid, 0); */

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


	    }
	  else /* It is an update */
	    {

	      if (atlast == IN_LAST_TABLE )
		{

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

		  /* Delete serial */
		  delete_serial_entry(serial);

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

		}
	      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 == 0)
		    {

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

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

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

		      /* Delete serial */
		      delete_serial_entry(serial);

		      /* Delete history */
		      delete_history_entry(objid, seqid);
		      
		    }
		  else
		    {

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

		      /* Delete serial */
		      delete_serial_entry(serial);

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

		    }

		}

	    }

	}

      /* Unlock relevant tables */
      unlock_all_tables();

    }

  SQ_free_result(result);



  return(0);

} /* archive_serials_and_history() */






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



int unlock_all_tables()
{

  char cmd[MAXCMDLEN];

  sprintf (cmd, "UNLOCK TABLES");

  return (execute_sql_command(cmd));

} /* unlock_all_tables() */


int update_hs_auxiliary_checkpoint(int ser_id, int obj_id, int seq_id, int timestamp, int checkpoint)
{

  char cmd[MAXCMDLEN];

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

  return (execute_sql_command(cmd));

} /* update_hs_auxiliary_checkpoint() */


int archive_serial (int ser_id, int obj_id, int 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 (%d, %d, %d, %d)",
	   ser_id, obj_id, seq_id, op);

  return (execute_sql_command(cmd));

} /* archive_serial() */




int archive_failed_transaction (int 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 = %d",
	   ser_id);

  return (execute_sql_command(cmd));

} /* archive_failed_transaction() */


int copy_into_history_archive (int ser_id, int obj_id, int 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)
		SELECT serials.object_id, serials.sequence_id, serials.serial_id, %s.prev_serial, %s.timestamp, %s.object_type, %s.object
		FROM serials,%s 
		WHERE serials.serial_id = %d 
		AND %s.object_id = %d 
		AND %s.sequence_id = %d",
	   tablename, tablename, tablename, tablename, tablename, ser_id, tablename, obj_id, tablename, seq_id);

  return (execute_sql_command(cmd));

} /* copy_into_history_archive() */



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

  /* 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. */

  char cmd[MAXCMDLEN];
  int affected_rows;

  sprintf (cmd,"INSERT INTO history_archive (object_id, sequence_id, serial, prev_serial, timestamp, object_type, object)
		SELECT serials.object_id, serials.sequence_id, serials.serial_id, %s.serial, %s.timestamp, %s.object_type, %s.object
		FROM serials,%s 
		WHERE serials.serial_id = %d 
		AND %s.object_id = %d 
		AND %s.sequence_id = %d",
	   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 (%d, %d, %d, %s): affected rows %d\n",ser_id,obj_id,seq_id,tablename,affected_rows);
  return (affected_rows);

} /* copy_deleted_object_into_history_archive() */



int update_history_sequence_id_and_timestamp (int ser_id, int new_seq_id, int new_timestamp)
{

  char cmd[MAXCMDLEN];

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

  return (execute_sql_command(cmd));

} /* update_history_sequence_id_and_timestamp() */



/* 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 (int obj_id, int seq_id)
{

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

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

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

  SQ_free_result(result);

  return(serial);

} /* check_if_next_is_deletion() */



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

  char cmd[MAXCMDLEN];

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

  return(execute_sql_command(cmd));

} /* update_prev_serial_of_object() */



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

  char cmd[MAXCMDLEN];

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

  return(execute_sql_command(cmd));

} /* update_serial_of_object() */



int delete_serial_entry (int ser_id)
{

  char cmd[MAXCMDLEN];

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

  return (execute_sql_command(cmd));

} /* delete_serial_entry() */


int delete_failed_transaction_entry (int ser_id)
{

  char cmd[MAXCMDLEN];

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

  return (execute_sql_command(cmd));

} /* delete_failed_transaction_entry() */



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

  char cmd[MAXCMDLEN];
  int affected_rows;

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

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

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

} /* delete_entry_from_object_table() */


int fetch_timestamp_from_last (int obj_id, int seq_id)
{

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

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

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

  SQ_free_result(result);

  return(timestamp);

}


int optimize_sql_table(const char* tablename)
{

  char cmd[MAXCMDLEN];

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

  return (execute_sql_command(cmd));

} /* optimize_sql_table() */


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

}



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

}




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

}


