List:Commits« Previous MessageNext Message »
From:Sergei Golubchik Date:January 10 2008 4:20pm
Subject:Re: bk commit into 5.1 tree (kostja:1.2653) BUG#12713
View as plain text  
Hi!

On Dec 29, konstantin@stripped wrote:
> ChangeSet@stripped, 2007-12-29 22:18:31+03:00, kostja@bodhi.(none) +27 -0
>   A fix and a test case for Bug#12713 "Error in a stored function called from 
>   a SELECT doesn't cause ROLLBACK of statem"
>   The latest changeset for a code review. 

See my questions below.
Thanks for solving this very complicated problem!

> diff -Nrup a/mysql-test/include/commit.inc b/mysql-test/include/commit.inc
> --- /dev/null	Wed Dec 31 16:00:00 196900
> +++ b/mysql-test/include/commit.inc	2007-12-29 22:18:25 +03:00
> @@ -0,0 +1,750 @@
>  .....
> +
> +insert into t2 (a) values (1001);
> +--error ER_DUP_ENTRY
> +insert into t1 (a) values (f2(1));
> +select * from t2;
> +rollback;
> +select * from t2;
> +commit;

why a commit ?

> +
>   .....
> +
> +--echo # We need at least one procedure to make sure the WHERE clause is
> +--echo # evaluated
> +create procedure dummy() begin end;

you probably need 'drop procedure if exists dummy' in the beginning

> +insert into t2 (a) values (1020);
> +--error ER_DUP_ENTRY
> +show procedure status where (f2(20) = 20);
> +select * from t2;
> +rollback;
> +select * from t2;
> +commit;
> +drop procedure dummy;
>  .....
> +--echo # 4. Read-write statement: UPDATE, update 0 rows, 1 row matches WHERE 
> +--echo #
> +update t1 set a=2;
> +call p_verify_status_increment(2, 2, 2, 2);
> +commit;
> +call p_verify_status_increment(2, 2, 2, 2);

why this one isn't optimized ?

> +
> +--echo # 5. Read-write statement: UPDATE, update 0 rows, 0 rows match WHERE 
> +--echo #
> +update t1 set a=3 where a=1;
> +call p_verify_status_increment(2, 0, 1, 0);
> +commit;
> +call p_verify_status_increment(2, 0, 1, 0);

why different results for mixed/row ?

>  .....
> +--echo # 8. Read-write statement: unqualified DELETE
> +--echo #
> +delete from t1;
> +call p_verify_status_increment(2, 2, 1, 0);
> +commit;
> +call p_verify_status_increment(2, 2, 1, 0);

oh, row/mixed are even optimized differently. why ?

> +--echo #
> +--echo # Replace the non-transactional table with a temporary
> +--echo # transactional table. Demonstrate that a change to a temporary
> +--echo # transactional table does not provoke 2-phase commit, although

good idea!

> +--echo # does trigger a commit and a binlog write (in statement mode).
> +--echo #
>  .....
> diff -Nrup a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result
> b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result
> --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result	2007-10-14
> 00:12:46 +04:00
> +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result	2007-12-29
> 22:18:20 +03:00
> @@ -349,11 +348,10 @@ master-bin.000001	#	Query	#	#	use `test`
>  master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 values (8,8)
>  master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 values (9,9)
>  master-bin.000001	#	Query	#	#	use `test`; TRUNCATE table t2
> -master-bin.000001	#	Xid	#	#	COMMIT /* XID */
>  master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 values (10,10)
>  master-bin.000001	#	Query	#	#	use `test`; BEGIN
>  master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t2 values (100,100)
> -master-bin.000001	#	Xid	#	#	COMMIT /* XID */
> +master-bin.000001	#	Query	#	#	use `test`; COMMIT
>  master-bin.000001	#	Query	#	#	use `test`; DROP TABLE t1,t2

ok, I understand why "COMMIT /* XID */" can be changed to "use `test`; COMMIT".
but how it can simlpy dissapear ?

>  reset master;
>  create table t1 (a int) engine=innodb;
> diff -Nrup a/mysql-test/suite/rpl_ndb/t/disabled.def
> b/mysql-test/suite/rpl_ndb/t/disabled.def
> --- a/mysql-test/suite/rpl_ndb/t/disabled.def	2007-12-11 13:51:00 +03:00
> +++ b/mysql-test/suite/rpl_ndb/t/disabled.def	2007-12-29 22:18:20 +03:00
> @@ -21,3 +21,5 @@ rpl_ndb_mix_innodb       : Bug #32720   
>  # the below testcase have been reworked to avoid the bug, test contains comment,
> keep bug open
>  
>  #rpl_ndb_dd_advance	 : Bug#25913 rpl_ndb_dd_advance fails randomly
> +rpl_ndb_circular         : DBUG ASSERT fails
> +rpl_ndb_circular_simplex : DBUG ASSERT fails

what DBUG_ASSERT ?
are you going to do anything about it ?

> diff -Nrup a/sql/handler.cc b/sql/handler.cc
> --- a/sql/handler.cc	2007-12-20 21:16:51 +03:00
> +++ b/sql/handler.cc	2007-12-29 22:18:20 +03:00
> @@ -577,6 +577,218 @@ void ha_close_connection(THD* thd)
>  
>  /* ========================================================================
>   ======================= TRANSACTIONS ===================================*/
> +/**
> +  Transaction handling in the server
> +  ==================================
> +
> +  In each client connection, MySQL maintains two transactions:
> +  - a statement transaction,

I'd explain here what a "statement transaction" is, because the term is
non-standard, it came from BDB - it was the first transactional engine,
and it used nested transactions for every statement. InnoDB uses
savepoints, and Heikki objects to the term "statement commit", for
example.  And he's right, in a sense, as it's not a commit strictly
speaking, it doesn't make any persistent changes - it simply marks end
of statement.  So, I would define here what is "statement transaction",
and "statement commit" in convensional language, without using BDB-isms.

> +  - a standard, also called "global", transaction.

global transaction is most often used in the context of distributed
transaction processing, and it means a distributed transaction managed
by the transaction coordinator, as opposite to the local transaction in
every particular resource manager.

Best is to avoid saying "global transaction" when you simply
mean a normal, local to one MySQL instance, transaction.

> +
> +  The statement transaction is started for each statement that
> +  accesses transactional tables or uses the binary log.
> +  If the statement succeeds, the statement transaction
> +  is committed. If the statement fails, the transaction
> +  is rolled back. Commits of statement transactions are not
> +  durable -- each such transaction is nested in the global
> +  transaction, and if the global transaction is rolled back, the
> +  effects of all enclosed statement transactions are undone as well.
> +  Technically, a statement transaction can be viewed as a savepoint
> +  maintained automatically in order to make effects of one
> +  statement atomic.
> +
> +  The global transaction is started by the user and is ended
> +  usually upon a user request as well. The global transaction
> +  encloses transactions of all statements issued between
> +  its beginning and its end.
> +  In autocommit mode, the global transaction is equivalent
> +  to the statement transaction.
> +
> +  Since MySQL supports PSEA (pluggable storage engine
> +  architecture), more than one transactional engine can be
> +  active at a time. Hence transactions, from the server
> +  point of view, are always distributed. In particular,
> +  transactional state is maintained independently for each
> +  engine. In order to commit a transaction two phase
> +  commit protocol is employed.
> +
> +  Finally, not all statements are executed in context
> +  of a transaction. Administrative and status information
> +  statements do not modify engine data, and thus do not start
> +  a statement transaction and have no effect on the global
> +  transaction. Examples of such statements are SHOW STATUS
> +  and RESET SLAVE.
> +  DDL statements, historically, were not transactional, and
> +  therefore a transaction is [almost] never started for a DDL
> +  statement.
> +  At last, SQL statements that work with non-transactional
> +  engines also have no effect on the transaction state of the
> +  connection. Even though they are written to the binary log,
> +  and the binary log is, overall, transactional, the writes
> +  are done in "write-through" mode, directly to the binlog
> +  file, followed with a OS cache sync, in other words,
> +  bypassing the binlog undo log (translog).
> +
> +  Data layout
> +  -----------
> +
> +  The server stores its transaction-related data in
> +  thd->transaction. This structure has two members of type
> +  THD_TRANS. These members correspond to the statement and
> +  global transactions respectively:
> +
> +  - thd->transaction.stmt contains a list of engines
> +  that participate in the given statement
> +  - thd->transaction.all contains a list of engines that
> +  participated in any of the statement transactions started
> +  within the context of the global transaction.
> +  Each element of the list contains a pointer to the storage
> +  engine, engine-specific transactional data, and engine-specific
> +  transaction flags.
> +
> +  In autocommit mode thd->transaction.all is not
> +  maintained. Instead data of thd->transaction.stmt is

"not maintained" or "is empty" ?

> +  used to commit/rollback the global transaction.
> +
> +  The list of registered engines has a few important properties:
> +  - no engine is registered in the list twice
> +  - the list has a reverse temporal order -- engines are
> +  always added to the beginning of the list.
> +
> +  Transaction life cycle
> +  ----------------------
> +
> +  When a new connection is established, thd->transaction
> +  members are initialized to an empty state.
> +  If a statement uses any tables, all affected engines
> +  are registered in the statement engine list. In
> +  non-autocommit mode, the same engines are registered in
> +  the global transaction list.
> +  At the end of the statement, the server issues a commit
> +  or a roll back for all engines in the statement list.
> +  At this point transaction flags of an engine, if any, are
> +  propagated from the statement list to the global transaction
> +  list.
> +  When commit/rollback is finished, the statement list is
> +  cleared. It will be filled in again by the next statement,
> +  and emptied again at the next statement's end.
> +
> +  The global transaction is committed in a similar way
> +  (by going over all engines in thd->transaction.all list)
> +  but at different times:
> +  - if COMMIT SQL statement is issued by the user
> +  - implicitly, by the server, at the beginning of a DDL statement
> +  or SET AUTOCOMMIT={0|1} statement.
> +
> +  It can be rolled back as well:
> +  - if the user has requested so, by issuing ROLLBACK SQL
> +  statement
> +  - if one of the storage engines requested a rollback
> +  by setting thd->transaction_rollback_request. In this case

add "(for example in case of internal deadlock)" or something else you
may find appropriate to explain why we need
transaction_rollback_request and how it can ever happen.

> +  the rollback is accompanied by an error sent to the user.
> +
> +  When committing a statement or a global transaction, the server
> +  either uses the two-phase commit protocol, or commits a transaction
> +  in each engine independently. The two-phase commit protocol
> +  is used only if:
> +  - all participating engines support two-phase commit (provide
> +    handlerton::prepare PSEA API call) and
> +  - transactions in at least two engines modify data (i.e. are
> +  not read-only).
> +
> +  When a connection is closed, the current global transaction, if
> +  any, is rolled back.
> +
> +  Roles and responsibilities
> +  --------------------------
> +
> +  The server has no way to know that a transaction has been started
> +  in the engine unless the engine says so. Thus, in order to be
> +  a part of a transaction, the engine must "register" itself.
> +  This is done by invoking trans_register_ha() server call.
> +  Normally the engine registers itself whenever handler::store_lock()

external_lock, not store_lock

> +  is called. trans_register_ha() can be invoked many times: if
> +  an engine is already registered, the call does nothing.
> +  In case autocommit is not set, the engine must register itself
> +  twice -- both in the statement lits and in the global list.
> +  In which list to register is a parameter of trans_register_ha().
> +
> +  Note, that although the registration interface in itself is
> +  fairly clear, the current usage practice often leads to undesired
> +  effects: e.g. since a call to trans_register_ha in most engines
> +  is embedded into implementation of handler::store_lock(), some

external_lock here and below

> +  DDL statements start a transaction (at least from the server
> +  point of view) even though they are not expected to. E.g.
> +  CREATE TABLE does not start a transaction, since
> +  handler::store_lock() is never called during CREATE TABLE. But
> +  CREATE TABLE ... SELECT does, since handler::store_lock() is
> +  called for the table that is being selected from. This has no
> +  practical effects currently, but must be kept in mind
> +  nevertheless.
> +
> +  Once an engine is registered, the server will do the rest
> +  of the work.
> +
.....
> @@ -722,12 +1006,25 @@ int ha_commit_trans(THD *thd, bool all)
>      if (is_real_trans)                          /* not a statement commit */
>        thd->stmt_map.close_transient_cursors();
>  
> -    if (!trans->no_2pc && trans->nht > 1)
> +    must_2pc= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);

do you need a call to ha_check_and_coalesce_trx_read_only() here ?
can you check and coalesce in the for() loop below
that does ht->prepare() ?

> +
> +    if (!trans->no_2pc && must_2pc)
>      {
> -      for (; *ht && !error; ht++)
> +      for (; ha_info && !error; ha_info= ha_info->next())
>        {
>          int err;
> -        if ((err= (*(*ht)->prepare)(*ht, thd, all)))
> +        handlerton *ht= ha_info->ht();
> +        /*
> +          Call two-phase commit even if this particular
> +          transaction is read-only, just in case. It should not
> +          produce any overhead, and normally should be a no-op,
> +          but gives the engine a chance to abort the distributed
> +          commit.

why it could be useful (I mean "chance to abort ...") ?

> +
> +          Sic: we know that prepare() is not NULL since otherwise
> +          trans->no_2pc would have been set.
> +        */
> +        if ((err= ht->prepare(ht, thd, all)))
>          {
>            my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
>            error= 1;
> @@ -805,8 +1104,17 @@ int ha_rollback_trans(THD *thd, bool all
>  {
>    int error=0;
>    THD_TRANS *trans=all ? &thd->transaction.all :
> &thd->transaction.stmt;
> -  bool is_real_trans=all || thd->transaction.all.nht == 0;
> +  Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
> +  bool is_real_trans=all || thd->transaction.all.ha_list == 0;
>    DBUG_ENTER("ha_rollback_trans");
> +
> +  /*
> +    We must not commit a global transaction if statement

s/commit/rollback/

> +    transaction is pending.
> +  */
> +  DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
> +              trans == &thd->transaction.stmt);
> +
>    if (thd->in_sub_stmt)
>    {
>      /*
> @@ -2500,6 +2824,33 @@ int handler::ha_check(THD *thd, HA_CHECK
>    return update_frm_version(table);
>  }
>  
> +/**
> +  A helper function to mark a transaction read-write,
> +  if it is started.
> +*/
> +
> +inline
> +void
> +handler::mark_trx_read_write()
> +{
> +  Ha_trx_info *ha_info= &ha_thd()->ha_data[ht->slot].ha_info[0];
> +  /*
> +    When a storage engine method is called, the transaction must
> +    have been started, unless it's a DDL call, for which the
> +    storage engine starts the transaction internally, and commits
> +    it internally, without registering in the ha_list.
> +    Unfortunately here we can't know know for sure if the engine
> +    has registered the transaction or not, so we must check.
> +  */
> +
> +  if (ha_info->is_started())
> +  {
> +    DBUG_ASSERT(has_transactions());
> +    if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)

when can table_share be NULL ?

> +      ha_info->set_trx_read_write();
> +  }
> +}
> +
>  
>  /**
>    Repair table: public interface.
> diff -Nrup a/sql/handler.h b/sql/handler.h
> --- a/sql/handler.h	2007-12-20 21:16:51 +03:00
> +++ b/sql/handler.h	2007-12-29 22:18:20 +03:00
> @@ -758,7 +758,106 @@ typedef struct st_thd_trans
>      saved value.
>    */
>    bool modified_non_trans_table;
> -} THD_TRANS;
> +
> +  void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; }
> +};
> +
> +
> +/**
> +  Either statement- or global- transaction- related
> +  thread-specific storage engine data.
> +
> +  If a storage engine participates in a statement/transaction,
> +  an instance of this class is present in
> +  thd->transaction.{stmt|all}.ha_list. The addition to
> +  {stmt|all}.ha_list is made by trans_register_ha().
> +
> +  When it's time to commit or rollback, each element of ha_list
> +  is used to access storage engine's prepare()/commit()/rollback()
> +  methods, and also to evaluate if a full two phase commit is
> +  necessary.
> +
> +  @sa General description of transaction handling in handler.cc.
> +*/
> +
> +class Ha_trx_info
> +{
> +public:
> +  /** Register this storage engine in the given transaction context. */
> +  void register_ha(THD_TRANS *trans, handlerton *ht_arg)
> +  {
> +    DBUG_ASSERT(m_flags == 0);
> +    DBUG_ASSERT(m_ht == NULL);
> +    DBUG_ASSERT(m_next == NULL);
> +
> +    m_ht= ht_arg;
> +    m_flags= (int) TRX_READ_ONLY; /* Assume read-only at start. */
> +
> +    m_next= trans->ha_list;
> +    trans->ha_list= this;
> +  }
> +
> +  /** Clear, prepare for reuse. */
> +  void reset()
> +  {
> +    m_next= NULL;
> +    m_ht= NULL;
> +    m_flags= 0;
> +  }
> +
> +  Ha_trx_info() { reset(); }
> +
> +  void set_trx_read_write()
> +  {
> +    DBUG_ASSERT(is_started());
> +    m_flags|= (int) TRX_READ_WRITE;
> +  }
> +  bool is_trx_read_write() const
> +  {
> +    DBUG_ASSERT(is_started());
> +    return m_flags & (int) TRX_READ_WRITE;
> +  }
> +  bool is_started() const { return m_ht != NULL; }
> +  /** Mark this transaction read-write if the argument is read-write. */
> +  void coalesce_trx_with(const Ha_trx_info *stmt_trx)
> +  {
> +    /*
> +      Must be called only after the transaction has been started.
> +      Can be called twice, e.g. when we have two read-write

better (less confusing): twice -> many times, two -> many

> +      statements in a global transaction.
> +    */
> +    DBUG_ASSERT(is_started());
> +    if (stmt_trx->is_trx_read_write())
> +      set_trx_read_write();
> +  }
> +  Ha_trx_info *next() const
> +  {
> +    DBUG_ASSERT(is_started());
> +    return m_next;
> +  }
> +  handlerton *ht() const
> +  {
> +    DBUG_ASSERT(is_started());
> +    return m_ht;
> +  }
> +private:
> +  enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 };
> +  /** Auxiliary, used for ha_list management */
> +  Ha_trx_info *m_next;
> +  /**
> +    Although a given Ha_trx_info instance is currently always used
> +    for the same storage engine, 'ht' is not-NULL only when the
> +    corresponding storage is a part of a transaction.
> +  */
> +  handlerton *m_ht;
> +  /**
> +    Transaction flags related to this engine.
> +    Not-null only if this instance is a part of transaction.
> +    May assume a combination of enum values above.
> +  */
> +  uchar       m_flags;
> +};
> +
>  
>  enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
>  			 ISO_REPEATABLE_READ, ISO_SERIALIZABLE};
> @@ -1640,8 +1739,15 @@ protected:
>      provide useful functionality.
>    */
>    virtual int rename_table(const char *from, const char *to);
> +  /**
> +    Delete a table in the engine. Called for temporary tables, intermediate
> +    tables of CREATE TABLE ... SELECT.

do you mean it's not called for normal tables ?

> +  */
>    virtual int delete_table(const char *name);
>  private:
> +  /* Private helpers */
> +  inline void mark_trx_read_write();
> +private:

why private: twice ?

>    /*
>      Low-level primitives for storage engines.  These should be
>      overridden by the storage engine class. To call these methods, use
> diff -Nrup a/sql/log.cc b/sql/log.cc
> --- a/sql/log.cc	2007-12-13 23:58:53 +03:00
> +++ b/sql/log.cc	2007-12-29 22:18:21 +03:00
> @@ -3321,6 +3321,16 @@ THD::binlog_start_trans_and_stmt()
>      if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
>        trans_register_ha(this, TRUE, binlog_hton);
>      trans_register_ha(this, FALSE, binlog_hton);
> +    /*
> +      Mark statement transaction as read/write. We never start
> +      a binary log transaction and keep it read-only,
> +      therefore it's best to mark the transaction read/write just
> +      at the same time we start it.

why it's best ? shouldn't it be marked read-write automatically
on the first ha_write/delete/update_row ?

I agree that it will be marked read-write sooner or later, and
that forcing read-write here doesn't hurt. But why it's necessary ?

> +      Not necessary to mark the global transaction read/write
> +      since the statement-level flag will be propagated automatically
> +      inside ha_commit_trans.
> +    */
> +    ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
>    }
>    DBUG_VOID_RETURN;
>  }
> diff -Nrup a/sql/rpl_injector.cc b/sql/rpl_injector.cc
> --- a/sql/rpl_injector.cc	2007-04-12 18:13:46 +04:00
> +++ b/sql/rpl_injector.cc	2007-12-29 22:18:23 +03:00
> @@ -61,7 +61,9 @@ injector::transaction::~transaction()
>  int injector::transaction::commit()
>  {
>     DBUG_ENTER("injector::transaction::commit()");
> +   printf("\n\n\n\n\nHERE WE GO\n\n\n\n");

:)

>     m_thd->binlog_flush_pending_rows_event(true);
> +   ha_autocommit_or_rollback(m_thd, 0);

ok, you've added it, I guess, to ensure that a statement
transaction is committed before main transaction.

but why did you need it ? every insert/update/delete ends
with ha_autocommit_or_rollback(), and a normal statements don't
need an extra ha_autocommit_or_rollback() right before end_trans().
do you mean that in rbr this invariant broken ?

>     end_trans(m_thd, COMMIT);
>     DBUG_RETURN(0);
>  }
> diff -Nrup a/sql/sp.cc b/sql/sp.cc
> --- a/sql/sp.cc	2007-10-30 20:08:11 +03:00
> +++ b/sql/sp.cc	2007-12-29 22:18:23 +03:00
> @@ -669,6 +669,13 @@ sp_returns_type(THD *thd, String &result
>                (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION).
>    @param sp   Stored routine object to store.
>  
> +  @note Opens and closes the thread tables. Therefore assumes
> +  that there are no locked tables in this thread on invokation.
> +  Unlike some other DDL statements, does close the tables
> +  in the end, since the caller normally issues the implicit
> +  grant to the definer of the created routine, which leads
> +  to open/modify/close of mysql.procs_priv table.
> +

Why does it need to close ? Shouldn't close_thread_tables() that is
called after mysql_parse() be enough ?

>    @return Error code. SP_OK is returned on success. Other SP_ constants are
>    used to indicate about errors.
>  */
> diff -Nrup a/sql/sql_acl.cc b/sql/sql_acl.cc
> --- a/sql/sql_acl.cc	2007-12-12 18:36:03 +03:00
> +++ b/sql/sql_acl.cc	2007-12-29 22:18:23 +03:00
> @@ -3862,7 +3862,7 @@ bool check_grant(THD *thd, ulong want_ac
>      of other queries). For simple queries first_not_own_table is 0.
>    */
>    for (i= 0, table= tables;
> -       table != first_not_own_table && i < number;
> +       table && table != first_not_own_table && i < number;

can that happen (table==0, I mean) ?
shouldn't "i < number" take care of that ?

>         table= table->next_global, i++)
>    {
>      /* Remove SHOW_VIEW_ACL, because it will be checked during making view */
> diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
> --- a/sql/sql_base.cc	2007-12-20 21:16:52 +03:00
> +++ b/sql/sql_base.cc	2007-12-29 22:18:23 +03:00
> @@ -1324,29 +1324,45 @@ void close_thread_tables(THD *thd)
>      Mark all temporary tables used by this statement as free for reuse.
>    */
>    mark_temp_tables_as_free_for_reuse(thd);
> -
> -  if (thd->locked_tables || prelocked_mode)
> -  {
>      /*
>        Let us commit transaction for statement. Since in 5.0 we only have
>        one statement transaction and don't allow several nested statement
>        transactions this call will do nothing if we are inside of stored
>        function or trigger (i.e. statement transaction is already active and
>        does not belong to statement for which we do close_thread_tables()).
>        TODO: This should be fixed in later releases.
>      */
> -    ha_commit_stmt(thd);
> +  if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
> +  {
> +    thd->main_da.can_overwrite_status= TRUE;
> +    ha_autocommit_or_rollback(thd, thd->is_error());
> +    thd->main_da.can_overwrite_status= FALSE;
>  
> +    /*
> +      Reset transaction state, but only if we're not inside a
> +      sub-statement of a prelocked statement.
> +    */

how can we get here if we are inside a sub-statement of a prelocked
statement ?

> +    if (! prelocked_mode || thd->lex->requires_prelocking())
> +      thd->transaction.stmt.reset();
> +  }
> +
> +  if (thd->locked_tables || prelocked_mode)
> +  {
> +
>      /* Ensure we are calling ha_reset() for all used tables */
>      mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
>  
> -    /* We are under simple LOCK TABLES so should not do anything else. */
> +    /*
> +      We are under simple LOCK TABLES or we're inside a sub-statement
> +      of a prelocked statement, so should not do anything else.
> +    */
>      if (!prelocked_mode || !thd->lex->requires_prelocking())
>        DBUG_VOID_RETURN;
>  
>      /*
> -      We are in prelocked mode, so we have to leave it now with doing
> -      implicit UNLOCK TABLES if need.
> +      We are in the top-level statement of a prelocked statement,
> +      so we have to leave the prelocked mode now with doing implicit
> +      UNLOCK TABLES if needed.
>      */
>      DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED"));
>      thd->prelocked_mode= NON_PRELOCKED;
> diff -Nrup a/sql/sql_delete.cc b/sql/sql_delete.cc
> --- a/sql/sql_delete.cc	2007-12-20 21:16:52 +03:00
> +++ b/sql/sql_delete.cc	2007-12-29 22:18:24 +03:00
> @@ -380,17 +388,6 @@ cleanup:
>    }
>    DBUG_ASSERT(transactional_table || !deleted ||
> thd->transaction.stmt.modified_non_trans_table);
>    free_underlaid_joins(thd, select_lex);
> -  if (transactional_table)
> -  {
> -    if (ha_autocommit_or_rollback(thd,error >= 0))
> -      error=1;
> -  }
> -
> -  if (thd->lock)
> -  {
> -    mysql_unlock_tables(thd, thd->lock);
> -    thd->lock=0;
> -  }

Hmm, if here (and below, and for insert/update/load) you don't call
ha_autocommit_or_rollback, then how can you ensure that
commit will succeed before sending an ok packet ?

>    if (error < 0 || (thd->lex->ignore && !thd->is_fatal_error))
>    {
>      thd->row_count_func= deleted;
 
Regards / Mit vielen Grüssen,
Sergei

-- 
   __  ___     ___ ____  __
  /  |/  /_ __/ __/ __ \/ /   Sergei Golubchik <serg@stripped>
 / /|_/ / // /\ \/ /_/ / /__  Principal Software Developer
/_/  /_/\_, /___/\___\_\___/  MySQL GmbH, Dachauer Str. 37, D-80335 München
       <___/                  Geschäftsführer: Kaj Arnö - HRB
München 162140
Thread
bk commit into 5.1 tree (kostja:1.2653) BUG#12713konstantin29 Dec
  • Re: bk commit into 5.1 tree (kostja:1.2653) BUG#12713Sergei Golubchik10 Jan
    • Re: bk commit into 5.1 tree (kostja:1.2653) BUG#12713Konstantin Osipov10 Jan
      • Re: bk commit into 5.1 tree (kostja:1.2653) BUG#12713Sergei Golubchik12 Jan