diff options
Diffstat (limited to 'sql/rpl_record.cc')
-rw-r--r-- | sql/rpl_record.cc | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc new file mode 100644 index 00000000..7da296b4 --- /dev/null +++ b/sql/rpl_record.cc @@ -0,0 +1,509 @@ +/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. + Copyright (c) 2008, 2019, MariaDB Corporation. + + 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-1335 USA */ + +#include "mariadb.h" +#include "sql_priv.h" +#include "unireg.h" +#include "rpl_rli.h" +#include "rpl_record.h" +#include "slave.h" // Need to pull in slave_print_msg +#include "rpl_utility.h" +#include "rpl_rli.h" + +/** + Pack a record of data for a table into a format suitable for + transfer via the binary log. + + The format for a row in transfer with N fields is the following: + + ceil(N/8) null bytes: + One null bit for every column *regardless of whether it can be + null or not*. This simplifies the decoding. Observe that the + number of null bits is equal to the number of set bits in the + @c cols bitmap. The number of null bytes is the smallest number + of bytes necessary to store the null bits. + + Padding bits are 1. + + N packets: + Each field is stored in packed format. + + + @param table Table describing the format of the record + + @param cols Bitmap with a set bit for each column that should + be stored in the row + + @param row_data Pointer to memory where row will be written + + @param record Pointer to record that should be packed. It is + assumed that the pointer refers to either @c + record[0] or @c record[1], but no such check is + made since the code does not rely on that. + + @return The number of bytes written at @c row_data. + */ +#if !defined(MYSQL_CLIENT) +size_t +pack_row(TABLE *table, MY_BITMAP const* cols, + uchar *row_data, const uchar *record) +{ + Field **p_field= table->field, *field; + int const null_byte_count= (bitmap_bits_set(cols) + 7) / 8; + uchar *pack_ptr = row_data + null_byte_count; + uchar *null_ptr = row_data; + my_ptrdiff_t const rec_offset= record - table->record[0]; + my_ptrdiff_t const def_offset= table->s->default_values - table->record[0]; + + DBUG_ENTER("pack_row"); + + /* + We write the null bits and the packed records using one pass + through all the fields. The null bytes are written little-endian, + i.e., the first fields are in the first byte. + */ + unsigned int null_bits= (1U << 8) - 1; + // Mask to mask out the correct but among the null bits + unsigned int null_mask= 1U; + for ( ; (field= *p_field) ; p_field++) + { + if (bitmap_is_set(cols, (uint)(p_field - table->field))) + { + my_ptrdiff_t offset; + if (field->is_null(rec_offset)) + { + offset= def_offset; + null_bits |= null_mask; + } + else + { + offset= rec_offset; + null_bits &= ~null_mask; + + /* + We only store the data of the field if it is non-null + + For big-endian machines, we have to make sure that the + length is stored in little-endian format, since this is the + format used for the binlog. + */ +#ifndef DBUG_OFF + const uchar *old_pack_ptr= pack_ptr; +#endif + pack_ptr= field->pack(pack_ptr, field->ptr + offset, + field->max_data_length()); + DBUG_PRINT("debug", ("field: %s; real_type: %d, pack_ptr: %p;" + " pack_ptr':%p; bytes: %d", + field->field_name.str, field->real_type(), + old_pack_ptr,pack_ptr, + (int) (pack_ptr - old_pack_ptr))); + DBUG_DUMP("packed_data", old_pack_ptr, pack_ptr - old_pack_ptr); + } + + null_mask <<= 1; + if ((null_mask & 0xFF) == 0) + { + DBUG_ASSERT(null_ptr < row_data + null_byte_count); + null_mask = 1U; + *null_ptr++ = null_bits; + null_bits= (1U << 8) - 1; + } + } + } + + /* + Write the last (partial) byte, if there is one + */ + if ((null_mask & 0xFF) > 1) + { + DBUG_ASSERT(null_ptr < row_data + null_byte_count); + *null_ptr++ = null_bits; + } + + /* + The null pointer should now point to the first byte of the + packed data. If it doesn't, something is very wrong. + */ + DBUG_ASSERT(null_ptr == row_data + null_byte_count); + DBUG_DUMP("row_data", row_data, pack_ptr - row_data); + DBUG_RETURN(static_cast<size_t>(pack_ptr - row_data)); +} +#endif + + +/** + Unpack a row into @c table->record[0]. + + The function will always unpack into the @c table->record[0] + record. This is because there are too many dependencies on where + the various member functions of Field and subclasses expect to + write. + + The row is assumed to only consist of the fields for which the + corresponding bit in bitset @c cols is set; the other parts of the + record are left alone. + + At most @c colcnt columns are read: if the table is larger than + that, the remaining fields are not filled in. + + @note The relay log information can be NULL, which means that no + checking or comparison with the source table is done, simply + because it is not used. This feature is used by MySQL Backup to + unpack a row from from the backup image, but can be used for other + purposes as well. + + @param rli Relay log info, which can be NULL + @param table Table to unpack into + @param colcnt Number of columns to read from record + @param row_data + Packed row data + @param cols Pointer to bitset describing columns to fill in + @param curr_row_end + Pointer to variable that will hold the value of the + one-after-end position for the current row + @param master_reclength + Pointer to variable that will be set to the length of the + record on the master side + @param row_end + Pointer to variable that will hold the value of the + end position for the data in the row event + + @retval 0 No error + + @retval HA_ERR_GENERIC + A generic, internal, error caused the unpacking to fail. + @retval HA_ERR_CORRUPT_EVENT + Found error when trying to unpack fields. + */ +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +int +unpack_row(rpl_group_info *rgi, + TABLE *table, uint const colcnt, + uchar const *const row_data, MY_BITMAP const *cols, + uchar const **const current_row_end, ulong *const master_reclength, + uchar const *const row_end) +{ + int error; + DBUG_ENTER("unpack_row"); + DBUG_ASSERT(row_data); + DBUG_ASSERT(table); + size_t const master_null_byte_count= (bitmap_bits_set(cols) + 7) / 8; + + uchar const *null_ptr= row_data; + uchar const *pack_ptr= row_data + master_null_byte_count; + + Field **const begin_ptr = table->field; + Field **field_ptr; + Field **const end_ptr= begin_ptr + colcnt; + + if (bitmap_is_clear_all(cols)) + { + /** + There was no data sent from the master, so there is + nothing to unpack. + */ + *current_row_end= pack_ptr; + *master_reclength= 0; + DBUG_RETURN(0); + } + DBUG_ASSERT(null_ptr < row_data + master_null_byte_count); + + // Mask to mask out the correct bit among the null bits + unsigned int null_mask= 1U; + // The "current" null bits + unsigned int null_bits= *null_ptr++; + uint i= 0; + table_def *tabledef= NULL; + TABLE *conv_table= NULL; + bool table_found= rgi && rgi->get_table_data(table, &tabledef, &conv_table); + DBUG_PRINT("debug", ("Table data: table_found: %d, tabldef: %p, conv_table: %p", + table_found, tabledef, conv_table)); + DBUG_ASSERT(table_found); + + /* + If rgi is NULL it means that there is no source table and that the + row shall just be unpacked without doing any checks. This feature + is used by MySQL Backup, but can be used for other purposes as + well. + */ + if (rgi && !table_found) + DBUG_RETURN(HA_ERR_GENERIC); + + for (field_ptr= begin_ptr ; field_ptr < end_ptr && *field_ptr ; ++field_ptr) + { + /* + If there is a conversion table, we pick up the field pointer to + the conversion table. If the conversion table or the field + pointer is NULL, no conversions are necessary. + */ + Field *conv_field= + conv_table ? conv_table->field[field_ptr - begin_ptr] : NULL; + Field *const f= + conv_field ? conv_field : *field_ptr; + DBUG_PRINT("debug", ("Conversion %srequired for field '%s' (#%ld)", + conv_field ? "" : "not ", + (*field_ptr)->field_name.str, + (long) (field_ptr - begin_ptr))); + DBUG_ASSERT(f != NULL); + + /* + No need to bother about columns that does not exist: they have + gotten default values when being emptied above. + */ + if (bitmap_is_set(cols, (uint)(field_ptr - begin_ptr))) + { + if ((null_mask & 0xFF) == 0) + { + DBUG_ASSERT(null_ptr < row_data + master_null_byte_count); + null_mask= 1U; + null_bits= *null_ptr++; + } + + DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set + + if (null_bits & null_mask) + { + if (f->maybe_null()) + { + DBUG_PRINT("debug", ("Was NULL; null mask: 0x%x; null bits: 0x%x", + null_mask, null_bits)); + /** + Calling reset just in case one is unpacking on top a + record with data. + + This could probably go into set_null() but doing so, + (i) triggers assertion in other parts of the code at + the moment; (ii) it would make us reset the field, + always when setting null, which right now doesn't seem + needed anywhere else except here. + + TODO: maybe in the future we should consider moving + the reset to make it part of set_null. But then + the assertions triggered need to be + addressed/revisited. + */ + f->reset(); + f->set_null(); + } + else + { + THD *thd= f->table->in_use; + + f->set_default(); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_NULL_ERROR, + ER_THD(thd, ER_BAD_NULL_ERROR), + f->field_name.str); + } + } + else + { + f->set_notnull(); + + /* + We only unpack the field if it was non-null. + Use the master's size information if available else call + normal unpack operation. + */ + uint16 const metadata= tabledef->field_metadata(i); + IF_DBUG(uchar const *const old_pack_ptr= pack_ptr;,) + + pack_ptr= f->unpack(f->ptr, pack_ptr, row_end, metadata); + DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;" + " pack_ptr: %p; pack_ptr': %p; bytes: %d", + f->field_name.str, metadata, + old_pack_ptr, pack_ptr, + (int) (pack_ptr - old_pack_ptr))); + if (!pack_ptr) + { + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, + rgi->gtid_info(), + "Could not read field '%s' of table '%s.%s'", + f->field_name.str, table->s->db.str, + table->s->table_name.str); + DBUG_RETURN(HA_ERR_CORRUPT_EVENT); + } + } + + /* + If conv_field is set, then we are doing a conversion. In this + case, we have unpacked the master data to the conversion + table, so we need to copy the value stored in the conversion + table into the final table and do the conversion at the same time. + */ + if (conv_field) + { + Copy_field copy; +#ifndef DBUG_OFF + char source_buf[MAX_FIELD_WIDTH]; + char value_buf[MAX_FIELD_WIDTH]; + String source_type(source_buf, sizeof(source_buf), system_charset_info); + String value_string(value_buf, sizeof(value_buf), system_charset_info); + conv_field->sql_type(source_type); + conv_field->val_str(&value_string); + DBUG_PRINT("debug", ("Copying field '%s' of type '%s' with value '%s'", + (*field_ptr)->field_name.str, + source_type.c_ptr_safe(), value_string.c_ptr_safe())); +#endif + copy.set(*field_ptr, f, TRUE); + (*copy.do_copy)(©); +#ifndef DBUG_OFF + char target_buf[MAX_FIELD_WIDTH]; + String target_type(target_buf, sizeof(target_buf), system_charset_info); + (*field_ptr)->sql_type(target_type); + (*field_ptr)->val_str(&value_string); + DBUG_PRINT("debug", ("Value of field '%s' of type '%s' is now '%s'", + (*field_ptr)->field_name.str, + target_type.c_ptr_safe(), value_string.c_ptr_safe())); +#endif + } + + null_mask <<= 1; + } + i++; + } + + /* + throw away master's extra fields + */ + uint max_cols= MY_MIN(tabledef->size(), cols->n_bits); + for (; i < max_cols; i++) + { + if (bitmap_is_set(cols, i)) + { + if ((null_mask & 0xFF) == 0) + { + DBUG_ASSERT(null_ptr < row_data + master_null_byte_count); + null_mask= 1U; + null_bits= *null_ptr++; + } + DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set + + if (!((null_bits & null_mask) && tabledef->maybe_null(i))) { + uint32 len= tabledef->calc_field_size(i, (uchar *) pack_ptr); + DBUG_DUMP("field_data", pack_ptr, len); + pack_ptr+= len; + } + null_mask <<= 1; + } + } + + /* + Add Extra slave persistent columns + */ + if (unlikely(error= fill_extra_persistent_columns(table, cols->n_bits))) + DBUG_RETURN(error); + + /* + We should now have read all the null bytes, otherwise something is + really wrong. + */ + DBUG_ASSERT(null_ptr == row_data + master_null_byte_count); + + DBUG_DUMP("row_data", row_data, pack_ptr - row_data); + + *current_row_end = pack_ptr; + if (master_reclength) + { + if (*field_ptr) + *master_reclength = (ulong)((*field_ptr)->ptr - table->record[0]); + else + *master_reclength = table->s->reclength; + } + + DBUG_RETURN(0); +} + +/** + Fills @c table->record[0] with default values. + + First @c restore_record() is called to restore the default values for + record concerning the given table. Then, if @c check is true, + a check is performed to see if fields are have default value or can + be NULL. Otherwise error is reported. + + @param table Table whose record[0] buffer is prepared. + @param skip Number of columns for which default/nullable check + should be skipped. + @param check Specifies if lack of default error needs checking. + + @returns 0 on success or a handler level error code + */ +int prepare_record(TABLE *const table, const uint skip, const bool check) +{ + DBUG_ENTER("prepare_record"); + + restore_record(table, s->default_values); + + /* + This skip should be revisited in 6.0, because in 6.0 RBR one + can have holes in the row (as the grain of the writeset is + the column and not the entire row). + */ + if (skip >= table->s->fields || !check) + DBUG_RETURN(0); + + /* + For fields the extra fields on the slave, we check if they have a default. + The check follows the same rules as the INSERT query without specifying an + explicit value for a field not having the explicit default + (@c check_that_all_fields_are_given_values()). + */ + for (Field **field_ptr= table->field+skip; *field_ptr; ++field_ptr) + { + Field *const f= *field_ptr; + if ((f->flags & NO_DEFAULT_VALUE_FLAG) && + (f->real_type() != MYSQL_TYPE_ENUM)) + { + THD *thd= f->table->in_use; + f->set_default(); + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + ER_NO_DEFAULT_FOR_FIELD, + ER_THD(thd, ER_NO_DEFAULT_FOR_FIELD), + f->field_name.str); + } + } + + DBUG_RETURN(0); +} +/** + Fills @c table->record[0] with computed values of extra persistent column + which are present on slave but not on master. + + @param table Table whose record[0] buffer is prepared. + @param master_cols No of columns on master + @returns 0 on success + */ +int fill_extra_persistent_columns(TABLE *table, int master_cols) +{ + int error= 0; + Field **vfield_ptr, *vfield; + + if (!table->vfield) + return 0; + for (vfield_ptr= table->vfield; *vfield_ptr; ++vfield_ptr) + { + vfield= *vfield_ptr; + if (vfield->field_index >= master_cols && vfield->stored_in_db()) + { + bitmap_set_bit(table->write_set, vfield->field_index); + error= vfield->vcol_info->expr->save_in_field(vfield,0); + } + } + return error; +} +#endif // HAVE_REPLICATION |