Skip to content

Commit 71ae8c6

Browse files
author
Tatiana Azundris Nuernberg
committed
Bug#33503328: Specific table schema causes table to become corrupted during MySQL 8 upgrade
Summary Upgrading a table CREATE TABLE t1 (c1 int GENERATED ALWAYS AS (0), c2 int) ENGINE=MyISAM; from 5.7 to 8.x would fail. This would only occur for a mix of regular columns and generated columns and when using MyISAM. The reason for this was that during that particular type of upgrade for use with the data dictionary, we would align NULL info one way, and the other way everywhere else. This was caught by our sanity checks. Analysis Using our above table as an example: We always use two loops to create columns; the first loop processes regular columns, while the the second loop processes generated columns. Therefore, we'd process c2 (a natural column) before c1 (a generated column). In older code, we'd count the NULL position up in order of column creation so that c2 (which is the second column in the CREATE statement, but the first column to be processed on account of being the first regular column) will be processed first and get the lower NULL position; c1 will be processed second (on account of being a generated column and getting created in the second loop) and get the higher NULL position. In short, we process all regular columns first, then all generated columns, and count up NULL position the entire time. In fill_columns_from_dd(), we processed columns in the same order (first all natural columns, then all generated columns), but would increase the NULL position even when no field was processed (e.g. when skipping a generated column in the first loop that only processes regular columns). Therefore, the NULL positions ended up wrong and tripped our sanity check. This patch aligns the NULL ordering so that the positions generated in fill_columns_from_dd() are compatible with the ordering created by earlier servers and indeed with the behavior elsewhere in contemporary servers. Change-Id: I7bd18e0822c1d86b9dd322a5d994200078ebf84c
1 parent d457550 commit 71ae8c6

File tree

4 files changed

+120
-19
lines changed

4 files changed

+120
-19
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
########################################################################
2+
# Bug#33503328: Specific table schema causes table to become corrupted
3+
# during MySQL 8 upgrade
4+
########################################################################
5+
########################################################################
6+
# Copy and unzip the datadir, and stop the server.
7+
########################################################################
8+
########################################################################
9+
# Restart the server to trigger upgrade.
10+
########################################################################
11+
# restart: --datadir=MYSQLD_DATADIR_UPGRADE --log-error=MYSQLD_LOG --log-error-verbosity=3
12+
########################################################################
13+
# Let's see whether we upgrade the MyISAM table t1 correctly.
14+
########################################################################
15+
DESC t1;
16+
Field Type Null Key Default Extra
17+
c1 int YES NULL VIRTUAL GENERATED
18+
c2 int YES NULL
19+
SHOW CREATE TABLE t1;
20+
Table Create Table
21+
t1 CREATE TABLE `t1` (
22+
`c1` int GENERATED ALWAYS AS (0) VIRTUAL,
23+
`c2` int DEFAULT NULL
24+
) ENGINE=MyISAM DEFAULT CHARSET=latin1
25+
SELECT * FROM t1;
26+
c1 c2
27+
REPAIR TABLE t1;
28+
Table Op Msg_type Msg_text
29+
test.t1 repair status OK
30+
########################################################################
31+
# Stop the server and do cleanup.
32+
########################################################################
33+
# restart:
Binary file not shown.

mysql-test/t/dd_upgrade_33503328.test

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
--source include/not_valgrind.inc
2+
3+
--echo ########################################################################
4+
--echo # Bug#33503328: Specific table schema causes table to become corrupted
5+
--echo # during MySQL 8 upgrade
6+
--echo ########################################################################
7+
8+
--let $MYSQLD_LOG= $MYSQLTEST_VARDIR/log/save_dd_upgrade.log
9+
10+
--echo ########################################################################
11+
--echo # Copy and unzip the datadir, and stop the server.
12+
--echo ########################################################################
13+
14+
# The datadir is created by building server version 5.7
15+
# and executing the following SQL statements:
16+
#
17+
# USE test;
18+
# CREATE TABLE `t1` (
19+
# `c1` int GENERATED ALWAYS AS (0),
20+
# `c2` int
21+
# ) ENGINE=MyISAM;
22+
#
23+
# (which SHOW CREATE TABLEs as
24+
# `c1` int GENERATED ALWAYS AS (0) VIRTUAL,
25+
# `c2` int DEFAULT NULL )
26+
#
27+
# Then, move data/ to data_50745_33503328/, and finally zip the entire
28+
# directory (zip -r data_50745_33503328.zip data_50745_33503328).
29+
30+
--copy_file $MYSQLTEST_VARDIR/std_data/upgrade/data_50745_33503328.zip $MYSQL_TMP_DIR/data_50745_33503328.zip
31+
--file_exists $MYSQL_TMP_DIR/data_50745_33503328.zip
32+
--exec unzip -qo $MYSQL_TMP_DIR/data_50745_33503328.zip -d $MYSQL_TMP_DIR
33+
--let $MYSQLD_DATADIR_UPGRADE = $MYSQL_TMP_DIR/data_50745_33503328
34+
35+
--echo ########################################################################
36+
--echo # Restart the server to trigger upgrade.
37+
--echo ########################################################################
38+
--let $shutdown_server_timeout= 300
39+
--let $wait_counter= 10000
40+
--let $restart_parameters= restart: --datadir=$MYSQLD_DATADIR_UPGRADE --log-error=$MYSQLD_LOG --log-error-verbosity=3
41+
--replace_result $MYSQLD_DATADIR_UPGRADE MYSQLD_DATADIR_UPGRADE $MYSQLD_LOG MYSQLD_LOG
42+
--source include/restart_mysqld.inc
43+
44+
--echo ########################################################################
45+
--echo # Let's see whether we upgrade the MyISAM table t1 correctly.
46+
--echo ########################################################################
47+
48+
DESC t1;
49+
50+
# Unpatched server will fail with ER_NOT_KEYFILE.
51+
SHOW CREATE TABLE t1;
52+
53+
# Unpatched server will fail with ER_NOT_KEYFILE.
54+
SELECT * FROM t1;
55+
56+
REPAIR TABLE t1;
57+
58+
59+
--echo ########################################################################
60+
--echo # Stop the server and do cleanup.
61+
--echo ########################################################################
62+
--let $shutdown_server_timeout= 300
63+
--source include/shutdown_mysqld.inc
64+
--remove_file $MYSQL_TMP_DIR/data_50745_33503328.zip
65+
--force-rmdir $MYSQL_TMP_DIR/data_50745_33503328
66+
--remove_file $MYSQLD_LOG
67+
--let $restart_parameters= restart:
68+
--source include/start_mysqld.inc

sql/dd_table_share.cc

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,19 +1142,20 @@ static bool fill_columns_from_dd(THD *thd, TABLE_SHARE *share,
11421142
rec_pos, field_nr))
11431143
return true;
11441144

1145+
/*
1146+
Account for NULL bits if it's a regular column.
1147+
If it's a generated column, we do it below so the NULL
1148+
bits end up in the expected order.
1149+
*/
1150+
if ((null_bit_pos += column_preamble_bits(col_obj)) > 7) {
1151+
null_pos++;
1152+
null_bit_pos -= 8;
1153+
}
1154+
11451155
rec_pos += share->field[field_nr]->pack_length_in_rec();
11461156
} else
11471157
has_vgc = true;
11481158

1149-
/*
1150-
Virtual generated columns still need to be accounted in null bits and
1151-
field_nr calculations, since they reside at the normal place in record
1152-
preamble and TABLE_SHARE::field array.
1153-
*/
1154-
if ((null_bit_pos += column_preamble_bits(col_obj)) > 7) {
1155-
null_pos++;
1156-
null_bit_pos -= 8;
1157-
}
11581159
field_nr++;
11591160
}
11601161

@@ -1167,8 +1168,6 @@ static bool fill_columns_from_dd(THD *thd, TABLE_SHARE *share,
11671168
static_cast<ulong>(rec_pos - share->default_values))
11681169
share->stored_rec_length = (rec_pos - share->default_values);
11691170

1170-
null_pos = share->default_values;
1171-
null_bit_pos = (share->db_create_options & HA_OPTION_PACK_RECORD) ? 0 : 1;
11721171
field_nr = 0;
11731172

11741173
for (const dd::Column *col_obj2 : tab_obj->columns()) {
@@ -1181,17 +1180,18 @@ static bool fill_columns_from_dd(THD *thd, TABLE_SHARE *share,
11811180
rec_pos, field_nr))
11821181
return true;
11831182

1183+
/*
1184+
Account for generated columns -- we do this separately here
1185+
so the NULL bits end up in the expected order.
1186+
*/
1187+
if ((null_bit_pos += column_preamble_bits(col_obj2)) > 7) {
1188+
null_pos++;
1189+
null_bit_pos -= 8;
1190+
}
1191+
11841192
rec_pos += share->field[field_nr]->pack_length_in_rec();
11851193
}
11861194

1187-
/*
1188-
Account for all columns while evaluating null_pos/null_bit_pos and
1189-
field_nr.
1190-
*/
1191-
if ((null_bit_pos += column_preamble_bits(col_obj2)) > 7) {
1192-
null_pos++;
1193-
null_bit_pos -= 8;
1194-
}
11951195
field_nr++;
11961196
}
11971197
}

0 commit comments

Comments
 (0)