summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <kroki/tomash@moonlight.intranet>2006-10-02 14:28:23 +0400
committerunknown <kroki/tomash@moonlight.intranet>2006-10-02 14:28:23 +0400
commitbe929087ec9a5b4e161591fcebab9687472779eb (patch)
treee117108af758e936764ac8da5bef48129654e9dc
parent04bf9cc7c6f17d9c6ab14a7521c1ab1708f50993 (diff)
downloadmariadb-git-be929087ec9a5b4e161591fcebab9687472779eb.tar.gz
BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID
Non-upper-level INSERTs (the ones in the body of stored procedure, stored function, or trigger) into a table that have AUTO_INCREMENT column didn't affected the result of LAST_INSERT_ID() on this level. The problem was introduced with the fix of bug 6880, which in turn was introduced with the fix of bug 3117, where current insert_id value was remembered on the first call to LAST_INSERT_ID() (bug 3117) and was returned from that function until it was reset before the next _upper-level_ statement (bug 6880). The fix for bug#21726 brings back the behaviour of version 4.0, and implements the following: remember insert_id value at the beginning of the statement or expression (which at that point equals to the first insert_id value generated by the previous statement), and return that remembered value from LAST_INSERT_ID() or @@LAST_INSERT_ID. Thus, the value returned by LAST_INSERT_ID() is not affected by values generated by current statement, nor by LAST_INSERT_ID(expr) calls in this statement. Version 5.1 does not have this bug (it was fixed by WL 3146). mysql-test/r/rpl_insert_id.result: Add results for bug#21726: Incorrect result with multiple invocations of LAST_INSERT_ID, and bug#20339: stored procedure using LAST_INSERT_ID() does not replicate statement-based. mysql-test/t/rpl_insert_id.test: Add test cases for bug#21726: Incorrect result with multiple invocations of LAST_INSERT_ID, and bug#20339: stored procedure using LAST_INSERT_ID() does not replicate statement-based. sql/item_func.cc: Add implementation of Item_func_last_insert_id::fix_fields(), where we remember in THD::current_insert_id the first value generated during execution of the previous statement, which is returned then from Item_func_last_insert_id::val_int(). sql/item_func.h: Add declaration of Item_func_last_insert_id::fix_fields(). sql/log_event.cc: Do not set THD::last_insert_id_used on LAST_INSERT_ID_EVENT. Though we know the statement will call LAST_INSERT_ID(), it wasn't called yet. sql/set_var.cc: In sys_var_last_insert_id::value_ptr() remember in THD::current_insert_id the first value generated during execution of the previous statement, and return this value for @@LAST_INSERT_ID. sql/sql_class.cc: Reset THD::last_insert_id_used after each statement execution. sql/sql_class.h: Rather then remember current insert_id value on first invocation of THD::insert_id(), remember it in Item_func_last_insert_id::fix_fields(), sys_var_last_insert_id::value_ptr(), or mysql_execute_command(). Remove THD::insert_id(), as it lost its value now. sql/sql_insert.cc: THD::insert_id() is removed, use THD::last_insert_id directly. sql/sql_load.cc: THD::insert_id() is removed, using THD::last_insert_id directly is OK. sql/sql_parse.cc: Remember in THD::current_insert_id first generated insert id value of the previous statement in mysql_execute_command(). No need to reset THD::last_insert_id_used in mysql_reset_thd_for_next_command(), it will be reset after each statement. sql/sql_select.cc: If "IS NULL" is replaced with "= <LAST_INSERT_ID>", use right value, which is THD::current_insert_id, and also set THD::last_insert_id_used to issue binary log LAST_INSERT_ID_EVENT. sql/sql_update.cc: THD::insert_id() is removed, use THD::last_insert_id directly. tests/mysql_client_test.c: Add test case for bug#21726: Incorrect result with multiple invocations of LAST_INSERT_ID.
-rw-r--r--mysql-test/r/rpl_insert_id.result129
-rw-r--r--mysql-test/t/rpl_insert_id.test112
-rw-r--r--sql/item_func.cc35
-rw-r--r--sql/item_func.h1
-rw-r--r--sql/log_event.cc1
-rw-r--r--sql/set_var.cc13
-rw-r--r--sql/sql_class.cc16
-rw-r--r--sql/sql_class.h48
-rw-r--r--sql/sql_insert.cc8
-rw-r--r--sql/sql_load.cc12
-rw-r--r--sql/sql_parse.cc16
-rw-r--r--sql/sql_select.cc11
-rw-r--r--sql/sql_update.cc4
-rw-r--r--tests/mysql_client_test.c40
14 files changed, 405 insertions, 41 deletions
diff --git a/mysql-test/r/rpl_insert_id.result b/mysql-test/r/rpl_insert_id.result
index e425cce9508..d0360c8b9a3 100644
--- a/mysql-test/r/rpl_insert_id.result
+++ b/mysql-test/r/rpl_insert_id.result
@@ -234,6 +234,135 @@ n b
2 100
3 350
drop table t1;
+DROP PROCEDURE IF EXISTS p1;
+DROP TABLE IF EXISTS t1, t2;
+SELECT LAST_INSERT_ID(0);
+LAST_INSERT_ID(0)
+0
+CREATE TABLE t1 (
+id INT NOT NULL DEFAULT 0,
+last_id INT,
+PRIMARY KEY (id)
+);
+CREATE TABLE t2 (
+id INT NOT NULL AUTO_INCREMENT,
+last_id INT,
+PRIMARY KEY (id)
+);
+CREATE PROCEDURE p1()
+BEGIN
+INSERT INTO t2 (last_id) VALUES (LAST_INSERT_ID());
+INSERT INTO t1 (last_id) VALUES (LAST_INSERT_ID());
+END|
+CALL p1();
+SELECT * FROM t1;
+id last_id
+0 1
+SELECT * FROM t2;
+id last_id
+1 0
+SELECT * FROM t1;
+id last_id
+0 1
+SELECT * FROM t2;
+id last_id
+1 0
+DROP PROCEDURE p1;
+DROP TABLE t1, t2;
+DROP PROCEDURE IF EXISTS p1;
+DROP FUNCTION IF EXISTS f1;
+DROP FUNCTION IF EXISTS f2;
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+i INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+j INT DEFAULT 0
+);
+CREATE TABLE t2 (i INT);
+CREATE PROCEDURE p1()
+BEGIN
+INSERT INTO t1 (i) VALUES (NULL);
+INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+INSERT INTO t1 (i) VALUES (NULL), (NULL);
+INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+END |
+CREATE FUNCTION f1() RETURNS INT MODIFIES SQL DATA
+BEGIN
+INSERT INTO t1 (i) VALUES (NULL);
+INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+INSERT INTO t1 (i) VALUES (NULL), (NULL);
+INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+RETURN 0;
+END |
+CREATE FUNCTION f2() RETURNS INT NOT DETERMINISTIC
+RETURN LAST_INSERT_ID() |
+INSERT INTO t1 VALUES (NULL, -1);
+CALL p1();
+SELECT f1();
+f1()
+0
+INSERT INTO t1 VALUES (NULL, f2()), (NULL, LAST_INSERT_ID()),
+(NULL, LAST_INSERT_ID()), (NULL, f2()), (NULL, f2());
+INSERT INTO t1 VALUES (NULL, f2());
+INSERT INTO t1 VALUES (NULL, LAST_INSERT_ID()), (NULL, LAST_INSERT_ID(5)),
+(NULL, @@LAST_INSERT_ID);
+INSERT INTO t1 VALUES (NULL, 0), (NULL, LAST_INSERT_ID());
+UPDATE t1 SET j= -1 WHERE i IS NULL;
+SELECT * FROM t1;
+i j
+1 -1
+2 0
+3 0
+4 0
+5 0
+6 0
+7 0
+8 3
+9 3
+10 3
+11 3
+12 3
+13 8
+14 13
+15 5
+16 13
+17 -1
+18 14
+SELECT * FROM t2;
+i
+2
+3
+5
+6
+SELECT * FROM t1;
+i j
+1 -1
+2 0
+3 0
+4 0
+5 0
+6 0
+7 0
+8 3
+9 3
+10 3
+11 3
+12 3
+13 8
+14 13
+15 5
+16 13
+17 -1
+18 14
+SELECT * FROM t2;
+i
+2
+3
+5
+6
+DROP PROCEDURE p1;
+DROP FUNCTION f1;
+DROP FUNCTION f2;
+DROP TABLE t1, t2;
# End of 5.0 tests
diff --git a/mysql-test/t/rpl_insert_id.test b/mysql-test/t/rpl_insert_id.test
index 3c46a5b4ca1..126aad68df4 100644
--- a/mysql-test/t/rpl_insert_id.test
+++ b/mysql-test/t/rpl_insert_id.test
@@ -244,6 +244,118 @@ select * from t1 order by n;
connection master;
drop table t1;
+
+#
+# BUG#20339: stored procedure using LAST_INSERT_ID() does not
+# replicate statement-based
+#
+--disable_warnings
+DROP PROCEDURE IF EXISTS p1;
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+# Reset result of LAST_INSERT_ID().
+SELECT LAST_INSERT_ID(0);
+
+CREATE TABLE t1 (
+ id INT NOT NULL DEFAULT 0,
+ last_id INT,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE t2 (
+ id INT NOT NULL AUTO_INCREMENT,
+ last_id INT,
+ PRIMARY KEY (id)
+);
+
+delimiter |;
+CREATE PROCEDURE p1()
+BEGIN
+ INSERT INTO t2 (last_id) VALUES (LAST_INSERT_ID());
+ INSERT INTO t1 (last_id) VALUES (LAST_INSERT_ID());
+END|
+delimiter ;|
+
+CALL p1();
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+sync_slave_with_master;
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+connection master;
+
+DROP PROCEDURE p1;
+DROP TABLE t1, t2;
+
+
+#
+# BUG#21726: Incorrect result with multiple invocations of
+# LAST_INSERT_ID
+#
+--disable_warnings
+DROP PROCEDURE IF EXISTS p1;
+DROP FUNCTION IF EXISTS f1;
+DROP FUNCTION IF EXISTS f2;
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (
+ i INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ j INT DEFAULT 0
+);
+CREATE TABLE t2 (i INT);
+
+delimiter |;
+CREATE PROCEDURE p1()
+BEGIN
+ INSERT INTO t1 (i) VALUES (NULL);
+ INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+ INSERT INTO t1 (i) VALUES (NULL), (NULL);
+ INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+END |
+
+CREATE FUNCTION f1() RETURNS INT MODIFIES SQL DATA
+BEGIN
+ INSERT INTO t1 (i) VALUES (NULL);
+ INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+ INSERT INTO t1 (i) VALUES (NULL), (NULL);
+ INSERT INTO t2 (i) VALUES (LAST_INSERT_ID());
+ RETURN 0;
+END |
+
+CREATE FUNCTION f2() RETURNS INT NOT DETERMINISTIC
+ RETURN LAST_INSERT_ID() |
+delimiter ;|
+
+INSERT INTO t1 VALUES (NULL, -1);
+CALL p1();
+SELECT f1();
+INSERT INTO t1 VALUES (NULL, f2()), (NULL, LAST_INSERT_ID()),
+ (NULL, LAST_INSERT_ID()), (NULL, f2()), (NULL, f2());
+INSERT INTO t1 VALUES (NULL, f2());
+INSERT INTO t1 VALUES (NULL, LAST_INSERT_ID()), (NULL, LAST_INSERT_ID(5)),
+ (NULL, @@LAST_INSERT_ID);
+# Test replication of substitution "IS NULL" -> "= LAST_INSERT_ID".
+INSERT INTO t1 VALUES (NULL, 0), (NULL, LAST_INSERT_ID());
+UPDATE t1 SET j= -1 WHERE i IS NULL;
+
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+sync_slave_with_master;
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+connection master;
+DROP PROCEDURE p1;
+DROP FUNCTION f1;
+DROP FUNCTION f2;
+DROP TABLE t1, t2;
+
+
sync_slave_with_master;
--echo
diff --git a/sql/item_func.cc b/sql/item_func.cc
index 2e594c74031..e395a7a3af5 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -3345,6 +3345,34 @@ longlong Item_func_release_lock::val_int()
}
+bool Item_func_last_insert_id::fix_fields(THD *thd, Item **ref)
+{
+ DBUG_ASSERT(fixed == 0);
+
+ if (Item_int_func::fix_fields(thd, ref))
+ return TRUE;
+
+ if (arg_count == 0)
+ {
+ if (!thd->last_insert_id_used)
+ {
+ /*
+ As this statement calls LAST_INSERT_ID(), set
+ THD::last_insert_id_used and remember first generated insert
+ id of the previous statement in THD::current_insert_id.
+ */
+ thd->last_insert_id_used= TRUE;
+ thd->current_insert_id= thd->last_insert_id;
+ }
+ null_value= FALSE;
+ }
+
+ thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
+
+ return FALSE;
+}
+
+
longlong Item_func_last_insert_id::val_int()
{
THD *thd= current_thd;
@@ -3354,12 +3382,13 @@ longlong Item_func_last_insert_id::val_int()
longlong value= args[0]->val_int();
thd->insert_id(value);
null_value= args[0]->null_value;
- return value; // Avoid side effect of insert_id()
+ return value;
}
- thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);
- return thd->last_insert_id_used ? thd->current_insert_id : thd->insert_id();
+
+ return thd->current_insert_id;
}
+
/* This function is just used to test speed of different functions */
longlong Item_func_benchmark::val_int()
diff --git a/sql/item_func.h b/sql/item_func.h
index 177daf0311f..31adc033034 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -891,6 +891,7 @@ public:
if (arg_count)
max_length= args[0]->max_length;
}
+ bool fix_fields(THD *thd, Item **ref);
};
diff --git a/sql/log_event.cc b/sql/log_event.cc
index 219434ab218..271658d8054 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -3365,7 +3365,6 @@ int Intvar_log_event::exec_event(struct st_relay_log_info* rli)
{
switch (type) {
case LAST_INSERT_ID_EVENT:
- thd->last_insert_id_used = 1;
thd->last_insert_id = val;
break;
case INSERT_ID_EVENT:
diff --git a/sql/set_var.cc b/sql/set_var.cc
index c667e2f2bcc..d00857a2bc1 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -2571,8 +2571,17 @@ bool sys_var_last_insert_id::update(THD *thd, set_var *var)
byte *sys_var_last_insert_id::value_ptr(THD *thd, enum_var_type type,
LEX_STRING *base)
{
- thd->sys_var_tmp.long_value= (long) thd->insert_id();
- return (byte*) &thd->last_insert_id;
+ if (!thd->last_insert_id_used)
+ {
+ /*
+ As this statement reads @@LAST_INSERT_ID, set
+ THD::last_insert_id_used and remember first generated insert id
+ of the previous statement in THD::current_insert_id.
+ */
+ thd->last_insert_id_used= TRUE;
+ thd->current_insert_id= thd->last_insert_id;
+ }
+ return (byte*) &thd->current_insert_id;
}
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 093173ab949..4d47ec338c0 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -553,10 +553,24 @@ bool THD::store_globals()
}
-/* Cleanup after a query */
+/*
+ Cleanup after query.
+
+ SYNOPSIS
+ THD::cleanup_after_query()
+ DESCRIPTION
+ This function is used to reset thread data to it's default state.
+
+ NOTE
+ This function is not suitable for setting thread data to some
+ non-default values, as there is only one replication thread, so
+ different master threads may overwrite data of each other on
+ slave.
+*/
void THD::cleanup_after_query()
{
+ last_insert_id_used= FALSE;
if (clear_next_insert_id)
{
clear_next_insert_id= 0;
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 039c133e885..ccc7a661446 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1252,17 +1252,29 @@ public:
ulonglong next_insert_id;
/* Remember last next_insert_id to reset it if something went wrong */
ulonglong prev_insert_id;
+
/*
- The insert_id used for the last statement or set by SET LAST_INSERT_ID=#
- or SELECT LAST_INSERT_ID(#). Used for binary log and returned by
- LAST_INSERT_ID()
+ At the beginning of the statement last_insert_id holds the first
+ generated value of the previous statement. During statement
+ execution it is updated to the value just generated, but then
+ restored to the value that was generated first, so for the next
+ statement it will again be "the first generated value of the
+ previous statement".
+
+ It may also be set with "LAST_INSERT_ID(expr)" or
+ "@@LAST_INSERT_ID= expr", but the effect of such setting will be
+ seen only in the next statement.
*/
ulonglong last_insert_id;
+
/*
- Set to the first value that LAST_INSERT_ID() returned for the last
- statement. When this is set, last_insert_id_used is set to true.
+ current_insert_id remembers the first generated value of the
+ previous statement, and does not change during statement
+ execution. Its value returned from LAST_INSERT_ID() and
+ @@LAST_INSERT_ID.
*/
ulonglong current_insert_id;
+
ulonglong limit_found_rows;
ulonglong options; /* Bitmap of states */
longlong row_count_func; /* For the ROW_COUNT() function */
@@ -1325,7 +1337,22 @@ public:
bool last_cuted_field;
bool no_errors, password, is_fatal_error;
bool query_start_used, rand_used, time_zone_used;
- bool last_insert_id_used,insert_id_used, clear_next_insert_id;
+
+ /*
+ last_insert_id_used is set when current statement calls
+ LAST_INSERT_ID() or reads @@LAST_INSERT_ID, so that binary log
+ LAST_INSERT_ID_EVENT be generated.
+ */
+ bool last_insert_id_used;
+
+ /*
+ insert_id_used is set when current statement updates
+ THD::last_insert_id, so that binary log INSERT_ID_EVENT be
+ generated.
+ */
+ bool insert_id_used;
+
+ bool clear_next_insert_id;
/* for IS NULL => = last_insert_id() fix in remove_eq_conds() */
bool substitute_null_with_insert_id;
bool in_lock_tables;
@@ -1461,15 +1488,6 @@ public:
insert_id_used=1;
substitute_null_with_insert_id= TRUE;
}
- inline ulonglong insert_id(void)
- {
- if (!last_insert_id_used)
- {
- last_insert_id_used=1;
- current_insert_id=last_insert_id;
- }
- return last_insert_id;
- }
inline ulonglong found_rows(void)
{
return limit_found_rows;
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 3db6eae8695..43d646404f1 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -590,10 +590,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
#endif
error=write_record(thd, table ,&info);
/*
- If auto_increment values are used, save the first one
- for LAST_INSERT_ID() and for the update log.
- We can't use insert_id() as we don't want to touch the
- last_insert_id_used flag.
+ If auto_increment values are used, save the first one for
+ LAST_INSERT_ID() and for the update log.
*/
if (! id && thd->insert_id_used)
{ // Get auto increment value
@@ -2447,7 +2445,7 @@ bool select_insert::send_data(List<Item> &values)
*/
table->next_number_field->reset();
if (!last_insert_id && thd->insert_id_used)
- last_insert_id= thd->insert_id();
+ last_insert_id= thd->last_insert_id;
}
}
DBUG_RETURN(error);
diff --git a/sql/sql_load.cc b/sql/sql_load.cc
index d5faf6ee7e9..bdc08b7bd2d 100644
--- a/sql/sql_load.cc
+++ b/sql/sql_load.cc
@@ -616,10 +616,8 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
thd->no_trans_update= no_trans_update;
/*
- If auto_increment values are used, save the first one
- for LAST_INSERT_ID() and for the binary/update log.
- We can't use insert_id() as we don't want to touch the
- last_insert_id_used flag.
+ If auto_increment values are used, save the first one for
+ LAST_INSERT_ID() and for the binary/update log.
*/
if (!id && thd->insert_id_used)
id= thd->last_insert_id;
@@ -784,10 +782,8 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
if (write_record(thd, table, &info))
DBUG_RETURN(1);
/*
- If auto_increment values are used, save the first one
- for LAST_INSERT_ID() and for the binary/update log.
- We can't use insert_id() as we don't want to touch the
- last_insert_id_used flag.
+ If auto_increment values are used, save the first one for
+ LAST_INSERT_ID() and for the binary/update log.
*/
if (!id && thd->insert_id_used)
id= thd->last_insert_id;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 18d048df393..1b69e266442 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -2422,6 +2422,20 @@ mysql_execute_command(THD *thd)
thd->net.no_send_error= 0;
/*
+ Remember first generated insert id value of the previous
+ statement. We remember it here at the beginning of the statement,
+ and also in Item_func_last_insert_id::fix_fields() and
+ sys_var_last_insert_id::value_ptr(). Last two places are required
+ because LAST_INSERT_ID() and @@LAST_INSERT_ID may also be used in
+ expression that is not executed with mysql_execute_command().
+
+ And we remember it here because some statements read
+ @@LAST_INSERT_ID indirectly, like "SELECT * FROM t1 WHERE id IS
+ NULL", that may replace "id IS NULL" with "id = <LAST_INSERT_ID>".
+ */
+ thd->current_insert_id= thd->last_insert_id;
+
+ /*
In many cases first table of main SELECT_LEX have special meaning =>
check that it is first table in global list and relink it first in
queries_tables list if it is necessary (we need such relinking only
@@ -5636,7 +5650,7 @@ void mysql_reset_thd_for_next_command(THD *thd)
DBUG_ENTER("mysql_reset_thd_for_next_command");
thd->free_list= 0;
thd->select_number= 1;
- thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0;
+ thd->query_start_used= thd->insert_id_used=0;
thd->is_fatal_error= thd->time_zone_used= 0;
thd->server_status&= ~ (SERVER_MORE_RESULTS_EXISTS |
SERVER_QUERY_NO_INDEX_USED |
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index b328d9162d5..87cbdef0522 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -8142,7 +8142,7 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value)
Field *field=((Item_field*) args[0])->field;
if (field->flags & AUTO_INCREMENT_FLAG && !field->table->maybe_null &&
(thd->options & OPTION_AUTO_IS_NULL) &&
- thd->insert_id() && thd->substitute_null_with_insert_id)
+ thd->current_insert_id && thd->substitute_null_with_insert_id)
{
#ifdef HAVE_QUERY_CACHE
query_cache_abort(&thd->net);
@@ -8150,9 +8150,16 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value)
COND *new_cond;
if ((new_cond= new Item_func_eq(args[0],
new Item_int("last_insert_id()",
- thd->insert_id(),
+ thd->current_insert_id,
21))))
{
+ /*
+ Set THD::last_insert_id_used manually, as this statement
+ uses LAST_INSERT_ID() in a sense, and should issue
+ LAST_INSERT_ID_EVENT.
+ */
+ thd->last_insert_id_used= TRUE;
+
cond=new_cond;
/*
Item_func_eq can't be fixed after creation so we do not check
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index a5a767b6ebc..50914fd3408 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -568,7 +568,7 @@ int mysql_update(THD *thd,
thd->row_count_func=
(thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated;
send_ok(thd, (ulong) thd->row_count_func,
- thd->insert_id_used ? thd->insert_id() : 0L,buff);
+ thd->insert_id_used ? thd->last_insert_id : 0L,buff);
DBUG_PRINT("info",("%d records updated",updated));
}
thd->count_cuted_fields= CHECK_FIELD_IGNORE; /* calc cuted fields */
@@ -1567,6 +1567,6 @@ bool multi_update::send_eof()
thd->row_count_func=
(thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated;
::send_ok(thd, (ulong) thd->row_count_func,
- thd->insert_id_used ? thd->insert_id() : 0L,buff);
+ thd->insert_id_used ? thd->last_insert_id : 0L,buff);
return FALSE;
}
diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c
index 8a444590301..d3f39296445 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -15237,6 +15237,43 @@ static void test_bug21206()
DBUG_VOID_RETURN;
}
+/*
+ Bug#21726: Incorrect result with multiple invocations of
+ LAST_INSERT_ID
+
+ Test that client gets updated value of insert_id on UPDATE that uses
+ LAST_INSERT_ID(expr).
+*/
+static void test_bug21726()
+{
+ const char *create_table[]=
+ {
+ "DROP TABLE IF EXISTS t1",
+ "CREATE TABLE t1 (i INT)",
+ "INSERT INTO t1 VALUES (1)",
+ };
+ const char *update_query= "UPDATE t1 SET i= LAST_INSERT_ID(i + 1)";
+ int rc;
+ my_ulonglong insert_id;
+
+ DBUG_ENTER("test_bug21726");
+ myheader("test_bug21726");
+
+ fill_tables(create_table, sizeof(create_table) / sizeof(*create_table));
+
+ rc= mysql_query(mysql, update_query);
+ myquery(rc);
+ insert_id= mysql_insert_id(mysql);
+ DIE_UNLESS(insert_id == 2);
+
+ rc= mysql_query(mysql, update_query);
+ myquery(rc);
+ insert_id= mysql_insert_id(mysql);
+ DIE_UNLESS(insert_id == 3);
+
+ DBUG_VOID_RETURN;
+}
+
/*
Read and parse arguments and MySQL options from my.cnf
@@ -15511,7 +15548,8 @@ static struct my_tests_st my_tests[]= {
{ "test_bug17667", test_bug17667 },
{ "test_bug19671", test_bug19671 },
{ "test_bug15752", test_bug15752 },
- { "test_bug21206", test_bug21206},
+ { "test_bug21206", test_bug21206 },
+ { "test_bug21726", test_bug21726 },
{ 0, 0 }
};