Index: mysys/mf_iocache2.c =================================================================== --- mysys/mf_iocache2.c (revision 1) +++ mysys/mf_iocache2.c (working copy) @@ -66,7 +66,42 @@ DBUG_RETURN(0); } +/* Flashback BY P.Linux */ +char *my_b_copy_to_string(IO_CACHE *cache, size_t *bytes_in_cache) +{ + char *buff; + char *tmp_buff; + size_t now_size; + size_t inc_size; + /* Reinit the cache to read from the beginning of the cache */ + if (reinit_io_cache(cache, READ_CACHE, 0L, FALSE, FALSE)) + return NULL; + + now_size= my_b_bytes_in_cache(cache); + inc_size= 0; + buff= (char *) my_malloc(now_size + 1, MYF(0)); + tmp_buff= buff; + do + { + now_size+= inc_size; + if(inc_size > 0) + { + buff= (char *) my_realloc(buff, now_size + 1, MYF(0)); + tmp_buff= buff + (now_size - inc_size); + memcpy(tmp_buff, cache->read_pos, inc_size); + } + memcpy(tmp_buff, cache->read_pos, now_size); + cache->read_pos= cache->read_end; + } while ((inc_size= my_b_fill(cache))); + buff[now_size]= '\0'; + + reinit_io_cache(cache, WRITE_CACHE, 0, FALSE, TRUE); + *bytes_in_cache= now_size; + return buff; +} +/* End */ + my_off_t my_b_append_tell(IO_CACHE* info) { /* Index: scripts/mysql_system_tables.sql =================================================================== --- scripts/mysql_system_tables.sql (revision 1) +++ scripts/mysql_system_tables.sql (working copy) @@ -139,6 +139,13 @@ SET @broken_pfs= (select @broken_tables + @broken_views + @broken_routines + @broken_events); +SET @cmd= "CREATE DATABASE `#bak_database` character set utf8"; + +SET @str = IF(@broken_pfs = 0, @cmd, 'SET @dummy = 0'); +PREPARE stmt FROM @str; +EXECUTE stmt; +DROP PREPARE stmt; + -- -- The performance schema database. -- Only drop and create the database if this is safe (no broken_pfs). Index: include/my_sys.h =================================================================== --- include/my_sys.h (revision 1) +++ include/my_sys.h (working copy) @@ -534,6 +534,7 @@ /* tell write offset in the SEQ_APPEND cache */ int my_b_copy_to_file(IO_CACHE *cache, FILE *file); +char* my_b_copy_to_string(IO_CACHE *cache, size_t *bytes_in_cache); // Flashback BY P.Linux my_off_t my_b_append_tell(IO_CACHE* info); my_off_t my_b_safe_tell(IO_CACHE* info); /* picks the correct tell() */ Index: sql/sql_class.h =================================================================== --- sql/sql_class.h (revision 1) +++ sql/sql_class.h (working copy) @@ -492,6 +492,7 @@ ulong binlog_format; ///< binlog format for this thd (see enum_binlog_format) my_bool binlog_direct_non_trans_update; my_bool sql_log_bin; + my_bool sql_log_flashback; ulong completion_type; ulong query_cache_type; ulong tx_isolation; @@ -2246,6 +2247,8 @@ bool slave_thread, one_shot_set; bool no_errors; + char flashback_stmt[2048]; + bool flashback_flag; uchar password; /** Set to TRUE if execution of the current compound statement Index: sql/log_event.h =================================================================== --- sql/log_event.h (revision 1) +++ sql/log_event.h (working copy) @@ -41,6 +41,10 @@ #include "hash.h" #include "rpl_tblmap.h" #include "rpl_tblmap.cc" +/* Flashback BY P.Linux */ +#include "sql_string.h" +#include "sql_string.cc" +/* End */ #endif #ifdef MYSQL_SERVER @@ -50,7 +54,9 @@ #endif /* Forward declarations */ +#ifndef MYSQL_CLIENT class String; +#endif #define PREFIX_SQL_LOAD "SQL_LOAD-" @@ -603,6 +609,8 @@ Existing events (except ENUM_END_EVENT) should never change their numbers */ + FLASHBACK_EVENT = 28, + ENUM_END_EVENT /* end marker */ }; @@ -1030,7 +1038,19 @@ void print_base64(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, bool is_more); #endif + /* Flashback BY P.Linux */ + my_bool is_flashback; + String output_buf; + void free_output_buffer() + { + if (!output_buf.is_empty()) + { + output_buf.free(); + } + } + /* End */ + static void *operator new(size_t size) { return (void*) my_malloc((uint)size, MYF(MY_WME|MY_FAE)); @@ -1089,7 +1109,15 @@ } Log_event(const char* buf, const Format_description_log_event *description_event); - virtual ~Log_event() { free_temp_buf();} + virtual ~Log_event() + { + free_temp_buf(); + /* Flashback BY P.Linux */ +#ifdef MYSQL_CLIENT + free_output_buffer(); +#endif + /* End */ + } void register_temp_buf(char* buf) { temp_buf = buf; } void free_temp_buf() { @@ -1709,6 +1737,8 @@ */ uint32 master_data_written; + bool flashback_flag; + #ifdef MYSQL_SERVER Query_log_event(THD* thd_arg, const char* query_arg, ulong query_length, @@ -1731,7 +1761,7 @@ if (data_buf) my_free(data_buf); } - Log_event_type get_type_code() { return QUERY_EVENT; } + Log_event_type get_type_code() { return flashback_flag ? FLASHBACK_EVENT: QUERY_EVENT; } #ifdef MYSQL_SERVER bool write(IO_CACHE* file); virtual bool write_post_header_for_derived(IO_CACHE* file) { return FALSE; } @@ -3572,12 +3602,14 @@ #ifdef MYSQL_CLIENT /* not for direct call, each derived has its own ::print() */ virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info)= 0; + void exchange_update_rows(PRINT_EVENT_INFO *print_event_info, uchar *rows_buff); // Flashback BY P.Linux void print_verbose(IO_CACHE *file, PRINT_EVENT_INFO *print_event_info); size_t print_verbose_one_row(IO_CACHE *file, table_def *td, PRINT_EVENT_INFO *print_event_info, MY_BITMAP *cols_bitmap, - const uchar *ptr, const uchar *prefix); + const uchar *ptr, const uchar *prefix, + const my_bool only_parse= 0); // Flashback BY P.Linux #endif #ifdef MYSQL_SERVER @@ -3657,6 +3689,7 @@ uchar *m_rows_buf; /* The rows in packed format */ uchar *m_rows_cur; /* One-after the end of the data */ uchar *m_rows_end; /* One-after the end of the allocated space */ + size_t m_rows_before_size; /* The length before m_rows_buf BY P.Linux */ flag_set m_flags; /* Flags for row-level events */ @@ -4041,6 +4074,13 @@ LEX_STRING m_message; }; +/* Flashback BY P.Linux */ +static inline char *copy_event_cache_to_string_and_reinit(IO_CACHE *cache, size_t *bytes_in_cache) +{ + return my_b_copy_to_string(cache, bytes_in_cache); +} +/* End */ + static inline bool copy_event_cache_to_file_and_reinit(IO_CACHE *cache, FILE *file) { Index: sql/sql_acl.cc =================================================================== --- sql/sql_acl.cc (revision 1) +++ sql/sql_acl.cc (working copy) @@ -4507,6 +4507,10 @@ DBUG_ENTER("check_grant"); DBUG_ASSERT(number > 0); + if (thd->flashback_flag) + { + return FALSE; + } /* Walk through the list of tables that belong to the query and save the requested access (orig_want_privilege) to be able to use it when Index: sql/sql_truncate.cc =================================================================== --- sql/sql_truncate.cc (revision 1) +++ sql/sql_truncate.cc (working copy) @@ -497,7 +497,47 @@ DBUG_RETURN(error); } +/** + bak table before TRUNCATE statement runs. + @return FALSE on success. +*/ + + +bool bak_table(TABLE_LIST *table) +{ + char new_name_buff[FN_REFLEN + 1]; + struct timeval tick_time; + gettimeofday(&tick_time, 0); + snprintf(new_name_buff, sizeof(new_name_buff), "%s_%ld", FLASHBACK_TBL_PREFIX, tick_time.tv_sec*1000000+tick_time.tv_usec); + + char buf[1024]; + snprintf(buf, sizeof(buf), "rename table `%s`.`%s` to `%s`.`%s`;", table->db, table->table_name, FLASHBACK_DB, new_name_buff); + + THD *thd= new THD; + thd->flashback_flag= true; + + Parser_state parser_state; + thd->set_query(buf, strlen(buf)+1); + if (!parser_state.init(thd, thd->query(), thd->query_length())) + { + mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); + } + + if (!thd->is_error()) + { + snprintf(buf, sizeof(buf), "create table `%s`.`%s` like `%s`.`%s`;", table->db, table->table_name, FLASHBACK_DB, new_name_buff); + + thd->set_query(buf, strlen(buf) + 1); + parser_state.init(thd, thd->query(), thd->query_length()); + mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); + } + + delete thd; + + return thd->is_error(); +} + /** Execute a TRUNCATE statement at runtime. @@ -515,6 +555,9 @@ if (check_one_table_access(thd, DROP_ACL, first_table)) DBUG_RETURN(res); + if (thd->variables.sql_log_flashback && bak_table(first_table)) + return FALSE; + if (! (res= truncate_table(thd, first_table))) my_ok(thd); Index: sql/log_event.cc =================================================================== --- sql/log_event.cc (revision 1) +++ sql/log_event.cc (working copy) @@ -218,18 +218,30 @@ constructor, but it would be possible to create a subclass holding the IO_CACHE itself. */ - Write_on_release_cache(IO_CACHE *cache, FILE *file, flag_set flags = 0) - : m_cache(cache), m_file(file), m_flags(flags) + Write_on_release_cache(IO_CACHE *cache, FILE *file, flag_set flags = 0, Log_event *ev = NULL) + : m_cache(cache), m_file(file), m_flags(flags), m_ev(ev) // Flashback BY P.Linux { reinit_io_cache(m_cache, WRITE_CACHE, 0L, FALSE, TRUE); } ~Write_on_release_cache() { + if(m_ev == NULL) + { copy_event_cache_to_file_and_reinit(m_cache, m_file); if (m_flags | FLUSH_F) fflush(m_file); } + /* Flashback BY P.Linux */ + else + { + size_t bytes_in_cache= 0; + char *buff= 0; + buff= copy_event_cache_to_string_and_reinit(m_cache, &bytes_in_cache); + m_ev->output_buf.append(buff, bytes_in_cache); + } + /* End */ + } /* Return a pointer to the internal IO_CACHE. @@ -258,6 +270,7 @@ IO_CACHE *m_cache; FILE *m_file; flag_set m_flags; + Log_event *m_ev; // Flashback BY P.Linux }; #ifndef DBUG_OFF @@ -1318,6 +1331,12 @@ case INCIDENT_EVENT: ev = new Incident_log_event(buf, event_len, description_event); break; +#ifdef MYSQL_CLIENT + case FLASHBACK_EVENT: + ev = new Query_log_event(buf, event_len, description_event, QUERY_EVENT); + break; +#endif + default: DBUG_PRINT("error",("Unknown event code: %d", (int) buf[EVENT_TYPE_OFFSET])); @@ -1860,7 +1879,184 @@ return 0; } +/* Flashback By P.Linux */ +static size_t +log_event_print_value(const uchar *ptr, + uint type, uint meta, + char *typestr, size_t typestr_length) +{ + uint32 length= 0; + if (type == MYSQL_TYPE_STRING) + { + if (meta >= 256) + { + uint byte0= meta >> 8; + uint byte1= meta & 0xFF; + + if ((byte0 & 0x30) != 0x30) + { + /* a long CHAR() field: see #37426 */ + length= byte1 | (((byte0 & 0x30) ^ 0x30) << 4); + type= byte0 | 0x30; + } + else + length = meta & 0xFF; + } + else + length= meta; + } + + switch (type) { + case MYSQL_TYPE_LONG: + { + return 4; + } + + case MYSQL_TYPE_TINY: + { + return 1; + } + + case MYSQL_TYPE_SHORT: + { + return 2; + } + + case MYSQL_TYPE_INT24: + { + return 3; + } + + case MYSQL_TYPE_LONGLONG: + { + return 8; + } + + case MYSQL_TYPE_NEWDECIMAL: + { + uint precision= meta >> 8; + uint decimals= meta & 0xFF; + uint bin_size= my_decimal_get_binary_size(precision, decimals); + return bin_size; + } + + case MYSQL_TYPE_FLOAT: + { + return 4; + } + + case MYSQL_TYPE_DOUBLE: + { + return 8; + } + + case MYSQL_TYPE_BIT: + { + /* Meta-data: bit_len, bytes_in_rec, 2 bytes */ + uint nbits= ((meta >> 8) * 8) + (meta & 0xFF); + length= (nbits + 7) / 8; + return length; + } + + case MYSQL_TYPE_TIMESTAMP: + { + return 4; + } + + case MYSQL_TYPE_DATETIME: + { + return 8; + } + + case MYSQL_TYPE_TIME: + { + return 3; + } + + case MYSQL_TYPE_NEWDATE: + { + return 3; + } + + case MYSQL_TYPE_DATE: + { + return 3; + } + + case MYSQL_TYPE_YEAR: + { + return 1; + } + + case MYSQL_TYPE_ENUM: + switch (meta & 0xFF) { + case 1: + return 1; + case 2: + { + return 2; + } + default: + return 0; + } + break; + + case MYSQL_TYPE_SET: + return meta & 0xFF; + + case MYSQL_TYPE_BLOB: + switch (meta) { + case 1: + length= *ptr; + return length + 1; + case 2: + length= uint2korr(ptr); + return length + 2; + case 3: + length= uint3korr(ptr); + return length + 3; + case 4: + length= uint4korr(ptr); + return length + 4; + default: + return 0; + } + + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + length= meta; + if (length < 256) + { + length= *ptr; + return length + 1; + } + else + { + length= uint2korr(ptr); + return length + 2; + } + + case MYSQL_TYPE_STRING: + if (length < 256) + { + length= *ptr; + return length + 1; + } + else + { + length= uint2korr(ptr); + return length + 2; + } + + break; + } + *typestr= 0; + return 0; +} + +/* End */ + /** Print a packed row into IO cache @@ -1879,7 +2075,8 @@ Rows_log_event::print_verbose_one_row(IO_CACHE *file, table_def *td, PRINT_EVENT_INFO *print_event_info, MY_BITMAP *cols_bitmap, - const uchar *value, const uchar *prefix) + const uchar *value, const uchar *prefix, + const my_bool only_parse) // Flashback BY P.Linux { const uchar *value0= value; const uchar *null_bits= value; @@ -1888,6 +2085,7 @@ value+= (m_width + 7) / 8; + if (!only_parse) // Flashback BY P.Linux my_b_printf(file, "%s", prefix); for (size_t i= 0; i < td->size(); i ++) @@ -1900,14 +2098,27 @@ if (is_null) { + if (!only_parse) // Flashback BY P.Linux my_b_printf(file, "### @%d=NULL", i + 1); } else { + size_t size= 0; // Flashback BY P.Linux + if (!only_parse) + { my_b_printf(file, "### @%d=", i + 1); - size_t size= log_event_print_value(file, value, + size= log_event_print_value(file, value, td->type(i), td->field_metadata(i), typestr, sizeof(typestr)); + } + /* Flashback BY P.Linux */ + else + { + size= log_event_print_value(value, + td->type(i), td->field_metadata(i), + typestr, sizeof(typestr)); + } + /* End */ if (!size) return 0; @@ -1916,8 +2127,11 @@ if (print_event_info->verbose > 1) { + if (!only_parse) // Flashback BY P.Linux my_b_printf(file, " /* "); + if (!only_parse) // Flashback BY P.Linux + { if (typestr[0]) my_b_printf(file, "%s ", typestr); else @@ -1928,7 +2142,9 @@ td->maybe_null(i), is_null); my_b_printf(file, "*/"); } + } + if (!only_parse) // Flashback BY P.Linux my_b_printf(file, "\n"); null_bit_index++; @@ -1936,7 +2152,53 @@ return value - value0; } +/* Flashback BY P.Linux */ +void Rows_log_event::exchange_update_rows(PRINT_EVENT_INFO *print_event_info, + uchar *rows_buff) +{ + Table_map_log_event *map; + table_def *td; + uchar *data_buff= rows_buff + m_rows_before_size; + if (!(map= print_event_info->m_table_map.get_table(m_table_id)) || + !(td= map->create_table_def())) + { + return; + } + + for (uchar *value= m_rows_buf; value < m_rows_end; ) + { + uchar *start_pos= value; + size_t length1; + if (!(length1= print_verbose_one_row(NULL, td, print_event_info, + &m_cols, value, + (const uchar*) "", TRUE))) + return; + value+= length1; + + size_t length2; + if (!(length2= print_verbose_one_row(NULL, td, print_event_info, + &m_cols, value, + (const uchar*) "", TRUE))) + return; + value+= length2; + + /* Swap SET and WHERE part */ + uchar *swap_buff1= (uchar *) my_malloc(length1, MYF(0)); + uchar *swap_buff2= (uchar *) my_malloc(length2, MYF(0)); + + memcpy(swap_buff1, start_pos, length1); // SET part + memcpy(swap_buff2, start_pos + length1, length2); // WHERE part + + memcpy(start_pos, swap_buff2, length2); + memcpy(start_pos + length2, swap_buff1, length1); + } + + /* Move to rows_buff */ + memcpy(data_buff, m_rows_buf, m_rows_end - m_rows_buf); +} +/* End */ + /** Print a row event into IO cache in human readable form (in SQL format) @@ -2018,7 +2280,7 @@ PRINT_EVENT_INFO* print_event_info, bool more) { - const uchar *ptr= (const uchar *)temp_buf; + uchar *ptr= (uchar *)temp_buf; uint32 size= uint4korr(ptr + EVENT_LEN_OFFSET); DBUG_ENTER("Log_event::print_base64"); @@ -2030,6 +2292,26 @@ DBUG_VOID_RETURN; } + /* Flashback BY P.Linux */ + if (is_flashback) + { + switch (ptr[4]) { + case WRITE_ROWS_EVENT: + ptr[4]= DELETE_ROWS_EVENT; + break; + case DELETE_ROWS_EVENT: + ptr[4]= WRITE_ROWS_EVENT; + break; + case UPDATE_ROWS_EVENT: + Rows_log_event *ev= NULL; + ev= new Update_rows_log_event((const char*) ptr, size, + glob_description_event); + ev->exchange_update_rows(print_event_info, ptr); + break; + } + } + /* End */ + if (base64_encode(ptr, (size_t) size, tmp_str)) { DBUG_ASSERT(0); @@ -2475,7 +2757,7 @@ lc_time_names_number(thd_arg->variables.lc_time_names->number), charset_database_number(0), table_map_for_update((ulonglong)thd_arg->table_map_for_update), - master_data_written(0) + master_data_written(0), flashback_flag(0) { time_t end_time; @@ -3146,11 +3428,30 @@ void Query_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) { - Write_on_release_cache cache(&print_event_info->head_cache, file); + Write_on_release_cache cache(&print_event_info->head_cache, file, 0); + + uint ev_type= temp_buf[EVENT_TYPE_OFFSET]; - print_query_header(&cache, print_event_info); - my_b_write(&cache, (uchar*) query, q_len); - my_b_printf(&cache, "\n%s\n", print_event_info->delimiter); + if (!is_flashback && ev_type == QUERY_EVENT) // Flashback BY P.Linux + { + print_query_header(&cache, print_event_info); + my_b_write(&cache, (uchar*) query, q_len); + my_b_printf(&cache, "\n%s\n", print_event_info->delimiter); + } + else if(is_flashback && ev_type == FLASHBACK_EVENT) + { + print_query_header(&cache, print_event_info); + + char *pos= strchr(query, ':') + 1; + int left_len= q_len - (pos - query); + + output_buf.append(pos, left_len); + output_buf.append('\n'); + output_buf.append(print_event_info->delimiter); + output_buf.append('\n'); + //my_b_write(&cache, (uchar*) pos, left_len); + //my_b_printf(&cache, "\n%s\n", print_event_info->delimiter); + } } #endif /* MYSQL_CLIENT */ @@ -5571,7 +5872,7 @@ void Xid_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) { Write_on_release_cache cache(&print_event_info->head_cache, file, - Write_on_release_cache::FLUSH_F); + Write_on_release_cache::FLUSH_F, this); // Flashback BY P.Linux if (!print_event_info->short_form) { @@ -7451,6 +7752,7 @@ m_rows_end= m_rows_buf + data_size; m_rows_cur= m_rows_end; memcpy(m_rows_buf, ptr_rows_data, data_size); + m_rows_before_size= ptr_rows_data - (const uchar *) buf; // Flashback BY P.Linux } else m_cols.bitmap= 0; // to not free it @@ -8109,8 +8411,16 @@ if (get_flags(STMT_END_F)) { - copy_event_cache_to_file_and_reinit(head, file); - copy_event_cache_to_file_and_reinit(body, file); + /* Flashback BY P.Linux */ + size_t bytes_in_cache= 0; + char *buff= 0; + + buff= copy_event_cache_to_string_and_reinit(head, &bytes_in_cache); + output_buf.append(buff, bytes_in_cache); + + buff= copy_event_cache_to_string_and_reinit(body, &bytes_in_cache); + output_buf.append(buff, bytes_in_cache); + /* End */ } } #endif Index: sql/sql_class.h.orig =================================================================== --- sql/sql_class.h.orig (revision 1) +++ sql/sql_class.h.orig (working copy) @@ -87,6 +87,7 @@ SLOG_F_TMP_TABLE, SLOG_F_TMP_DISK, SLOG_F_FILESORT, SLOG_F_FILESORT_DISK }; +enum enum_log_warnings_suppress { log_warnings_suppress_1592 }; enum enum_slave_exec_mode { SLAVE_EXEC_MODE_STRICT, SLAVE_EXEC_MODE_IDEMPOTENT, SLAVE_EXEC_MODE_LAST_BIT}; @@ -552,6 +553,7 @@ double long_query_time_double; + my_bool expand_fast_index_creation; } SV; @@ -1706,6 +1708,8 @@ */ enum enum_server_command command; uint32 server_id; + // Used to save the command, before it is set to COM_SLEEP. + enum enum_server_command old_command; uint32 file_id; // for LOAD DATA INFILE /* remote (peer) port */ uint16 peer_port; @@ -2215,6 +2219,8 @@ */ enum_tx_isolation tx_isolation; enum_check_fields count_cuted_fields; + ha_rows updated_row_count; + ha_rows sent_row_count_2; /* for userstat */ DYNAMIC_ARRAY user_var_events; /* For user variables replication */ MEM_ROOT *user_var_events_alloc; /* Allocate above array elements here */ @@ -2309,6 +2315,49 @@ */ LOG_INFO* current_linfo; NET* slave_net; // network connection from slave -> m. + + /* + Used to update global user stats. The global user stats are updated + occasionally with the 'diff' variables. After the update, the 'diff' + variables are reset to 0. + */ + // Time when the current thread connected to MySQL. + time_t current_connect_time; + // Last time when THD stats were updated in global_user_stats. + time_t last_global_update_time; + // Busy (non-idle) time for just one command. + double busy_time; + // Busy time not updated in global_user_stats yet. + double diff_total_busy_time; + // Cpu (non-idle) time for just one thread. + double cpu_time; + // Cpu time not updated in global_user_stats yet. + double diff_total_cpu_time; + /* bytes counting */ + ulonglong bytes_received; + ulonglong diff_total_bytes_received; + ulonglong bytes_sent; + ulonglong diff_total_bytes_sent; + ulonglong binlog_bytes_written; + ulonglong diff_total_binlog_bytes_written; + + // Number of rows not reflected in global_user_stats yet. + ha_rows diff_total_sent_rows, diff_total_updated_rows, diff_total_read_rows; + // Number of commands not reflected in global_user_stats yet. + ulonglong diff_select_commands, diff_update_commands, diff_other_commands; + // Number of transactions not reflected in global_user_stats yet. + ulonglong diff_commit_trans, diff_rollback_trans; + // Number of connection errors not reflected in global_user_stats yet. + ulonglong diff_denied_connections, diff_lost_connections; + // Number of db access denied, not reflected in global_user_stats yet. + ulonglong diff_access_denied_errors; + // Number of queries that return 0 rows + ulonglong diff_empty_queries; + + // Per account query delay in miliseconds. When not 0, sleep this number of + // milliseconds before every SQL command. + ulonglong query_delay_millis; + /* Used by the sys_var class to store temporary values */ union { @@ -2389,6 +2438,11 @@ alloc_root. */ void init_for_queries(); + void reset_stats(void); + void reset_diff_stats(void); + // ran_command is true when this is called immediately after a + // command has been run. + void update_stats(bool ran_command); void change_user(void); void cleanup(void); void cleanup_after_query(); @@ -2861,6 +2915,15 @@ } thd_scheduler scheduler; + /* Returns string as 'IP:port' for the client-side + of the connnection represented + by 'client' as displayed by SHOW PROCESSLIST. + Allocates memory from the heap of + this THD and that is not reclaimed + immediately, so use sparingly. May return NULL. + */ + char *get_client_host_port(THD *client); + public: inline Internal_error_handler *get_internal_handler() { return m_internal_handler; } @@ -3017,6 +3080,14 @@ LEX_STRING get_invoker_user() { return invoker_user; } LEX_STRING get_invoker_host() { return invoker_host; } bool has_invoker() { return invoker_user.length > 0; } + void clear_wakeup_ready() { wakeup_ready= false; } + /* + Sleep waiting for others to wake us up with signal_wakeup_ready(). + Must call clear_wakeup_ready() before waiting. + */ + void wait_for_wakeup_ready(); + /* Wake this thread up from wait_for_wakeup_ready(). */ + void signal_wakeup_ready(); private: /** The current internal error handler for this thread, or NULL. */ @@ -3059,8 +3130,22 @@ */ LEX_STRING invoker_user; LEX_STRING invoker_host; + /* + Flag, mutex and condition for a thread to wait for a signal from another + thread. + + Currently used to wait for group commit to complete, can also be used for + other purposes. + */ + bool wakeup_ready; + mysql_mutex_t LOCK_wakeup_ready; + mysql_cond_t COND_wakeup_ready; }; +/* Returns string as 'IP' for the client-side of the connection represented by + 'client'. Does not allocate memory. May return "". +*/ +const char *get_client_host(THD *client); /** A short cut for thd->stmt_da->set_ok_status(). */ Index: sql/sql_class.cc =================================================================== --- sql/sql_class.cc (revision 1) +++ sql/sql_class.cc (working copy) @@ -1053,6 +1053,8 @@ m_binlog_invoker= FALSE; memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_host, 0, sizeof(invoker_host)); + + flashback_flag= flashback_stmt[0]= 0; } @@ -5242,7 +5244,33 @@ spcont == NULL && !binlog_evt_union.do_union) issue_unsafe_warnings(); + if (flashback_stmt[0] && variables.sql_log_flashback) + { + int stmt_id; + int stmt_num= flashback_stmt[0]; + char *stmt_pos= &flashback_stmt[1]; + for (stmt_id= 0; stmt_id < stmt_num; stmt_id++) + { + Query_log_event info(this, stmt_pos, strlen(stmt_pos), false, true, false, 0); + info.flashback_flag= TRUE; + + if (mysql_bin_log.write(&info)) + { + sql_print_warning("write flashback statement %s error.\n", flashback_stmt); + break; + } + + stmt_pos+= strlen(stmt_pos) + 1; + } + + flashback_stmt[0]= '\0'; + } + + /* this is a flashback thd, not need to record the original statement*/ + if (flashback_flag) + DBUG_RETURN(0); + switch (qtype) { /* ROW_QUERY_TYPE means that the statement may be logged either in Index: sql/sys_vars.cc =================================================================== --- sql/sys_vars.cc (revision 1) +++ sql/sys_vars.cc (working copy) @@ -2490,6 +2490,12 @@ DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_sql_log_bin), ON_UPDATE(fix_sql_log_bin_after_update)); +static Sys_var_mybool Sys_log_flashback( + "sql_log_flashback", "sql_log_flashback", + SESSION_VAR(sql_log_flashback), CMD_LINE(REQUIRED_ARG), + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, NULL, + NULL); + static Sys_var_bit Sys_sql_warnings( "sql_warnings", "sql_warnings", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_WARNINGS, Index: sql/sql_priv.h =================================================================== --- sql/sql_priv.h (revision 1) +++ sql/sql_priv.h (working copy) @@ -297,6 +297,9 @@ #define IS_TABLESPACES_NODEGROUP_ID 7 #define IS_TABLESPACES_TABLESPACE_COMMENT 8 +#define FLASHBACK_DB "#bak_database" +#define FLASHBACK_TBL_PREFIX "#bak_table" + #endif /* MYSQL_SERVER */ #endif /* MYSQL_CLIENT */ Index: sql/sql_table.cc =================================================================== --- sql/sql_table.cc (revision 1) +++ sql/sql_table.cc (working copy) @@ -6164,6 +6164,11 @@ } else { + if (thd->variables.sql_log_flashback) + { + snprintf(thd->flashback_stmt, sizeof(thd->flashback_stmt), "%c%s.%s:RENAME TABLE `%s`.`%s` TO `%s`.`%s`", '\1', new_db, new_alias, new_db, new_alias, db, table_name); + } + *fn_ext(new_name)=0; if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) error= -1; @@ -6895,9 +6900,26 @@ FN_FROM_IS_TMP); } - if (! error) - (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + if (!error) + { + if (need_copy_table != ALTER_TABLE_DATA_CHANGED || !thd->variables.sql_log_flashback) + { + (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + } + else + { + struct timeval tick_time; + gettimeofday(&tick_time, 0); + snprintf(new_name_buff, sizeof(new_name_buff), "%s_%ld", FLASHBACK_TBL_PREFIX, tick_time.tv_sec*1000000+tick_time.tv_usec); + int len= snprintf(thd->flashback_stmt, sizeof(thd->flashback_stmt), "%c%s.%s:RENAME TABLE `%s`.`%s` TO `%s`.`%s`", '\2', db, new_name, FLASHBACK_DB, new_name_buff, db, new_name); + + snprintf(&thd->flashback_stmt[len + 1], sizeof(thd->flashback_stmt) - len - 1, "%s.%s:DROP TABLE `%s`.`%s`", db, new_name, db, new_name); + + (void) mysql_rename_table(old_db_type, db, old_name, FLASHBACK_DB, new_name_buff, FN_FROM_IS_TMP); + } + } + if (error) { /* This shouldn't happen. But let us play it safe. */ Index: sql/sql_parse.cc =================================================================== --- sql/sql_parse.cc (revision 1) +++ sql/sql_parse.cc (working copy) @@ -2489,6 +2489,12 @@ TABLE_LIST *create_table= first_table; TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global; + if (thd->variables.sql_log_flashback) + { + snprintf(thd->flashback_stmt, sizeof(thd->flashback_stmt), "%c%s.%s:DROP TABLE `%s`.`%s`", + '\1', create_table->db, create_table->table_name, create_table->db, create_table->table_name); + } + /* Code below (especially in mysql_create_table() and select_create methods) may modify HA_CREATE_INFO structure in LEX, so we have to @@ -2816,6 +2822,13 @@ goto error; } + if (thd->variables.sql_log_flashback) + { + snprintf(thd->flashback_stmt, sizeof(thd->flashback_stmt), "%c%s.%s:RENAME TABLE `%s`.`%s` TO `%s`.`%s`", + '\1', first_table->next_local->db, first_table->next_local->table_name, first_table->next_local->db, + first_table->next_local->table_name, first_table->db, first_table->table_name); + } + if (mysql_rename_tables(thd, first_table, 0)) goto error; break; @@ -3247,8 +3260,32 @@ thd->variables.option_bits|= OPTION_KEEP_LOG; } /* DDL and binlog write order are protected by metadata locks. */ - res= mysql_rm_table(thd, first_table, lex->drop_if_exists, + + if (thd->variables.sql_log_flashback) + { + char new_name_buff[FN_REFLEN + 1]; + struct timeval tick_time; + gettimeofday(&tick_time, 0); + snprintf(new_name_buff, sizeof(new_name_buff), "%s_%ld", FLASHBACK_TBL_PREFIX, tick_time.tv_sec*1000000+tick_time.tv_usec); + + snprintf(thd->flashback_stmt, sizeof(thd->flashback_stmt), "%c%s.%s:RENAME TABLE `%s`.`%s` TO `%s`.`%s`", + '\1', FLASHBACK_DB, first_table->db, FLASHBACK_DB, new_name_buff, first_table->db, first_table->table_name); + + TABLE_LIST second_table; + memcpy(&second_table, first_table, sizeof(second_table)); + second_table.db= FLASHBACK_DB; + second_table.table_name= new_name_buff; + first_table->next_local= &second_table; + res= mysql_rename_tables(thd, first_table, 0); + + first_table->next_local= NULL; + } + else + { + res= mysql_rm_table(thd, first_table, lex->drop_if_exists, + lex->drop_temporary); + } } break; case SQLCOM_SHOW_PROCESSLIST: @@ -4867,6 +4904,9 @@ GRANT_INTERNAL_INFO *grant_internal_info, bool dont_check_global_grants, bool no_errors) { + if (thd->flashback_flag) + return FALSE; + Security_context *sctx= thd->security_ctx; ulong db_access; Index: client/mysqlbinlog.cc =================================================================== --- client/mysqlbinlog.cc (revision 1) +++ client/mysqlbinlog.cc (working copy) @@ -44,6 +44,11 @@ #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES) + +/* Flashback BY P.Linux */ +DYNAMIC_ARRAY binlog_events; +/* End */ + char server_version[SERVER_VERSION_LENGTH]; ulong server_id = 0; @@ -103,6 +108,8 @@ static MYSQL* mysql = NULL; static char* dirname_for_local_load= 0; +static my_bool flashback_opt; // Flashback BY P.Linux + /** Pointer to the Format_description_log_event of the currently active binlog. @@ -699,6 +706,8 @@ print_event_info->short_form= short_form; Exit_status retval= OK_CONTINUE; + + ev->is_flashback= flashback_opt; // Flashback BY P.Linux /* Format events are not concerned by --offset and such, we always need to read them to be able to process the wanted events. @@ -736,7 +745,12 @@ goto end; } if (!short_form) + { + if (!flashback_opt) + { fprintf(result_file, "# at %s\n",llstr(pos,ll_buff)); + } + } if (!opt_hexdump) print_event_info->hexdump_from= 0; /* Disabled */ @@ -1003,6 +1017,17 @@ */ if (ev) { + /* Flashback BY P.Linux */ + if(!ev->output_buf.is_empty()) + { + String *tmp_str= new String[1]; + tmp_str->copy(ev->output_buf); + (void) push_dynamic(&binlog_events, (uchar *) tmp_str); + if (!flashback_opt) + printf("%s", ev->output_buf.ptr()); + ev->free_output_buffer(); + } + /* End */ if (remote_opt) ev->temp_buf= 0; if (destroy_evt) /* destroy it later if not set (ignored table map) */ @@ -1098,6 +1123,11 @@ {"read-from-remote-server", 'R', "Read binary logs from a MySQL server.", &remote_opt, &remote_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + /* Flashback BY P.Linux */ + {"flashback", 'B', "Flashback data to start_postition or start_datetime.", + &flashback_opt, &flashback_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + /* End */ {"result-file", 'r', "Direct output to a given file.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"server-id", OPT_SERVER_ID, @@ -1330,6 +1360,11 @@ case 'R': remote_opt= 1; break; + /* Flashback BY P.Linux */ + case 'B': + flashback_opt= 1; + break; + /* End */ case OPT_MYSQL_PROTOCOL: opt_protocol= find_type_or_exit(argument, &sql_protocol_typelib, opt->name); @@ -1456,9 +1491,14 @@ rc= (remote_opt ? dump_remote_log_entries(&print_event_info, logname) : dump_local_log_entries(&print_event_info, logname)); + /* Flashback BY P.Linux */ + if (!flashback_opt) + { /* Set delimiter back to semicolon */ fprintf(result_file, "DELIMITER ;\n"); strmov(print_event_info.delimiter, ";"); + } + /* End */ return rc; } @@ -2034,6 +2074,10 @@ DBUG_ENTER("main"); DBUG_PROCESS(argv[0]); + /* Flashback BY P.Linux*/ + (void) my_init_dynamic_array(&binlog_events, sizeof(String), 1024, 1024); + /* End */ + my_init_time(); // for time functions if (load_defaults("my", load_default_groups, &argc, &argv)) @@ -2111,6 +2155,21 @@ start_position= BIN_LOG_HEADER_SIZE; } + /* Flashback BY P.Linux */ + if(flashback_opt) + { + int i= 0; + for (i= binlog_events.elements; i > 0; --i) + { + String *event_str= dynamic_element(&binlog_events, i - 1, String*); + printf("%s", event_str->ptr()); + } + delete_dynamic(&binlog_events); + /* Set delimiter back to semicolon */ + fprintf(result_file, "DELIMITER ;\n"); + } + /* End */ + /* Issue a ROLLBACK in case the last printed binlog was crashed and had half of transaction.