From 0d6a52d152334aeaf7bde5356f22c3c35da15bbf Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 29 Jul 2026 21:26:30 +0300 Subject: [PATCH] MDEV-40480 LOAD DATA leaves a stale STORED generated column after a BEFORE INSERT trigger changes its base column On the LOAD DATA path the base columns are filled directly from the input file and fill_record_n_invoke_before_triggers() is then called with an empty field list (there is no SET clause). After the BEFORE INSERT trigger changed a base column, the stored generated columns were not recomputed, so they kept the value derived from the pre-trigger input (e.g. g=24 instead of 40 for g=v*2 with v set to 20 by the trigger). A regular INSERT was unaffected. The recompute was guarded by "fields.elements". That condition is a leftover from the original computed-columns implementation (f7a75b999b4), where fill_record_n_invoke_before_triggers() had no TABLE* argument and had to reverse-derive the table from the first item of the field list: if (fields.elements) { fld= (Item_field*)f++; item_field= fld->field_for_view_update(); table= item_field->field->table; ... } With an empty field list there was no way to obtain the table, so the recompute was silently skipped. Since bc4a456758c (MDEV-452) the function receives TABLE* explicitly, which made the whole derivation dead code (as the in-place DBUG_ASSERT(table == item_field->field->table) confirmed). Recompute the virtual fields unconditionally on table->vfield, the same way the Field** overload of fill_record_n_invoke_before_triggers() already does. --- mysql-test/suite/gcol/r/gcol_bugfixes.result | 18 ++++++++++++++ mysql-test/suite/gcol/t/gcol_bugfixes.test | 25 ++++++++++++++++++++ sql/sql_base.cc | 14 +++-------- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/mysql-test/suite/gcol/r/gcol_bugfixes.result b/mysql-test/suite/gcol/r/gcol_bugfixes.result index 5dfa416a540b1..20df7443a4e7d 100644 --- a/mysql-test/suite/gcol/r/gcol_bugfixes.result +++ b/mysql-test/suite/gcol/r/gcol_bugfixes.result @@ -782,3 +782,21 @@ a c1 Warnings: Warning 1292 Incorrect datetime value: '0' DROP TABLE t1; +# +# MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +# BEFORE INSERT trigger changes its base column +# +create table t ( +id int primary key, +v int not null, +g int generated always as (v * 2) stored, +note varchar(20) +) engine=innodb; +create trigger t_bi before insert on t for each row set new.v = 20; +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); +# The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; +id v g note +2 20 40 new +drop table t; diff --git a/mysql-test/suite/gcol/t/gcol_bugfixes.test b/mysql-test/suite/gcol/t/gcol_bugfixes.test index c4beb69058a52..967b26c055426 100644 --- a/mysql-test/suite/gcol/t/gcol_bugfixes.test +++ b/mysql-test/suite/gcol/t/gcol_bugfixes.test @@ -753,3 +753,28 @@ UPDATE t1 SET a=2; UPDATE t1 SET c1=1; SELECT * FROM t1; DROP TABLE t1; + + +--echo # +--echo # MDEV-40480 LOAD DATA leaves a stale STORED generated column after a +--echo # BEFORE INSERT trigger changes its base column +--echo # + +create table t ( + id int primary key, + v int not null, + g int generated always as (v * 2) stored, + note varchar(20) +) engine=innodb; + +create trigger t_bi before insert on t for each row set new.v = 20; + +select 2, 12, 'new' into outfile 'load_40480'; +load data infile 'load_40480' into table t (id, v, note); + +--echo # The trigger sets v=20, so the STORED column g must be v*2 = 40, not 24. +select * from t; + +drop table t; +--let $datadir= `select @@datadir` +--remove_file $datadir/test/load_40480 diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 544df995f2f4c..160d7d4776d60 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -9372,17 +9372,9 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Re-calculate virtual fields to cater for cases when base columns are updated by the triggers. */ - if (table->vfield && fields.elements) - { - Item *fld= (Item_field*) fields.head(); - Item_field *item_field= fld->field_for_view_update(); - if (item_field) - { - DBUG_ASSERT(table == item_field->field->table); - result|= table->update_virtual_fields(table->file, - VCOL_UPDATE_FOR_WRITE); - } - } + if (table->vfield) + result|= table->update_virtual_fields(table->file, + VCOL_UPDATE_FOR_WRITE); } return result; }