Skip to content

Commit 790b6f5

Browse files
committed
MDEV-18598: Wrong results after instant integer conversions
Field_str::is_equal(): Do not allow instant conversions between BIT (which is stored big-endian) and integer types (which can be stored big-endian or little-endian, depending on storage engine). row_sel_field_store_in_mysql_format_func(): Properly extend narrower integer and DATA_FIXBINARY values to the current format. DATA_FIXBINARY was incorrectly padded with 0x20 instead of 0.
1 parent e9e4788 commit 790b6f5

File tree

5 files changed

+228
-30
lines changed

5 files changed

+228
-30
lines changed

mysql-test/suite/innodb/r/instant_alter.result

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,52 @@ DROP TABLE t1;
737737
CREATE TABLE t1(t TEXT NOT NULL, FULLTEXT(t)) ENGINE=InnoDB ROW_FORMAT=REDUNDANT;
738738
ALTER TABLE t1 MODIFY COLUMN t TEXT;
739739
DROP TABLE t1;
740+
CREATE TABLE t1 (f TINYINT, g SMALLINT UNSIGNED) ENGINE=InnoDB ROW_FORMAT=REDUNDANT;
741+
INSERT INTO t1 VALUES(127,6502),(-128,33101);
742+
ALTER TABLE t1 MODIFY f SMALLINT DEFAULT 12345,
743+
MODIFY g BIGINT UNSIGNED DEFAULT 1234567;
744+
affected rows: 0
745+
info: Records: 0 Duplicates: 0 Warnings: 0
746+
SELECT * FROM t1;
747+
f g
748+
127 6502
749+
-128 33101
750+
DROP TABLE t1;
751+
CREATE TABLE t1 (f BIT(8)) ENGINE=InnoDB ROW_FORMAT=REDUNDANT;
752+
INSERT INTO t1 VALUES (b'10000000'),(b'00000001');
753+
ALTER TABLE t1 MODIFY f BIT(16);
754+
affected rows: 2
755+
info: Records: 2 Duplicates: 0 Warnings: 0
756+
INSERT INTO t1 VALUES (b'1000000010101111'),(b'10000000');
757+
SELECT HEX(f) FROM t1;
758+
HEX(f)
759+
80
760+
1
761+
80AF
762+
80
763+
ALTER TABLE t1 MODIFY f SMALLINT;
764+
ERROR 22003: Out of range value for column 'f' at row 3
765+
ALTER TABLE t1 MODIFY f SMALLINT UNSIGNED;
766+
affected rows: 4
767+
info: Records: 4 Duplicates: 0 Warnings: 0
768+
SELECT * FROM t1;
769+
f
770+
128
771+
1
772+
32943
773+
128
774+
ALTER TABLE t1 MODIFY f BIT;
775+
ERROR 22001: Data too long for column 'f' at row 1
776+
ALTER TABLE t1 MODIFY f BIT(15);
777+
ERROR 22001: Data too long for column 'f' at row 3
778+
DELETE FROM t1 LIMIT 3;
779+
ALTER TABLE t1 MODIFY f BIT(15);
780+
affected rows: 1
781+
info: Records: 1 Duplicates: 0 Warnings: 0
782+
SELECT HEX(f) FROM t1;
783+
HEX(f)
784+
80
785+
DROP TABLE t1;
740786
CREATE TABLE t1
741787
(id INT PRIMARY KEY, c2 INT UNIQUE,
742788
c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'),
@@ -1420,6 +1466,52 @@ DROP TABLE t1;
14201466
CREATE TABLE t1(t TEXT NOT NULL, FULLTEXT(t)) ENGINE=InnoDB ROW_FORMAT=COMPACT;
14211467
ALTER TABLE t1 MODIFY COLUMN t TEXT;
14221468
DROP TABLE t1;
1469+
CREATE TABLE t1 (f TINYINT, g SMALLINT UNSIGNED) ENGINE=InnoDB ROW_FORMAT=COMPACT;
1470+
INSERT INTO t1 VALUES(127,6502),(-128,33101);
1471+
ALTER TABLE t1 MODIFY f SMALLINT DEFAULT 12345,
1472+
MODIFY g BIGINT UNSIGNED DEFAULT 1234567;
1473+
affected rows: 2
1474+
info: Records: 2 Duplicates: 0 Warnings: 0
1475+
SELECT * FROM t1;
1476+
f g
1477+
127 6502
1478+
-128 33101
1479+
DROP TABLE t1;
1480+
CREATE TABLE t1 (f BIT(8)) ENGINE=InnoDB ROW_FORMAT=COMPACT;
1481+
INSERT INTO t1 VALUES (b'10000000'),(b'00000001');
1482+
ALTER TABLE t1 MODIFY f BIT(16);
1483+
affected rows: 2
1484+
info: Records: 2 Duplicates: 0 Warnings: 0
1485+
INSERT INTO t1 VALUES (b'1000000010101111'),(b'10000000');
1486+
SELECT HEX(f) FROM t1;
1487+
HEX(f)
1488+
80
1489+
1
1490+
80AF
1491+
80
1492+
ALTER TABLE t1 MODIFY f SMALLINT;
1493+
ERROR 22003: Out of range value for column 'f' at row 3
1494+
ALTER TABLE t1 MODIFY f SMALLINT UNSIGNED;
1495+
affected rows: 4
1496+
info: Records: 4 Duplicates: 0 Warnings: 0
1497+
SELECT * FROM t1;
1498+
f
1499+
128
1500+
1
1501+
32943
1502+
128
1503+
ALTER TABLE t1 MODIFY f BIT;
1504+
ERROR 22001: Data too long for column 'f' at row 1
1505+
ALTER TABLE t1 MODIFY f BIT(15);
1506+
ERROR 22001: Data too long for column 'f' at row 3
1507+
DELETE FROM t1 LIMIT 3;
1508+
ALTER TABLE t1 MODIFY f BIT(15);
1509+
affected rows: 1
1510+
info: Records: 1 Duplicates: 0 Warnings: 0
1511+
SELECT HEX(f) FROM t1;
1512+
HEX(f)
1513+
80
1514+
DROP TABLE t1;
14231515
CREATE TABLE t1
14241516
(id INT PRIMARY KEY, c2 INT UNIQUE,
14251517
c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'),
@@ -2103,6 +2195,52 @@ DROP TABLE t1;
21032195
CREATE TABLE t1(t TEXT NOT NULL, FULLTEXT(t)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
21042196
ALTER TABLE t1 MODIFY COLUMN t TEXT;
21052197
DROP TABLE t1;
2198+
CREATE TABLE t1 (f TINYINT, g SMALLINT UNSIGNED) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
2199+
INSERT INTO t1 VALUES(127,6502),(-128,33101);
2200+
ALTER TABLE t1 MODIFY f SMALLINT DEFAULT 12345,
2201+
MODIFY g BIGINT UNSIGNED DEFAULT 1234567;
2202+
affected rows: 2
2203+
info: Records: 2 Duplicates: 0 Warnings: 0
2204+
SELECT * FROM t1;
2205+
f g
2206+
127 6502
2207+
-128 33101
2208+
DROP TABLE t1;
2209+
CREATE TABLE t1 (f BIT(8)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
2210+
INSERT INTO t1 VALUES (b'10000000'),(b'00000001');
2211+
ALTER TABLE t1 MODIFY f BIT(16);
2212+
affected rows: 2
2213+
info: Records: 2 Duplicates: 0 Warnings: 0
2214+
INSERT INTO t1 VALUES (b'1000000010101111'),(b'10000000');
2215+
SELECT HEX(f) FROM t1;
2216+
HEX(f)
2217+
80
2218+
1
2219+
80AF
2220+
80
2221+
ALTER TABLE t1 MODIFY f SMALLINT;
2222+
ERROR 22003: Out of range value for column 'f' at row 3
2223+
ALTER TABLE t1 MODIFY f SMALLINT UNSIGNED;
2224+
affected rows: 4
2225+
info: Records: 4 Duplicates: 0 Warnings: 0
2226+
SELECT * FROM t1;
2227+
f
2228+
128
2229+
1
2230+
32943
2231+
128
2232+
ALTER TABLE t1 MODIFY f BIT;
2233+
ERROR 22001: Data too long for column 'f' at row 1
2234+
ALTER TABLE t1 MODIFY f BIT(15);
2235+
ERROR 22001: Data too long for column 'f' at row 3
2236+
DELETE FROM t1 LIMIT 3;
2237+
ALTER TABLE t1 MODIFY f BIT(15);
2238+
affected rows: 1
2239+
info: Records: 1 Duplicates: 0 Warnings: 0
2240+
SELECT HEX(f) FROM t1;
2241+
HEX(f)
2242+
80
2243+
DROP TABLE t1;
21062244
disconnect analyze;
21072245
SELECT variable_value-@old_instant instants
21082246
FROM information_schema.global_status
Binary file not shown.

mysql-test/suite/innodb/t/instant_alter.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,40 @@ eval CREATE TABLE t1(t TEXT NOT NULL, FULLTEXT(t)) $engine;
638638
ALTER TABLE t1 MODIFY COLUMN t TEXT;
639639
DROP TABLE t1;
640640

641+
# MDEV-18598 Assertions and wrong results after MDEV-15563 extending INT
642+
eval CREATE TABLE t1 (f TINYINT, g SMALLINT UNSIGNED) $engine;
643+
INSERT INTO t1 VALUES(127,6502),(-128,33101);
644+
--enable_info
645+
ALTER TABLE t1 MODIFY f SMALLINT DEFAULT 12345,
646+
MODIFY g BIGINT UNSIGNED DEFAULT 1234567;
647+
--disable_info
648+
SELECT * FROM t1;
649+
DROP TABLE t1;
650+
651+
eval CREATE TABLE t1 (f BIT(8)) $engine;
652+
INSERT INTO t1 VALUES (b'10000000'),(b'00000001');
653+
--enable_info
654+
ALTER TABLE t1 MODIFY f BIT(16);
655+
--disable_info
656+
INSERT INTO t1 VALUES (b'1000000010101111'),(b'10000000');
657+
SELECT HEX(f) FROM t1;
658+
--error ER_WARN_DATA_OUT_OF_RANGE
659+
ALTER TABLE t1 MODIFY f SMALLINT;
660+
--enable_info
661+
ALTER TABLE t1 MODIFY f SMALLINT UNSIGNED;
662+
--disable_info
663+
SELECT * FROM t1;
664+
--error ER_DATA_TOO_LONG
665+
ALTER TABLE t1 MODIFY f BIT;
666+
--error ER_DATA_TOO_LONG
667+
ALTER TABLE t1 MODIFY f BIT(15);
668+
DELETE FROM t1 LIMIT 3;
669+
--enable_info
670+
ALTER TABLE t1 MODIFY f BIT(15);
671+
--disable_info
672+
SELECT HEX(f) FROM t1;
673+
DROP TABLE t1;
674+
641675
dec $format;
642676
}
643677
disconnect analyze;

sql/field.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9550,9 +9550,35 @@ uint Field_num::is_equal(Create_field *new_field)
95509550

95519551
if (th == new_th && new_field->pack_length == pack_length())
95529552
return IS_EQUAL_YES;
9553+
/* FIXME: Test and consider returning IS_EQUAL_YES for the following:
9554+
TINYINT UNSIGNED to BIT(8)
9555+
SMALLINT UNSIGNED to BIT(16)
9556+
MEDIUMINT UNSIGNED to BIT(24)
9557+
INT UNSIGNED to BIT(32)
9558+
BIGINT UNSIGNED to BIT(64)
9559+
9560+
BIT(1..7) to TINYINT, or BIT(1..8) to TINYINT UNSIGNED
9561+
BIT(9..15) to SMALLINT, or BIT(9..16) to SMALLINT UNSIGNED
9562+
BIT(17..23) to MEDIUMINT, or BIT(17..24) to MEDIUMINT UNSIGNED
9563+
BIT(25..31) to INT, or BIT(25..32) to INT UNSIGNED
9564+
BIT(57..63) to BIGINT, or BIT(57..64) to BIGINT UNSIGNED
9565+
9566+
Note: InnoDB stores integers in big-endian format, and BIT appears
9567+
to use big-endian format. For storage engines that use little-endian
9568+
format for integers, we can only return IS_EQUAL_YES for the TINYINT
9569+
conversion. */
95539570

95549571
if (table->file->ha_table_flags() & HA_EXTENDED_TYPES_CONVERSION)
95559572
{
9573+
/* For now, prohibit instant conversion between BIT and integers.
9574+
Note: pack_length(), which is compared below, is measured in
9575+
bytes, and for BIT the last byte may be partially occupied. We
9576+
must not allow instant conversion to BIT such that the last byte
9577+
is partially occupied.
9578+
We could allow converting TINYINT UNSIGNED to BIT(8) or wider. */
9579+
if (th != new_th &&
9580+
(th == &type_handler_bit || new_th == &type_handler_bit))
9581+
return IS_EQUAL_NO;
95569582
if (th->result_type() == new_th->result_type() &&
95579583
new_field->pack_length >= pack_length())
95589584
return IS_EQUAL_PACK_LENGTH_EXT;

storage/innobase/row/row0sel.cc

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,7 +2708,6 @@ row_sel_field_store_in_mysql_format_func(
27082708
const byte* data,
27092709
ulint len)
27102710
{
2711-
byte* ptr;
27122711
#ifdef UNIV_DEBUG
27132712
const dict_field_t* field
27142713
= templ->is_virtual
@@ -2720,37 +2719,15 @@ row_sel_field_store_in_mysql_format_func(
27202719
UNIV_MEM_ASSERT_W(dest, templ->mysql_col_len);
27212720
UNIV_MEM_INVALID(dest, templ->mysql_col_len);
27222721

2722+
byte* pad = dest + len;
2723+
27232724
switch (templ->type) {
27242725
const byte* field_end;
2725-
byte* pad;
2726-
case DATA_INT:
2727-
/* Convert integer data from Innobase to a little-endian
2728-
format, sign bit restored to normal */
2729-
2730-
ptr = dest + len;
2731-
2732-
for (;;) {
2733-
ptr--;
2734-
*ptr = *data;
2735-
if (ptr == dest) {
2736-
break;
2737-
}
2738-
data++;
2739-
}
2740-
2741-
if (!templ->is_unsigned) {
2742-
dest[len - 1] = (byte) (dest[len - 1] ^ 128);
2743-
}
2744-
2745-
ut_ad(templ->mysql_col_len == len
2746-
|| !index->table->not_redundant());
2747-
break;
2748-
2726+
case DATA_CHAR:
2727+
case DATA_FIXBINARY:
27492728
case DATA_VARCHAR:
27502729
case DATA_VARMYSQL:
27512730
case DATA_BINARY:
2752-
case DATA_CHAR:
2753-
case DATA_FIXBINARY:
27542731
field_end = dest + templ->mysql_col_len;
27552732

27562733
if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) {
@@ -2771,7 +2748,14 @@ row_sel_field_store_in_mysql_format_func(
27712748

27722749
/* Pad with trailing spaces. */
27732750

2774-
pad = dest + len;
2751+
if (pad == field_end) {
2752+
break;
2753+
}
2754+
2755+
if (UNIV_UNLIKELY(templ->type == DATA_FIXBINARY)) {
2756+
memset(pad, 0, field_end - pad);
2757+
break;
2758+
}
27752759

27762760
ut_ad(templ->mbminlen <= templ->mbmaxlen);
27772761

@@ -2849,7 +2833,7 @@ row_sel_field_store_in_mysql_format_func(
28492833
done in row0mysql.cc, function
28502834
row_mysql_store_col_in_innobase_format(). */
28512835

2852-
memset(dest + len, 0x20, templ->mysql_col_len - len);
2836+
memset(pad, 0x20, templ->mysql_col_len - len);
28532837
}
28542838
break;
28552839

@@ -2864,13 +2848,29 @@ row_sel_field_store_in_mysql_format_func(
28642848
case DATA_FLOAT:
28652849
case DATA_DOUBLE:
28662850
case DATA_DECIMAL:
2867-
/* Above are the valid column types for MySQL data. */
28682851
#endif /* UNIV_DEBUG */
28692852
ut_ad((templ->is_virtual && !field)
28702853
|| (field && field->prefix_len
28712854
? field->prefix_len == len
28722855
: templ->mysql_col_len == len));
28732856
memcpy(dest, data, len);
2857+
break;
2858+
2859+
case DATA_INT:
2860+
/* Convert InnoDB big-endian integer to little-endian
2861+
format, sign bit restored to 2's complement form */
2862+
DBUG_ASSERT(templ->mysql_col_len >= len);
2863+
2864+
byte* ptr = pad;
2865+
do *--ptr = *data++; while (ptr != dest);
2866+
byte b = templ->is_unsigned || !((pad[-1] ^= 0x80) & 0x80)
2867+
? 0 : 0xff;
2868+
2869+
if (ulint l = templ->mysql_col_len - len) {
2870+
DBUG_ASSERT(!index->table->not_redundant());
2871+
memset(pad, b, l);
2872+
}
2873+
break;
28742874
}
28752875
}
28762876

0 commit comments

Comments
 (0)