1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

WL#1580: --start-datetime, --stop-datetime, --start-position (alias for --position) and --stop-position

options for mysqlbinlog, with a test file.
This enables user to say "recover my database to how it was this morning at 10:30"
(mysqlbinlog "--stop-datetime=2003-07-29 10:30:00").
Using time functions into client/ made me move them out of sql/ into sql-common/.
+ (small) fix for BUG#4507 "mysqlbinlog --read-from-remote-server sometimes
cannot accept 2 binlogs" (that is, on command line).
This commit is contained in:
guilhem@mysql.com
2004-07-29 23:25:58 +02:00
parent 6fdafa5635
commit 0f3e279a05
10 changed files with 957 additions and 238 deletions

View File

@ -17,7 +17,7 @@
#define MYSQL_CLIENT
#undef MYSQL_SERVER
#include "client_priv.h"
#include <time.h>
#include <my_time.h>
#include "log_event.h"
#define BIN_LOG_HEADER_SIZE 4
@ -53,10 +53,18 @@ static int port = MYSQL_PORT;
static const char* sock= 0;
static const char* user = 0;
static char* pass = 0;
static ulonglong position = 0;
static ulonglong start_position, stop_position;
#define start_position_mot ((my_off_t)start_position)
#define stop_position_mot ((my_off_t)stop_position)
static char *start_datetime_str, *stop_datetime_str;
static my_time_t start_datetime= 0, stop_datetime= MY_TIME_T_MAX;
static ulonglong rec_count= 0;
static short binlog_flags = 0;
static MYSQL* mysql = NULL;
static const char* dirname_for_local_load= 0;
static bool stop_passed= 0;
static int dump_local_log_entries(const char* logname);
static int dump_remote_log_entries(const char* logname);
@ -302,15 +310,36 @@ Create_file event for file_id: %u\n",ae->file_id);
Load_log_processor load_processor;
/*
RETURN
0 ok and continue
1 error and terminate
-1 ok and terminate
int process_event(ulonglong *rec_count, char *last_db, Log_event *ev,
my_off_t pos, int old_format)
TODO
This function returns 0 even in some error cases. This should be changed.
*/
int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format)
{
char ll_buff[21];
DBUG_ENTER("process_event");
if ((*rec_count) >= offset)
if ((rec_count >= offset) &&
((my_time_t)(ev->when) >= start_datetime))
{
/*
We have found an event after start_datetime, from now on print
everything (in case the binlog has timestamps increasing and decreasing,
we do this to avoid cutting the middle).
*/
start_datetime= 0;
offset= 0; // print everything and protect against cycling rec_count
if (((my_time_t)(ev->when) >= stop_datetime)
|| (pos >= stop_position_mot))
{
stop_passed= 1; // skip all next binlogs
DBUG_RETURN(-1);
}
if (!short_form)
fprintf(result_file, "# at %s\n",llstr(pos,ll_buff));
@ -387,7 +416,7 @@ Create_file event for file_id: %u\n",exv->file_id);
}
end:
(*rec_count)++;
rec_count++;
if (ev)
delete ev;
DBUG_RETURN(0);
@ -417,13 +446,14 @@ static struct my_option my_long_options[] =
{"port", 'P', "Use port to connect to the remote server.",
(gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, MYSQL_PORT, 0, 0,
0, 0, 0},
{"position", 'j', "Start reading the binlog at position N.",
(gptr*) &position, (gptr*) &position, 0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0,
0, 0},
{"position", 'j', "Deprecated. Use --start-position instead.",
(gptr*) &start_position, (gptr*) &start_position, 0, GET_ULL,
REQUIRED_ARG, BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE,
/* COM_BINLOG_DUMP accepts only 4 bytes for the position */
(ulonglong)(~(uint32)0), 0, 0, 0},
{"protocol", OPT_MYSQL_PROTOCOL,
"The protocol of connection (tcp,socket,pipe,memory).",
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"result-file", 'r', "Direct output to a given file.", 0, 0, 0, GET_STR,
REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"read-from-remote-server", 'R', "Read binary logs from a MySQL server",
@ -439,6 +469,35 @@ static struct my_option my_long_options[] =
{"socket", 'S', "Socket file to use for connection.",
(gptr*) &sock, (gptr*) &sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0,
0, 0},
{"start-datetime", OPT_START_DATETIME,
"Start reading the binlog at first event having a datetime equal or "
"posterior to the argument; the argument must be a date and time "
"in the local time zone, in any format accepted by the MySQL server "
"for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 "
"(you should probably use quotes for your shell to set it properly).",
(gptr*) &start_datetime_str, (gptr*) &start_datetime_str,
0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"stop-datetime", OPT_STOP_DATETIME,
"Stop reading the binlog at first event having a datetime equal or "
"posterior to the argument; the argument must be a date and time "
"in the local time zone, in any format accepted by the MySQL server "
"for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 "
"(you should probably use quotes for your shell to set it properly).",
(gptr*) &stop_datetime_str, (gptr*) &stop_datetime_str,
0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"start-position", OPT_START_POSITION,
"Start reading the binlog at position N. Applies to the first binlog "
"passed on the command line.",
(gptr*) &start_position, (gptr*) &start_position, 0, GET_ULL,
REQUIRED_ARG, BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE,
/* COM_BINLOG_DUMP accepts only 4 bytes for the position */
(ulonglong)(~(uint32)0), 0, 0, 0},
{"stop-position", OPT_STOP_POSITION,
"Stop reading the binlog at position N. Applies to the last binlog "
"passed on the command line.",
(gptr*) &stop_position, (gptr*) &stop_position, 0, GET_ULL,
REQUIRED_ARG, (ulonglong)(~(my_off_t)0), BIN_LOG_HEADER_SIZE,
(ulonglong)(~(my_off_t)0), 0, 0, 0},
{"to-last-log", 't', "Requires -R. Will not stop at the end of the \
requested binlog but rather continue printing until the end of the last \
binlog of the MySQL server. If you send the output to the same MySQL server, \
@ -513,6 +572,29 @@ the mysql command line client\n\n");
my_print_variables(my_long_options);
}
static my_time_t convert_str_to_timestamp(const char* str)
{
int was_cut;
MYSQL_TIME l_time;
long dummy_my_timezone;
bool dummy_in_dst_time_gap;
/* We require a total specification (date AND time) */
if (str_to_datetime(str, strlen(str), &l_time, 0, &was_cut) !=
MYSQL_TIMESTAMP_DATETIME || was_cut)
{
fprintf(stderr, "Incorrect date and time argument: %s\n", str);
exit(1);
}
/*
Note that Feb 30th, Apr 31st cause no error messages and are mapped to
the next existing day, like in mysqld. Maybe this could be changed when
mysqld is changed too (with its "strict" mode?).
*/
return
my_system_gmt_sec(&l_time, &dummy_my_timezone, &dummy_in_dst_time_gap);
}
#include <help_end.h>
extern "C" my_bool
@ -559,7 +641,12 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
}
break;
}
break;
case OPT_START_DATETIME:
start_datetime= convert_str_to_timestamp(start_datetime_str);
break;
case OPT_STOP_DATETIME:
stop_datetime= convert_str_to_timestamp(stop_datetime_str);
break;
case 'V':
print_version();
exit(0);
@ -604,9 +691,8 @@ static MYSQL* safe_connect()
static int dump_log_entries(const char* logname)
{
if (remote_opt)
return dump_remote_log_entries(logname);
return dump_local_log_entries(logname);
return (remote_opt ? dump_remote_log_entries(logname) :
dump_local_log_entries(logname));
}
@ -663,21 +749,27 @@ static int dump_remote_log_entries(const char* logname)
char buf[128];
char last_db[FN_REFLEN+1] = "";
uint len, logname_len;
NET* net = &mysql->net;
NET* net;
int old_format;
int error= 0;
my_off_t old_off= start_position_mot;
char fname[FN_REFLEN+1];
DBUG_ENTER("dump_remote_log_entries");
/*
Even if we already read one binlog (case of >=2 binlogs on command line),
we cannot re-use the same connection as before, because it is now dead
(COM_BINLOG_DUMP kills the thread when it finishes).
*/
mysql= safe_connect();
net= &mysql->net;
old_format = check_master_version(mysql);
if (!position)
position = BIN_LOG_HEADER_SIZE; // protect the innocent from spam
if (position < BIN_LOG_HEADER_SIZE)
{
position = BIN_LOG_HEADER_SIZE;
// warn the user
sql_print_error("Warning: The position in the binary log can't be less than %d.\nStarting from position %d\n", BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE);
}
int4store(buf, position);
/*
COM_BINLOG_DUMP accepts only 4 bytes for the position, so we are forced to
cast to uint32.
*/
int4store(buf, (uint32)start_position);
int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags);
logname_len = (uint) strlen(logname);
int4store(buf + 6, 0);
@ -685,33 +777,32 @@ static int dump_remote_log_entries(const char* logname)
if (simple_command(mysql, COM_BINLOG_DUMP, buf, logname_len + 10, 1))
{
fprintf(stderr,"Got fatal error sending the log dump command\n");
DBUG_RETURN(1);
error= 1;
goto err;
}
my_off_t old_off= position;
ulonglong rec_count= 0;
char fname[FN_REFLEN+1];
for (;;)
{
const char *error;
const char *error_msg;
len = net_safe_read(mysql);
if (len == packet_error)
{
fprintf(stderr, "Got error reading packet from server: %s\n",
mysql_error(mysql));
DBUG_RETURN(1);
error= 1;
goto err;
}
if (len < 8 && net->read_pos[0] == 254)
break; // end of data
DBUG_PRINT("info",( "len= %u, net->read_pos[5] = %d\n",
len, net->read_pos[5]));
Log_event *ev = Log_event::read_log_event((const char*) net->read_pos + 1 ,
len - 1, &error, old_format);
len - 1, &error_msg, old_format);
if (!ev)
{
fprintf(stderr, "Could not construct log event object\n");
DBUG_RETURN(1);
error= 1;
goto err;
}
Log_event_type type= ev->get_type_code();
@ -735,22 +826,32 @@ static int dump_remote_log_entries(const char* logname)
which are about the binlogs, so which would trigger the end-detection
below.
*/
if ((rev->when == 0) && !to_last_remote_log)
if (rev->when == 0)
{
if ((rev->ident_len != logname_len) ||
memcmp(rev->new_log_ident, logname, logname_len))
DBUG_RETURN(0);
/*
Otherwise, this is a fake Rotate for our log, at the very beginning
for sure. Skip it, because it was not in the original log. If we
are running with to_last_remote_log, we print it, because it serves
as a useful marker between binlogs then.
*/
continue;
if (!to_last_remote_log)
{
if ((rev->ident_len != logname_len) ||
memcmp(rev->new_log_ident, logname, logname_len))
{
error= 0;
goto err;
}
/*
Otherwise, this is a fake Rotate for our log, at the very
beginning for sure. Skip it, because it was not in the original
log. If we are running with to_last_remote_log, we print it,
because it serves as a useful marker between binlogs then.
*/
continue;
}
len= 1; // fake Rotate, so don't increment old_off
}
}
if (process_event(&rec_count,last_db,ev,old_off,old_format))
DBUG_RETURN(1);
if ((error= process_event(last_db,ev,old_off,old_format)))
{
error= ((error < 0) ? 0 : 1);
goto err;
}
}
else
{
@ -760,29 +861,35 @@ static int dump_remote_log_entries(const char* logname)
File file;
if ((file= load_processor.prepare_new_file_for_old_format(le,fname)) < 0)
DBUG_RETURN(1);
{
error= 1;
goto err;
}
if (process_event(&rec_count,last_db,ev,old_off,old_format))
if ((error= process_event(last_db,ev,old_off,old_format)))
{
my_close(file,MYF(MY_WME));
DBUG_RETURN(1);
error= ((error < 0) ? 0 : 1);
goto err;
}
if (load_processor.load_old_format_file(net,old_fname,old_len,file))
{
my_close(file,MYF(MY_WME));
DBUG_RETURN(1);
error= 1;
goto err;
}
my_close(file,MYF(MY_WME));
}
/*
Let's adjust offset for remote log as for local log to produce
similar text. As we don't print the fake Rotate event, all events are
real so we can simply add the length.
similar text.
*/
old_off+= len-1;
}
DBUG_RETURN(0);
err:
mysql_close(mysql);
DBUG_RETURN(error);
}
@ -817,7 +924,6 @@ static int dump_local_log_entries(const char* logname)
{
File fd = -1;
IO_CACHE cache,*file= &cache;
ulonglong rec_count = 0;
char last_db[FN_REFLEN+1];
byte tmp_buff[BIN_LOG_HEADER_SIZE];
bool old_format = 0;
@ -829,7 +935,7 @@ static int dump_local_log_entries(const char* logname)
{
if ((fd = my_open(logname, O_RDONLY | O_BINARY, MYF(MY_WME))) < 0)
return 1;
if (init_io_cache(file, fd, 0, READ_CACHE, (my_off_t) position, 0,
if (init_io_cache(file, fd, 0, READ_CACHE, start_position_mot, 0,
MYF(MY_WME | MY_NABP)))
{
my_close(fd, MYF(MY_WME));
@ -843,12 +949,12 @@ static int dump_local_log_entries(const char* logname)
0, MYF(MY_WME | MY_NABP | MY_DONT_CHECK_FILESIZE)))
return 1;
old_format = check_header(file);
if (position)
if (start_position)
{
/* skip 'position' characters from stdout */
/* skip 'start_position' characters from stdout */
byte buff[IO_SIZE];
my_off_t length,tmp;
for (length= (my_off_t) position ; length > 0 ; length-=tmp)
for (length= start_position_mot ; length > 0 ; length-=tmp)
{
tmp=min(length,sizeof(buff));
if (my_b_read(file, buff, (uint) tmp))
@ -858,11 +964,11 @@ static int dump_local_log_entries(const char* logname)
}
}
}
file->pos_in_file=position;
file->pos_in_file= start_position_mot;
file->seek_not_done=0;
}
if (!position)
if (!start_position)
{
// Skip header
if (my_b_read(file, tmp_buff, BIN_LOG_HEADER_SIZE))
@ -891,9 +997,10 @@ static int dump_local_log_entries(const char* logname)
// file->error == 0 means EOF, that's OK, we break in this case
break;
}
if (process_event(&rec_count,last_db,ev,old_off,false))
if ((error= process_event(last_db,ev,old_off,false)))
{
error= 1;
if (error < 0)
error= 0;
break;
}
}
@ -909,11 +1016,14 @@ end:
int main(int argc, char** argv)
{
static char **defaults_argv;
int exit_value;
int exit_value= 0;
ulonglong save_stop_position;
MY_INIT(argv[0]);
DBUG_ENTER("main");
DBUG_PROCESS(argv[0]);
init_time(); // for time functions
parse_args(&argc, (char***)&argv);
defaults_argv=argv;
@ -925,8 +1035,6 @@ int main(int argc, char** argv)
}
my_set_max_open_files(open_files_limit);
if (remote_opt)
mysql = safe_connect();
MY_TMPDIR tmpdir;
tmpdir.list= 0;
@ -944,24 +1052,26 @@ int main(int argc, char** argv)
else
load_processor.init_by_cur_dir();
exit_value= 0;
fprintf(result_file,
"/*!40019 SET @@session.max_insert_delayed_threads=0*/;\n");
while (--argc >= 0)
for (save_stop_position= stop_position, stop_position= ~(my_off_t)0 ;
(--argc >= 0) && !stop_passed ; )
{
if (argc == 0) // last log, --stop-position applies
stop_position= save_stop_position;
if (dump_log_entries(*(argv++)))
{
exit_value=1;
break;
}
// For next log, --start-position does not apply
start_position= BIN_LOG_HEADER_SIZE;
}
if (tmpdir.list)
free_tmpdir(&tmpdir);
if (result_file != stdout)
my_fclose(result_file, MYF(0));
if (remote_opt)
mysql_close(mysql);
cleanup();
free_defaults(defaults_argv);
my_free_open_file_info();