From 62b4a8efc969adaac483e04d60a28f0a7978cfcc Mon Sep 17 00:00:00 2001 From: fengchuanheng Date: Mon, 18 May 2026 20:17:28 +0800 Subject: [PATCH] Bug#118060 LOAD DATA leaves tmp_null state dirty on error During LOAD DATA execution, fields are temporarily marked as nullable while processing input rows. When LOAD DATA fails (e.g., NULL supplied to a NOT NULL column under STRICT mode), this temporary NULL state on fields is not reset before returning. Subsequent statements in the same session may then incorrectly treat valid non-NULL values as NULL, producing spurious ER_BAD_NULL_ERROR. Fix: reset temporary NULL flags on all table fields in the LOAD DATA error exit path and after restoring default records in read_sep_field(). --- mysql-test/r/loaddata_null_state_leak.result | 28 +++++++++++++ .../t/loaddata_null_state_leak-master.opt | 2 + mysql-test/t/loaddata_null_state_leak.test | 39 +++++++++++++++++++ sql/sql_load.cc | 8 ++++ 4 files changed, 77 insertions(+) create mode 100644 mysql-test/r/loaddata_null_state_leak.result create mode 100644 mysql-test/t/loaddata_null_state_leak-master.opt create mode 100644 mysql-test/t/loaddata_null_state_leak.test diff --git a/mysql-test/r/loaddata_null_state_leak.result b/mysql-test/r/loaddata_null_state_leak.result new file mode 100644 index 00000000000..7d2a3e7e88d --- /dev/null +++ b/mysql-test/r/loaddata_null_state_leak.result @@ -0,0 +1,28 @@ +SET @old_sql_mode = @@sql_mode; +SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'; +CREATE TABLE t2 ( +id INT NOT NULL, +a CHAR(20) NOT NULL DEFAULT '', +b CHAR(20) NOT NULL DEFAULT '', +c JSON NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +LOAD DATA LOCAL INFILE 'MYSQLTEST_VARDIR/tmp/load_data_null_state_leak.dat' +INTO TABLE t2 +FIELDS TERMINATED BY 0x03 OPTIONALLY ENCLOSED BY '"' +(id,a,b,c); +Warnings: +Warning 1263 Column set to default value; NULL supplied to NOT NULL column 'b' at row 1 +Warning 1263 Column set to default value; NULL supplied to NOT NULL column 'b' at row 2 +# After fix, this should succeed (no session contamination) +INSERT INTO t2 VALUES (501,'abc','bb','{"abc":2}'); +SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'; +INSERT INTO t2 VALUES (502,'abc','bb','{"abc":2}'); +SELECT * FROM t2 ORDER BY id; +id a b c +1 101 {"adf": 3} +2 102 {"adf": 3} +501 abc bb {"abc": 2} +502 abc bb {"abc": 2} +DROP TABLE t2; +SET sql_mode = @old_sql_mode; diff --git a/mysql-test/t/loaddata_null_state_leak-master.opt b/mysql-test/t/loaddata_null_state_leak-master.opt new file mode 100644 index 00000000000..a98b26f7437 --- /dev/null +++ b/mysql-test/t/loaddata_null_state_leak-master.opt @@ -0,0 +1,2 @@ +--local-infile=true + diff --git a/mysql-test/t/loaddata_null_state_leak.test b/mysql-test/t/loaddata_null_state_leak.test new file mode 100644 index 00000000000..f1e36a079aa --- /dev/null +++ b/mysql-test/t/loaddata_null_state_leak.test @@ -0,0 +1,39 @@ +--source include/have_local_infile.inc + +SET @old_sql_mode = @@sql_mode; +SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'; + +CREATE TABLE t2 ( + id INT NOT NULL, + a CHAR(20) NOT NULL DEFAULT '', + b CHAR(20) NOT NULL DEFAULT '', + c JSON NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +--exec printf "1\003\"101\"\003NULL\003\"{\\\"adf\\\":3}\"\n2\003\"102\"\003NULL\003\"{\\\"adf\\\":3}\"\n" > $MYSQLTEST_VARDIR/tmp/load_data_null_state_leak.dat + +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval LOAD DATA LOCAL INFILE '$MYSQLTEST_VARDIR/tmp/load_data_null_state_leak.dat' +INTO TABLE t2 +FIELDS TERMINATED BY 0x03 OPTIONALLY ENCLOSED BY '"' +(id,a,b,c); + +--echo # After fix, this should succeed (no session contamination) +INSERT INTO t2 VALUES (501,'abc','bb','{"abc":2}'); + +connect (con1,localhost,root,,); +SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'; + +INSERT INTO t2 VALUES (502,'abc','bb','{"abc":2}'); + +connection default; +SELECT * FROM t2 ORDER BY id; + +disconnect con1; + +DROP TABLE t2; +--remove_file $MYSQLTEST_VARDIR/tmp/load_data_null_state_leak.dat + +SET sql_mode = @old_sql_mode; + diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 6d2c3c8b918..0fd3cb6a06d 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -117,6 +117,12 @@ class READ_INFO; using std::max; using std::min; +static void reset_tmp_null_flags(TABLE *table) { + for (Field **field = table->field; *field; ++field) { + (*field)->reset_tmp_null(); + } +} + class XML_TAG { public: int level; @@ -1319,6 +1325,7 @@ bool Sql_cmd_load_table::execute_inner(THD *thd, /* ok to client sent only after binlog write and engine commit */ my_ok(thd, info.stats.copied + info.stats.deleted, 0L, name); err: + reset_tmp_null_flags(table); assert(table->file->has_transactions() || !(info.stats.copied || info.stats.deleted) || thd->get_transaction()->cannot_safely_rollback(Transaction_ctx::STMT)); @@ -1595,6 +1602,7 @@ bool Sql_cmd_load_table::read_sep_field(THD *thd, COPY_INFO &info, } restore_record(table, s->default_values); + reset_tmp_null_flags(table); /* Check whether default values of the fields not specified in column list are correct or not. -- 2.43.7