Commit f603c1cc authored by unknown's avatar unknown
Browse files

BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID.

Note: bug#21726 does not directly apply to 4.1, as it doesn't have stored
procedures.  However, 4.1 had some bugs that were fixed in 5.0 by the
patch for bug#21726, and this patch is a backport of those fixes.
Namely, in 4.1 it fixes:

  - LAST_INSERT_ID(expr) didn't return value of expr (4.1 specific).

  - LAST_INSERT_ID() could return the value generated by current
    statement if the call happens after the generation, like in

      CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT);
      INSERT INTO t1 VALUES (NULL, 0), (NULL, LAST_INSERT_ID());

  - Redundant binary log LAST_INSERT_ID_EVENTs could be generated.


mysql-test/r/rpl_insert_id.result:
  Add result for bug#21726: Incorrect result with multiple invocations
  of LAST_INSERT_ID.
mysql-test/t/rpl_insert_id.test:
  Add test case for bug#21726: Incorrect result with multiple invocations
  of LAST_INSERT_ID.
sql/item_func.cc:
  Add implementation of Item_func_last_insert_id::fix_fields(), where we
  set THD::last_insert_id_used when statement calls LAST_INSERT_ID().
  In Item_func_last_insert_id::val_int(), return THD::current_insert_id
  if called like LAST_INSERT_ID(), otherwise return value of argument if
  called like LAST_INSERT_ID(expr).
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(), set THD::last_insert_id_used,
  and return THD::current_insert_id for @@LAST_INSERT_ID.
sql/sql_class.h:
  Update comments.
  Remove THD::insert_id(), as it has lost its purpose now.
sql/sql_insert.cc:
  Now it is OK to read THD::last_insert_id directly.
sql/sql_load.cc:
  Now it is OK to read THD::last_insert_id directly.
sql/sql_parse.cc:
  In mysql_execute_command(), remember THD::last_insert_id (first
  generated value of the previous statement) in THD::current_insert_id,
  which then will be returned for LAST_INSERT_ID() and @@LAST_INSERT_ID.
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:
  Now it is OK to read 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.
parent 5d46e299
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -108,6 +108,33 @@ a
1
drop table t1;
drop table t2;
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (
i INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
j INT DEFAULT 0
);
INSERT INTO t1 VALUES (NULL, -1);
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	1
3	5
4	1
5	-1
6	2
SELECT * FROM t1;
i	j
1	-1
2	1
3	5
4	1
5	-1
6	2
DROP TABLE t1;
#
# End of 4.1 tests
#
+32 −0
Original line number Diff line number Diff line
@@ -108,6 +108,38 @@ drop table t1;
drop table t2;
sync_slave_with_master;


#
# BUG#21726: Incorrect result with multiple invocations of
# LAST_INSERT_ID
#
connection master;

--disable_warnings
DROP TABLE IF EXISTS t1;
--enable_warnings

CREATE TABLE t1 (
    i INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    j INT DEFAULT 0
);

INSERT INTO t1 VALUES (NULL, -1);
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;

sync_slave_with_master;
SELECT * FROM t1;

connection master;
DROP TABLE t1;


--echo #
--echo # End of 4.1 tests
--echo #
+28 −3
Original line number Diff line number Diff line
@@ -2230,6 +2230,30 @@ longlong Item_func_release_lock::val_int()
}


bool Item_func_last_insert_id::fix_fields(THD *thd, TABLE_LIST *tables,
                                          Item **ref)
{
  DBUG_ASSERT(fixed == 0);

  if (Item_int_func::fix_fields(thd, tables, ref))
    return TRUE;

  if (arg_count == 0)
  {
    /*
      As this statement calls LAST_INSERT_ID(), set
      THD::last_insert_id_used.
    */
    thd->last_insert_id_used= TRUE;
    null_value= FALSE;
  }

  thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);

  return FALSE;
}


longlong Item_func_last_insert_id::val_int()
{
  DBUG_ASSERT(fixed == 1);
@@ -2239,12 +2263,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;
  }
  else
    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()
+1 −0
Original line number Diff line number Diff line
@@ -758,6 +758,7 @@ class Item_func_last_insert_id :public Item_int_func
  longlong val_int();
  const char *func_name() const { return "last_insert_id"; }
  void fix_length_and_dec() { if (arg_count) max_length= args[0]->max_length; }
  bool fix_fields(THD *thd, TABLE_LIST *tables, Item **ref);
};

class Item_func_benchmark :public Item_int_func
+0 −1
Original line number Diff line number Diff line
@@ -2255,7 +2255,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:
Loading