From 5f12767b145e7c7d7611a0c0ab592805a20dda01 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 29 Jan 2025 18:54:58 +0530 Subject: [PATCH 01/14] PS-9603 ERROR 1712 (HY000): Index PRIMARY is corrupted https://perconadev.atlassian.net/browse/PS-9603 Bug#34574604 mysql/mysql-server@632ca5cfe1b Background: When an online DDL is on-going and there are concurrent INSERTs, UPDATEs or DELETEs on the table, the changes in the rows are logged in the row log. After the DDL is complete, the row log is applied on the new table. If an error occurs when applying the row logs, the DDL reports the error and aborts. When applying the row log, if the base row contains any externally stored BLOB columns, then any UPDATE in the row log is applied as a DELETE followed by an INSERT. This is done first on the clustered index, then on all secondary indexes. The entries to be DELETEd and INSERTed are built from the row log entry. Any secondary index on virtual columns will need the virtual column information in the row log to build the correct entry, since the index entry contains the primary key value and the computed virtual column value. The row log entry writes the virtual column information only when the UPDATE changes the ordering field of the clustered index. An update not changing the ordering field will not write virtual columns to row log. This is irrespective of presence of secondary index on these columns. Issue: Applying the row log fails in the scenario illustrated by the test file. The failure occurs when the entry to be deleted in the secondary index is not found, as it is not built correctly. The table contains a BLOB column, a virtually generated column and a secondary index on the virtually generated column. The row being updated has the BLOB stored externally. The test performs an online DDL by adding a primary key, and concurrently updating the row such that the ordering of the new primary key does not change. Since the update does not change the ordering field, the row log does not contain the virtual column information. Since the row contains an externally stored BLOB, the row log's UPDATE entry is applied as a DELETE followed by an INSERT. The DELETE step fails when applied on the secondary index. The entry to be deleted from the secondary index is built incorrectly, as the virtual column value required is not present in the row log entry. Thus the incorrect entry built is not found in the secondary index, causing the DDL to abort. Fix: Log the virutal column information if the row contains externally stored BLOBs, either before or after the UPDATE. Change-Id: I90bac31cf293565467b3f0b19741c8c5d646f63f --- mysql-test/suite/innodb/r/bug34574604.result | 352 +++++++++++++++++++ mysql-test/suite/innodb/t/bug34574604.test | 297 ++++++++++++++++ storage/innobase/row/row0upd.cc | 12 + 3 files changed, 661 insertions(+) create mode 100644 mysql-test/suite/innodb/r/bug34574604.result create mode 100644 mysql-test/suite/innodb/t/bug34574604.test diff --git a/mysql-test/suite/innodb/r/bug34574604.result b/mysql-test/suite/innodb/r/bug34574604.result new file mode 100644 index 000000000000..9f04e53bbfcc --- /dev/null +++ b/mysql-test/suite/innodb/r/bug34574604.result @@ -0,0 +1,352 @@ +# Case 1. Extern BLOB with sec index on v_col undergoes UPDATE during online DDL +CREATE TABLE t1 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t1 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t1; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t1 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t1; +c1 c2 c4 +1 1 2 +UPDATE t1 SET c2=2; +SELECT c1, c2, c4 FROM t1; +c1 c2 c4 +1 2 2 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t1; +c1 c2 c4 +1 2 2 +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +# Case 2. BLOB with sec index on v_col undergoes UPDATE during online DDL +CREATE TABLE t2 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t2 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t2; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t2 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t2; +c1 c2 c4 +1 1 2 +UPDATE t2 SET c2=2; +SELECT c1, c2, c4 FROM t2; +c1 c2 c4 +1 2 2 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t2; +c1 c2 c4 +1 2 2 +CHECK TABLE t2; +Table Op Msg_type Msg_text +test.t2 check status OK +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t2; +# Case 3. (case 1) with update on base column of v_col +CREATE TABLE t3 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t3 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t3; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t3 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t3; +c1 c2 c4 +1 1 2 +UPDATE t3 SET c1=10; +SELECT c1, c2, c4 FROM t3; +c1 c2 c4 +10 1 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t3; +c1 c2 c4 +10 1 11 +CHECK TABLE t3; +Table Op Msg_type Msg_text +test.t3 check status OK +SHOW CREATE TABLE t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t3; +# Case 4. (case 2) with update on base column of v_col +CREATE TABLE t4 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t4 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t4; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t4; +Table Create Table +t4 CREATE TABLE `t4` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t4 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t4; +c1 c2 c4 +1 1 2 +UPDATE t4 SET c1=10; +SELECT c1, c2, c4 FROM t4; +c1 c2 c4 +10 1 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t4; +c1 c2 c4 +10 1 11 +CHECK TABLE t4; +Table Op Msg_type Msg_text +test.t4 check status OK +SHOW CREATE TABLE t4; +Table Create Table +t4 CREATE TABLE `t4` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t4; +# Case 5. (case 1) with update on BLOB without changing extern status +CREATE TABLE t5 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t5 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t5; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t5; +Table Create Table +t5 CREATE TABLE `t5` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t5 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t5; +c1 c2 c4 +1 1 2 +UPDATE t5 SET c3=REPEAT('abcdefghcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281); +UPDATE t5 SET c2=2; +UPDATE t5 SET c1=10; +SELECT c1, c2, c4 FROM t5; +c1 c2 c4 +10 2 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t5; +c1 c2 c4 +10 2 11 +CHECK TABLE t5; +Table Op Msg_type Msg_text +test.t5 check status OK +SHOW CREATE TABLE t5; +Table Create Table +t5 CREATE TABLE `t5` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t5; +# Case 6. (case 2) with update on BLOB without changing extern status +CREATE TABLE t6 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t6 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t6; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t6; +Table Create Table +t6 CREATE TABLE `t6` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t6 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t6; +c1 c2 c4 +1 1 2 +UPDATE t6 SET c3=REPEAT('B', 256); +UPDATE t6 SET c2=2; +UPDATE t6 SET c1=10; +SELECT c1, c2, c4 FROM t6; +c1 c2 c4 +10 2 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t6; +c1 c2 c4 +10 2 11 +CHECK TABLE t6; +Table Op Msg_type Msg_text +test.t6 check status OK +SHOW CREATE TABLE t6; +Table Create Table +t6 CREATE TABLE `t6` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t6; +# Case 7. (case 1) with update on BLOB inverting BLOB's extern status +CREATE TABLE t7 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t7 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t7; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t7; +Table Create Table +t7 CREATE TABLE `t7` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t7 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t7; +c1 c2 c4 +1 1 2 +UPDATE t7 SET c3=REPEAT('B', 256); +UPDATE t7 SET c2=2; +UPDATE t7 SET c1=10; +SELECT c1, c2, c4 FROM t7; +c1 c2 c4 +10 2 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t7; +c1 c2 c4 +10 2 11 +CHECK TABLE t7; +Table Op Msg_type Msg_text +test.t7 check status OK +SHOW CREATE TABLE t7; +Table Create Table +t7 CREATE TABLE `t7` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t7; +# Case 8. (case 2) with update on BLOB inverting BLOB's extern status +CREATE TABLE t8 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t8 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t8; +c1 c2 c4 +1 1 2 +SHOW CREATE TABLE t8; +Table Create Table +t8 CREATE TABLE `t8` ( + `c1` int(11) DEFAULT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +ALTER TABLE t8 ADD PRIMARY KEY (c1); +SET DEBUG_SYNC='now WAIT_FOR online'; +SELECT c1, c2, c4 FROM t8; +c1 c2 c4 +1 1 2 +UPDATE t8 SET c3=REPEAT('abcdefghcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281); +UPDATE t8 SET c2=2; +UPDATE t8 SET c1=10; +SELECT c1, c2, c4 FROM t8; +c1 c2 c4 +10 2 11 +SET DEBUG_SYNC='now SIGNAL upd'; +SELECT c1, c2, c4 FROM t8; +c1 c2 c4 +10 2 11 +CHECK TABLE t8; +Table Op Msg_type Msg_text +test.t8 check status OK +SHOW CREATE TABLE t8; +Table Create Table +t8 CREATE TABLE `t8` ( + `c1` int(11) NOT NULL, + `c2` int(11) DEFAULT NULL, + `c3` blob, + `c4` int(11) GENERATED ALWAYS AS ((`c1` + 1)) VIRTUAL, + PRIMARY KEY (`c1`), + KEY `id` (`c4`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t8; diff --git a/mysql-test/suite/innodb/t/bug34574604.test b/mysql-test/suite/innodb/t/bug34574604.test new file mode 100644 index 000000000000..3ffe7e805b5c --- /dev/null +++ b/mysql-test/suite/innodb/t/bug34574604.test @@ -0,0 +1,297 @@ +--source include/have_debug_sync.inc + +--echo # Case 1. Extern BLOB with sec index on v_col undergoes UPDATE during online DDL + +CREATE TABLE t1 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t1 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t1; + +SHOW CREATE TABLE t1; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t1 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t1; +UPDATE t1 SET c2=2; +SELECT c1, c2, c4 FROM t1; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t1; +CHECK TABLE t1; +SHOW CREATE TABLE t1; + +DROP TABLE t1; + +--echo # Case 2. BLOB with sec index on v_col undergoes UPDATE during online DDL + +CREATE TABLE t2 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t2 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t2; + +SHOW CREATE TABLE t2; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t2 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t2; +UPDATE t2 SET c2=2; +SELECT c1, c2, c4 FROM t2; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t2; +CHECK TABLE t2; +SHOW CREATE TABLE t2; + +DROP TABLE t2; + +--echo # Case 3. (case 1) with update on base column of v_col + +CREATE TABLE t3 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t3 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t3; + +SHOW CREATE TABLE t3; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t3 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t3; +UPDATE t3 SET c1=10; +SELECT c1, c2, c4 FROM t3; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t3; +CHECK TABLE t3; +SHOW CREATE TABLE t3; + +DROP TABLE t3; + +--echo # Case 4. (case 2) with update on base column of v_col + +CREATE TABLE t4 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t4 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t4; + +SHOW CREATE TABLE t4; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t4 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t4; +UPDATE t4 SET c1=10; +SELECT c1, c2, c4 FROM t4; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t4; +CHECK TABLE t4; +SHOW CREATE TABLE t4; + +DROP TABLE t4; + +--echo # Case 5. (case 1) with update on BLOB without changing extern status + +CREATE TABLE t5 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t5 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t5; + +SHOW CREATE TABLE t5; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t5 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t5; +UPDATE t5 SET c3=REPEAT('abcdefghcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281); +UPDATE t5 SET c2=2; +UPDATE t5 SET c1=10; +SELECT c1, c2, c4 FROM t5; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t5; +CHECK TABLE t5; +SHOW CREATE TABLE t5; + +DROP TABLE t5; + +--echo # Case 6. (case 2) with update on BLOB without changing extern status + +CREATE TABLE t6 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t6 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t6; + +SHOW CREATE TABLE t6; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t6 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t6; +UPDATE t6 SET c3=REPEAT('B', 256); +UPDATE t6 SET c2=2; +UPDATE t6 SET c1=10; +SELECT c1, c2, c4 FROM t6; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t6; +CHECK TABLE t6; +SHOW CREATE TABLE t6; + +DROP TABLE t6; + +--echo # Case 7. (case 1) with update on BLOB inverting BLOB's extern status + +CREATE TABLE t7 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t7 VALUES (1, 1, REPEAT('rocalrulcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281), DEFAULT); +SELECT c1, c2, c4 FROM t7; + +SHOW CREATE TABLE t7; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t7 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t7; +UPDATE t7 SET c3=REPEAT('B', 256); +UPDATE t7 SET c2=2; +UPDATE t7 SET c1=10; +SELECT c1, c2, c4 FROM t7; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t7; +CHECK TABLE t7; +SHOW CREATE TABLE t7; + +DROP TABLE t7; + +--echo # Case 8. (case 2) with update on BLOB inverting BLOB's extern status + +CREATE TABLE t8 (c1 INT, c2 INT, c3 BLOB, c4 INT AS (c1 + 1), INDEX id(c4) ); +INSERT INTO t8 VALUES (1, 1, REPEAT('A', 256), DEFAULT); +SELECT c1, c2, c4 FROM t8; + +SHOW CREATE TABLE t8; + +connect (con1, localhost, root, , ); + +connection con1; +SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL online WAIT_FOR upd'; +send; +ALTER TABLE t8 ADD PRIMARY KEY (c1); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR online'; + +SELECT c1, c2, c4 FROM t8; +UPDATE t8 SET c3=REPEAT('abcdefghcrcaurcuccoolrouuocacaooaucauualcucuoucucclolcllloocuarcoorlaccarocouuaoorcolloucraoaaooc', 281); +UPDATE t8 SET c2=2; +UPDATE t8 SET c1=10; +SELECT c1, c2, c4 FROM t8; + +SET DEBUG_SYNC='now SIGNAL upd'; + +connection con1; +reap; + +connection default; +disconnect con1; + +SELECT c1, c2, c4 FROM t8; +CHECK TABLE t8; +SHOW CREATE TABLE t8; + +DROP TABLE t8; diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index c2133dfb8646..3bd82f4a6a1e 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -2658,6 +2658,7 @@ row_upd_clust_rec( btr_cur_t* btr_cur; dberr_t err; const dtuple_t* rebuilt_old_pk = NULL; + bool is_old_or_new_rec_extern = false; ut_ad(node); ut_ad(dict_index_is_clust(index)); @@ -2672,6 +2673,7 @@ row_upd_clust_rec( ut_ad(rec_offs_validate(btr_cur_get_rec(btr_cur), index, offsets)); if (dict_index_is_online_ddl(index)) { + is_old_or_new_rec_extern = rec_offs_any_extern(offsets); rebuilt_old_pk = row_log_table_get_pk( btr_cur_get_rec(btr_cur), index, offsets, NULL, &heap); } @@ -2758,9 +2760,19 @@ row_upd_clust_rec( dtuple_t* new_v_row = NULL; dtuple_t* old_v_row = NULL; + /* In case UPDATE modifies extern BLOB and makes it fit within record + after above update, we still need old virtual col */ + is_old_or_new_rec_extern |= rec_offs_any_extern(offsets); + if (!(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { new_v_row = node->upd_row; old_v_row = node->update->old_vrow; + } else if (is_old_or_new_rec_extern) { + /* Row log treats UPDATE on extern BLOB as DELETE + INSERT. + This requires virtual col info. Since no change in virtual + col, new value is same as old */ + old_v_row = node->update->old_vrow; + new_v_row = old_v_row; } row_log_table_update( From 25b1890d36e607ff2448994564058cf4cca884f2 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Tue, 11 Feb 2025 14:39:06 +0530 Subject: [PATCH 02/14] Revert "PS-7940 ON DELETE CASCADE with generated column crashes in innobase_get_computed_value" This reverts commit 4bdd2da59dd9e3df52364801d4d0fef8675cd451. --- mysql-test/suite/innodb/r/foreign_key.result | 20 ---------------- mysql-test/suite/innodb/t/foreign_key.test | 25 -------------------- storage/innobase/row/row0ins.cc | 9 ++----- 3 files changed, 2 insertions(+), 52 deletions(-) diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index 1fcbed63146d..e573cd9adfd2 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -297,23 +297,3 @@ date_sent 2020-10-22 13:32:41 DROP TABLE `email_stats`; DROP TABLE `emails`; -# -# PS-7940 :ON DELETE CASCADE with generated column crashes in innobase_get_computed_value -# -CREATE TABLE `t1` (`t1_id` int unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY(`t1_id`)) ENGINE=InnoDB; -INSERT INTO `t1` VALUES (10); -CREATE TABLE `t2` ( -`t2_id` int unsigned NOT NULL AUTO_INCREMENT, -`c1` decimal(14,2) NOT NULL DEFAULT '0.00', -`t1_id` int unsigned DEFAULT NULL, -`testycol` bit(1) GENERATED ALWAYS AS (`c1` <> 0) VIRTUAL, -PRIMARY KEY (`t2_id`), -UNIQUE KEY `t1_id` (`t1_id`), -KEY `testycol` (`testycol`), -CONSTRAINT `t2_ibfk_3` FOREIGN KEY (`t1_id`) REFERENCES `t1` (`t1_id`) ON DELETE CASCADE -) ENGINE=InnoDB; -INSERT INTO `t2` (`t2_id`, `c1`, `t1_id`) VALUES (1,5.00,10); -# restart -DELETE FROM `t1` WHERE `t1_id`=10; -DROP TABLE `t2`; -DROP TABLE `t1`; diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index cff3d59b9d51..7368a8e29d85 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -253,28 +253,3 @@ SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-'; #clean up. DROP TABLE `email_stats`; DROP TABLE `emails`; - ---echo # ---echo # PS-7940 :ON DELETE CASCADE with generated column crashes in innobase_get_computed_value ---echo # - -CREATE TABLE `t1` (`t1_id` int unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY(`t1_id`)) ENGINE=InnoDB; -INSERT INTO `t1` VALUES (10); -CREATE TABLE `t2` ( - `t2_id` int unsigned NOT NULL AUTO_INCREMENT, - `c1` decimal(14,2) NOT NULL DEFAULT '0.00', - `t1_id` int unsigned DEFAULT NULL, - `testycol` bit(1) GENERATED ALWAYS AS (`c1` <> 0) VIRTUAL, - PRIMARY KEY (`t2_id`), - UNIQUE KEY `t1_id` (`t1_id`), - KEY `testycol` (`testycol`), - CONSTRAINT `t2_ibfk_3` FOREIGN KEY (`t1_id`) REFERENCES `t1` (`t1_id`) ON DELETE CASCADE -) ENGINE=InnoDB; -INSERT INTO `t2` (`t2_id`, `c1`, `t1_id`) VALUES (1,5.00,10); - ---source include/restart_mysqld.inc - -DELETE FROM `t1` WHERE `t1_id`=10; - -DROP TABLE `t2`; -DROP TABLE `t1`; diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 63f6ea116961..2c86b0778790 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1306,13 +1306,8 @@ row_ins_foreign_check_on_constraint( clust_index, tmp_heap); } - /* A cascade delete from the parent table triggers delete on the child - table. Before a clustered index record is deleted in the child table, - a copy of row is built to remove secondary index records. This copy of - the row requires virtual columns to be materialized. Hence, if child - table has any virtual columns, we have to initialize virtual column - template */ - if (cascade->is_delete && dict_table_get_n_v_cols(table) > 0 + if (cascade->is_delete && foreign->v_cols != NULL + && foreign->v_cols->size() > 0 && table->vc_templ == NULL) { innobase_init_vc_templ(table); } From 42f6a0b0e02df849a62f3e6cc984e2c73cd66534 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 02:31:47 +0530 Subject: [PATCH 03/14] PS-9603 ON DELETE CASCADE with generated column crashes in innobase_get_computed_value https://perconadev.atlassian.net/browse/PS-9603 Bug#33691659 mysql/mysql-server@551288baa7a ANALYSIS ======== 1. A cascade delete from the parent table triggers delete on the child table. Before a clustered index record is deleted in the child table, a copy of row is built to remove secondary index record, since the secondary index is defined on the virtual column it is necessary to materialize the virtual columns. 2. We were skipping this materialization which causes it to fail on uninitialized template in innobase_get_computed_value() when trying to remove the secondary index rows defined on virtual column. FIX === 1. We must initialize virtual column template for materializing virtual column if child table has an index on a virtual column. Change-Id: Ib2381d81aa96024ed395261650421f6318f380b8 --- mysql-test/suite/innodb/r/foreign_key.result | 23 +++++++++++++++++ mysql-test/suite/innodb/t/foreign_key.test | 26 ++++++++++++++++++++ storage/innobase/row/row0ins.cc | 12 ++++++--- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index e573cd9adfd2..72125def57cc 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -297,3 +297,26 @@ date_sent 2020-10-22 13:32:41 DROP TABLE `email_stats`; DROP TABLE `emails`; +# +# Bug#33691659: ON DELETE CASCADE with generated column crashes in innobase_get_computed_value +# +CREATE TABLE `t1` ( +`t1_id` int unsigned NOT NULL AUTO_INCREMENT, +PRIMARY KEY(`t1_id`) +) ENGINE=InnoDB; +INSERT INTO `t1` VALUES (10); +CREATE TABLE `t2` ( +`t2_id` int unsigned NOT NULL AUTO_INCREMENT, +`c1` decimal(14,2) NOT NULL DEFAULT '0.00', +`t1_id` int unsigned DEFAULT NULL, +`testycol` bit(1) GENERATED ALWAYS AS (`c1` <> 0) VIRTUAL, +PRIMARY KEY (`t2_id`), +UNIQUE KEY `t1_id` (`t1_id`), +KEY `testycol` (`testycol`), +CONSTRAINT `t2_ibfk_3` FOREIGN KEY (`t1_id`) REFERENCES `t1` (`t1_id`) ON DELETE CASCADE +) ENGINE=InnoDB; +INSERT INTO `t2` (`t2_id`, `c1`, `t1_id`) VALUES (1,5.00,10); +# restart +DELETE FROM `t1` WHERE `t1_id`=10; +DROP TABLE `t2`; +DROP TABLE `t1`; diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index 7368a8e29d85..6e8122811596 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -253,3 +253,29 @@ SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-'; #clean up. DROP TABLE `email_stats`; DROP TABLE `emails`; + +--echo # +--echo # Bug#33691659: ON DELETE CASCADE with generated column crashes in innobase_get_computed_value +--echo # +CREATE TABLE `t1` ( + `t1_id` int unsigned NOT NULL AUTO_INCREMENT, + PRIMARY KEY(`t1_id`) +) ENGINE=InnoDB; +INSERT INTO `t1` VALUES (10); + +CREATE TABLE `t2` ( + `t2_id` int unsigned NOT NULL AUTO_INCREMENT, + `c1` decimal(14,2) NOT NULL DEFAULT '0.00', + `t1_id` int unsigned DEFAULT NULL, + `testycol` bit(1) GENERATED ALWAYS AS (`c1` <> 0) VIRTUAL, + PRIMARY KEY (`t2_id`), + UNIQUE KEY `t1_id` (`t1_id`), + KEY `testycol` (`testycol`), + CONSTRAINT `t2_ibfk_3` FOREIGN KEY (`t1_id`) REFERENCES `t1` (`t1_id`) ON DELETE CASCADE +) ENGINE=InnoDB; +INSERT INTO `t2` (`t2_id`, `c1`, `t1_id`) VALUES (1,5.00,10); + +--source include/restart_mysqld.inc +DELETE FROM `t1` WHERE `t1_id`=10; +DROP TABLE `t2`; +DROP TABLE `t1`; diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 2c86b0778790..a9f6fa8d31e8 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1306,11 +1306,17 @@ row_ins_foreign_check_on_constraint( clust_index, tmp_heap); } - if (cascade->is_delete && foreign->v_cols != NULL - && foreign->v_cols->size() > 0 - && table->vc_templ == NULL) { + /* A cascade delete from the parent table triggers delete on the child + table. Before a clustered index record is deleted in the child table, + a copy of row is built to remove secondary index records. This copy of + the row requires virtual columns to be materialized. Hence, if child + table has any virtual columns which are indexed, we have to initialize + virtual column template. */ + if (cascade->is_delete && dict_table_has_indexed_v_cols(table) && + table->vc_templ == NULL) { innobase_init_vc_templ(table); } + if (node->is_delete ? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) : (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { From b3938045d8bb7f1c305dd6fa0049a94410efa9a9 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Tue, 4 Feb 2025 12:39:28 +0530 Subject: [PATCH 04/14] PS-9603 db cache is not flushed on DROP USER https://perconadev.atlassian.net/browse/PS-9603 Bug#37132323 mysql/mysql-server@dd8b0c5c7d6 The DB grant cache is not cleaned up at DROP USER. This was causing ghost grants until FLUSH PRIVILEGES. Fixed by flushing the cache. Change-Id: I1096558daffab2d5f75fe12af723680d5c1d2416 --- sql/auth/sql_user.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sql/auth/sql_user.cc b/sql/auth/sql_user.cc index 9608836e28ff..ff93cdf73cb9 100644 --- a/sql/auth/sql_user.cc +++ b/sql/auth/sql_user.cc @@ -1655,6 +1655,8 @@ bool mysql_drop_user(THD *thd, List &list, bool if_exists) /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ rebuild_check_host(); + /* Clear privilege cache */ + acl_cache->clear(1); mysql_mutex_unlock(&acl_cache->lock); From e87c07230b4ab26f5ec06955faeef54ab7be0565 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 02:24:45 +0530 Subject: [PATCH 05/14] PS-9603 InnoDB: Failing assertion: result != FTS_INVALID https://perconadev.atlassian.net/browse/PS-9603 Bug#36234681 mysql/mysql-server@a8c96ce81ab ANALYSIS DELETE operation on a table with self referential foreign key constraint and full text index may trigger an assert. When performing the DELETE operation, marking of deletion of the full text row happens twice due to the self referential foreign key constraint. Hence the state of the row becomes invalid and the assert is triggered. Fix Avoid updating the full text index if the foreign key has self referential constraint. Testcase added. --- .../r/self_referencing_foreign_key.result | 20 +++++++++ .../t/self_referencing_foreign_key.test | 24 +++++++++++ storage/innobase/dict/dict0mem.cc | 28 ++++++++++++- storage/innobase/include/dict0mem.h | 10 ++++- storage/innobase/row/row0ins.cc | 41 +++---------------- 5 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 mysql-test/suite/innodb_fts/r/self_referencing_foreign_key.result create mode 100644 mysql-test/suite/innodb_fts/t/self_referencing_foreign_key.test diff --git a/mysql-test/suite/innodb_fts/r/self_referencing_foreign_key.result b/mysql-test/suite/innodb_fts/r/self_referencing_foreign_key.result new file mode 100644 index 000000000000..b6ba0a223d6a --- /dev/null +++ b/mysql-test/suite/innodb_fts/r/self_referencing_foreign_key.result @@ -0,0 +1,20 @@ +CREATE TABLE articles ( +FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, +title char(30), +fk_col char(30), +content VARCHAR(255) NOT NULL, +INDEX(title), +FULLTEXT INDEX(fk_col) +); +ALTER TABLE articles ADD CONSTRAINT fk FOREIGN KEY (fk_col) REFERENCES articles(title) ON DELETE SET NULL; +INSERT INTO articles (title, content) VALUES +('Root Article', 'This is the main topic'); +INSERT INTO articles (title, fk_col, content) VALUES +('Sub Article 1', 'Root Article', 'This expands on the main topic'), +('Sub Article 2', 'Root Article', 'Another perspective on the topic'); +SELECT * FROM articles WHERE MATCH(fk_col) AGAINST ('Root'); +FTS_DOC_ID title fk_col content +2 Sub Article 1 Root Article This expands on the main topic +3 Sub Article 2 Root Article Another perspective on the topic +DELETE FROM articles WHERE content LIKE '%This%'; +DROP TABLE articles; diff --git a/mysql-test/suite/innodb_fts/t/self_referencing_foreign_key.test b/mysql-test/suite/innodb_fts/t/self_referencing_foreign_key.test new file mode 100644 index 000000000000..d686686df611 --- /dev/null +++ b/mysql-test/suite/innodb_fts/t/self_referencing_foreign_key.test @@ -0,0 +1,24 @@ +# Bug#36234681: InnoDB: Failing assertion: result != FTS_INVALID + +CREATE TABLE articles ( + FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, + title char(30), + fk_col char(30), + content VARCHAR(255) NOT NULL, + INDEX(title), + FULLTEXT INDEX(fk_col) +); + +ALTER TABLE articles ADD CONSTRAINT fk FOREIGN KEY (fk_col) REFERENCES articles(title) ON DELETE SET NULL; + +INSERT INTO articles (title, content) VALUES +('Root Article', 'This is the main topic'); + +INSERT INTO articles (title, fk_col, content) VALUES +('Sub Article 1', 'Root Article', 'This expands on the main topic'), +('Sub Article 2', 'Root Article', 'Another perspective on the topic'); + +SELECT * FROM articles WHERE MATCH(fk_col) AGAINST ('Root'); +DELETE FROM articles WHERE content LIKE '%This%'; + +DROP TABLE articles; diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc index 0c36d539c452..70381e075d9d 100644 --- a/storage/innobase/dict/dict0mem.cc +++ b/storage/innobase/dict/dict0mem.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2023, Oracle and/or its affiliates. +Copyright (c) 1996, 2024, Oracle and/or its affiliates. Copyright (c) 2012, Facebook Inc. This program is free software; you can redistribute it and/or modify @@ -1132,6 +1132,32 @@ dict_foreign_set_validate( && dict_foreign_set_validate(table.referenced_set)); } +bool dict_foreign_t::is_fts_col_affected() const +{ + /* The check is skipped: + - if the table has no full text index defined. + - if it is a self referential foreign constaint. This is because + in the context of cascading DML operation, only the referenced + table is relevant for the validation even if the current table + has FTS index. */ + if (!foreign_table->fts || foreign_table == referenced_table) { + return false; + } + + for (ulint i = 0; i < n_fields; i++) + { + const dict_col_t* col = dict_index_get_nth_col( + foreign_index, i); + + if (dict_table_is_fts_column(foreign_table->fts->indexes, + dict_col_get_no(col), + dict_col_is_virtual(col)) != ULINT_UNDEFINED) { + return true; + } + } + return false; +} + std::ostream& operator<< (std::ostream& out, const dict_foreign_t& foreign) { diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 4c8bcf66722a..ac1c9f17d710 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2023, Oracle and/or its affiliates. +Copyright (c) 1996, 2024, Oracle and/or its affiliates. Copyright (c) 2012, Facebook Inc. This program is free software; you can redistribute it and/or modify @@ -1107,6 +1107,14 @@ struct dict_foreign_t{ dict_vcol_set* v_cols; /*!< set of virtual columns affected by foreign key constraint. */ + /** Check whether foreign key constraint contains a column with a full + index on it. The function is used in the context of cascading DML + operations. + @retval true if the column has FTS index on it. + @retval false if the FK table has no FTS index or has self referential + relationship + */ + bool is_fts_col_affected() const; }; std::ostream& diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index a9f6fa8d31e8..9098d4857c34 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -534,7 +534,7 @@ row_ins_cascade_calc_update_vec( n_fields_updated = 0; - *fts_col_affected = FALSE; + *fts_col_affected = foreign->is_fts_col_affected(); if (table->fts) { doc_id_pos = dict_table_get_nth_col_pos( @@ -654,16 +654,6 @@ row_ins_cascade_calc_update_vec( padded_data, min_size); } - /* Check whether the current column has - FTS index on it */ - if (table->fts - && dict_table_is_fts_column( - table->fts->indexes, - dict_col_get_no(col), - dict_col_is_virtual(col)) - != ULINT_UNDEFINED) { - *fts_col_affected = TRUE; - } /* If Doc ID is updated, check whether the Doc ID is valid */ @@ -1114,7 +1104,6 @@ row_ins_foreign_check_on_constraint( trx_t* trx; mem_heap_t* tmp_heap = NULL; doc_id_t doc_id = FTS_NULL_DOC_ID; - ibool fts_col_affacted = FALSE; DBUG_ENTER("row_ins_foreign_check_on_constraint"); ut_a(thr); @@ -1345,17 +1334,9 @@ row_ins_foreign_check_on_constraint( ufield->exp = NULL; dfield_set_null(&ufield->new_val); - if (table->fts && dict_table_is_fts_column( - table->fts->indexes, - dict_index_get_nth_col_no(index, i), - dict_col_is_virtual( - dict_index_get_nth_col(index, i))) - != ULINT_UNDEFINED) { - fts_col_affacted = TRUE; - } } - if (fts_col_affacted) { + if (foreign->is_fts_col_affected()) { fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } @@ -1372,31 +1353,21 @@ row_ins_foreign_check_on_constraint( } else if (table->fts && cascade->is_delete) { /* DICT_FOREIGN_ON_DELETE_CASCADE case */ - for (i = 0; i < foreign->n_fields; i++) { - if (table->fts && dict_table_is_fts_column( - table->fts->indexes, - dict_index_get_nth_col_no(index, i), - dict_col_is_virtual( - dict_index_get_nth_col(index, i))) - != ULINT_UNDEFINED) { - fts_col_affacted = TRUE; - } - } - - if (fts_col_affacted) { + if (foreign->is_fts_col_affected()) { fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } } if (!node->is_delete && (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE)) { + ibool fts_col_affected = FALSE; /* Build the appropriate update vector which sets changing foreign->n_fields first fields in rec to new values */ n_to_update = row_ins_cascade_calc_update_vec( node, foreign, tmp_heap, - trx, &fts_col_affacted); + trx, &fts_col_affected); if (foreign->v_cols != NULL @@ -1437,7 +1408,7 @@ row_ins_foreign_check_on_constraint( } /* Mark the old Doc ID as deleted */ - if (fts_col_affacted) { + if (fts_col_affected) { ut_ad(table->fts); fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } From bf492358110b033f1e083c67006ec9c364bbbca0 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 15:20:24 +0530 Subject: [PATCH 06/14] PS-9603 buffer overrun in my_print_help https://perconadev.atlassian.net/browse/PS-9603 Bug#36615714 mysql/mysql-server@6e08d4427fc Breaking the comment for a mysys option definition requires that no single word at the end of the comment (or every 57 chars if the comment is longer than 57 chars * 2) is longer than 57 chars. Fixed the logic to handle the lack of space. Change-Id: I2f48da5e0d066c208606058d095c7706dc12722c --- mysys_ssl/my_getopt.cc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/mysys_ssl/my_getopt.cc b/mysys_ssl/my_getopt.cc index 2ac5347e2a30..15cdb8c8d797 100644 --- a/mysys_ssl/my_getopt.cc +++ b/mysys_ssl/my_getopt.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2002, 2023, Oracle and/or its affiliates. +/* Copyright (c) 2002, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -1777,11 +1777,24 @@ void my_print_help(const struct my_option *options) while ((uint) (end - comment) > comment_space) { - for (line_end= comment + comment_space; *line_end != ' '; line_end--) + bool had_space; + // find the last space before the limit to break the comment on + for (line_end= comment + comment_space; + line_end > comment && *line_end != ' '; + line_end--) {} + // if no space is found break at the limit - 1 (for the new line) + if (line_end == comment && *comment != ' ') + { + line_end = comment + comment_space - 1; + had_space = false; + } + else + had_space = true; for (; comment != line_end; comment++) putchar(*comment); - comment++; /* skip the space, as a newline will take it's place now */ + if (had_space) + comment++; /* skip the space, as a newline will take it's place now */ putchar('\n'); for (col= 0; col < name_space; col++) putchar(' '); From 4feea1e479b973cee8d11983d83f49e68a13a6d8 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 15:32:30 +0530 Subject: [PATCH 07/14] PS-9603 AppArmor denies access to files /proc/self/task//mem https://perconadev.atlassian.net/browse/PS-9603 Bug#37063288 mysql/mysql-server@b76c48b031d Access to /proc/$pid/task/$thread_id/mem is required by stacktrace code. Change-Id: I6b6f8dd5c07ae5e867e0358fcbeccd5c5cd29bde --- policy/apparmor/usr.sbin.mysqld | 1 + 1 file changed, 1 insertion(+) diff --git a/policy/apparmor/usr.sbin.mysqld b/policy/apparmor/usr.sbin.mysqld index 942c6828c54e..6bb2d23de1c0 100644 --- a/policy/apparmor/usr.sbin.mysqld +++ b/policy/apparmor/usr.sbin.mysqld @@ -42,6 +42,7 @@ /run/mysqld/mysqld.sock w, /sys/devices/system/cpu/ r, owner /tmp/** lk, + /proc/*/mem r, /tmp/** rw, /usr/lib/mysql/plugin/ r, /usr/lib/mysql/plugin/*.so* mr, From 8bcb674e17b6afaba0f09d3217c511fab0528772 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 15:38:40 +0530 Subject: [PATCH 08/14] PS-9603 CORE CLIENT CANNOT SEND QUERY WITH NUMBER SIGN OR DOUBLE DASH IN HINT COMMENT https://perconadev.atlassian.net/browse/PS-9603 Bug#30875669 mysql/mysql-server@c510fe1470a mysql/mysql-server@ebdc6a2cd6c Problem: Server do not allow using '#' and '-- ' inside hint comment if preserve comments is turned off. Fix: Allow using '#' and '-- ' inside hint comment irrespective of preserve comments status because that is what is documented. Change-Id: I976f98e2954d9bc7f007b4acd6627726416b72ba --- client/mysql.cc | 4 ++-- mysql-test/r/bug30875669.result | 7 +++++++ mysql-test/t/bug30875669.test | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 mysql-test/r/bug30875669.result create mode 100644 mysql-test/t/bug30875669.test diff --git a/client/mysql.cc b/client/mysql.cc index f7a913ef370c..7602b691d510 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -2684,8 +2684,8 @@ static bool add_line(String &buffer, char *line, size_t line_length, } buffer.length(0); } - else if (!*ml_comment && (!*in_string && (inchar == '#' || - (inchar == '-' && pos[1] == '-' && + else if (!*ml_comment && (!*in_string && ss_comment != SSC_HINT && (inchar == '#' || + (inchar == '-' && pos[1] == '-' && /* The third byte is either whitespace or is the end of the line -- which would occur only diff --git a/mysql-test/r/bug30875669.result b/mysql-test/r/bug30875669.result new file mode 100644 index 000000000000..927df316d1dd --- /dev/null +++ b/mysql-test/r/bug30875669.result @@ -0,0 +1,7 @@ +# Bug#30875669 CORE CLIENT CANNOT SEND QUERY WITH NUMBER SIGN OR DOUBLE DASH IN HINT COMMENT +CREATE USER myuser; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used +DROP USER myuser; diff --git a/mysql-test/t/bug30875669.test b/mysql-test/t/bug30875669.test new file mode 100644 index 000000000000..3ac0639074b9 --- /dev/null +++ b/mysql-test/t/bug30875669.test @@ -0,0 +1,11 @@ +--source include/not_embedded.inc + +--echo # Bug#30875669 CORE CLIENT CANNOT SEND QUERY WITH NUMBER SIGN OR DOUBLE DASH IN HINT COMMENT + +CREATE USER myuser; + +--exec $MYSQL --user=myuser --skip-comments -e "explain select /*+ QB_NAME(`select#1`) */ 1;" + +--exec $MYSQL --user=myuser --skip-comments -e "explain select /*+ QB_NAME(`select-- `) */ 1;" + +DROP USER myuser; From 05a806257485eff1c1ca4d369929b46724a06a65 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 16:10:31 +0530 Subject: [PATCH 09/14] PS-9603 Crashing and widespread corruption of spatial indexes after INPLACE alter table https://perconadev.atlassian.net/browse/PS-9603 Bug#37189985 mysql/mysql-server@0fe52ea5c8f ANALYSIS ALTER TABLE, INPLACE operation on a table containing both a spatial index and an auto increment column can cause corruption. This is due to auto increment column value getting overwritten in the old records of spatial index while preparing the new record. The records are accessed through a cursor, cached into a tuple vector m_dtuple_vec in the index_tuple_info_t::add(). The row is built with shallow copying of fields, and the auto-increment value is prepared locally and copied. This process repeats until page traversal is complete. In subsequent iterations, shallow-copied pointers are overwritten, but since the source record remains intact and page is latched, they stay valid. The row continues to reference the old auto-incremented value, causing only the auto-increment field to be overwritten, and older rows to point to the latest auto-incremented value. FIX Perform deep copy of the auto-increment field while processing the spatial index records. Change-Id: Ib8852ef5977d881bf06dadcc35d41acfa760c96f --- storage/innobase/row/row0merge.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 55b63a381722..633975db2a47 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2005, 2023, Oracle and/or its affiliates. +Copyright (c) 2005, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -2174,9 +2174,15 @@ row_merge_read_clustered_index( ut_ad(add_autoinc < dict_table_get_n_user_cols(new_table)); - const dfield_t* dfield; + dfield_t *dfield = dtuple_get_nth_field(row, add_autoinc); + if (num_spatial > 0) { + /* Perform a deep copy of the field because for + spatial indexes, the default tuple allocation is + overwritten, as tuples are processed at the end + of the page. */ + dfield_dup(dfield, sp_heap); + } - dfield = dtuple_get_nth_field(row, add_autoinc); if (dfield_is_null(dfield)) { goto write_buffers; } From 5d715bdb9696ecf49a7891dbd0a87ecd0a70b940 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 16:12:59 +0530 Subject: [PATCH 10/14] PS-9603 FK: assertion failure in row_MySQL_pad_col https://perconadev.atlassian.net/browse/PS-9603 Bug#33327093 mysql/mysql-server@0ac176453bf Analysis -------- 1. The parent table has 3 column of datatype (i) int (PK) (ii) varchar (iii) a virtual column which is generated by varchar column. 2. The child table has a column which is referring to the primary key of the parent table. 3. There is a update in the parent table which changes the primary key and the varchar column, so it tries to build the update node to propagate to the child table. 4. While building the update node it is trying to build the update field for virtual column which is causing the crash. FIX === 1. The virtual column update should not be present in the update node, because no column in child table can have a foreign key relationship with a virtual column. 2. So skip the update to the virtual column when building the update node for child table Change-Id: Iff7d1f32c7c9d846453c587fc0c5b8604ec679ec --- storage/innobase/include/data0data.h | 6 +++++- storage/innobase/include/data0type.h | 4 +++- storage/innobase/include/row0upd.h | 5 ++++- storage/innobase/row/row0ins.cc | 7 ++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/storage/innobase/include/data0data.h b/storage/innobase/include/data0data.h index 594478526b07..72ba1ce86de2 100644 --- a/storage/innobase/include/data0data.h +++ b/storage/innobase/include/data0data.h @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1994, 2023, Oracle and/or its affiliates. +Copyright (c) 1994, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -597,6 +597,10 @@ struct dfield_t{ created. @return the cloned object. */ dfield_t* clone(mem_heap_t* heap); + + /** Checks if the field is virtual + @return true if virtual else returns false. */ + bool is_virtual() const { return (type.is_virtual()); } }; /** Structure for an SQL data tuple of fields (logical record) */ diff --git a/storage/innobase/include/data0type.h b/storage/innobase/include/data0type.h index 445edd029ca8..feb24f1f83f7 100644 --- a/storage/innobase/include/data0type.h +++ b/storage/innobase/include/data0type.h @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2023, Oracle and/or its affiliates. +Copyright (c) 1996, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -602,6 +602,8 @@ struct dtype_t{ mbminlen=DATA_MBMINLEN(mbminmaxlen); mbmaxlen=DATA_MBMINLEN(mbminmaxlen) */ #endif /* !UNIV_HOTBACKUP */ + + bool is_virtual() const { return ((prtype & DATA_VIRTUAL) == DATA_VIRTUAL); } }; #ifndef UNIV_NONINL diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index a04b7d7a4aec..2e96b6168e58 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2023, Oracle and/or its affiliates. +Copyright (c) 1996, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -463,6 +463,9 @@ struct upd_field_t{ #endif /* !UNIV_HOTBACKUP */ dfield_t new_val; /*!< new value for the column */ dfield_t* old_v_val; /*!< old value for the virtual column */ + + bool is_virtual() const { return (new_val.is_virtual()); } + }; diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 9098d4857c34..3747521a540e 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2023, Oracle and/or its affiliates. +Copyright (c) 1996, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -551,6 +551,11 @@ row_ins_cascade_calc_update_vec( const upd_field_t* parent_ufield = &parent_update->fields[j]; + /* Skip if the updated field is virtual */ + if (parent_ufield->is_virtual()) { + continue; + } + if (parent_ufield->field_no == parent_field_no) { ulint min_size; From 56f305e24dc7b148387d6c50e5a01c35d1b63667 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 16:17:59 +0530 Subject: [PATCH 11/14] PS-9603 MySQL server 8.3.0 crashes at Item_rollup_sum_switcher::current_arg https://perconadev.atlassian.net/browse/PS-9603 Bug#36593235 mysql/mysql-server@9a32e4495c5 Problem is seen when a subquery containing a aggregate function with rollup is part of a row value comparator and if there were no rows returned from the subquery. cmp_item_row::store_value() evaluates the subquery and goes ahead to store the values of the expressions in the comparator without checking if the result was assigned for the subquery. This leads to evaluation of a rollup expression when it is marked as not to be evaluated as aggregation was completed earlier. For the failing query, AggregateIterator() does evaluate the expressions. However HAVING clause does not qualify the rows. So the result of the aggregation is never cached. If a subquery returns empty result, bring_value() would set the "null_value" to true. cmp_item_row::store_value() now stores the value of the underlying comparator objects only when the result is not null. Else the result is set to null. Change-Id: I7bb17e621f1e5b439f6284c279ce69b80dace237 --- sql/item_cmpfunc.cc | 40 ++++++++++++++++++++++++++++------------ sql/item_cmpfunc.h | 10 +++++++++- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 2691e850c0fb..9b7b042c45bc 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2023, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -4928,12 +4928,20 @@ void cmp_item_row::store_value(Item *item) assert(comparators); if (comparators) { - item->bring_value(); item->null_value= 0; - for (uint i= 0; i < n; i++) + item->bring_value(); + if (item->null_value) { - comparators[i]->store_value(item->element_index(i)); - item->null_value|= item->element_index(i)->null_value; + set_null_value(/*nv=*/true); + } + else + { + item->null_value= false; + for (uint i= 0; i < n; i++) + { + comparators[i]->store_value(item->element_index(i)); + item->null_value|= item->element_index(i)->null_value; + } } } DBUG_VOID_RETURN; @@ -4951,15 +4959,23 @@ void cmp_item_row::store_value_by_template(cmp_item *t, Item *item) n= tmpl->n; if ((comparators= (cmp_item **) sql_alloc(sizeof(cmp_item *)*n))) { - item->bring_value(); item->null_value= 0; - for (uint i=0; i < n; i++) + item->bring_value(); + if (item->null_value) { - if (!(comparators[i]= tmpl->comparators[i]->make_same())) - break; // new failed - comparators[i]->store_value_by_template(tmpl->comparators[i], - item->element_index(i)); - item->null_value|= item->element_index(i)->null_value; + set_null_value(/*nv=*/true); + } + else + { + item->null_value= false; + for (uint i= 0; i < n; i++) + { + if (!(comparators[i]= tmpl->comparators[i]->make_same())) + break; // new failed + comparators[i]->store_value_by_template(tmpl->comparators[i], + item->element_index(i)); + item->null_value|= item->element_index(i)->null_value; + } } } } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index c9d5976a4032..e02fe3232498 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -1,7 +1,7 @@ #ifndef ITEM_CMPFUNC_INCLUDED #define ITEM_CMPFUNC_INCLUDED -/* Copyright (c) 2000, 2023, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -1420,6 +1420,7 @@ class cmp_item :public Sql_alloc { store_value(item); } + virtual void set_null_value(bool nv) = 0; }; /// cmp_item which stores a scalar (i.e. non-ROW). @@ -1769,6 +1770,13 @@ class cmp_item_row :public cmp_item int compare(const cmp_item *arg) const; cmp_item *make_same(); void store_value_by_template(cmp_item *tmpl, Item *); + void set_null_value(bool nv) + { + for (uint i= 0; i < n; i++) + { + comparators[i]->set_null_value(nv); + } + } friend void Item_func_in::fix_length_and_dec(); }; From 61cabf8c852c03f24c56211a319c8e3863a9daac Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 16:20:01 +0530 Subject: [PATCH 12/14] PS-9603 Crash in mysqld initialization when compiling with XCode 16 due to a bad comparator https://perconadev.atlassian.net/browse/PS-9603 Bug#37068527 mysql/mysql-server@04b8be52e59 Fixed the comparator to adhere to the C++ standard Change-Id: I78652a2e13dd814525b131e1ac9b5d9c68dca940 (cherry picked from commit e189888d621c735656a8239c91c980e5804be022) --- sql/sql_table.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 88835fc4efb8..4dfb85718080 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3031,6 +3031,7 @@ bool quick_rm_table(THD *thd, handlerton *base, const char *db, static int sort_keys(KEY *a, KEY *b) { + if (&a == &b) return 0; ulong a_flags= a->flags, b_flags= b->flags; if (a_flags & HA_NOSAME) From 69f4e933ac26ddb7f65d6f90e1fb7b740677a98b Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 5 Feb 2025 16:29:34 +0530 Subject: [PATCH 13/14] PS-9603 [InnoDB] FULLTEXT index limits FTS_DOC_ID to max unsigned 32-bit value https://perconadev.atlassian.net/browse/PS-9603 Bug#36879147 mysql/mysql-server@daa02f8852e Issue: FTS_DOC_ID is a 64-bit field and can have values 2^32 and higher. However current implementation only supports 32-bit value range. This limtation takes the form of: - Assertions - Use of unsigned long type which resolves to 32-bit on some platforms - VLC (variable length coding) implementation supporting up to 35 bits Fix: Support 64-bit doc IDs: - Remove assertions - Replace use of unsigned long for doc ID deltas with uint64_t - Extend VLC functions to support full unsigned 64-bit range Change-Id: Ifb56b33c5ec75e578391612eb371c41fc6aeef31 --- storage/innobase/fts/fts0fts.cc | 3 +- storage/innobase/fts/fts0opt.cc | 10 +- storage/innobase/fts/fts0que.cc | 21 +- storage/innobase/handler/i_s.cc | 18 +- storage/innobase/include/fts0types.h | 11 +- storage/innobase/include/fts0vlc.ic | 126 +++++----- unittest/gunit/innodb/CMakeLists.txt | 1 + unittest/gunit/innodb/fts0vlc-t.cc | 352 +++++++++++++++++++++++++++ 8 files changed, 450 insertions(+), 92 deletions(-) create mode 100644 unittest/gunit/innodb/fts0vlc-t.cc diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index f8cb740f2851..057c3935633b 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -1283,7 +1283,6 @@ fts_cache_node_add_positions( ulint enc_len; ulint last_pos; byte* ptr_start; - ulint doc_id_delta; #ifdef UNIV_DEBUG if (cache) { @@ -1294,7 +1293,7 @@ fts_cache_node_add_positions( ut_ad(doc_id >= node->last_doc_id); /* Calculate the space required to store the ilist. */ - doc_id_delta = (ulint)(doc_id - node->last_doc_id); + const uint64_t doc_id_delta = doc_id - node->last_doc_id; enc_len = fts_get_encoded_len(doc_id_delta); last_pos = 0; diff --git a/storage/innobase/fts/fts0opt.cc b/storage/innobase/fts/fts0opt.cc index cb02ad97ab4b..4e6b96b6562b 100644 --- a/storage/innobase/fts/fts0opt.cc +++ b/storage/innobase/fts/fts0opt.cc @@ -1175,7 +1175,7 @@ fts_optimize_encode_node( /* Calculate the space required to store the ilist. */ ut_ad(doc_id > node->last_doc_id); doc_id_delta = doc_id - node->last_doc_id; - enc_len = fts_get_encoded_len(static_cast(doc_id_delta)); + enc_len = fts_get_encoded_len(doc_id_delta); /* Calculate the size of the encoded pos array. */ while (*src) { @@ -1219,9 +1219,8 @@ fts_optimize_encode_node( src = enc->src_ilist_ptr; dst = node->ilist + node->ilist_size; - /* Encode the doc id. Cast to ulint, the delta should be small and - therefore no loss of precision. */ - dst += fts_encode_int((ulint) doc_id_delta, dst); + /* Encode the doc id. */ + dst += fts_encode_int(doc_id_delta, dst); /* Copy the encoded pos array. */ memcpy(dst, src, pos_enc_len); @@ -1265,10 +1264,9 @@ fts_optimize_node( while (copied < src_node->ilist_size && dst_node->ilist_size < FTS_ILIST_MAX_SIZE) { - doc_id_t delta; doc_id_t del_doc_id = FTS_NULL_DOC_ID; - delta = fts_decode_vlc(&enc->src_ilist_ptr); + doc_id_t delta = fts_decode_vlc(&enc->src_ilist_ptr); test_again: /* Check whether the doc id is in the delete list, if diff --git a/storage/innobase/fts/fts0que.cc b/storage/innobase/fts/fts0que.cc index ff2b6ef27655..ce234361df83 100644 --- a/storage/innobase/fts/fts0que.cc +++ b/storage/innobase/fts/fts0que.cc @@ -51,6 +51,7 @@ Completed 2011/7/10 Sunny and Jimmy Yang #endif #include +#include #include #define FTS_ELEM(t, n, i, j) (t[(i) * n + (j)]) @@ -3195,14 +3196,17 @@ fts_query_find_doc_id( ulint freq = 0; ulint min_pos = 0; ulint last_pos = 0; - ulint pos = fts_decode_vlc(&ptr); + const uint64_t delta = fts_decode_vlc(&ptr); /* Add the delta. */ - doc_id += pos; + doc_id += delta; while (*ptr) { ++freq; - last_pos += fts_decode_vlc(&ptr); + const uint64_t pos_delta = fts_decode_vlc(&ptr); + ut_ad(uint64_t(last_pos) + pos_delta <= + std::numeric_limits::max()); + last_pos += static_cast(pos_delta); /* Only if min_pos is not set and the current term exists in a position greater than the @@ -3275,15 +3279,15 @@ fts_query_filter_doc_ids( fts_doc_freq_t* doc_freq; fts_match_t* match = NULL; ulint last_pos = 0; - ulint pos = fts_decode_vlc(&ptr); + const uint64_t delta = fts_decode_vlc(&ptr); /* Some sanity checks. */ if (doc_id == 0) { - ut_a(pos == node->first_doc_id); + ut_a(delta == node->first_doc_id); } /* Add the delta. */ - doc_id += pos; + doc_id += delta; if (calc_doc_count) { word_freq->doc_count++; @@ -3313,7 +3317,10 @@ fts_query_filter_doc_ids( /* Unpack the positions within the document. */ while (*ptr) { - last_pos += fts_decode_vlc(&ptr); + const uint64_t decoded_pos = fts_decode_vlc(&ptr); + ut_ad(uint64_t(last_pos) + decoded_pos + <= std::numeric_limits::max()); + last_pos += static_cast(decoded_pos); /* Collect the matching word positions, for phrase matching later. */ diff --git a/storage/innobase/handler/i_s.cc b/storage/innobase/handler/i_s.cc index a44a7e593ab4..cd4ae9cf781a 100644 --- a/storage/innobase/handler/i_s.cc +++ b/storage/innobase/handler/i_s.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2007, 2023, Oracle and/or its affiliates. +Copyright (c) 2007, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -3387,13 +3387,15 @@ i_s_fts_index_cache_fill_one_index( ptr = node->ilist; while (decoded < node->ilist_size) { - ulint pos = fts_decode_vlc(&ptr); + const uint64_t delta = fts_decode_vlc(&ptr); - doc_id += pos; + doc_id += delta; /* Get position info */ while (*ptr) { - pos = fts_decode_vlc(&ptr); + const uint64_t decoded_pos = fts_decode_vlc(&ptr); + ut_ad(decoded_pos <= std::numeric_limits::max()); + const ulint pos = static_cast(decoded_pos); OK(field_store_string( fields[I_S_FTS_WORD], @@ -3768,13 +3770,15 @@ i_s_fts_index_table_fill_one_fetch( ptr = node->ilist; while (decoded < node->ilist_size) { - ulint pos = fts_decode_vlc(&ptr); + const uint64_t delta = fts_decode_vlc(&ptr); - doc_id += pos; + doc_id += delta; /* Get position info */ while (*ptr) { - pos = fts_decode_vlc(&ptr); + const uint64_t decoded_pos = fts_decode_vlc(&ptr); + ut_ad(decoded_pos <= std::numeric_limits::max()); + const ulint pos = static_cast(decoded_pos); OK(field_store_string( fields[I_S_FTS_WORD], diff --git a/storage/innobase/include/fts0types.h b/storage/innobase/include/fts0types.h index 643fb8d3ed01..cdd8257d55d8 100644 --- a/storage/innobase/include/fts0types.h +++ b/storage/innobase/include/fts0types.h @@ -34,6 +34,7 @@ Created 2007-03-27 Sunny Bains #ifndef INNOBASE_FTS0TYPES_H #define INNOBASE_FTS0TYPES_H +#include #include "univ.i" #include "fts0fts.h" #include "fut0fut.h" @@ -306,7 +307,7 @@ extern const fts_index_selector_t fts_index_selector[]; /******************************************************************//** Decode and return the integer that was encoded using our VLC scheme.*/ UNIV_INLINE -ulint +uint64_t fts_decode_vlc( /*===========*/ /*!< out: value decoded */ @@ -330,22 +331,22 @@ fts_string_dup( /******************************************************************//** Return length of val if it were encoded using our VLC scheme. */ UNIV_INLINE -ulint +unsigned int fts_get_encoded_len( /*================*/ /*!< out: length of value encoded, in bytes */ - ulint val); /*!< in: value to encode */ + uint64_t val); /*!< in: value to encode */ /******************************************************************//** Encode an integer using our VLC scheme and return the length in bytes. */ UNIV_INLINE -ulint +unsigned int fts_encode_int( /*===========*/ /*!< out: length of value encoded, in bytes */ - ulint val, /*!< in: value to encode */ + uint64_t val, /*!< in: value to encode */ byte* buf); /*!< in: buffer, must have enough space */ diff --git a/storage/innobase/include/fts0vlc.ic b/storage/innobase/include/fts0vlc.ic index 2656e34213a1..d7b6d2f79f80 100644 --- a/storage/innobase/include/fts0vlc.ic +++ b/storage/innobase/include/fts0vlc.ic @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2007, 2023, Oracle and/or its affiliates. +Copyright (c) 2007, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -34,101 +34,97 @@ Created 2007-03-27 Sunny Bains #ifndef INNOBASE_FTS0VLC_IC #define INNOBASE_FTS0VLC_IC +#include +#include #include "fts0types.h" /******************************************************************//** Return length of val if it were encoded using our VLC scheme. -FIXME: We will need to be able encode 8 bytes value @return length of value encoded, in bytes */ UNIV_INLINE -ulint +unsigned int fts_get_encoded_len( /*================*/ - ulint val) /* in: value to encode */ + uint64_t val) /* in: value to encode */ { - if (val <= 127) { - return(1); - } else if (val <= 16383) { - return(2); - } else if (val <= 2097151) { - return(3); - } else if (val <= 268435455) { - return(4); - } else { - /* Possibly we should care that on 64-bit machines ulint can - contain values that we can't encode in 5 bytes, but - fts_encode_int doesn't handle them either so it doesn't much - matter. */ - - return(5); - } + unsigned int length = 1; + for (;;) + { + val >>= 7; + if (val != 0) + { + ++length; + } + else + { + break; + } + } + return length; } /******************************************************************//** Encode an integer using our VLC scheme and return the length in bytes. @return length of value encoded, in bytes */ UNIV_INLINE -ulint +unsigned int fts_encode_int( /*===========*/ - ulint val, /* in: value to encode */ + uint64_t val, /* in: value to encode */ byte* buf) /* in: buffer, must have enough space */ { - ulint len; - - if (val <= 127) { - *buf = (byte) val; - - len = 1; - } else if (val <= 16383) { - *buf++ = (byte)(val >> 7); - *buf = (byte)(val & 0x7F); - - len = 2; - } else if (val <= 2097151) { - *buf++ = (byte)(val >> 14); - *buf++ = (byte)((val >> 7) & 0x7F); - *buf = (byte)(val & 0x7F); - - len = 3; - } else if (val <= 268435455) { - *buf++ = (byte)(val >> 21); - *buf++ = (byte)((val >> 14) & 0x7F); - *buf++ = (byte)((val >> 7) & 0x7F); - *buf = (byte)(val & 0x7F); - - len = 4; - } else { - /* Best to keep the limitations of the 32/64 bit versions - identical, at least for the time being. */ - ut_ad(val <= 4294967295u); - - *buf++ = (byte)(val >> 28); - *buf++ = (byte)((val >> 21) & 0x7F); - *buf++ = (byte)((val >> 14) & 0x7F); - *buf++ = (byte)((val >> 7) & 0x7F); - *buf = (byte)(val & 0x7F); - - len = 5; + const unsigned int max_length = 10; + /* skip leading zeros */ + unsigned int count = max_length - 1; + while (count > 0) + { + /* We split the value into 7 bit batches); so val >= 2^63 need 10 bytes, + 2^63 > val >= 2^56 needs 9 bytes, 2^56 > val >= 2^49 needs 8 bytes etc. + */ + if (val >= uint64_t(1) << (count * 7)) + { + break; + } + --count; } - /* High-bit on means "last byte in the encoded integer". */ - *buf |= 0x80; - - return(len); + unsigned int length = count + 1; + + byte *bufptr= buf; + + for (;;) + { + *bufptr = (byte)((val >> (7 * count)) & 0x7f); + if (count == 0) + { + /* High-bit on means "last byte in the encoded integer". */ + *bufptr |= 0x80; + break; + } + else + { + --count; + ++bufptr; + } + } + + ut_ad(length <= max_length); + ut_a(bufptr - buf == ptrdiff_t(length) - 1); + + return length; } /******************************************************************//** Decode and return the integer that was encoded using our VLC scheme. @return value decoded */ UNIV_INLINE -ulint +uint64_t fts_decode_vlc( /*===========*/ byte** ptr) /* in: ptr to decode from, this ptr is incremented by the number of bytes decoded */ { - ulint val = 0; + uint64_t val = 0; for (;;) { byte b = **ptr; @@ -144,7 +140,7 @@ fts_decode_vlc( } } - return(val); + return val; } #endif diff --git a/unittest/gunit/innodb/CMakeLists.txt b/unittest/gunit/innodb/CMakeLists.txt index dd75c7ed6914..1e400001f3d0 100644 --- a/unittest/gunit/innodb/CMakeLists.txt +++ b/unittest/gunit/innodb/CMakeLists.txt @@ -33,6 +33,7 @@ INCLUDE(${CMAKE_SOURCE_DIR}/storage/innobase/innodb.cmake) SET(TESTS #example + fts0vlc ha_innodb mem0mem ut0crc32 diff --git a/unittest/gunit/innodb/fts0vlc-t.cc b/unittest/gunit/innodb/fts0vlc-t.cc new file mode 100644 index 000000000000..ffa059005d48 --- /dev/null +++ b/unittest/gunit/innodb/fts0vlc-t.cc @@ -0,0 +1,352 @@ +/* Copyright (c) 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 +#include +#include +#include +#include "fts0vlc.ic" + +namespace innodb_fts0vlc_unittest { + +namespace { +const uint64_t max_64 = std::numeric_limits::max(); +} + +TEST(fts0vlc, fts_get_encoded_len) { + EXPECT_EQ(fts_get_encoded_len(0ULL), 1); + EXPECT_EQ(fts_get_encoded_len(127ULL), 1); + EXPECT_EQ(fts_get_encoded_len(128ULL), 2); + EXPECT_EQ(fts_get_encoded_len((1ULL << 14) - 1), 2); + EXPECT_EQ(fts_get_encoded_len(1ULL << 14), 3); + EXPECT_EQ(fts_get_encoded_len((1ULL << 21) - 1), 3); + EXPECT_EQ(fts_get_encoded_len(1ULL << 21), 4); + EXPECT_EQ(fts_get_encoded_len((1ULL << 28) - 1), 4); + EXPECT_EQ(fts_get_encoded_len(1ULL << 28), 5); + + /* max 32-bit unsigned integer */ + EXPECT_EQ(fts_get_encoded_len((1ULL << 32) - 1), 5); + + EXPECT_EQ(fts_get_encoded_len((1ULL << 35) - 1), 5); + EXPECT_EQ(fts_get_encoded_len(1ULL << 35), 6); + EXPECT_EQ(fts_get_encoded_len((1ULL << 42) - 1), 6); + EXPECT_EQ(fts_get_encoded_len(1ULL << 42), 7); + EXPECT_EQ(fts_get_encoded_len((1ULL << 49) - 1), 7); + EXPECT_EQ(fts_get_encoded_len(1ULL << 49), 8); + EXPECT_EQ(fts_get_encoded_len((1ULL << 56) - 1), 8); + EXPECT_EQ(fts_get_encoded_len(1ULL << 56), 9); + EXPECT_EQ(fts_get_encoded_len((1ULL << 63) - 1), 9); + EXPECT_EQ(fts_get_encoded_len(1ULL << 63), 10); + EXPECT_EQ(fts_get_encoded_len(max_64), 10); +} + +TEST(fts0vlc, fts_encode_int_decode) { + /* Variable-length integer coding packs consecutive groups of 7 bits, + starting with most-significant byte, into subsequent bytes of + the buffer, using the most-significant bit to mark the end of + the encoded sequence + */ + + byte buf[12]; + byte *bufptr = buf; + const byte filler = 0xa5; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(0ULL, bufptr), 1); + EXPECT_EQ(bufptr[0], 0x80); + EXPECT_EQ(bufptr[1], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 0ULL); + EXPECT_EQ(bufptr, buf + 1); + + EXPECT_EQ(fts_encode_int(10ULL, bufptr), 1); + EXPECT_EQ(buf[1], 0x8a); + EXPECT_EQ(buf[2], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 10ULL); + EXPECT_EQ(bufptr, buf + 2); + + EXPECT_EQ(fts_encode_int(127ULL, bufptr), 1); + EXPECT_EQ(buf[2], 0xff); + EXPECT_EQ(buf[3], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 127ULL); + EXPECT_EQ(bufptr, buf + 3); + + EXPECT_EQ(fts_encode_int(128ULL, bufptr), 2); + EXPECT_EQ(buf[3], 0x01); + EXPECT_EQ(buf[4], 0x80); + EXPECT_EQ(buf[5], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 128ULL); + EXPECT_EQ(bufptr, buf + 5); + + EXPECT_EQ(fts_encode_int(130ULL, bufptr), 2); + EXPECT_EQ(buf[5], 0x01); + EXPECT_EQ(buf[6], 0x82); + EXPECT_EQ(buf[7], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 130ULL); + EXPECT_EQ(bufptr, buf + 7); + + EXPECT_EQ(fts_encode_int((1ULL << 14) - 1, bufptr), 2); + EXPECT_EQ(buf[7], 0x7f); + EXPECT_EQ(buf[8], 0xff); + EXPECT_EQ(buf[9], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 14) - 1); + EXPECT_EQ(bufptr, buf + 9); + + EXPECT_EQ(fts_encode_int(1ULL << 14, bufptr), 3); + EXPECT_EQ(buf[9], 0x01); + EXPECT_EQ(buf[10], 0x00); + EXPECT_EQ(buf[11], 0x80); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 14); + EXPECT_EQ(bufptr, buf + 12); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int((1ULL << 14) + 256, bufptr), 3); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x02); + EXPECT_EQ(buf[2], 0x80); + EXPECT_EQ(buf[3], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 14) + 256); + EXPECT_EQ(bufptr, buf + 3); + + EXPECT_EQ(fts_encode_int((1ULL << 21) - 1, bufptr), 3); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0x7f); + EXPECT_EQ(buf[5], 0xff); + EXPECT_EQ(buf[6], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 21) - 1); + EXPECT_EQ(bufptr, buf + 6); + + EXPECT_EQ(fts_encode_int(1ULL << 21, bufptr), 4); + EXPECT_EQ(buf[6], 0x01); + EXPECT_EQ(buf[7], 0x00); + EXPECT_EQ(buf[8], 0x00); + EXPECT_EQ(buf[9], 0x80); + EXPECT_EQ(buf[10], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 21); + EXPECT_EQ(bufptr, buf + 10); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int((1ULL << 28) - 1, bufptr), 4); + EXPECT_EQ(buf[0], 0x7f); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0xff); + EXPECT_EQ(buf[4], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 28) - 1); + EXPECT_EQ(bufptr, buf + 4); + + EXPECT_EQ(fts_encode_int(1ULL << 28, bufptr), 5); + EXPECT_EQ(buf[4], 0x01); + EXPECT_EQ(buf[5], 0x00); + EXPECT_EQ(buf[6], 0x00); + EXPECT_EQ(buf[7], 0x00); + EXPECT_EQ(buf[8], 0x80); + EXPECT_EQ(buf[9], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 28); + EXPECT_EQ(bufptr, buf + 9); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + /* maximum 32-bit integer */ + EXPECT_EQ(fts_encode_int((1ULL << 32) - 1, bufptr), 5); + EXPECT_EQ(buf[0], 0x0f); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0xff); + EXPECT_EQ(buf[5], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 32) - 1); + EXPECT_EQ(bufptr, buf + 5); + + EXPECT_EQ(fts_encode_int((1ULL << 35) - 1, bufptr), 5); + EXPECT_EQ(buf[5], 0x7f); + EXPECT_EQ(buf[6], 0x7f); + EXPECT_EQ(buf[7], 0x7f); + EXPECT_EQ(buf[8], 0x7f); + EXPECT_EQ(buf[9], 0xff); + EXPECT_EQ(buf[10], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 35) - 1); + EXPECT_EQ(bufptr, buf + 10); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(1ULL << 35, bufptr), 6); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x00); + EXPECT_EQ(buf[2], 0x00); + EXPECT_EQ(buf[3], 0x00); + EXPECT_EQ(buf[4], 0x00); + EXPECT_EQ(buf[5], 0x80); + EXPECT_EQ(buf[6], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 35); + EXPECT_EQ(bufptr, buf + 6); + + EXPECT_EQ(fts_encode_int((1ULL << 42) - 1, bufptr), 6); + EXPECT_EQ(buf[6], 0x7f); + EXPECT_EQ(buf[7], 0x7f); + EXPECT_EQ(buf[8], 0x7f); + EXPECT_EQ(buf[9], 0x7f); + EXPECT_EQ(buf[10], 0x7f); + EXPECT_EQ(buf[11], 0xff); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 42) - 1); + EXPECT_EQ(bufptr, buf + 12); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(1ULL << 42, bufptr), 7); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x00); + EXPECT_EQ(buf[2], 0x00); + EXPECT_EQ(buf[3], 0x00); + EXPECT_EQ(buf[4], 0x00); + EXPECT_EQ(buf[5], 0x00); + EXPECT_EQ(buf[6], 0x80); + EXPECT_EQ(buf[7], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 42); + EXPECT_EQ(bufptr, buf + 7); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int((1ULL << 49) - 1, bufptr), 7); + EXPECT_EQ(buf[0], 0x7f); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0x7f); + EXPECT_EQ(buf[5], 0x7f); + EXPECT_EQ(buf[6], 0xff); + EXPECT_EQ(buf[7], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 49) - 1); + EXPECT_EQ(bufptr, buf + 7); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(1ULL << 49, bufptr), 8); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x00); + EXPECT_EQ(buf[2], 0x00); + EXPECT_EQ(buf[3], 0x00); + EXPECT_EQ(buf[4], 0x00); + EXPECT_EQ(buf[5], 0x00); + EXPECT_EQ(buf[6], 0x00); + EXPECT_EQ(buf[7], 0x80); + EXPECT_EQ(buf[8], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 49); + EXPECT_EQ(bufptr, buf + 8); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int((1ULL << 56) - 1, bufptr), 8); + EXPECT_EQ(buf[0], 0x7f); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0x7f); + EXPECT_EQ(buf[5], 0x7f); + EXPECT_EQ(buf[6], 0x7f); + EXPECT_EQ(buf[7], 0xff); + EXPECT_EQ(buf[8], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 56) - 1); + EXPECT_EQ(bufptr, buf + 8); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(1ULL << 56, bufptr), 9); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x00); + EXPECT_EQ(buf[2], 0x00); + EXPECT_EQ(buf[3], 0x00); + EXPECT_EQ(buf[4], 0x00); + EXPECT_EQ(buf[5], 0x00); + EXPECT_EQ(buf[6], 0x00); + EXPECT_EQ(buf[7], 0x00); + EXPECT_EQ(buf[8], 0x80); + EXPECT_EQ(buf[9], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 56); + EXPECT_EQ(bufptr, buf + 9); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int((1ULL << 63) - 1, bufptr), 9); + EXPECT_EQ(buf[0], 0x7f); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0x7f); + EXPECT_EQ(buf[5], 0x7f); + EXPECT_EQ(buf[6], 0x7f); + EXPECT_EQ(buf[7], 0x7f); + EXPECT_EQ(buf[8], 0xff); + EXPECT_EQ(buf[9], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), (1ULL << 63) - 1); + EXPECT_EQ(bufptr, buf + 9); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(1ULL << 63, bufptr), 10); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x00); + EXPECT_EQ(buf[2], 0x00); + EXPECT_EQ(buf[3], 0x00); + EXPECT_EQ(buf[4], 0x00); + EXPECT_EQ(buf[5], 0x00); + EXPECT_EQ(buf[6], 0x00); + EXPECT_EQ(buf[7], 0x00); + EXPECT_EQ(buf[8], 0x00); + EXPECT_EQ(buf[9], 0x80); + EXPECT_EQ(buf[10], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), 1ULL << 63); + EXPECT_EQ(bufptr, buf + 10); + + bufptr = buf; + memset(buf, filler, sizeof(buf)); + + EXPECT_EQ(fts_encode_int(max_64, bufptr), 10); + EXPECT_EQ(buf[0], 0x01); + EXPECT_EQ(buf[1], 0x7f); + EXPECT_EQ(buf[2], 0x7f); + EXPECT_EQ(buf[3], 0x7f); + EXPECT_EQ(buf[4], 0x7f); + EXPECT_EQ(buf[5], 0x7f); + EXPECT_EQ(buf[6], 0x7f); + EXPECT_EQ(buf[7], 0x7f); + EXPECT_EQ(buf[8], 0x7f); + EXPECT_EQ(buf[9], 0xff); + EXPECT_EQ(buf[10], filler); + EXPECT_EQ(fts_decode_vlc(&bufptr), max_64); + EXPECT_EQ(bufptr, buf + 10); +} + +} // namespace innodb_fts0vlc_unittest From ee29b03ac84e174b5105934d0ab72c82269da7e0 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 13 Feb 2025 23:58:13 +0530 Subject: [PATCH 14/14] PS-9603 Update the versions numbers https://perconadev.atlassian.net/browse/PS-9603 Raised MYSQL_VERSION_EXTRA to 53 in MYSQL_VERSION file. Raised PERCONA_INNODB_VERSION to 53 in univ.i file. --- MYSQL_VERSION | 2 +- storage/innobase/include/univ.i | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MYSQL_VERSION b/MYSQL_VERSION index 33f3e366a1b6..51c4bc9a9ef0 100644 --- a/MYSQL_VERSION +++ b/MYSQL_VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=7 MYSQL_VERSION_PATCH=44 -MYSQL_VERSION_EXTRA=-52 +MYSQL_VERSION_EXTRA=-53 diff --git a/storage/innobase/include/univ.i b/storage/innobase/include/univ.i index 93ade2ec27a5..92c55e0fe96d 100644 --- a/storage/innobase/include/univ.i +++ b/storage/innobase/include/univ.i @@ -55,7 +55,7 @@ Created 1/20/1994 Heikki Tuuri #define INNODB_VERSION_BUGFIX MYSQL_VERSION_PATCH #ifndef PERCONA_INNODB_VERSION -#define PERCONA_INNODB_VERSION 52 +#define PERCONA_INNODB_VERSION 53 #endif /* The following is the InnoDB version as shown in