summaryrefslogtreecommitdiff
path: root/sql/field.cc
diff options
context:
space:
mode:
authorMats Kindahl <mats@mysql.com>2008-06-30 22:11:18 +0200
committerMats Kindahl <mats@mysql.com>2008-06-30 22:11:18 +0200
commit711305e2c5772d3fd1f555a27ebd5bcb94fd6a77 (patch)
tree99d0103a1bedf9e6a2c68aa7ca343d20d8dafc6d /sql/field.cc
parent8a2f17d19c731934d954b57f01ba3f2f330012ed (diff)
downloadmariadb-git-711305e2c5772d3fd1f555a27ebd5bcb94fd6a77.tar.gz
BUG#37426: RBR breaks for CHAR() UTF-8 fields > 85 chars
In order to handle CHAR() fields, 8 bits were reserved for the size of the CHAR field. However, instead of denoting the number of characters in the field, field_length was used which denotes the number of bytes in the field. Since UTF-8 fields can have three bytes per character (and has been extended to have four bytes per character in 6.0), an extra two bits have been encoded in the field metadata work for fields of type Field_string (i.e., CHAR fields). Since the metadata word is filled, the extra bits have been encoded in the upper 4 bits of the real type (the most significant byte of the metadata word) by computing the bitwise xor of the extra two bits. Since the upper 4 bits of the real type always is 1111 for Field_string, this means that for fields of length <256, the encoding is identical to the encoding used in pre-5.1.26 servers, but for lengths of 256 or more, an unrecognized type is formed, causing an old slave (that does not handle lengths of 256 or more) to stop. mysql-test/extra/rpl_tests/rpl_row_basic.test: Adding test cases for replicating UTF-8 fields of lengths of 256 or more (bytes). mysql-test/suite/binlog/r/binlog_base64_flag.result: Result file change. mysql-test/suite/binlog/t/binlog_base64_flag.test: Adding tests to trigger check that an error is generated when replicating from a 5.1.25 server for tables with a CHAR(128) but not when replicating a table with a CHAR(63). Although the bug indicates that the limit is 83, we elected to use CHAR(63) since 6.0 uses 4-byte UTF-8, and anything exceeding 63 would then cause the test to fail when the patch is merged to 6.0. mysql-test/suite/bugs/combinations: Adding combinations file to run all bug reports in all binlog modes (where applicable). mysql-test/suite/bugs/r/rpl_bug37426.result: Result file change. mysql-test/suite/bugs/t/rpl_bug37426.test: Added test for reported bug. mysql-test/suite/rpl/r/rpl_row_basic_2myisam.result: Result file change. mysql-test/suite/rpl/r/rpl_row_basic_3innodb.result: Result file change. sql/field.cc: Encoding an extra two bits in the most significant nibble (4 bits) of the metadata word. Adding assertions to ensure that no attempt is made to use lengths longer than supported. Extending compatible_field_size() function with an extra parameter holding a Relay_log_instace for error reporting. Field_string::compatible_field_size() now reports an error if field size for a CHAR is >255. sql/field.h: Field length is now computed from most significant 4 bits of metadata word, or is equal to the row pack length if there is no metadata. Extending compatible_field_size() function with an extra parameter holding a Relay_log_instace for error reporting. sql/rpl_utility.cc: Adding relay log parameter to compatible_field_size(). Minor refactoring to eliminate duplicate code. sql/slave.cc: Extending rpl_master_has_bug() with a single-argument predicate function and a parameter to the predicate function. The predicate function can be used to test for extra conditions for the bug before writing an error message. sql/slave.h: Extending rpl_master_has_bug() with a single-argument predicate function and a parameter to the predicate function. The predicate function can be used to test for extra conditions for the bug before writing an error message. Also removing gratuitous default argument. sql/sql_insert.cc: Changing calls to rpl_master_has_bug() to adapt to changed signature.
Diffstat (limited to 'sql/field.cc')
-rw-r--r--sql/field.cc93
1 files changed, 83 insertions, 10 deletions
diff --git a/sql/field.cc b/sql/field.cc
index 9bf6df55b30..21e26b518e8 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -27,6 +27,8 @@
#include "mysql_priv.h"
#include "sql_select.h"
+#include "rpl_rli.h" // Pull in Relay_log_info
+#include "slave.h" // Pull in rpl_master_has_bug()
#include <m_ctype.h>
#include <errno.h>
#ifdef HAVE_FCONVERT
@@ -1375,7 +1377,8 @@ bool Field::send_binary(Protocol *protocol)
@retval 0 if this field's size is < the source field's size
@retval 1 if this field's size is >= the source field's size
*/
-int Field::compatible_field_size(uint field_metadata)
+int Field::compatible_field_size(uint field_metadata,
+ const Relay_log_info *rli_arg __attribute__((unused)))
{
uint const source_size= pack_length_from_metadata(field_metadata);
uint const destination_size= row_pack_length();
@@ -2837,7 +2840,8 @@ uint Field_new_decimal::pack_length_from_metadata(uint field_metadata)
@retval 0 if this field's size is < the source field's size
@retval 1 if this field's size is >= the source field's size
*/
-int Field_new_decimal::compatible_field_size(uint field_metadata)
+int Field_new_decimal::compatible_field_size(uint field_metadata,
+ const Relay_log_info * __attribute__((unused)))
{
int compatible= 0;
uint const source_precision= (field_metadata >> 8U) & 0x00ff;
@@ -4037,7 +4041,6 @@ Field_real::pack(uchar *to, const uchar *from,
{
DBUG_ENTER("Field_real::pack");
DBUG_ASSERT(max_length >= pack_length());
- DBUG_PRINT("debug", ("pack_length(): %u", pack_length()));
#ifdef WORDS_BIGENDIAN
if (low_byte_first != table->s->db_low_byte_first)
{
@@ -4056,7 +4059,6 @@ Field_real::unpack(uchar *to, const uchar *from,
uint param_data, bool low_byte_first)
{
DBUG_ENTER("Field_real::unpack");
- DBUG_PRINT("debug", ("pack_length(): %u", pack_length()));
#ifdef WORDS_BIGENDIAN
if (low_byte_first != table->s->db_low_byte_first)
{
@@ -6638,6 +6640,36 @@ my_decimal *Field_string::val_decimal(my_decimal *decimal_value)
}
+struct Check_field_param {
+ Field *field;
+};
+
+static bool
+check_field_for_37426(const void *param_arg)
+{
+ Check_field_param *param= (Check_field_param*) param_arg;
+ DBUG_ASSERT(param->field->real_type() == MYSQL_TYPE_STRING);
+ DBUG_PRINT("debug", ("Field %s - type: %d, size: %d",
+ param->field->field_name,
+ param->field->real_type(),
+ param->field->row_pack_length()));
+ return param->field->row_pack_length() > 255;
+}
+
+
+int Field_string::compatible_field_size(uint field_metadata,
+ const Relay_log_info *rli_arg)
+{
+#ifdef HAVE_REPLICATION
+ const Check_field_param check_param = { this };
+ if (rpl_master_has_bug(rli_arg, 37426, TRUE,
+ check_field_for_37426, &check_param))
+ return FALSE; // Not compatible field sizes
+#endif
+ return Field::compatible_field_size(field_metadata, rli_arg);
+}
+
+
int Field_string::cmp(const uchar *a_ptr, const uchar *b_ptr)
{
uint a_len, b_len;
@@ -6724,6 +6756,9 @@ uchar *Field_string::pack(uchar *to, const uchar *from,
@c param_data argument contains the result of field->real_type() from
the master.
+ @note For information about how the length is packed, see @c
+ Field_string::do_save_field_metadata
+
@param to Destination of the data
@param from Source of the data
@param param_data Real type (upper) and length (lower) values
@@ -6736,10 +6771,24 @@ Field_string::unpack(uchar *to,
uint param_data,
bool low_byte_first __attribute__((unused)))
{
- uint from_length=
- param_data ? min(param_data & 0x00ff, field_length) : field_length;
- uint length;
+ uint from_length, length;
+ /*
+ Compute the declared length of the field on the master. This is
+ used to decide if one or two bytes should be read as length.
+ */
+ if (param_data)
+ from_length= (((param_data >> 4) & 0x300) ^ 0x300) + (param_data & 0x00ff);
+ else
+ from_length= field_length;
+
+ DBUG_PRINT("debug",
+ ("param_data: 0x%x, field_length: %u, from_length: %u",
+ param_data, field_length, from_length));
+ /*
+ Compute the actual length of the data by reading one or two bits
+ (depending on the declared field length on the master).
+ */
if (from_length > 255)
{
length= uint2korr(from);
@@ -6762,14 +6811,37 @@ Field_string::unpack(uchar *to,
second byte of the field metadata array at index of *metadata_ptr and
*(metadata_ptr + 1).
+ @note In order to be able to handle lengths exceeding 255 and be
+ backwards-compatible with pre-5.1.26 servers, an extra two bits of
+ the length has been added to the metadata in such a way that if
+ they are set, a new unrecognized type is generated. This will
+ cause pre-5.1-26 servers to stop due to a field type mismatch,
+ while new servers will be able to extract the extra bits. If the
+ length is <256, there will be no difference and both a new and an
+ old server will be able to handle it.
+
+ @note The extra two bits are added to bits 13 and 14 of the
+ parameter data (with 1 being the least siginficant bit and 16 the
+ most significant bit of the word) by xoring the extra length bits
+ with the real type. Since all allowable types have 0xF as most
+ significant bits of the metadata word, lengths <256 will not affect
+ the real type at all, while all other values will result in a
+ non-existant type in the range 17-244.
+
+ @see Field_string::unpack
+
@param metadata_ptr First byte of field metadata
@returns number of bytes written to metadata_ptr
*/
int Field_string::do_save_field_metadata(uchar *metadata_ptr)
{
- *metadata_ptr= real_type();
- *(metadata_ptr + 1)= field_length;
+ DBUG_ASSERT(field_length < 1024);
+ DBUG_ASSERT((real_type() & 0xF0) == 0xF0);
+ DBUG_PRINT("debug", ("field_length: %u, real_type: %u",
+ field_length, real_type()));
+ *metadata_ptr= (real_type() ^ ((field_length & 0x300) >> 4));
+ *(metadata_ptr + 1)= field_length & 0xFF;
return 2;
}
@@ -9118,7 +9190,8 @@ uint Field_bit::pack_length_from_metadata(uint field_metadata)
@retval 0 if this field's size is < the source field's size
@retval 1 if this field's size is >= the source field's size
*/
-int Field_bit::compatible_field_size(uint field_metadata)
+int Field_bit::compatible_field_size(uint field_metadata,
+ const Relay_log_info * __attribute__((unused)))
{
int compatible= 0;
uint const source_size= pack_length_from_metadata(field_metadata);