/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "rpl_info_table.h" #include "dynamic_ids.h" // Server_ids #include "log.h" // sql_print_error #include "rpl_info_table_access.h" // Rpl_info_table_access #include "rpl_info_values.h" // Rpl_info_values #include "sql_class.h" // THD Rpl_info_table::Rpl_info_table(uint nparam, const char* param_schema, const char *param_table, const uint param_n_pk_fields, const uint *param_pk_field_indexes) :Rpl_info_handler(nparam), is_transactional(FALSE) { str_schema.str= str_table.str= NULL; str_schema.length= str_table.length= 0; size_t schema_length= strlen(param_schema); if ((str_schema.str= (char *) my_malloc(key_memory_Rpl_info_table, schema_length + 1, MYF(0)))) { str_schema.length= schema_length; strmake(str_schema.str, param_schema, schema_length); } size_t table_length= strlen(param_table); if ((str_table.str= (char *) my_malloc(key_memory_Rpl_info_table, table_length + 1, MYF(0)))) { str_table.length= table_length; strmake(str_table.str, param_table, table_length); } if ((description= (char *) my_malloc(key_memory_Rpl_info_table, str_schema.length + str_table.length + 2, MYF(0)))) { char *pos= my_stpcpy(description, param_schema); pos= my_stpcpy(pos, "."); pos= my_stpcpy(pos, param_table); } m_n_pk_fields= param_n_pk_fields; m_pk_field_indexes= param_pk_field_indexes; access= new Rpl_info_table_access(); } Rpl_info_table::~Rpl_info_table() { delete access; my_free(description); my_free(str_table.str); my_free(str_schema.str); } int Rpl_info_table::do_init_info() { return do_init_info(FIND_KEY, 0); } int Rpl_info_table::do_init_info(uint instance) { return do_init_info(FIND_KEY, instance); } int Rpl_info_table::do_init_info(enum_find_method method, uint instance) { int error= 1; enum enum_return_id res= FOUND_ID; TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; DBUG_ENTER("Rlp_info_table::do_init_info"); THD *thd= access->create_thd(); saved_mode= thd->variables.sql_mode; tmp_disable_binlog(thd); /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_WRITE, &table, &backup)) goto end; if (verify_table_primary_key_fields(table)) goto end; /* Points the cursor at the row to be read according to the keys. */ switch (method) { case FIND_KEY: res= access->find_info(field_values, table); break; case FIND_SCAN: res= access->scan_info(table, instance); break; default: DBUG_ASSERT(0); break; } if (res == FOUND_ID) { /* Reads the information stored in the rpl_info table into a set of variables. If there is a failure, an error is returned. */ if (access->load_info_values(get_number_info(), table->field, field_values)) goto end; } error= (res == ERROR_ID); end: /* Unlocks and closes the rpl_info table. */ error= access->close_table(thd, table, &backup, error) || error; reenable_binlog(thd); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(error); } int Rpl_info_table::do_flush_info(const bool force) { int error= 1; enum enum_return_id res= FOUND_ID; TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; DBUG_ENTER("Rpl_info_table::do_flush_info"); if (!(force || (sync_period && ++(sync_counter) >= sync_period))) DBUG_RETURN(0); THD *thd= access->create_thd(); sync_counter= 0; saved_mode= thd->variables.sql_mode; tmp_disable_binlog(thd); thd->is_operating_substatement_implicitly= true; /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_WRITE, &table, &backup)) goto end; /* Points the cursor at the row to be read according to the keys. If the row is not found an error is reported. */ if ((res= access->find_info(field_values, table)) == NOT_FOUND_ID) { /* Prepares the information to be stored before calling ha_write_row. */ empty_record(table); if (access->store_info_values(get_number_info(), table->field, field_values)) goto end; /* Inserts a new row into rpl_info table. */ if ((error= table->file->ha_write_row(table->record[0]))) { table->file->print_error(error, MYF(0)); /* This makes sure that the error is 1 and not the status returned by the handler. */ error= 1; goto end; } error= 0; } else if (res == FOUND_ID) { /* Prepares the information to be stored before calling ha_update_row. */ store_record(table, record[1]); if (access->store_info_values(get_number_info(), table->field, field_values)) goto end; /* Updates a row in the rpl_info table. */ if ((error= table->file->ha_update_row(table->record[1], table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) { table->file->print_error(error, MYF(0)); /* This makes sure that the error is 1 and not the status returned by the handler. */ error= 1; goto end; } error= 0; } end: DBUG_EXECUTE_IF("mts_debug_concurrent_access", { while (thd->system_thread == SYSTEM_THREAD_SLAVE_WORKER && mts_debug_concurrent_access < 2 && mts_debug_concurrent_access > 0) { DBUG_PRINT("mts", ("Waiting while locks are acquired to show " "concurrency in mts: %u %u\n", mts_debug_concurrent_access, thd->thread_id())); my_sleep(6000000); } }; ); /* Unlocks and closes the rpl_info table. */ error= access->close_table(thd, table, &backup, error) || error; thd->is_operating_substatement_implicitly= false; reenable_binlog(thd); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(error); } int Rpl_info_table::do_remove_info() { return do_clean_info(); } int Rpl_info_table::do_clean_info() { int error= 1; enum enum_return_id res= FOUND_ID; TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; DBUG_ENTER("Rpl_info_table::do_remove_info"); THD *thd= access->create_thd(); saved_mode= thd->variables.sql_mode; tmp_disable_binlog(thd); /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_WRITE, &table, &backup)) goto end; /* Points the cursor at the row to be deleted according to the keys. If the row is not found, the execution proceeds normally. */ if ((res= access->find_info(field_values, table)) == FOUND_ID) { /* Deletes a row in the rpl_info table. */ if ((error= table->file->ha_delete_row(table->record[0]))) { table->file->print_error(error, MYF(0)); goto end; } } error= (res == ERROR_ID); end: /* Unlocks and closes the rpl_info table. */ error= access->close_table(thd, table, &backup, error) || error; reenable_binlog(thd); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(error); } /** Removes records belonging to the channel_name parameter's channel. @param nparam number of fields in the table @param param_schema schema name @param param_table table name @param channel_name channel name @param channel_field_idx channel name field index @return 0 on success 1 when a failure happens */ int Rpl_info_table::do_reset_info(uint nparam, const char* param_schema, const char *param_table, const char *channel_name, uint channel_field_idx) { int error= 0; TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; Rpl_info_table *info= NULL; THD *thd= NULL; int handler_error= 0; DBUG_ENTER("Rpl_info_table::do_reset_info"); if (!(info= new Rpl_info_table(nparam, param_schema, param_table))) DBUG_RETURN(1); thd= info->access->create_thd(); saved_mode= thd->variables.sql_mode; tmp_disable_binlog(thd); /* Opens and locks the rpl_info table before accessing it. */ if (info->access->open_table(thd, info->str_schema, info->str_table, info->get_number_info(), TL_WRITE, &table, &backup)) { error= 1; goto end; } if (!(handler_error= table->file->ha_index_init(0, 1))) { KEY *key_info= table->key_info; /* Currently this method is used only for Worker info table resetting. todo: for another table in future, consider to make use of the passed parameter to locate the lookup key. */ DBUG_ASSERT(strcmp(info->str_table.str, "slave_worker_info") == 0); if (info->verify_table_primary_key_fields(table)) { error= 1; table->file->ha_index_end(); goto end; } uint fieldnr= key_info->key_part[0].fieldnr - 1; table->field[fieldnr]->store(channel_name, strlen(channel_name), &my_charset_bin); uint key_len= key_info->key_part[0].store_length; uchar *key_buf= table->field[fieldnr]->ptr; if (!(handler_error= table->file->ha_index_read_map(table->record[0], key_buf, (key_part_map) 1, HA_READ_KEY_EXACT))) { do { if ((handler_error= table->file->ha_delete_row(table->record[0]))) break; } while (!(handler_error= table->file->ha_index_next_same(table->record[0], key_buf, key_len))); if (handler_error != HA_ERR_END_OF_FILE) error= 1; } else { /* Being reset table can be even empty, and that's benign. */ if (handler_error != HA_ERR_KEY_NOT_FOUND) error= 1; } if (error) table->file->print_error(handler_error, MYF(0)); table->file->ha_index_end(); } end: /* Unlocks and closes the rpl_info table. */ error= info->access->close_table(thd, table, &backup, error) || error; reenable_binlog(thd); thd->variables.sql_mode= saved_mode; info->access->drop_thd(thd); delete info; DBUG_RETURN(error); } enum_return_check Rpl_info_table::do_check_info() { TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; enum_return_check return_check= ERROR_CHECKING_REPOSITORY; DBUG_ENTER("Rpl_info_table::do_check_info"); THD *thd= access->create_thd(); saved_mode= thd->variables.sql_mode; /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_READ, &table, &backup)) { sql_print_warning("Info table is not ready to be used. Table " "'%s.%s' cannot be opened.", str_schema.str, str_table.str); return_check= ERROR_CHECKING_REPOSITORY; goto end; } /* Points the cursor at the row to be read according to the keys. */ if (access->find_info(field_values, table) != FOUND_ID) { /* We cannot simply call my_error here because it does not really means that there was a failure but only that the record was not found. */ return_check= REPOSITORY_DOES_NOT_EXIST; goto end; } return_check= REPOSITORY_EXISTS; end: /* Unlocks and closes the rpl_info table. */ access->close_table(thd, table, &backup, return_check == ERROR_CHECKING_REPOSITORY); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(return_check); } enum_return_check Rpl_info_table::do_check_info(uint instance) { TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; enum_return_check return_check= ERROR_CHECKING_REPOSITORY; DBUG_ENTER("Rpl_info_table::do_check_info"); THD *thd= access->create_thd(); saved_mode= thd->variables.sql_mode; /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_READ, &table, &backup)) { sql_print_warning("Info table is not ready to be used. Table " "'%s.%s' cannot be opened.", str_schema.str, str_table.str); return_check= ERROR_CHECKING_REPOSITORY; goto end; } if (verify_table_primary_key_fields(table)) { return_check= ERROR_CHECKING_REPOSITORY; goto end; } /* Points the cursor at the row to be read according to the keys. */ if (access->scan_info(table, instance) != FOUND_ID) { /* We cannot simply call my_error here because it does not really means that there was a failure but only that the record was not found. */ return_check= REPOSITORY_DOES_NOT_EXIST; goto end; } return_check= REPOSITORY_EXISTS; end: /* Unlocks and closes the rpl_info table. */ access->close_table(thd, table, &backup, return_check == ERROR_CHECKING_REPOSITORY); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(return_check); } bool Rpl_info_table::do_count_info(uint nparam, const char* param_schema, const char *param_table, uint* counter) { int error= 1; TABLE *table= NULL; sql_mode_t saved_mode; Open_tables_backup backup; Rpl_info_table *info= NULL; THD *thd= NULL; DBUG_ENTER("Rpl_info_table::do_count_info"); if (!(info= new Rpl_info_table(nparam, param_schema, param_table))) DBUG_RETURN(true); thd= info->access->create_thd(); saved_mode= thd->variables.sql_mode; /* Opens and locks the rpl_info table before accessing it. */ if (info->access->open_table(thd, info->str_schema, info->str_table, info->get_number_info(), TL_READ, &table, &backup)) { /* We cannot simply print out a warning message at this point because this may represent a bootstrap. */ error= 0; goto end; } /* Counts entries in the rpl_info table. */ if (info->access->count_info(table, counter)) { sql_print_warning("Info table is not ready to be used. Table " "'%s.%s' cannot be scanned.", info->str_schema.str, info->str_table.str); goto end; } error= 0; end: /* Unlocks and closes the rpl_info table. */ error= info->access->close_table(thd, table, &backup, error) || error; thd->variables.sql_mode= saved_mode; info->access->drop_thd(thd); delete info; DBUG_RETURN(error); } void Rpl_info_table::do_end_info() { } int Rpl_info_table::do_prepare_info_for_read() { if (!field_values) return TRUE; cursor= 0; prv_error= FALSE; return FALSE; } int Rpl_info_table::do_prepare_info_for_write() { return(do_prepare_info_for_read()); } uint Rpl_info_table::do_get_rpl_info_type() { return INFO_REPOSITORY_TABLE; } bool Rpl_info_table::do_set_info(const int pos, const char *value) { return (field_values->value[pos].copy(value, strlen(value), &my_charset_bin)); } bool Rpl_info_table::do_set_info(const int pos, const uchar *value, const size_t size) { return (field_values->value[pos].copy((char *) value, size, &my_charset_bin)); } bool Rpl_info_table::do_set_info(const int pos, const ulong value) { return (field_values->value[pos].set_int(value, TRUE, &my_charset_bin)); } bool Rpl_info_table::do_set_info(const int pos, const int value) { return (field_values->value[pos].set_int(value, FALSE, &my_charset_bin)); } bool Rpl_info_table::do_set_info(const int pos, const float value) { return (field_values->value[pos].set_real(value, NOT_FIXED_DEC, &my_charset_bin)); } bool Rpl_info_table::do_set_info(const int pos, const Server_ids *value) { if (const_cast(value)->pack_dynamic_ids(&field_values->value[pos])) return TRUE; return FALSE; } bool Rpl_info_table::do_get_info(const int pos, char *value, const size_t size, const char *default_value) { if (field_values->value[pos].length()) strmake(value, field_values->value[pos].c_ptr_safe(), field_values->value[pos].length()); else if (default_value) strmake(value, default_value, strlen(default_value)); else *value= '\0'; return FALSE; } bool Rpl_info_table::do_get_info(const int pos, uchar *value, const size_t size, const uchar *default_value MY_ATTRIBUTE((unused))) { if (field_values->value[pos].length() == size) return (!memcpy((char *) value, field_values->value[pos].c_ptr_safe(), size)); return TRUE; } bool Rpl_info_table::do_get_info(const int pos, ulong *value, const ulong default_value) { if (field_values->value[pos].length()) { *value= strtoul(field_values->value[pos].c_ptr_safe(), 0, 10); return FALSE; } else if (default_value) { *value= default_value; return FALSE; } return TRUE; } bool Rpl_info_table::do_get_info(const int pos, int *value, const int default_value) { if (field_values->value[pos].length()) { *value= atoi(field_values->value[pos].c_ptr_safe()); return FALSE; } else if (default_value) { *value= default_value; return FALSE; } return TRUE; } bool Rpl_info_table::do_get_info(const int pos, float *value, const float default_value) { if (field_values->value[pos].length()) { if (sscanf(field_values->value[pos].c_ptr_safe(), "%f", value) != 1) return TRUE; return FALSE; } else if (default_value != 0.0) { *value= default_value; return FALSE; } return TRUE; } bool Rpl_info_table::do_get_info(const int pos, Server_ids *value, const Server_ids *default_value MY_ATTRIBUTE((unused))) { if (value->unpack_dynamic_ids(field_values->value[pos].c_ptr_safe())) return TRUE; return FALSE; } char* Rpl_info_table::do_get_description_info() { return description; } bool Rpl_info_table::do_is_transactional() { return is_transactional; } bool Rpl_info_table::do_update_is_transactional() { bool error= TRUE; sql_mode_t saved_mode; TABLE *table= NULL; Open_tables_backup backup; DBUG_ENTER("Rpl_info_table::do_update_is_transactional"); DBUG_EXECUTE_IF("simulate_update_is_transactional_error", { DBUG_RETURN(TRUE); }); THD *thd= access->create_thd(); saved_mode= thd->variables.sql_mode; tmp_disable_binlog(thd); /* Opens and locks the rpl_info table before accessing it. */ if (access->open_table(thd, str_schema, str_table, get_number_info(), TL_READ, &table, &backup)) goto end; is_transactional= table->file->has_transactions(); error= FALSE; end: error= access->close_table(thd, table, &backup, 0) || error; reenable_binlog(thd); thd->variables.sql_mode= saved_mode; access->drop_thd(thd); DBUG_RETURN(error); } bool Rpl_info_table::verify_table_primary_key_fields(TABLE *table) { DBUG_ENTER("Rpl_info_table::verify_table_primary_key_fields"); KEY *key_info= table->key_info; bool error; /* If the table has no keys or has less key fields than expected, it must be corrupted. */ if ((error= !key_info || key_info->user_defined_key_parts == 0 || (m_n_pk_fields > 0 && key_info->user_defined_key_parts != m_n_pk_fields))) { sql_print_error("Corrupted table %s.%s. Check out table definition.", str_schema.str, str_table.str); } if (!error && m_n_pk_fields && m_pk_field_indexes) { /* If any of its primary key fields are not at the expected position, the table must be corrupted. */ for (uint idx= 0; idx < m_n_pk_fields; idx++) { if (key_info->key_part[idx].field != table->field[m_pk_field_indexes[idx]]) { const char *key_field_name= key_info->key_part[idx].field->field_name; const char *table_field_name= table->field[m_pk_field_indexes[idx]]->field_name; sql_print_error("Info table has a problem with its key field(s). " "Table '%s.%s' expected field #%u to be '%s' but " "found '%s' instead.", str_schema.str, str_table.str, m_pk_field_indexes[idx], key_field_name, table_field_name); error= true; break; } } } DBUG_RETURN(error); }