List:Commits« Previous MessageNext Message »
From:Chuck Bell Date:October 18 2008 10:08pm
Subject:bzr commit into mysql-6.0-backup branch (cbell:2712) Bug#33364
View as plain text  
#At file:///C:/source/bzr/mysql-6.0-bug-33364/

 2712 Chuck Bell	2008-10-18
      BUG#33364 Online Backup: Purge statement missing 
      
      This patch adds the following commands:
      
      PURGE BACKUP LOGS;
      PURGE BACKUP LOGS TO <id>;
      PURGE BACKUP LOGS BEFORE <datetime>;
      PURGE BACKUP IMAGES <wildcard>;
      PURGE BACKUP IMAGES TO <id>;
      PURGE BACKUP IMAGES BEFORE <datetime>;
added:
  mysql-test/suite/backup/r/backup_logs_purge.result
  mysql-test/suite/backup/t/backup_logs_purge.test
modified:
  include/my_base.h
  sql/backup/backup_kernel.h
  sql/backup/kernel.cc
  sql/backup/stream.cc
  sql/backup/stream.h
  sql/lex.h
  sql/log.cc
  sql/log.h
  sql/mysqld.cc
  sql/share/errmsg.txt
  sql/sql_lex.h
  sql/sql_parse.cc
  sql/sql_yacc.yy
  storage/csv/ha_tina.cc

per-file messages:
  include/my_base.h
    Added property to allow delete from logs (table only).
  mysql-test/suite/backup/r/backup_logs_purge.result
    New result file.
  mysql-test/suite/backup/t/backup_logs_purge.test
    New test for purge commands.
  sql/backup/backup_kernel.h
    Added method is_backup_image() to API.
  sql/backup/kernel.cc
    Added new method to check if a file is a backup image.
  sql/backup/stream.cc
    Added suppression of bad magic number error message.
  sql/backup/stream.h
    Added method to allow suppression of bad magic number error for 
    is_backup_image() method.
  sql/lex.h
    Added new symbol 'IMAGES' for PURGE BACKUP commands.
  sql/log.cc
    Added methods to allow purging of backup logs and images.
  sql/log.h
    Added method declarations to allow purging of backup logs and images.
  sql/mysqld.cc
    Added new command enums for 'the big switch'.
  sql/share/errmsg.txt
    New error messages.
  sql/sql_lex.h
    Added additional values to store backup id and wildcard string specified
    on command.
    Added new SQLCOM enums for 'the big switch'.
  sql/sql_parse.cc
    Added code to execute the PURGE BACKUP commands.
  sql/sql_yacc.yy
    Added new commands to parser.
  storage/csv/ha_tina.cc
    Added code to allow delete from log tables.
=== modified file 'include/my_base.h'
--- a/include/my_base.h	2008-07-11 13:36:54 +0000
+++ b/include/my_base.h	2008-10-18 22:08:01 +0000
@@ -206,7 +206,8 @@ enum ha_extra_function {
   HA_EXTRA_IS_ATTACHED_CHILDREN,
   HA_EXTRA_DETACH_CHILDREN,
   HA_EXTRA_ORDERBY_LIMIT,
-  HA_EXTRA_NO_ORDERBY_LIMIT
+  HA_EXTRA_NO_ORDERBY_LIMIT,
+  HA_EXTRA_ALLOW_LOG_DELETE
 };
 
 /* Compatible option, to be deleted in 6.0 */

=== added file 'mysql-test/suite/backup/r/backup_logs_purge.result'
--- a/mysql-test/suite/backup/r/backup_logs_purge.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/r/backup_logs_purge.result	2008-10-18 22:08:01 +0000
@@ -0,0 +1,231 @@
+PURGE BACKUP LOGS;
+PURGE BACKUP IMAGES '%.bak';
+
+Now starting real tests
+
+DROP DATABASE IF EXISTS backup_logs;
+CREATE DATABASE backup_logs;
+Create table and populate with data.
+CREATE TABLE backup_logs.t1 (a char(30)) ENGINE=MYISAM;
+CREATE TABLE backup_logs.t2 (a char(30)) ENGINE=INNODB;
+CREATE TABLE backup_logs.t3 (a char(30)) ENGINE=MEMORY;
+INSERT INTO backup_logs.t1 VALUES ("01 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("02 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("03 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("04 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("05 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("06 Test #1 - progress");
+INSERT INTO backup_logs.t1 VALUES ("07 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("01 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("02 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("03 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("04 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("05 Test #1 - progress");
+INSERT INTO backup_logs.t2 VALUES ("06 Test #1 - progress");
+INSERT INTO backup_logs.t3 VALUES ("01 Test #1 - progress");
+INSERT INTO backup_logs.t3 VALUES ("02 Test #1 - progress");
+INSERT INTO backup_logs.t3 VALUES ("03 Test #1 - progress");
+INSERT INTO backup_logs.t3 VALUES ("04 Test #1 - progress");
+SET SESSION debug="d,set_backup_id";
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup1.bak';
+backup_id
+500
+SET SESSION debug="d";
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup2.bak';
+backup_id
+501
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup3.bak';
+backup_id
+502
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup4.bak';
+backup_id
+503
+Do restore of database
+RESTORE from 'backup1.bak';
+backup_id
+504
+Do restore of database
+RESTORE from 'backup2.bak';
+backup_id
+505
+Do restore of database
+RESTORE from 'backup3.bak';
+backup_id
+506
+Do restore of database
+RESTORE from 'backup4.bak';
+backup_id
+507
+Purge all backup images to 502.
+PURGE BACKUP IMAGES TO 502;
+Purge all backup images.
+PURGE BACKUP IMAGES '%.bak';
+Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+backup_id
+500
+501
+502
+503
+504
+505
+506
+507
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+backup_id
+500
+501
+502
+503
+504
+505
+506
+507
+SELECT count(*) FROM mysql.backup_history;
+count(*)
+8
+SELECT count(*) FROM mysql.backup_progress;
+count(*)
+36
+Purge all backup log data prior to id = 504.
+PURGE BACKUP LOGS TO 502;
+Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+backup_id
+502
+503
+504
+505
+506
+507
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+backup_id
+502
+503
+504
+505
+506
+507
+SELECT count(*) FROM mysql.backup_history;
+count(*)
+6
+SELECT count(*) FROM mysql.backup_progress;
+count(*)
+24
+Purge all backup log data.
+PURGE BACKUP LOGS;
+Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+backup_id
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+backup_id
+SELECT count(*) FROM mysql.backup_history;
+count(*)
+0
+SELECT count(*) FROM mysql.backup_progress;
+count(*)
+0
+PURGE BACKUP LOGS BEFORE 123123;
+ERROR HY000: The datetime specified is invalid for the 'PURGE BACKUP LOGS BEFORE' command.
+PURGE BACKUP IMAGES BEFORE 123123;
+ERROR HY000: The datetime specified is invalid for the 'PURGE BACKUP IMAGES BEFORE' command.
+Cleanup the logs and images for later testing.
+PURGE BACKUP LOGS;
+SET SESSION debug="d,set_backup_id";
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup1.bak';
+backup_id
+500
+SET SESSION debug="d";
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup2.bak';
+backup_id
+501
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup3.bak';
+backup_id
+502
+SET SESSION debug="d,set_start_time";
+Do backup of database
+BACKUP DATABASE backup_logs to 'backup4.bak';
+backup_id
+503
+SET SESSION debug="d";
+Do restore of database
+RESTORE from 'backup1.bak';
+backup_id
+504
+Do restore of database
+RESTORE from 'backup2.bak';
+backup_id
+505
+Do restore of database
+RESTORE from 'backup3.bak';
+backup_id
+506
+SET SESSION debug="d,set_start_time";
+Do restore of database
+RESTORE from 'backup4.bak';
+backup_id
+507
+SET SESSION debug="d";
+Display the results.
+SELECT backup_id FROM mysql.backup_history;
+backup_id
+500
+501
+502
+503
+504
+505
+506
+507
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+backup_id
+500
+501
+502
+503
+504
+505
+506
+507
+SET @now_time= sysdate();
+PURGE BACKUP IMAGES BEFORE @now_time;
+PURGE BACKUP LOGS BEFORE @now_time;
+SELECT backup_id FROM mysql.backup_history;
+backup_id
+503
+507
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+backup_id
+503
+507
+PURGE BACKUP LOGS;
+Perform backup
+SET SESSION debug="d,set_backup_id";
+BACKUP DATABASE backup_logs TO '../bup_logs_dir.bak';
+backup_id
+500
+SET SESSION debug="d";
+Ensure backup image file went to the correct location
+SELECT count(*) FROM mysql.backup_history;
+count(*)
+1
+SELECT count(*) FROM mysql.backup_progress;
+count(*)
+6
+PURGE BACKUP IMAGES TO 501;
+PURGE BACKUP LOGS TO 501;
+SELECT count(*) FROM mysql.backup_history;
+count(*)
+0
+SELECT count(*) FROM mysql.backup_progress;
+count(*)
+0
+DROP DATABASE backup_logs;
+PURGE BACKUP LOGS;

=== added file 'mysql-test/suite/backup/t/backup_logs_purge.test'
--- a/mysql-test/suite/backup/t/backup_logs_purge.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/t/backup_logs_purge.test	2008-10-18 22:08:01 +0000
@@ -0,0 +1,266 @@
+#
+# This test includes tests for ensuring the backup logs can be purged
+# of data.
+#
+
+--source include/have_log_bin.inc
+--source include/have_innodb.inc
+--source include/not_embedded.inc
+--source include/have_debug.inc
+
+PURGE BACKUP LOGS;
+PURGE BACKUP IMAGES '%.bak';
+
+--echo 
+--echo Now starting real tests
+--echo 
+
+# Create a database and some data to work with.
+
+--disable_warnings
+DROP DATABASE IF EXISTS backup_logs;
+--enable warnings
+CREATE DATABASE backup_logs;
+
+--echo Create table and populate with data.
+
+CREATE TABLE backup_logs.t1 (a char(30)) ENGINE=MYISAM;
+CREATE TABLE backup_logs.t2 (a char(30)) ENGINE=INNODB;
+CREATE TABLE backup_logs.t3 (a char(30)) ENGINE=MEMORY;
+
+INSERT INTO backup_logs.t1 VALUES ("01 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("02 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("03 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("04 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("05 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("06 Test #1 - progress"); 
+INSERT INTO backup_logs.t1 VALUES ("07 Test #1 - progress"); 
+
+INSERT INTO backup_logs.t2 VALUES ("01 Test #1 - progress"); 
+INSERT INTO backup_logs.t2 VALUES ("02 Test #1 - progress"); 
+INSERT INTO backup_logs.t2 VALUES ("03 Test #1 - progress"); 
+INSERT INTO backup_logs.t2 VALUES ("04 Test #1 - progress"); 
+INSERT INTO backup_logs.t2 VALUES ("05 Test #1 - progress"); 
+INSERT INTO backup_logs.t2 VALUES ("06 Test #1 - progress"); 
+
+INSERT INTO backup_logs.t3 VALUES ("01 Test #1 - progress"); 
+INSERT INTO backup_logs.t3 VALUES ("02 Test #1 - progress"); 
+INSERT INTO backup_logs.t3 VALUES ("03 Test #1 - progress"); 
+INSERT INTO backup_logs.t3 VALUES ("04 Test #1 - progress"); 
+
+#
+# Now test read of backupid with known id using debug insertion
+#
+SET SESSION debug="d,set_backup_id";
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup1.bak';
+
+SET SESSION debug="d";
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup2.bak';
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup3.bak';
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup4.bak';
+
+--echo Do restore of database
+RESTORE from 'backup1.bak';
+
+--echo Do restore of database
+RESTORE from 'backup2.bak';
+
+--echo Do restore of database
+RESTORE from 'backup3.bak';
+
+--echo Do restore of database
+RESTORE from 'backup4.bak';
+
+--file_exists $MYSQLTEST_VARDIR/master-data/backup1.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup2.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup3.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+--echo Purge all backup images to 502.
+PURGE BACKUP IMAGES TO 502;
+
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup1.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup2.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup3.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+--echo Purge all backup images.
+PURGE BACKUP IMAGES '%.bak';
+
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup1.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup2.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup3.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+--echo Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+SELECT count(*) FROM mysql.backup_history;
+SELECT count(*) FROM mysql.backup_progress;
+
+--echo Purge all backup log data prior to id = 504.
+PURGE BACKUP LOGS TO 502;
+
+--echo Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+SELECT count(*) FROM mysql.backup_history;
+SELECT count(*) FROM mysql.backup_progress;
+
+--echo Purge all backup log data.
+PURGE BACKUP LOGS;
+
+--echo Display results from backup logs
+SELECT backup_id FROM mysql.backup_history;
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+SELECT count(*) FROM mysql.backup_history;
+SELECT count(*) FROM mysql.backup_progress;
+
+#
+# Test purge before a datetime.
+#
+
+--error ER_BACKUP_PURGE_DATETIME
+PURGE BACKUP LOGS BEFORE 123123;
+
+--error ER_BACKUP_PURGE_DATETIME
+PURGE BACKUP IMAGES BEFORE 123123;
+
+--echo Cleanup the logs and images for later testing.
+PURGE BACKUP LOGS;
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup1.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup2.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup3.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+SET SESSION debug="d,set_backup_id";
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup1.bak';
+
+SET SESSION debug="d";
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup2.bak';
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup3.bak';
+
+SET SESSION debug="d,set_start_time";
+
+--echo Do backup of database
+BACKUP DATABASE backup_logs to 'backup4.bak';
+
+SET SESSION debug="d";
+
+--echo Do restore of database
+RESTORE from 'backup1.bak';
+
+--echo Do restore of database
+RESTORE from 'backup2.bak';
+
+--echo Do restore of database
+RESTORE from 'backup3.bak';
+
+SET SESSION debug="d,set_start_time";
+
+--echo Do restore of database
+RESTORE from 'backup4.bak';
+
+SET SESSION debug="d";
+
+--file_exists $MYSQLTEST_VARDIR/master-data/backup1.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup2.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup3.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+--echo Display the results.
+SELECT backup_id FROM mysql.backup_history;
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+
+#
+# We need to sleep to allow time to pass in order to ensure the time
+# compare method in log.cc has two different times to compare.
+#
+real_sleep 3;
+
+SET @now_time= sysdate();
+
+PURGE BACKUP IMAGES BEFORE @now_time;
+
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup1.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup2.bak
+--error 1
+--file_exists $MYSQLTEST_VARDIR/master-data/backup3.bak
+--file_exists $MYSQLTEST_VARDIR/master-data/backup4.bak
+
+PURGE BACKUP LOGS BEFORE @now_time;
+
+SELECT backup_id FROM mysql.backup_history;
+SELECT DISTINCT backup_id FROM mysql.backup_progress;
+
+PURGE BACKUP LOGS;
+
+#
+# Test purge of images for locations other than backupdir.
+#
+--echo Perform backup
+SET SESSION debug="d,set_backup_id";
+
+BACKUP DATABASE backup_logs TO '../bup_logs_dir.bak';
+
+SET SESSION debug="d";
+
+--echo Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/bup_logs_dir.bak
+
+SELECT count(*) FROM mysql.backup_history;
+SELECT count(*) FROM mysql.backup_progress;
+
+PURGE BACKUP IMAGES TO 501;
+
+--error 1
+--file_exists $MYSQLTEST_VARDIR/bup_backupdir2.bak
+
+PURGE BACKUP LOGS TO 501;
+
+SELECT count(*) FROM mysql.backup_history;
+SELECT count(*) FROM mysql.backup_progress;
+
+#
+# Cleanup.
+#
+DROP DATABASE backup_logs;
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup1.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup2.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup3.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/backup4.bak
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/bup_backupdir2.bak
+
+PURGE BACKUP LOGS;
+

=== modified file 'sql/backup/backup_kernel.h'
--- a/sql/backup/backup_kernel.h	2008-10-15 20:00:48 +0000
+++ b/sql/backup/backup_kernel.h	2008-10-18 22:08:01 +0000
@@ -32,6 +32,13 @@ void backup_shutdown();
 */
 int execute_backup_command(THD*, LEX*, String*);
 
+/*
+  Called from log.cc to check to see if a given file is a backup
+  image. Returns TRUE if file is a backup image, false if the
+  file cannot be read or is not a backup image.
+*/
+bool is_backup_image(THD *thd, String *path);
+
 // forward declarations
 
 class Backup_info;
@@ -81,6 +88,11 @@ class Backup_restore_ctx: public backup:
   int close();
 
   THD* thd() const { return m_thd; }
+
+  /*
+    Used by is_backup_image() to initialize memory.
+  */
+  void minimal_prepare();
 
  private:
 

=== modified file 'sql/backup/kernel.cc'
--- a/sql/backup/kernel.cc	2008-10-15 20:00:48 +0000
+++ b/sql/backup/kernel.cc	2008-10-18 22:08:01 +0000
@@ -324,6 +324,59 @@ int send_reply(Backup_restore_ctx &conte
                                   ? "BACKUP" : "RESTORE"));
 }
 
+/**
+  Check to see if path is a backup image file.
+
+  This method attempts to open the file specified and identify
+  it as a backup image file. The method is called from the 
+  purge_backup_image() method in log.cc. 
+  
+  @param[IN]  thd   The current thread.
+  @param[IN]  path  The full path+filename of the file to check.
+
+  @returns TRUE if file is a backup image, false if the
+           file cannot be read or is not a backup image.
+*/
+bool is_backup_image(THD *thd, String *path)
+{
+  using namespace backup;  
+  bool is_image= FALSE;
+
+  /*
+    Get a restore context and logger instance.
+  */
+  Backup_restore_ctx context(thd); // reports errors
+  Logger log= Logger(thd);
+
+  /*
+    Set context to a minimal set for open and reading magic numbers.
+  */
+  context.minimal_prepare();
+
+  /*
+    Attempt to open the stream. This will check the magic numbers and 
+    if successful return 0. Any other return value is an error of 
+    either cannot read or wrong file type. Either way, we don't want
+    (or can't) delete so return FALSE.
+  */
+  Input_stream *s= new Input_stream(log, path);
+  Stream *m_stream= s;
+  if (!s)
+    return is_image;
+
+  // Turn off the error for magic number failure (not a backup image).
+  s->suppress_errors_on_magic_number(TRUE);
+  
+  int my_open_status= s->open();
+  is_image= (my_open_status == 0);
+
+  /*
+    Cleanup. Play nice!
+  */
+  s->close();
+  context.close();
+  return is_image;
+}
 
 namespace backup {
 
@@ -542,8 +595,6 @@ int Backup_restore_ctx::prepare(String *
   */
   prepare_path(backupdir, location);
 
-  //m_path= location.str;
-
   // create new instance of memory allocator for backup stream library
 
   using namespace backup;
@@ -993,6 +1044,21 @@ int Backup_restore_ctx::close()
   m_state= CLOSED;
   return error;
 }
+
+
+/**
+  Do minimal setup for reading a backup image file.
+
+  Used by is_backup_image() to initialize memory for reading image file
+  header and set current operation.
+*/
+void Backup_restore_ctx::minimal_prepare()
+{
+  using namespace backup;
+  mem_alloc= new Mem_allocator();
+  current_op= this;
+}
+
 
 /**
   Create backup archive.

=== modified file 'sql/backup/stream.cc'
--- a/sql/backup/stream.cc	2008-10-15 20:00:48 +0000
+++ b/sql/backup/stream.cc	2008-10-18 22:08:01 +0000
@@ -478,6 +478,7 @@ Input_stream::Input_stream(Logger &log, 
 {
   m_with_compression= false;
   stream.read= stream_read;
+  m_suppress_magic_number_error= FALSE;
 }
 
 /**
@@ -512,7 +513,8 @@ bool Input_stream::init()
 
   if (len <= 0)
   {
-    m_log.report_error(ER_BACKUP_BAD_MAGIC);
+    if (!m_suppress_magic_number_error)
+      m_log.report_error(ER_BACKUP_BAD_MAGIC);
     return FALSE;
   }
 

=== modified file 'sql/backup/stream.h'
--- a/sql/backup/stream.h	2008-10-15 20:00:48 +0000
+++ b/sql/backup/stream.h	2008-10-18 22:08:01 +0000
@@ -141,10 +141,16 @@ class Input_stream:
 
   int next_chunk();
 
+  void suppress_errors_on_magic_number(bool suppress)
+  { 
+    m_suppress_magic_number_error= suppress;
+  }
  private:
 
   int check_magic_and_version();
   bool init();
+
+  bool m_suppress_magic_number_error;
 };
 
 

=== modified file 'sql/lex.h'
--- a/sql/lex.h	2008-07-09 07:12:43 +0000
+++ b/sql/lex.h	2008-10-18 22:08:01 +0000
@@ -245,6 +245,7 @@ static SYMBOL symbols[] = {
   { "IDENTIFIED",	SYM(IDENTIFIED_SYM)},
   { "IF",		SYM(IF)},
   { "IGNORE",		SYM(IGNORE_SYM)},
+  { "IMAGES",   SYM(IMAGES)},
   { "IMPORT",		SYM(IMPORT)},
   { "IN",		SYM(IN_SYM)},
   { "INDEX",		SYM(INDEX_SYM)},

=== modified file 'sql/log.cc'
--- a/sql/log.cc	2008-10-15 20:00:48 +0000
+++ b/sql/log.cc	2008-10-18 22:08:01 +0000
@@ -55,6 +55,11 @@ LOGGER logger;
 MYSQL_BIN_LOG mysql_bin_log;
 ulong sync_binlog_counter= 0;
 
+/*
+  Backup kernel API method
+*/
+bool is_backup_image(THD *thd, String *path);
+
 static bool test_if_number(const char *str,
 			   long *res, bool allow_wildcards);
 static int binlog_init(void *p);
@@ -856,6 +861,11 @@ bool Log_to_csv_event_handler::
   if (history_data->start)
   {
     MYSQL_TIME time;
+    /*
+      Set time ahead a few hours to allow backup purge test to test
+      PURGE BACKUP LOGS/IMAGES BEFORE command.
+    */
+    DBUG_EXECUTE_IF("set_start_time", history_data->start= my_time(0) + 100000;);
     my_tz_OFFSET0->gmt_sec_to_TIME(&time, (my_time_t)history_data->start);
 
     table->field[ET_OBH_FIELD_START_TIME]->set_notnull();
@@ -1096,6 +1106,331 @@ err:
   return result;
 }
 
+/**
+  Delete a row from a log table.
+
+  This method is used to delete a row from a log that is being
+  accessed as a table.
+
+  @param[IN]  hdl   Table handler.
+  @param[IN]  tbl   Table class.
+
+  @returns Result of delete operation
+*/
+bool Log_to_csv_event_handler::delete_log_row(handler *hdl, TABLE *tbl)
+{
+  bool result= 0;
+
+  /*
+    Tell the handler to allow deletes for this log table
+    by turning off log table flag.
+  */
+  hdl->extra(HA_EXTRA_ALLOW_LOG_DELETE);
+  result= hdl->ha_delete_row(tbl->record[0]);
+  hdl->extra(HA_EXTRA_MARK_AS_LOG_TABLE);
+  return result;
+}
+
+/**
+  Delete an image specified by the table field.
+
+  This method attempts to delete a file using the my_delete()
+  method. The name of the file is combined with the path stored
+  in the backup_file_path field.
+
+  @param[IN]  tbl  Table class.
+  @returns Result of my_delete() method call.
+*/
+bool Log_to_csv_event_handler::delete_image(TABLE *tbl)
+{
+  String path;
+  String fname;
+
+  tbl->field[ET_OBH_FIELD_BACKUP_FILE_PATH]->val_str(&path);
+  tbl->field[ET_OBH_FIELD_BACKUP_FILE]->val_str(&fname);
+  path.append(fname);
+  return (my_delete(path.c_ptr(), MYF(0)));
+}
+
+/**
+  Perform a table scan of the backup log tables for deleting rows.
+
+  This method is a helper method designed to delete rows either by ID or DATE
+  (the table columns backup_id and start respectfully). 
+
+  There are several parameters used to control what is being deleted.
+  @c log_name - is used to determine which log to process 
+                (backup_history or backup_progress).
+  @c backup_id - value specified on the command for deleting rows by id.
+  @ field_num - is used to tell the method which field to process. For ID,
+                the fields are either ET_OBH_FIELD_BACKUP_ID or
+                ET_OBP_FIELD_BACKUP_ID_FK.
+  @dtm - value specified on the command for deleting rows by date
+  @delete_log_entries - if TRUE, process rows from the logs else delete images
+                        specified in the backup_history log file via column 
+                        'backup_file'.
+  @rows - the number of rows
+
+  For example, to delete all rows < backup_id for the backup_history table, 
+  set the following:
+    * log_name = BACKUP_HISTORY_LOG_NAME
+    * field_num = ET_OBH_FIELD_BACKUP_ID
+    * backup_id = id specified by the user
+    * delete_log_entries = TRUE
+
+  @param[IN]  THD                 The current thread
+  @param[IN]  log_name            Name of the log to process
+  @param[IN]  field_num           Number of field where value is stored
+  @param[IN]  backup_id           Value for backup id if specified
+  @param[IN]  dtm                 Value for start (date) if specified
+  @param[IN]  delete_log_entries  If TRUE, process logs else process images
+  @param[IN]  delete_exact_row    If TRUE, delete only the rows = backup_id
+  @param[OUT] num                 Number of rows affected.
+
+  @retval num  Number of rows affected.
+
+  @returns TRUE if error
+*/
+bool Log_to_csv_event_handler::scan_backup_log(THD *thd, 
+                                               LEX_STRING log_name, 
+                                               uint field_num, 
+                                               ulonglong backup_id, 
+                                               my_time_t *dtm,
+                                               bool delete_log_entries,
+                                               bool delete_exact_row,
+                                               int *rows)
+{
+  TABLE_LIST table_list;
+  handler *hdl;
+  TABLE *tbl;
+  bool res= FALSE;
+  uint result= 0;
+  int num_rows= 0;
+  Open_tables_state open_tables_backup;
+
+  /*
+    Setup table list for open.
+  */
+  bzero(&table_list, sizeof(TABLE_LIST));
+  table_list.alias= table_list.table_name= log_name.str;
+  table_list.table_name_length= log_name.length;
+
+  table_list.lock_type= TL_WRITE_CONCURRENT_INSERT;
+
+  table_list.db= MYSQL_SCHEMA_NAME.str;
+  table_list.db_length= MYSQL_SCHEMA_NAME.length;
+
+  tbl= open_performance_schema_table(thd, &table_list,
+                                       &open_tables_backup);
+  hdl= tbl->file;
+  hdl->extra(HA_EXTRA_MARK_AS_LOG_TABLE);
+
+  /*
+    Begin table scan.
+  */
+  result= hdl->ha_rnd_init(1);
+  result= hdl->rnd_next(tbl->record[0]);
+  while (result != HA_ERR_END_OF_FILE)
+  {
+    ulonglong id= tbl->field[0]->val_int();
+    /*
+      Delete by id.
+    */
+    if ((field_num == ET_OBH_FIELD_BACKUP_ID) ||
+        (field_num == ET_OBP_FIELD_BACKUP_ID_FK))
+    {
+      if (delete_exact_row)
+      {
+        if (delete_log_entries && (id == backup_id)) // delete log row
+          result= delete_log_row(hdl, tbl);
+      }
+      else if (id < backup_id)
+      {
+        if (delete_log_entries) // delete log row
+        {
+          result= delete_log_row(hdl, tbl);
+          num_rows++;
+        }
+        else if (field_num == ET_OBH_FIELD_BACKUP_ID)  // delete image specified
+        {
+          result= delete_image(tbl);
+          if (!result) // only count deletes that succeed
+            num_rows++;
+        }
+      }
+    }
+    /*
+      Delete by date
+    */
+    else  
+    {
+      MYSQL_TIME time;      
+      MYSQL_TIME tmp;
+
+      tbl->field[field_num]->get_date(&time, TIME_NO_ZERO_DATE);
+      bzero(&tmp, sizeof(MYSQL_TIME));
+      thd->variables.time_zone->gmt_sec_to_TIME(&tmp, *dtm);
+      if (my_time_compare(&time, &tmp) < 0)
+      {
+        if (delete_log_entries) // delete log row
+        {
+          /*
+            When deleting by date for the backup_history table,
+            we must also delete rows from the backup_progress table
+            for this backup id.
+          */
+          if ((my_strcasecmp(system_charset_info, log_name.str, 
+               BACKUP_HISTORY_LOG_NAME.str) == 0) &&
+              opt_backup_progress_log && 
+              (log_backup_output_options & LOG_TABLE))
+          {
+            int r= 0;
+
+            scan_backup_log(thd, BACKUP_PROGRESS_LOG_NAME, 
+              ET_OBP_FIELD_BACKUP_ID_FK, id, dtm, TRUE, TRUE, &r);
+          }
+          result= delete_log_row(hdl, tbl);
+          num_rows++;
+        }
+        else if (field_num == ET_OBH_FIELD_START_TIME)  // delete images
+        {
+          result= delete_image(tbl);
+          if (!result) // only count deletes that succeed
+            num_rows++;
+        }
+      }
+    }
+    result= hdl->rnd_next(tbl->record[0]);
+  }
+  result= hdl->ha_rnd_end();
+  *rows= num_rows;
+  close_performance_schema_table(thd, &open_tables_backup);
+  return result;
+}
+
+/**
+  Remove all rows from the backup logs.
+
+  This method removes (via truncate) all data from the backup logs. It checks
+  if the backup logs are turned on and if the log_backup_output_option is set
+  to include writing to tables. If these conditions are true, each log 
+  (backup_history, backup_progress) is truncated.
+
+  @param[IN] THD  current thread
+
+  @returns TRUE = success, FALSE = failed
+*/
+bool Log_to_csv_event_handler::purge_backup_logs(THD *thd)
+{
+  TABLE_LIST tables;
+  bool res= FALSE;
+
+  if (opt_backup_history_log && (log_backup_output_options & LOG_TABLE))
+  {
+    // need to truncate the table.
+    tables.init_one_table("mysql", strlen("mysql"), 
+                          "backup_history", strlen("backup_history"),
+                          "backup_history", TL_READ);
+    alloc_mdl_locks(&tables, thd->mem_root);
+    res= mysql_truncate(thd, &tables, 1);
+    close_thread_tables(thd);
+    if (res)
+      goto err;
+  }
+  if (opt_backup_progress_log && (log_backup_output_options & LOG_TABLE))
+  {
+    // need to truncate the table.
+    tables.init_one_table("mysql", strlen("mysql"), 
+                          "backup_progress", strlen("backup_progress"),
+                          "backup_progress", TL_READ);
+    alloc_mdl_locks(&tables, thd->mem_root);
+    res= mysql_truncate(thd, &tables, 1);
+    close_thread_tables(thd);
+  }
+err:
+  return res;
+}
+
+/**
+  Purge backup logs of rows less than backup_id passed.
+
+  This method walks the backup log tables deleting all rows whose
+  backup id is less than @c backup_id passed. The parameter
+  @c delete_log_rows is used to indicate delete applies to log rows (TRUE)
+  or images files (FALSE).
+
+  @param[IN]  THD                 The current thread
+  @param[IN]  backup_id           Value for backup id
+  @param[IN]  delete_log_rows     If TRUE, process logs else process images
+  @param[OUT] num                 Number of rows affected.
+
+  @retval num  Number of rows/images affected.
+
+  @returns TRUE if error
+*/
+bool 
+Log_to_csv_event_handler::purge_backup_logs_before_id(THD *thd, 
+                                                      ulonglong backup_id,
+                                                      bool delete_log_rows,
+                                                      int *rows)
+{
+  bool res= FALSE;
+  my_time_t t= 0;
+
+  if (opt_backup_history_log && (log_backup_output_options & LOG_TABLE))
+  { 
+    res= scan_backup_log(thd, BACKUP_HISTORY_LOG_NAME, 
+                         ET_OBH_FIELD_BACKUP_ID, backup_id, 
+                         &t, delete_log_rows, FALSE, rows);
+    if (res)
+      goto err;
+  }
+  if (opt_backup_progress_log && (log_backup_output_options & LOG_TABLE) &&
+      delete_log_rows)
+  {
+    int r= 0;   //ignore this for progress log
+
+    res= scan_backup_log(thd, BACKUP_PROGRESS_LOG_NAME, 
+                         ET_OBP_FIELD_BACKUP_ID_FK, backup_id, 
+                         &t, delete_log_rows, FALSE, &r);
+  }
+err:
+  return res;
+}
+
+/**
+  Purge backup logs of rows previous to start date passed.
+
+  This method walks the backup log tables deleting all rows whose
+  start column value is less than @c t passed. The parameter
+  @c delete_log_rows is used to indicate delete applies to log rows (TRUE)
+  or images files (FALSE).
+
+  @param[IN]  THD                 The current thread
+  @param[IN]  t                   Value for start datetime
+  @param[IN]  delete_log_rows     If TRUE, process logs else process images
+  @param[OUT] num                 Number of rows affected.
+
+  @retval num  Number of rows/images affected.
+
+  @returns TRUE if error
+*/
+bool 
+Log_to_csv_event_handler::purge_backup_logs_before_date(THD *thd, 
+                                                        my_time_t *t,
+                                                        bool delete_log_rows,
+                                                        int *rows)
+{
+  bool res= FALSE;
+
+  if (opt_backup_history_log && (log_backup_output_options & LOG_TABLE))
+    res= scan_backup_log(thd, BACKUP_HISTORY_LOG_NAME, 
+                         ET_OBH_FIELD_START_TIME, 0, 
+                         t, delete_log_rows, FALSE, rows);
+  return res;
+}
+
+
 bool Log_to_csv_event_handler::
   log_error(enum loglevel level, const char *format, va_list args)
 {
@@ -1256,12 +1591,55 @@ void Log_to_file_event_handler::flush_ba
     reopen log files 
 
     Where TRUE means perform open on history file (backup_history) and
-    FALSE means perform open on the progress file (backkup_progress).
+    FALSE means perform open on the progress file (backup_progress).
   */
   mysql_backup_history_log.reopen_file(TRUE);
   mysql_backup_progress_log.reopen_file(FALSE);
 }
 
+/**
+  Purge the backup logs of all data.
+
+  This method truncates the backup log files. It will only do so if the
+  log_backup_output_options includes logging to FILE. It also checks to
+  see if each log is turned on (backup_history and backup_progress) and
+  if so, truncates it. Truncate in this case means resetting the size
+  to 0 bytes using my_chsize().
+
+  @returns TRUE = success, FALSE = failed.
+*/
+bool Log_to_file_event_handler::purge_backup_logs()
+{
+  bool res= FALSE;
+
+  if (opt_backup_history_log && (log_backup_output_options & LOG_FILE))
+  {
+    MYSQL_BACKUP_LOG *backup_log= logger.get_backup_history_log_file_handler();
+
+    pthread_mutex_lock(backup_log->get_log_lock());
+    res= my_sync(backup_log->get_file(), MYF(MY_WME));
+    if (!res)
+      res= my_chsize(backup_log->get_file(), 0, 0, MYF(MY_WME));
+    pthread_mutex_unlock(backup_log->get_log_lock());
+    if (res)
+      goto err;
+  }
+  if (opt_backup_progress_log && (log_backup_output_options & LOG_FILE))
+  {
+    MYSQL_BACKUP_LOG *backup_log= logger.get_backup_progress_log_file_handler();
+
+    pthread_mutex_lock(backup_log->get_log_lock());
+    res= my_sync(backup_log->get_file(), MYF(MY_WME));
+    if (!res)
+      res= my_chsize(backup_log->get_file(), 0, 0, MYF(MY_WME));
+    pthread_mutex_unlock(backup_log->get_log_lock());
+    if (res)
+      goto err;
+  }
+err:
+  return res;
+}
+
 void Log_to_file_event_handler::cleanup()
 {
   mysql_log.cleanup();
@@ -1411,6 +1789,225 @@ bool LOGGER::flush_backup_logs(THD *thd)
   return rc;
 }
 
+/**
+  Delete contents of backup logs.
+
+  This method deletes the data from the backup logs. This applies to both
+  log file and table destinations.
+
+  @param[IN]  thd  The current thread.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_logs(THD *thd)
+{
+  my_bool res= FALSE;
+
+  DBUG_ENTER("LOGGER::purge_backup_logs");
+  /*
+    Here we need to truncate the files if writing to files.
+  */
+  res= file_log_handler->purge_backup_logs();
+  if (res)
+    goto err;
+
+  /*
+    We also need to truncate the table if writing to tables.
+  */
+  res= table_log_handler->purge_backup_logs(thd);
+
+err:
+  DBUG_RETURN(res);
+}
+
+/**
+  Delete contents of backup logs where backup id is less than a given id.
+
+  This method deletes the data from the backup logs where a backup id is 
+  less than the backup id specified. This applies only to table destinations.
+
+  @param[IN]  thd        The current thread.
+  @param[IN]  backup_id  The backup id to compare rows to.
+  @param[OUT] rows       The number of rows affected.
+
+  @retval  num  The number of rows affected.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_logs_before_id(THD *thd, 
+                                         ulonglong backup_id, 
+                                         int *rows)
+{
+  my_bool res= FALSE;
+
+  DBUG_ENTER("LOGGER::purge_backup_logs_before_id");
+
+  res= table_log_handler->purge_backup_logs_before_id(thd, backup_id, 
+    TRUE, rows);
+
+  DBUG_RETURN(res);
+}
+
+/**
+  Delete backup rows less than a certain date.
+
+  This method scans the backup history log and deletes the rows for those
+  operations that were created (start column) previous to the date specified in 
+  @c t.
+
+  @param[IN]  thd        The current thread.
+  @param[IN]  t          The date to compare rows to.
+  @param[OUT] rows       The number of rows affected.
+
+  @retval  rows  The number of rows affected.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_logs_before_date(THD *thd, 
+                                           my_time_t *t, 
+                                           int *rows)
+{
+  my_bool res= FALSE;
+
+  DBUG_ENTER("LOGGER::purge_backup_logs_before_date");
+
+  res= table_log_handler->purge_backup_logs_before_date(thd, t, TRUE, rows);
+
+  DBUG_RETURN(res);
+}
+
+/**
+  Delete backup images.
+
+  This method deletes all backup images in the backupdir path. 
+
+  @param[IN]  thd   The current thread.
+  @param[IN]  wild  Wildcard specified on the command.
+  @param[OUT] num   The number of images deleted.
+
+  @retval  num  The number of images deleted.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_imgs(THD *thd, 
+                               char *wild, 
+                               int *num)
+{
+  my_bool res= FALSE;
+  DBUG_ENTER("purge_backup_images");
+
+  /*
+    Delete all backup images in this folder that match the wildcard string.
+  */
+  int num_del= 0;
+  uint                  i;
+  struct st_my_dir     *dir_info;
+  reg1 struct fileinfo *file_info;
+  FILEINFO *file;
+
+  if (!(dir_info = my_dir(sys_var_backupdir.value,MYF(MY_DONT_SORT))))
+  {		
+    DBUG_RETURN(1);
+  }
+  file_info= dir_info->dir_entry;
+  for (i=dir_info->number_off_files ; i-- ; file_info++)
+  {
+    /*
+      Skip the usual suspects...and directories.
+    */
+    if ((file_info->name[0] == '.' &&
+        ((file_info->name[1] == '.' && 
+          file_info->name[2] == '\0') ||
+          file_info->name[1] == '\0')))
+      continue;
+    file=dir_info->dir_entry+i;
+    MY_STAT *st= my_stat(file_info->name, file->mystat, MYF(0));
+    if (st && MY_S_ISDIR(st->st_mode))
+      continue;
+    if (wild && !wild_compare(file_info->name, wild, 1))
+    {
+      String full_path;
+      full_path.copy(sys_var_backupdir.value, 
+                     sys_var_backupdir.value_length,
+                     system_charset_info);
+      full_path.append(file_info->name);
+#ifndef EMBEDDED_LIBRARY
+      /*
+        Check to see if file is a backup image and delete it IFF
+        it is a backup image.
+      */
+      if (is_backup_image(thd, &full_path))
+      {
+        res= my_delete(full_path.c_ptr(), MYF(0));
+        if (res)
+          goto err;
+        num_del++;
+      }
+#endif
+    }
+  }
+  my_dirend(dir_info);
+  *num= num_del;
+err:
+  DBUG_RETURN(res);
+}
+
+/**
+  Deletes backup image files where backup id is less than a given id.
+
+  This method deletes the backup image files specified in the backup_history log
+  where the backup id is less than the backup id specified. This applies only to 
+  table destinations.
+
+  @param[IN]  thd        The current thread.
+  @param[IN]  backup_id  The backup id to compare rows to.
+  @param[OUT] num       The number of rows affected.
+
+  @retval  num  The number of images affected.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_imgs_before_id(THD *thd, 
+                                         ulonglong backup_id, 
+                                         int *num)
+{
+  my_bool res= FALSE;
+
+  DBUG_ENTER("LOGGER::purge_backup_imgs_before_id");
+
+  res= table_log_handler->purge_backup_logs_before_id(thd, backup_id, 
+    FALSE, num);
+
+  DBUG_RETURN(res);
+}
+
+/**
+  Delete backup image files less than a certain date.
+
+  This method scans the backup history log and deletes the image files for those
+  operations that were created (start column) previous to the date specified in 
+  @c t.
+
+  @param[IN]  thd        The current thread.
+  @param[IN]  t          The date to compare rows to.
+  @param[OUT] num        The number of images affected.
+
+  @retval  num  The number of images affected.
+
+  @returns TRUE if error.
+*/
+bool LOGGER::purge_backup_imgs_before_date(THD *thd, 
+                                           my_time_t *t, 
+                                           int *num)
+{
+  my_bool res= FALSE;
+
+  DBUG_ENTER("LOGGER::purge_backup_logs_before_date");
+
+  res= table_log_handler->purge_backup_logs_before_date(thd, t, FALSE, num);
+
+  DBUG_RETURN(res);
+}
 
 /*
   Log slow query with all enabled log event handlers

=== modified file 'sql/log.h'
--- a/sql/log.h	2008-10-15 20:00:48 +0000
+++ b/sql/log.h	2008-10-18 22:08:01 +0000
@@ -307,6 +307,8 @@ public:
   }
   ulonglong get_next_backup_id();
   my_bool check_backup_logs(THD *thd);
+  File get_file() { return log_file.file; }
+  inline pthread_mutex_t* get_log_lock() { return &LOCK_log; }
 
 private:
   bool write_int(uint num);
@@ -576,9 +578,30 @@ public:
                                    longlong progress,
                                    int error_num,
                                    const char *notes);
+  bool purge_backup_logs(THD *thd);
+  bool purge_backup_logs_before_id(THD *thd, 
+                                   ulonglong backup_id, 
+                                   bool delete_log_rows,
+                                   int *rows);
+  bool purge_backup_logs_before_date(THD *thd, 
+                                     my_time_t *t, 
+                                     bool delete_log_rows,
+                                     int *rows);
 
   int activate_log(THD *thd, uint log_type);
 
+private: 
+  bool delete_log_row(handler *hdl, TABLE *tbl);
+  bool delete_image(TABLE *tbl);
+  bool scan_backup_log(THD *thd, 
+                       LEX_STRING log_name, 
+                       uint field_num, 
+                       ulonglong backup_id, 
+                       my_time_t *dtm,
+                       bool delete_log_entries,
+                       bool delete_exact_row,
+                       int *rows);
+
 };
 
 
@@ -629,6 +652,7 @@ public:
 
   void flush();
   void flush_backup_logs();
+  bool purge_backup_logs();
   void init_pthread_objects();
   MYSQL_QUERY_LOG *get_mysql_slow_log() { return &mysql_slow_log; }
   MYSQL_QUERY_LOG *get_mysql_log() { return &mysql_log; }
@@ -683,6 +707,20 @@ public:
   void init_log_tables();
   bool flush_logs(THD *thd);
   bool flush_backup_logs(THD *thd);
+  bool purge_backup_logs(THD *thd);
+  bool purge_backup_logs_before_id(THD *thd, 
+                                   ulonglong backup_id, 
+                                   int *rows);
+  bool purge_backup_logs_before_date(THD *thd, 
+                                     my_time_t *t, 
+                                     int *rows);
+  bool purge_backup_imgs(THD *thd, char *wild, int *num);
+  bool purge_backup_imgs_before_id(THD *thd, 
+                                   ulonglong backup_id, 
+                                   int *num);
+  bool purge_backup_imgs_before_date(THD *thd, 
+                                     my_time_t *t, 
+                                     int *num);
   /* Perform basic logger cleanup. this will leave e.g. error log open. */
   void cleanup_base();
   /* Free memory. Nothing could be logged after this function is called */

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2008-09-26 16:30:56 +0000
+++ b/sql/mysqld.cc	2008-10-18 22:08:01 +0000
@@ -3243,6 +3243,12 @@ SHOW_VAR com_status_vars[]= {
   {"prepare_sql",          (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PREPARE]), SHOW_LONG_STATUS},
   {"purge",                (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE]), SHOW_LONG_STATUS},
   {"purge_before_date",    (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BEFORE]), SHOW_LONG_STATUS},
+  {"purge_bup_log",        (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_LOG]), SHOW_LONG_STATUS},
+  {"purge_bup_log_date",   (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_LOG_DATE]), SHOW_LONG_STATUS},
+  {"purge_bup_log_id",     (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_LOG_ID]), SHOW_LONG_STATUS},
+  {"purge_bup_img",        (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_IMG]), SHOW_LONG_STATUS},
+  {"purge_bup_img_date",   (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_IMG_DATE]), SHOW_LONG_STATUS},
+  {"purge_bup_img_id",     (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BUP_IMG_ID]), SHOW_LONG_STATUS},
   {"release_savepoint",    (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RELEASE_SAVEPOINT]), SHOW_LONG_STATUS},
   {"rename_table",         (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_TABLE]), SHOW_LONG_STATUS},
   {"rename_user",          (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_USER]), SHOW_LONG_STATUS},

=== modified file 'sql/share/errmsg.txt'
--- a/sql/share/errmsg.txt	2008-10-15 06:48:36 +0000
+++ b/sql/share/errmsg.txt	2008-10-18 22:08:01 +0000
@@ -6404,4 +6404,15 @@ ER_BACKUP_BINLOG
         eng "Error on accessing binlog during BACKUP"
         nor "Lesing av binlog feilet under BACKUP"
         norwegian-ny "Lesing av binlog feila under BACKUP"
-
+ER_BACKUP_LOG_OUTPUT
+  eng "The operation could not be completed because the log_backup_output option does not include writing to tables."
+ER_BACKUP_CANT_PURGE_IMAGE
+  eng "Cannot delete backup image %-.64s."
+ER_BACKUP_PURGE_NOT_ALLOWED
+  eng "You cannot purge backup images using a global wildcard '%' when backupdir is the same path as datadir."
+ER_BACKUP_PURGE_DATETIME
+  eng "The datetime specified is invalid for the '%-.64s' command."
+ER_BACKUP_LOGS_DELETED
+  eng "Backup log entries deleted: "
+ER_BACKUP_IMAGES_DELETED
+  eng "Backup image files deleted: "

=== modified file 'sql/sql_lex.h'
--- a/sql/sql_lex.h	2008-09-04 18:30:34 +0000
+++ b/sql/sql_lex.h	2008-10-18 22:08:01 +0000
@@ -89,6 +89,8 @@ enum enum_sql_command {
   SQLCOM_BEGIN, SQLCOM_CHANGE_MASTER,
   SQLCOM_RENAME_TABLE,  
   SQLCOM_RESET, SQLCOM_PURGE, SQLCOM_PURGE_BEFORE, SQLCOM_SHOW_BINLOGS,
+  SQLCOM_PURGE_BUP_LOG, SQLCOM_PURGE_BUP_LOG_DATE, SQLCOM_PURGE_BUP_LOG_ID,
+  SQLCOM_PURGE_BUP_IMG, SQLCOM_PURGE_BUP_IMG_DATE, SQLCOM_PURGE_BUP_IMG_ID,
   SQLCOM_SHOW_OPEN_TABLES,
   SQLCOM_HA_OPEN, SQLCOM_HA_CLOSE, SQLCOM_HA_READ,
   SQLCOM_SHOW_SLAVE_HOSTS, SQLCOM_DELETE_MULTI, SQLCOM_UPDATE_MULTI,
@@ -1528,6 +1530,8 @@ struct LEX: public Query_tables_list
   LEX_STRING backup_dir;				/* For RESTORE/BACKUP */
   bool backup_compression;
   char* to_log;                                 /* For PURGE MASTER LOGS TO */
+  ulonglong backup_id;     /* For PURGE BACKUP LOGS|IMAGES */
+  char *backup_log_wild;   /* For PURGE BACKUP IMAGES */
   char* x509_subject,*x509_issuer,*ssl_cipher;
   String *wild;
   sql_exchange *exchange;

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2008-09-26 16:30:56 +0000
+++ b/sql/sql_parse.cc	2008-10-18 22:08:01 +0000
@@ -2098,6 +2098,147 @@ mysql_execute_command(THD *thd)
     res = purge_master_logs_before_date(thd, (ulong)it->val_int());
     break;
   }
+  /*
+    Check log status to see if the command applies.
+  */
+  case SQLCOM_PURGE_BUP_LOG_DATE:
+  case SQLCOM_PURGE_BUP_LOG_ID:
+  case SQLCOM_PURGE_BUP_IMG_DATE:
+  case SQLCOM_PURGE_BUP_IMG_ID:
+  {
+    char buff[256];
+    int num= 0;
+    res= 0;
+
+    /*
+      If we are attempting to purge to a specified date or backup_id, we
+      must ensure the backup history log is turned on and is
+      being written to a table.
+    */
+    if (opt_backup_history_log && !(log_backup_output_options & LOG_TABLE))
+    {
+      my_error(ER_BACKUP_LOG_OUTPUT, MYF(0), ER(ER_BACKUP_LOG_OUTPUT));
+      goto error;
+    }
+
+    if (check_global_access(thd, SUPER_ACL))
+      goto error;
+
+    /*
+      We allow the other purge backup commands (except PURGE BACKUP IMAGES;)
+      to float down the switch so that we can consolidate some of the 
+      repetitive code.
+    */
+    if ((lex->sql_command == SQLCOM_PURGE_BUP_LOG_DATE) ||
+        (lex->sql_command == SQLCOM_PURGE_BUP_IMG_DATE))
+    {
+      Item *it;
+      it= (Item *)lex->value_list.head();
+      if ((!it->fixed && it->fix_fields(lex->thd, &it)) ||
+          it->check_cols(1))
+      {
+        my_error(ER_WRONG_ARGUMENTS, MYF(0), "PURGE BACKUP BEFORE");
+        goto error;
+      }
+      it= new Item_func_unix_timestamp(it);
+      /*
+        it is OK only emulate fix_fields, because we need only
+        value of constant
+      */
+      it->quick_fix_field();
+
+      if ((ulong)it->val_int() == 0)
+      {
+        if (lex->sql_command == SQLCOM_PURGE_BUP_LOG_DATE)
+          my_error(ER_BACKUP_PURGE_DATETIME, MYF(0), "PURGE BACKUP LOGS BEFORE");
+        else
+          my_error(ER_BACKUP_PURGE_DATETIME, MYF(0), "PURGE BACKUP IMAGES BEFORE");
+        goto error;
+      }
+
+      my_time_t t= (ulong)it->val_int();
+
+      /*
+        Validate datetime.
+      */
+      if (lex->sql_command == SQLCOM_PURGE_BUP_LOG_DATE)
+        res= logger.purge_backup_logs_before_date(thd, &t, &num);
+      else
+        res= logger.purge_backup_imgs_before_date(thd, &t, &num);
+    }
+    else if (lex->sql_command == SQLCOM_PURGE_BUP_LOG_ID)
+      res= logger.purge_backup_logs_before_id(thd, thd->lex->backup_id, &num);
+    else
+      res= logger.purge_backup_imgs_before_id(thd, thd->lex->backup_id, &num);
+    if (res)
+      goto error;
+    if ((lex->sql_command == SQLCOM_PURGE_BUP_IMG_DATE) ||
+        (lex->sql_command == SQLCOM_PURGE_BUP_IMG_ID))
+    {
+      my_sprintf(buff, (buff, "%s %d.", ER(ER_BACKUP_IMAGES_DELETED), num));
+      my_ok(thd, 0, 0, buff);
+    }
+    else
+    {
+      my_sprintf(buff, (buff, "%s %d.", ER(ER_BACKUP_LOGS_DELETED), num));
+      my_ok(thd, num, 0, buff);
+    }
+    break;
+  }
+  case SQLCOM_PURGE_BUP_LOG:
+  case SQLCOM_PURGE_BUP_IMG:
+  {
+    char buff[256];
+    int num= 0;
+
+    res= 0;
+    if (check_global_access(thd, SUPER_ACL))
+      goto error;
+
+    /*
+      We allow the other purge backup commands (except PURGE BACKUP IMAGES;)
+      to float down the switch so that we can consolidate some of the 
+      repetitive code.
+    */
+    if (lex->sql_command == SQLCOM_PURGE_BUP_LOG)        // PURGE BACKUP LOGS;
+    {
+      if (sys_var_backupdir.value_length > 0)
+        res= logger.purge_backup_logs(thd);
+      if (!res)
+      {
+        my_sprintf(buff, (buff, "%s %d.", ER(ER_BACKUP_LOGS_DELETED), num));
+        my_ok(thd, num, 0, buff);
+      }
+      break;
+    }
+    else        // PURGE BACKUP IMAGES;
+    {
+      /*
+        Check to see if user is attempting a delete when backupdir is
+        the same as datadir and a global wildcard is specified.
+      */
+      if ((my_strcasecmp(system_charset_info, sys_var_backupdir.value,
+          mysql_real_data_home) == 0) && 
+          (my_strcasecmp(system_charset_info, lex->backup_log_wild, "%") == 0))
+      {
+        my_error(ER_BACKUP_PURGE_NOT_ALLOWED, MYF(0));
+        DBUG_RETURN(-1);
+      }
+      if (sys_var_backupdir.value_length > 0)
+        res= logger.purge_backup_imgs(thd, lex->backup_log_wild, &num);
+      if (!res)
+      {
+        my_sprintf(buff, (buff, "%s %d.", ER(ER_BACKUP_IMAGES_DELETED), num));
+        my_ok(thd, 0, 0, buff);
+      }
+      break;
+    }
+
+    if (res)
+      goto error;
+
+    break;
+  }
 #endif
   case SQLCOM_SHOW_WARNS:
   {

=== modified file 'sql/sql_yacc.yy'
--- a/sql/sql_yacc.yy	2008-09-16 08:34:30 +0000
+++ b/sql/sql_yacc.yy	2008-10-18 22:08:01 +0000
@@ -819,6 +819,7 @@ bool my_yyoverflow(short **a, YYSTYPE **
 %token  IDENT_QUOTED
 %token  IF
 %token  IGNORE_SYM
+%token  IMAGES
 %token  IMPORT
 %token  INDEXES
 %token  INDEX_SYM
@@ -10796,7 +10797,36 @@ reset_option:
         ;
 
 purge:
-          PURGE
+          PURGE BACKUP_SYM LOGS_SYM 
+          {
+            LEX *lex=Lex;
+            lex->type=0;
+            lex->sql_command = SQLCOM_PURGE_BUP_LOG;
+          }
+          purge_bup_log_option {}
+          | PURGE BACKUP_SYM IMAGES TEXT_STRING_sys
+          {
+            LEX *lex=Lex;
+            lex->type=0;
+            lex->backup_log_wild= $4.str;
+            lex->sql_command = SQLCOM_PURGE_BUP_IMG;
+          }
+          | PURGE BACKUP_SYM IMAGES BEFORE_SYM expr
+          {
+            LEX *lex=Lex;
+            lex->type=0;
+            lex->value_list.empty();
+            lex->value_list.push_front($5);
+            lex->sql_command= SQLCOM_PURGE_BUP_IMG_DATE;
+          }
+          | PURGE BACKUP_SYM IMAGES TO_SYM NUM_literal
+          {
+            LEX *lex= Lex;
+            lex->type=0;
+            lex->backup_id= (ulonglong)$5->val_int(); 
+            lex->sql_command= SQLCOM_PURGE_BUP_IMG_ID;
+          }
+          | PURGE
           {
             LEX *lex=Lex;
             lex->type=0;
@@ -10804,8 +10834,24 @@ purge:
           }
           purge_options
           {}
-        ;
+          ; 
 
+purge_bup_log_option:
+          {}
+          | BEFORE_SYM expr
+          {
+            LEX *lex= Lex;
+            lex->value_list.empty();
+            lex->value_list.push_front($2);
+            lex->sql_command= SQLCOM_PURGE_BUP_LOG_DATE;
+          }
+          | TO_SYM NUM_literal
+          {
+            LEX *lex= Lex;
+            lex->backup_id= (ulonglong)$2->val_int(); 
+            lex->sql_command= SQLCOM_PURGE_BUP_LOG_ID;
+          }
+          ;
 purge_options:
           master_or_binary LOGS_SYM purge_option
         ;

=== modified file 'storage/csv/ha_tina.cc'
--- a/storage/csv/ha_tina.cc	2008-08-15 18:34:18 +0000
+++ b/storage/csv/ha_tina.cc	2008-10-18 22:08:01 +0000
@@ -80,7 +80,6 @@ static handler *tina_create_handler(hand
                                     TABLE_SHARE *table, 
                                     MEM_ROOT *mem_root);
 
-
 /*****************************************************************************
  ** TINA tables
  *****************************************************************************/
@@ -995,7 +994,11 @@ int ha_tina::delete_row(const uchar * bu
   share->rows_recorded--;
   pthread_mutex_unlock(&share->mutex);
 
-  /* DELETE should never happen on the log table */
+  /* 
+     DELETE should never happen on the log table
+     UNLESS it is a backup log table in which case extra() should be
+     called with HA_EXTRA_ALLOW_LOG_DELETE
+  */
   DBUG_ASSERT(!share->is_log_table);
 
   DBUG_RETURN(0);
@@ -1169,6 +1172,12 @@ int ha_tina::extra(enum ha_extra_functio
  {
    pthread_mutex_lock(&share->mutex);
    share->is_log_table= TRUE;
+   pthread_mutex_unlock(&share->mutex);
+ }
+ else if (operation == HA_EXTRA_ALLOW_LOG_DELETE)
+ {
+   pthread_mutex_lock(&share->mutex);
+   share->is_log_table= FALSE;
    pthread_mutex_unlock(&share->mutex);
  }
   DBUG_RETURN(0);

Thread
bzr commit into mysql-6.0-backup branch (cbell:2712) Bug#33364Chuck Bell19 Oct