From: Date: October 19 2008 12:10am Subject: bzr commit into mysql-6.0-backup branch (cbell:2712) Bug#33364 List-Archive: http://lists.mysql.com/commits/56534 X-Bug: 33364 Message-Id: <200810182210.m9IMAkse015602@mail.mysql.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit #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 ; PURGE BACKUP LOGS BEFORE ; PURGE BACKUP IMAGES ; PURGE BACKUP IMAGES TO ; PURGE BACKUP IMAGES BEFORE ; 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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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,227 @@ 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; +#ifndef EMBEDDED_LIBRARY +err: +#endif + 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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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:10:41 +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);