~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to drizzled/transaction_services.cc

  • Committer: Monty Taylor
  • Date: 2010-03-11 18:27:20 UTC
  • mfrom: (1333 staging)
  • mto: This revision was merged to the branch mainline in revision 1348.
  • Revision ID: mordred@inaugust.com-20100311182720-hd1h87y6cb1b1mp0
Merged trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3
3
 *
4
4
 *  Copyright (C) 2008 Sun Microsystems
 
5
 *  Copyright (c) 2010 Jay Pipes <jaypipes@gmail.com>
5
6
 *
6
7
 *  This program is free software; you can redistribute it and/or modify
7
8
 *  it under the terms of the GNU General Public License as published by
31
32
#include "drizzled/sql_base.h"
32
33
#include "drizzled/replication_services.h"
33
34
#include "drizzled/transaction_services.h"
 
35
#include "drizzled/transaction_context.h"
 
36
#include "drizzled/resource_context.h"
34
37
#include "drizzled/lock.h"
35
38
#include "drizzled/item/int.h"
36
39
#include "drizzled/item/empty_string.h"
37
40
#include "drizzled/field/timestamp.h"
38
41
#include "drizzled/plugin/client.h"
 
42
#include "drizzled/plugin/monitored_in_transaction.h"
 
43
#include "drizzled/plugin/transactional_storage_engine.h"
 
44
#include "drizzled/plugin/xa_resource_manager.h"
39
45
#include "drizzled/internal/my_sys.h"
40
46
 
41
47
using namespace std;
42
48
 
 
49
#include <vector>
 
50
#include <algorithm>
 
51
#include <functional>
 
52
 
43
53
namespace drizzled
44
54
{
45
55
 
46
56
/**
47
 
  Transaction handling in the server
48
 
  ==================================
49
 
 
50
 
  In each client connection, MySQL maintains two transactional
51
 
  states:
52
 
  - a statement transaction,
53
 
  - a standard, also called normal transaction.
54
 
 
55
 
  Historical note
56
 
  ---------------
57
 
  "Statement transaction" is a non-standard term that comes
58
 
  from the times when MySQL supported BerkeleyDB storage engine.
59
 
 
60
 
  First of all, it should be said that in BerkeleyDB auto-commit
61
 
  mode auto-commits operations that are atomic to the storage
62
 
  engine itself, such as a write of a record, and are too
63
 
  high-granular to be atomic from the application perspective
64
 
  (MySQL). One SQL statement could involve many BerkeleyDB
65
 
  auto-committed operations and thus BerkeleyDB auto-commit was of
66
 
  little use to MySQL.
67
 
 
68
 
  Secondly, instead of SQL standard savepoints, BerkeleyDB
69
 
  provided the concept of "nested transactions". In a nutshell,
70
 
  transactions could be arbitrarily nested, but when the parent
71
 
  transaction was committed or aborted, all its child (nested)
72
 
  transactions were handled committed or aborted as well.
73
 
  Commit of a nested transaction, in turn, made its changes
74
 
  visible, but not durable: it destroyed the nested transaction,
75
 
  all its changes would become available to the parent and
76
 
  currently active nested transactions of this parent.
77
 
 
78
 
  So the mechanism of nested transactions was employed to
79
 
  provide "all or nothing" guarantee of SQL statements
80
 
  required by the standard.
81
 
  A nested transaction would be created at start of each SQL
82
 
  statement, and destroyed (committed or aborted) at statement
83
 
  end. Such nested transaction was internally referred to as
84
 
  a "statement transaction" and gave birth to the term.
85
 
 
86
 
  <Historical note ends>
87
 
 
88
 
  Since then a statement transaction is started for each statement
89
 
  that accesses transactional tables or uses the binary log.  If
90
 
  the statement succeeds, the statement transaction is committed.
91
 
  If the statement fails, the transaction is rolled back. Commits
92
 
  of statement transactions are not durable -- each such
93
 
  transaction is nested in the normal transaction, and if the
94
 
  normal transaction is rolled back, the effects of all enclosed
95
 
  statement transactions are undone as well.  Technically,
96
 
  a statement transaction can be viewed as a savepoint which is
97
 
  maintained automatically in order to make effects of one
98
 
  statement atomic.
99
 
 
100
 
  The normal transaction is started by the user and is ended
101
 
  usually upon a user request as well. The normal transaction
102
 
  encloses transactions of all statements issued between
103
 
  its beginning and its end.
104
 
  In autocommit mode, the normal transaction is equivalent
105
 
  to the statement transaction.
106
 
 
107
 
  Since MySQL supports PSEA (pluggable storage engine
108
 
  architecture), more than one transactional engine can be
109
 
  active at a time. Hence transactions, from the server
110
 
  point of view, are always distributed. In particular,
111
 
  transactional state is maintained independently for each
112
 
  engine. In order to commit a transaction the two phase
113
 
  commit protocol is employed.
114
 
 
115
 
  Not all statements are executed in context of a transaction.
116
 
  Administrative and status information statements do not modify
117
 
  engine data, and thus do not start a statement transaction and
118
 
  also have no effect on the normal transaction. Examples of such
119
 
  statements are SHOW STATUS and RESET SLAVE.
120
 
 
121
 
  Similarly DDL statements are not transactional,
122
 
  and therefore a transaction is [almost] never started for a DDL
123
 
  statement. The difference between a DDL statement and a purely
124
 
  administrative statement though is that a DDL statement always
125
 
  commits the current transaction before proceeding, if there is
126
 
  any.
127
 
 
128
 
  At last, SQL statements that work with non-transactional
129
 
  engines also have no effect on the transaction state of the
130
 
  connection. Even though they are written to the binary log,
131
 
  and the binary log is, overall, transactional, the writes
132
 
  are done in "write-through" mode, directly to the binlog
133
 
  file, followed with a OS cache sync, in other words,
134
 
  bypassing the binlog undo log (translog).
135
 
  They do not commit the current normal transaction.
136
 
  A failure of a statement that uses non-transactional tables
137
 
  would cause a rollback of the statement transaction, but
138
 
  in case there no non-transactional tables are used,
139
 
  no statement transaction is started.
140
 
 
141
 
  Data layout
142
 
  -----------
143
 
 
144
 
  The server stores its transaction-related data in
145
 
  session->transaction. This structure has two members of type
146
 
  Session_TRANS. These members correspond to the statement and
147
 
  normal transactions respectively:
148
 
 
149
 
  - session->transaction.stmt contains a list of engines
150
 
  that are participating in the given statement
151
 
  - session->transaction.all contains a list of engines that
152
 
  have participated in any of the statement transactions started
153
 
  within the context of the normal transaction.
154
 
  Each element of the list contains a pointer to the storage
155
 
  engine, engine-specific transactional data, and engine-specific
156
 
  transaction flags.
157
 
 
158
 
  In autocommit mode session->transaction.all is empty.
159
 
  Instead, data of session->transaction.stmt is
160
 
  used to commit/rollback the normal transaction.
161
 
 
162
 
  The list of registered engines has a few important properties:
163
 
  - no engine is registered in the list twice
164
 
  - engines are present in the list a reverse temporal order --
165
 
  new participants are always added to the beginning of the list.
166
 
 
167
 
  Transaction life cycle
168
 
  ----------------------
169
 
 
170
 
  When a new connection is established, session->transaction
171
 
  members are initialized to an empty state.
172
 
  If a statement uses any tables, all affected engines
173
 
  are registered in the statement engine list. In
174
 
  non-autocommit mode, the same engines are registered in
175
 
  the normal transaction list.
176
 
  At the end of the statement, the server issues a commit
177
 
  or a roll back for all engines in the statement list.
178
 
  At this point transaction flags of an engine, if any, are
179
 
  propagated from the statement list to the list of the normal
180
 
  transaction.
181
 
  When commit/rollback is finished, the statement list is
182
 
  cleared. It will be filled in again by the next statement,
183
 
  and emptied again at the next statement's end.
184
 
 
185
 
  The normal transaction is committed in a similar way
186
 
  (by going over all engines in session->transaction.all list)
187
 
  but at different times:
188
 
  - upon COMMIT SQL statement is issued by the user
189
 
  - implicitly, by the server, at the beginning of a DDL statement
190
 
  or SET AUTOCOMMIT={0|1} statement.
191
 
 
192
 
  The normal transaction can be rolled back as well:
193
 
  - if the user has requested so, by issuing ROLLBACK SQL
194
 
  statement
195
 
  - if one of the storage engines requested a rollback
196
 
  by setting session->transaction_rollback_request. This may
197
 
  happen in case, e.g., when the transaction in the engine was
198
 
  chosen a victim of the internal deadlock resolution algorithm
199
 
  and rolled back internally. When such a situation happens, there
200
 
  is little the server can do and the only option is to rollback
201
 
  transactions in all other participating engines.  In this case
202
 
  the rollback is accompanied by an error sent to the user.
203
 
 
204
 
  As follows from the use cases above, the normal transaction
205
 
  is never committed when there is an outstanding statement
206
 
  transaction. In most cases there is no conflict, since
207
 
  commits of the normal transaction are issued by a stand-alone
208
 
  administrative or DDL statement, thus no outstanding statement
209
 
  transaction of the previous statement exists. Besides,
210
 
  all statements that manipulate with the normal transaction
211
 
  are prohibited in stored functions and triggers, therefore
212
 
  no conflicting situation can occur in a sub-statement either.
213
 
  The remaining rare cases when the server explicitly has
214
 
  to commit the statement transaction prior to committing the normal
215
 
  one cover error-handling scenarios (see for example
216
 
  ?).
217
 
 
218
 
  When committing a statement or a normal transaction, the server
219
 
  either uses the two-phase commit protocol, or issues a commit
220
 
  in each engine independently. The two-phase commit protocol
221
 
  is used only if:
222
 
  - all participating engines support two-phase commit (provide
223
 
    plugin::StorageEngine::prepare PSEA API call) and
224
 
  - transactions in at least two engines modify data (i.e. are
225
 
  not read-only).
226
 
 
227
 
  Note that the two phase commit is used for
228
 
  statement transactions, even though they are not durable anyway.
229
 
  This is done to ensure logical consistency of data in a multiple-
230
 
  engine transaction.
231
 
  For example, imagine that some day MySQL supports unique
232
 
  constraint checks deferred till the end of statement. In such
233
 
  case a commit in one of the engines may yield ER_DUP_KEY,
234
 
  and MySQL should be able to gracefully abort statement
235
 
  transactions of other participants.
236
 
 
237
 
  After the normal transaction has been committed,
238
 
  session->transaction.all list is cleared.
239
 
 
240
 
  When a connection is closed, the current normal transaction, if
241
 
  any, is rolled back.
242
 
 
243
 
  Roles and responsibilities
244
 
  --------------------------
245
 
 
246
 
  The server has no way to know that an engine participates in
247
 
  the statement and a transaction has been started
248
 
  in it unless the engine says so. Thus, in order to be
249
 
  a part of a transaction, the engine must "register" itself.
250
 
  This is done by invoking trans_register_ha() server call.
251
 
  Normally the engine registers itself whenever Cursor::external_lock()
252
 
  is called. trans_register_ha() can be invoked many times: if
253
 
  an engine is already registered, the call does nothing.
254
 
  In case autocommit is not set, the engine must register itself
255
 
  twice -- both in the statement list and in the normal transaction
256
 
  list.
257
 
  In which list to register is a parameter of trans_register_ha().
258
 
 
259
 
  Note, that although the registration interface in itself is
260
 
  fairly clear, the current usage practice often leads to undesired
261
 
  effects. E.g. since a call to trans_register_ha() in most engines
262
 
  is embedded into implementation of Cursor::external_lock(), some
263
 
  DDL statements start a transaction (at least from the server
264
 
  point of view) even though they are not expected to. E.g.
265
 
  CREATE TABLE does not start a transaction, since
266
 
  Cursor::external_lock() is never called during CREATE TABLE. But
267
 
  CREATE TABLE ... SELECT does, since Cursor::external_lock() is
268
 
  called for the table that is being selected from. This has no
269
 
  practical effects currently, but must be kept in mind
270
 
  nevertheless.
271
 
 
272
 
  Once an engine is registered, the server will do the rest
273
 
  of the work.
274
 
 
275
 
  During statement execution, whenever any of data-modifying
276
 
  PSEA API methods is used, e.g. Cursor::write_row() or
277
 
  Cursor::update_row(), the read-write flag is raised in the
278
 
  statement transaction for the involved engine.
279
 
  Currently All PSEA calls are "traced", and the data can not be
280
 
  changed in a way other than issuing a PSEA call. Important:
281
 
  unless this invariant is preserved the server will not know that
282
 
  a transaction in a given engine is read-write and will not
283
 
  involve the two-phase commit protocol!
284
 
 
285
 
  At the end of a statement, server call
286
 
  ha_autocommit_or_rollback() is invoked. This call in turn
287
 
  invokes plugin::StorageEngine::prepare() for every involved engine.
288
 
  Prepare is followed by a call to plugin::StorageEngine::commit_one_phase()
289
 
  If a one-phase commit will suffice, plugin::StorageEngine::prepare() is not
290
 
  invoked and the server only calls plugin::StorageEngine::commit_one_phase().
291
 
  At statement commit, the statement-related read-write engine
292
 
  flag is propagated to the corresponding flag in the normal
293
 
  transaction.  When the commit is complete, the list of registered
294
 
  engines is cleared.
295
 
 
296
 
  Rollback is handled in a similar fashion.
297
 
 
298
 
  Additional notes on DDL and the normal transaction.
299
 
  ---------------------------------------------------
300
 
 
301
 
  DDLs and operations with non-transactional engines
302
 
  do not "register" in session->transaction lists, and thus do not
303
 
  modify the transaction state. Besides, each DDL in
304
 
  MySQL is prefixed with an implicit normal transaction commit
305
 
  (a call to Session::endActiveTransaction()), and thus leaves nothing
306
 
  to modify.
307
 
  However, as it has been pointed out with CREATE TABLE .. SELECT,
308
 
  some DDL statements can start a *new* transaction.
309
 
 
310
 
  Behaviour of the server in this case is currently badly
311
 
  defined.
312
 
  DDL statements use a form of "semantic" logging
313
 
  to maintain atomicity: if CREATE TABLE .. SELECT failed,
314
 
  the newly created table is deleted.
315
 
  In addition, some DDL statements issue interim transaction
316
 
  commits: e.g. ALTER Table issues a commit after data is copied
317
 
  from the original table to the internal temporary table. Other
318
 
  statements, e.g. CREATE TABLE ... SELECT do not always commit
319
 
  after itself.
320
 
  And finally there is a group of DDL statements such as
321
 
  RENAME/DROP Table that doesn't start a new transaction
322
 
  and doesn't commit.
323
 
 
324
 
  This diversity makes it hard to say what will happen if
325
 
  by chance a stored function is invoked during a DDL --
326
 
  whether any modifications it makes will be committed or not
327
 
  is not clear. Fortunately, SQL grammar of few DDLs allows
328
 
  invocation of a stored function.
329
 
 
330
 
  A consistent behaviour is perhaps to always commit the normal
331
 
  transaction after all DDLs, just like the statement transaction
332
 
  is always committed at the end of all statements.
333
 
*/
334
 
 
335
 
/**
336
 
  Register a storage engine for a transaction.
337
 
 
338
 
  Every storage engine MUST call this function when it starts
339
 
  a transaction or a statement (that is it must be called both for the
340
 
  "beginning of transaction" and "beginning of statement").
341
 
  Only storage engines registered for the transaction/statement
342
 
  will know when to commit/rollback it.
343
 
 
344
 
  @note
345
 
    trans_register_ha is idempotent - storage engine may register many
346
 
    times per transaction.
347
 
 
348
 
*/
349
 
void TransactionServices::trans_register_ha(Session *session, bool all, plugin::StorageEngine *engine)
350
 
{
351
 
  Session_TRANS *trans;
352
 
  Ha_trx_info *ha_info;
353
 
 
354
 
  if (all)
355
 
  {
356
 
    trans= &session->transaction.all;
357
 
    session->server_status|= SERVER_STATUS_IN_TRANS;
358
 
  }
359
 
  else
360
 
    trans= &session->transaction.stmt;
361
 
 
362
 
  ha_info= session->getEngineInfo(engine, all ? 1 : 0);
363
 
 
364
 
  if (ha_info->is_started())
365
 
    return; /* already registered, return */
366
 
 
367
 
  ha_info->register_ha(trans, engine);
368
 
 
369
 
  trans->no_2pc|= not engine->has_2pc();
370
 
  if (session->transaction.xid_state.xid.is_null())
371
 
    session->transaction.xid_state.xid.set(session->getQueryId());
 
57
 * @defgroup Transactions
 
58
 *
 
59
 * @brief
 
60
 *
 
61
 * Transaction handling in the server
 
62
 *
 
63
 * @detail
 
64
 *
 
65
 * In each client connection, Drizzle maintains two transaction
 
66
 * contexts representing the state of the:
 
67
 *
 
68
 * 1) Statement Transaction
 
69
 * 2) Normal Transaction
 
70
 *
 
71
 * These two transaction contexts represent the transactional
 
72
 * state of a Session's SQL and XA transactions for a single
 
73
 * SQL statement or a series of SQL statements.
 
74
 *
 
75
 * When the Session's connection is in AUTOCOMMIT mode, there
 
76
 * is no practical difference between the statement and the
 
77
 * normal transaction, as each SQL statement is committed or
 
78
 * rolled back depending on the success or failure of the
 
79
 * indvidual SQL statement.
 
80
 *
 
81
 * When the Session's connection is NOT in AUTOCOMMIT mode, OR
 
82
 * the Session has explicitly begun a normal SQL transaction using
 
83
 * a BEGIN WORK/START TRANSACTION statement, then the normal
 
84
 * transaction context tracks the aggregate transaction state of
 
85
 * the SQL transaction's individual statements, and the SQL
 
86
 * transaction's commit or rollback is done atomically for all of
 
87
 * the SQL transaction's statement's data changes.
 
88
 *
 
89
 * Technically, a statement transaction can be viewed as a savepoint 
 
90
 * which is maintained automatically in order to make effects of one
 
91
 * statement atomic.
 
92
 *
 
93
 * The normal transaction is started by the user and is typically
 
94
 * ended (COMMIT or ROLLBACK) upon an explicity user request as well.
 
95
 * The exception to this is that DDL statements implicitly COMMIT
 
96
 * any previously active normal transaction before they begin executing.
 
97
 *
 
98
 * In Drizzle, unlike MySQL, plugins other than a storage engine
 
99
 * may participate in a transaction.  All plugin::TransactionalStorageEngine
 
100
 * plugins will automatically be monitored by Drizzle's transaction 
 
101
 * manager (implemented in this source file), as will all plugins which
 
102
 * implement plugin::XaResourceManager and register with the transaction
 
103
 * manager.
 
104
 *
 
105
 * If Drizzle's transaction manager sees that more than one resource
 
106
 * manager (transactional storage engine or XA resource manager) has modified
 
107
 * data state during a statement or normal transaction, the transaction
 
108
 * manager will automatically use a two-phase commit protocol for all
 
109
 * resources which support XA's distributed transaction protocol.  Unlike
 
110
 * MySQL, storage engines need not manually register with the transaction
 
111
 * manager during a statement's execution.  Previously, in MySQL, all
 
112
 * handlertons would have to call trans_register_ha() at some point after
 
113
 * modifying data state in order to have MySQL include that handler in
 
114
 * an XA transaction.  Drizzle does all of this grunt work behind the
 
115
 * scenes for the storage engine implementers.
 
116
 *
 
117
 * When a connection is closed, the current normal transaction, if
 
118
 * any is currently active, is rolled back.
 
119
 *
 
120
 * Transaction life cycle
 
121
 * ----------------------
 
122
 *
 
123
 * When a new connection is established, session->transaction
 
124
 * members are initialized to an empty state. If a statement uses any tables, 
 
125
 * all affected engines are registered in the statement engine list automatically
 
126
 * in plugin::StorageEngine::startStatement() and 
 
127
 * plugin::TransactionalStorageEngine::startTransaction().
 
128
 *
 
129
 * You can view the lifetime of a normal transaction in the following
 
130
 * call-sequence:
 
131
 *
 
132
 * drizzled::statement::Statement::execute()
 
133
 *   drizzled::plugin::TransactionalStorageEngine::startTransaction()
 
134
 *     drizzled::TransactionServices::registerResourceForTransaction()
 
135
 *     drizzled::TransactionServices::registerResourceForStatement()
 
136
 *     drizzled::plugin::StorageEngine::startStatement()
 
137
 *       drizzled::Cursor::write_row() <-- example...could be update_row(), etc
 
138
 *     drizzled::plugin::StorageEngine::endStatement()
 
139
 *   drizzled::TransactionServices::autocommitOrRollback()
 
140
 *     drizzled::TransactionalStorageEngine::commit() <-- or ::rollback()
 
141
 *     drizzled::XaResourceManager::xaCommit() <-- or rollback()
 
142
 *
 
143
 * Roles and responsibilities
 
144
 * --------------------------
 
145
 *
 
146
 * Beginning of SQL Statement (and Statement Transaction)
 
147
 * ------------------------------------------------------
 
148
 *
 
149
 * At the start of each SQL statement, for each storage engine
 
150
 * <strong>that is involved in the SQL statement</strong>, the kernel 
 
151
 * calls the engine's plugin::StoragEngine::startStatement() method.  If the
 
152
 * engine needs to track some data for the statement, it should use
 
153
 * this method invocation to initialize this data.  This is the
 
154
 * beginning of what is called the "statement transaction".
 
155
 *
 
156
 * <strong>For transaction storage engines (those storage engines
 
157
 * that inherit from plugin::TransactionalStorageEngine)</strong>, the
 
158
 * kernel automatically determines if the start of the SQL statement 
 
159
 * transaction should <em>also</em> begin the normal SQL transaction.
 
160
 * This occurs when the connection is in NOT in autocommit mode. If
 
161
 * the kernel detects this, then the kernel automatically starts the
 
162
 * normal transaction w/ plugin::TransactionalStorageEngine::startTransaction()
 
163
 * method and then calls plugin::StorageEngine::startStatement()
 
164
 * afterwards.
 
165
 *
 
166
 * Beginning of an SQL "Normal" Transaction
 
167
 * ----------------------------------------
 
168
 *
 
169
 * As noted above, a "normal SQL transaction" may be started when
 
170
 * an SQL statement is started in a connection and the connection is
 
171
 * NOT in AUTOCOMMIT mode.  This is automatically done by the kernel.
 
172
 *
 
173
 * In addition, when a user executes a START TRANSACTION or
 
174
 * BEGIN WORK statement in a connection, the kernel explicitly
 
175
 * calls each transactional storage engine's startTransaction() method.
 
176
 *
 
177
 * Ending of an SQL Statement (and Statement Transaction)
 
178
 * ------------------------------------------------------
 
179
 *
 
180
 * At the end of each SQL statement, for each of the aforementioned
 
181
 * involved storage engines, the kernel calls the engine's
 
182
 * plugin::StorageEngine::endStatement() method.  If the engine
 
183
 * has initialized or modified some internal data about the
 
184
 * statement transaction, it should use this method to reset or destroy
 
185
 * this data appropriately.
 
186
 *
 
187
 * Ending of an SQL "Normal" Transaction
 
188
 * -------------------------------------
 
189
 *
 
190
 * The end of a normal transaction is either a ROLLBACK or a COMMIT, 
 
191
 * depending on the success or failure of the statement transaction(s) 
 
192
 * it encloses.
 
193
 *
 
194
 * The end of a "normal transaction" occurs when any of the following
 
195
 * occurs:
 
196
 *
 
197
 * 1) If a statement transaction has completed and AUTOCOMMIT is ON,
 
198
 *    then the normal transaction which encloses the statement
 
199
 *    transaction ends
 
200
 * 2) If a COMMIT or ROLLBACK statement occurs on the connection
 
201
 * 3) Just before a DDL operation occurs, the kernel will implicitly
 
202
 *    commit the active normal transaction
 
203
 *
 
204
 * Transactions and Non-transactional Storage Engines
 
205
 * --------------------------------------------------
 
206
 *
 
207
 * For non-transactional engines, this call can be safely ignored, an
 
208
 * the kernel tracks whether a non-transactional engine has changed
 
209
 * any data state, and warns the user appropriately if a transaction
 
210
 * (statement or normal) is rolled back after such non-transactional
 
211
 * data changes have been made.
 
212
 *
 
213
 * XA Two-phase Commit Protocol
 
214
 * ----------------------------
 
215
 *
 
216
 * During statement execution, whenever any of data-modifying
 
217
 * PSEA API methods is used, e.g. Cursor::write_row() or
 
218
 * Cursor::update_row(), the read-write flag is raised in the
 
219
 * statement transaction for the involved engine.
 
220
 * Currently All PSEA calls are "traced", and the data can not be
 
221
 * changed in a way other than issuing a PSEA call. Important:
 
222
 * unless this invariant is preserved the server will not know that
 
223
 * a transaction in a given engine is read-write and will not
 
224
 * involve the two-phase commit protocol!
 
225
 *
 
226
 * At the end of a statement, TransactionServices::autocommitOrRollback()
 
227
 * is invoked. This call in turn
 
228
 * invokes plugin::XaResourceManager::xapPepare() for every involved XA
 
229
 * resource manager.
 
230
 *
 
231
 * Prepare is followed by a call to plugin::TransactionalStorageEngine::commit()
 
232
 * or plugin::XaResourceManager::xaCommit() (depending on what the resource
 
233
 * is...)
 
234
 * 
 
235
 * If a one-phase commit will suffice, plugin::StorageEngine::prepare() is not
 
236
 * invoked and the server only calls plugin::StorageEngine::commit_one_phase().
 
237
 * At statement commit, the statement-related read-write engine
 
238
 * flag is propagated to the corresponding flag in the normal
 
239
 * transaction.  When the commit is complete, the list of registered
 
240
 * engines is cleared.
 
241
 *
 
242
 * Rollback is handled in a similar fashion.
 
243
 *
 
244
 * Additional notes on DDL and the normal transaction.
 
245
 * ---------------------------------------------------
 
246
 *
 
247
 * CREATE TABLE .. SELECT can start a *new* normal transaction
 
248
 * because of the fact that SELECTs on a transactional storage
 
249
 * engine participate in the normal SQL transaction (due to
 
250
 * isolation level issues and consistent read views).
 
251
 *
 
252
 * Behaviour of the server in this case is currently badly
 
253
 * defined.
 
254
 *
 
255
 * DDL statements use a form of "semantic" logging
 
256
 * to maintain atomicity: if CREATE TABLE .. SELECT failed,
 
257
 * the newly created table is deleted.
 
258
 * 
 
259
 * In addition, some DDL statements issue interim transaction
 
260
 * commits: e.g. ALTER TABLE issues a COMMIT after data is copied
 
261
 * from the original table to the internal temporary table. Other
 
262
 * statements, e.g. CREATE TABLE ... SELECT do not always commit
 
263
 * after itself.
 
264
 *
 
265
 * And finally there is a group of DDL statements such as
 
266
 * RENAME/DROP TABLE that doesn't start a new transaction
 
267
 * and doesn't commit.
 
268
 *
 
269
 * A consistent behaviour is perhaps to always commit the normal
 
270
 * transaction after all DDLs, just like the statement transaction
 
271
 * is always committed at the end of all statements.
 
272
 */
 
273
void TransactionServices::registerResourceForStatement(Session *session,
 
274
                                                       plugin::MonitoredInTransaction *monitored,
 
275
                                                       plugin::TransactionalStorageEngine *engine)
 
276
{
 
277
  if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
 
278
  {
 
279
    /* 
 
280
     * Now we automatically register this resource manager for the
 
281
     * normal transaction.  This is fine because a statement
 
282
     * transaction registration should always enlist the resource
 
283
     * in the normal transaction which contains the statement
 
284
     * transaction.
 
285
     */
 
286
    registerResourceForTransaction(session, monitored, engine);
 
287
  }
 
288
 
 
289
  TransactionContext *trans= &session->transaction.stmt;
 
290
  ResourceContext *resource_context= session->getResourceContext(monitored, 0);
 
291
 
 
292
  if (resource_context->isStarted())
 
293
    return; /* already registered, return */
 
294
 
 
295
  assert(monitored->participatesInSqlTransaction());
 
296
  assert(not monitored->participatesInXaTransaction());
 
297
 
 
298
  resource_context->setMonitored(monitored);
 
299
  resource_context->setTransactionalStorageEngine(engine);
 
300
  trans->registerResource(resource_context);
 
301
 
 
302
  trans->no_2pc|= true;
 
303
}
 
304
 
 
305
void TransactionServices::registerResourceForStatement(Session *session,
 
306
                                                       plugin::MonitoredInTransaction *monitored,
 
307
                                                       plugin::TransactionalStorageEngine *engine,
 
308
                                                       plugin::XaResourceManager *resource_manager)
 
309
{
 
310
  if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
 
311
  {
 
312
    /* 
 
313
     * Now we automatically register this resource manager for the
 
314
     * normal transaction.  This is fine because a statement
 
315
     * transaction registration should always enlist the resource
 
316
     * in the normal transaction which contains the statement
 
317
     * transaction.
 
318
     */
 
319
    registerResourceForTransaction(session, monitored, engine, resource_manager);
 
320
  }
 
321
 
 
322
  TransactionContext *trans= &session->transaction.stmt;
 
323
  ResourceContext *resource_context= session->getResourceContext(monitored, 0);
 
324
 
 
325
  if (resource_context->isStarted())
 
326
    return; /* already registered, return */
 
327
 
 
328
  assert(monitored->participatesInXaTransaction());
 
329
  assert(monitored->participatesInSqlTransaction());
 
330
 
 
331
  resource_context->setMonitored(monitored);
 
332
  resource_context->setTransactionalStorageEngine(engine);
 
333
  resource_context->setXaResourceManager(resource_manager);
 
334
  trans->registerResource(resource_context);
 
335
 
 
336
  trans->no_2pc|= false;
 
337
}
 
338
 
 
339
void TransactionServices::registerResourceForTransaction(Session *session,
 
340
                                                         plugin::MonitoredInTransaction *monitored,
 
341
                                                         plugin::TransactionalStorageEngine *engine)
 
342
{
 
343
  TransactionContext *trans= &session->transaction.all;
 
344
  ResourceContext *resource_context= session->getResourceContext(monitored, 1);
 
345
 
 
346
  if (resource_context->isStarted())
 
347
    return; /* already registered, return */
 
348
 
 
349
  session->server_status|= SERVER_STATUS_IN_TRANS;
 
350
 
 
351
 
 
352
  trans->registerResource(resource_context);
 
353
 
 
354
  assert(monitored->participatesInSqlTransaction());
 
355
  assert(not monitored->participatesInXaTransaction());
 
356
 
 
357
  resource_context->setMonitored(monitored);
 
358
  resource_context->setTransactionalStorageEngine(engine);
 
359
  trans->no_2pc|= true;
 
360
 
 
361
  if (session->transaction.xid_state.xid.is_null())
 
362
    session->transaction.xid_state.xid.set(session->getQueryId());
 
363
 
 
364
  /* Only true if user is executing a BEGIN WORK/START TRANSACTION */
 
365
  if (! session->getResourceContext(monitored, 0)->isStarted())
 
366
    registerResourceForStatement(session, monitored, engine);
 
367
}
 
368
 
 
369
void TransactionServices::registerResourceForTransaction(Session *session,
 
370
                                                         plugin::MonitoredInTransaction *monitored,
 
371
                                                         plugin::TransactionalStorageEngine *engine,
 
372
                                                         plugin::XaResourceManager *resource_manager)
 
373
{
 
374
  TransactionContext *trans= &session->transaction.all;
 
375
  ResourceContext *resource_context= session->getResourceContext(monitored, 1);
 
376
 
 
377
  if (resource_context->isStarted())
 
378
    return; /* already registered, return */
 
379
 
 
380
  session->server_status|= SERVER_STATUS_IN_TRANS;
 
381
 
 
382
  trans->registerResource(resource_context);
 
383
 
 
384
  assert(monitored->participatesInSqlTransaction());
 
385
 
 
386
  resource_context->setMonitored(monitored);
 
387
  resource_context->setXaResourceManager(resource_manager);
 
388
  resource_context->setTransactionalStorageEngine(engine);
 
389
  trans->no_2pc|= true;
 
390
 
 
391
  if (session->transaction.xid_state.xid.is_null())
 
392
    session->transaction.xid_state.xid.set(session->getQueryId());
 
393
 
 
394
  /* Only true if user is executing a BEGIN WORK/START TRANSACTION */
 
395
  if (! session->getResourceContext(monitored, 0)->isStarted())
 
396
    registerResourceForStatement(session, monitored, engine, resource_manager);
372
397
}
373
398
 
374
399
/**
386
411
*/
387
412
static
388
413
bool
389
 
ha_check_and_coalesce_trx_read_only(Session *session, Ha_trx_info *ha_list,
390
 
                                    bool all)
 
414
ha_check_and_coalesce_trx_read_only(Session *session,
 
415
                                    TransactionContext::ResourceContexts &resource_contexts,
 
416
                                    bool normal_transaction)
391
417
{
392
418
  /* The number of storage engines that have actual changes. */
393
 
  unsigned rw_ha_count= 0;
394
 
  Ha_trx_info *ha_info;
 
419
  unsigned num_resources_modified_data= 0;
 
420
  ResourceContext *resource_context;
395
421
 
396
 
  for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
 
422
  for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
423
       it != resource_contexts.end();
 
424
       ++it)
397
425
  {
398
 
    if (ha_info->is_trx_read_write())
399
 
      ++rw_ha_count;
 
426
    resource_context= *it;
 
427
    if (resource_context->hasModifiedData())
 
428
      ++num_resources_modified_data;
400
429
 
401
 
    if (! all)
 
430
    if (! normal_transaction)
402
431
    {
403
 
      Ha_trx_info *ha_info_all= session->getEngineInfo(ha_info->engine(), 1);
404
 
      assert(ha_info != ha_info_all);
 
432
      ResourceContext *resource_context_normal= session->getResourceContext(resource_context->getMonitored(), true);
 
433
      assert(resource_context != resource_context_normal);
405
434
      /*
406
435
        Merge read-only/read-write information about statement
407
436
        transaction to its enclosing normal transaction. Do this
408
437
        only if in a real transaction -- that is, if we know
409
 
        that ha_info_all is registered in session->transaction.all.
 
438
        that resource_context_all is registered in session->transaction.all.
410
439
        Since otherwise we only clutter the normal transaction flags.
411
440
      */
412
 
      if (ha_info_all->is_started()) /* false if autocommit. */
413
 
        ha_info_all->coalesce_trx_with(ha_info);
 
441
      if (resource_context_normal->isStarted()) /* false if autocommit. */
 
442
        resource_context_normal->coalesceWith(resource_context);
414
443
    }
415
 
    else if (rw_ha_count > 1)
 
444
    else if (num_resources_modified_data > 1)
416
445
    {
417
446
      /*
418
447
        It is a normal transaction, so we don't need to merge read/write
422
451
      break;
423
452
    }
424
453
  }
425
 
  return rw_ha_count > 1;
 
454
  return num_resources_modified_data > 1;
426
455
}
427
456
 
428
457
 
440
469
    stored functions or triggers. So we simply do nothing now.
441
470
    TODO: This should be fixed in later ( >= 5.1) releases.
442
471
*/
443
 
int TransactionServices::ha_commit_trans(Session *session, bool all)
 
472
int TransactionServices::ha_commit_trans(Session *session, bool normal_transaction)
444
473
{
445
474
  int error= 0, cookie= 0;
446
475
  /*
447
476
    'all' means that this is either an explicit commit issued by
448
477
    user, or an implicit commit issued by a DDL.
449
478
  */
450
 
  Session_TRANS *trans= all ? &session->transaction.all : &session->transaction.stmt;
451
 
  bool is_real_trans= all || session->transaction.all.ha_list == 0;
452
 
  Ha_trx_info *ha_info= trans->ha_list;
 
479
  TransactionContext *trans= normal_transaction ? &session->transaction.all : &session->transaction.stmt;
 
480
  TransactionContext::ResourceContexts &resource_contexts= trans->getResourceContexts();
 
481
 
 
482
  bool is_real_trans= normal_transaction || session->transaction.all.getResourceContexts().empty();
453
483
 
454
484
  /*
455
485
    We must not commit the normal transaction if a statement
457
487
    flags will not get propagated to its normal transaction's
458
488
    counterpart.
459
489
  */
460
 
  assert(session->transaction.stmt.ha_list == NULL ||
 
490
  assert(session->transaction.stmt.getResourceContexts().empty() ||
461
491
              trans == &session->transaction.stmt);
462
492
 
463
 
  if (ha_info)
 
493
  if (resource_contexts.empty() == false)
464
494
  {
465
495
    bool must_2pc;
466
496
 
467
497
    if (is_real_trans && wait_if_global_read_lock(session, 0, 0))
468
498
    {
469
 
      ha_rollback_trans(session, all);
 
499
      ha_rollback_trans(session, normal_transaction);
470
500
      return 1;
471
501
    }
472
502
 
473
 
    must_2pc= ha_check_and_coalesce_trx_read_only(session, ha_info, all);
 
503
    must_2pc= ha_check_and_coalesce_trx_read_only(session, resource_contexts, normal_transaction);
474
504
 
475
 
    if (!trans->no_2pc && must_2pc)
 
505
    if (! trans->no_2pc && must_2pc)
476
506
    {
477
 
      for (; ha_info && !error; ha_info= ha_info->next())
 
507
      for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
508
           it != resource_contexts.end() && ! error;
 
509
           ++it)
478
510
      {
 
511
        ResourceContext *resource_context= *it;
479
512
        int err;
480
 
        plugin::StorageEngine *engine= ha_info->engine();
481
513
        /*
482
514
          Do not call two-phase commit if this particular
483
515
          transaction is read-only. This allows for simpler
484
516
          implementation in engines that are always read-only.
485
517
        */
486
 
        if (! ha_info->is_trx_read_write())
 
518
        if (! resource_context->hasModifiedData())
487
519
          continue;
488
 
        /*
489
 
          Sic: we know that prepare() is not NULL since otherwise
490
 
          trans->no_2pc would have been set.
491
 
        */
492
 
        if ((err= engine->prepare(session, all)))
 
520
 
 
521
        plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
522
 
 
523
        if (resource->participatesInXaTransaction())
493
524
        {
494
 
          my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
495
 
          error= 1;
 
525
          if ((err= resource_context->getXaResourceManager()->xaPrepare(session, normal_transaction)))
 
526
          {
 
527
            my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 
528
            error= 1;
 
529
          }
 
530
          else
 
531
          {
 
532
            status_var_increment(session->status_var.ha_prepare_count);
 
533
          }
496
534
        }
497
 
        status_var_increment(session->status_var.ha_prepare_count);
498
535
      }
499
536
      if (error)
500
537
      {
501
 
        ha_rollback_trans(session, all);
 
538
        ha_rollback_trans(session, normal_transaction);
502
539
        error= 1;
503
540
        goto end;
504
541
      }
505
542
    }
506
 
    error=ha_commit_one_phase(session, all) ? (cookie ? 2 : 1) : 0;
 
543
    error= ha_commit_one_phase(session, normal_transaction) ? (cookie ? 2 : 1) : 0;
507
544
end:
508
545
    if (is_real_trans)
509
546
      start_waiting_global_read_lock(session);
515
552
  @note
516
553
  This function does not care about global read lock. A caller should.
517
554
*/
518
 
int TransactionServices::ha_commit_one_phase(Session *session, bool all)
 
555
int TransactionServices::ha_commit_one_phase(Session *session, bool normal_transaction)
519
556
{
520
557
  int error=0;
521
 
  Session_TRANS *trans=all ? &session->transaction.all : &session->transaction.stmt;
522
 
  bool is_real_trans=all || session->transaction.all.ha_list == 0;
523
 
  Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
524
 
  if (ha_info)
 
558
  TransactionContext *trans= normal_transaction ? &session->transaction.all : &session->transaction.stmt;
 
559
  TransactionContext::ResourceContexts &resource_contexts= trans->getResourceContexts();
 
560
 
 
561
  bool is_real_trans= normal_transaction || session->transaction.all.getResourceContexts().empty();
 
562
 
 
563
  if (resource_contexts.empty() == false)
525
564
  {
526
 
    for (; ha_info; ha_info= ha_info_next)
 
565
    for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
566
         it != resource_contexts.end();
 
567
         ++it)
527
568
    {
528
569
      int err;
529
 
      plugin::StorageEngine *engine= ha_info->engine();
530
 
      if ((err= engine->commit(session, all)))
531
 
      {
532
 
        my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
533
 
        error=1;
534
 
      }
535
 
      status_var_increment(session->status_var.ha_commit_count);
536
 
      ha_info_next= ha_info->next();
537
 
      ha_info->reset(); /* keep it conveniently zero-filled */
 
570
      ResourceContext *resource_context= *it;
 
571
 
 
572
      plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
573
 
 
574
      if (resource->participatesInXaTransaction())
 
575
      {
 
576
        if ((err= resource_context->getXaResourceManager()->xaCommit(session, normal_transaction)))
 
577
        {
 
578
          my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 
579
          error= 1;
 
580
        }
 
581
        else
 
582
        {
 
583
          status_var_increment(session->status_var.ha_commit_count);
 
584
        }
 
585
      }
 
586
      else if (resource->participatesInSqlTransaction())
 
587
      {
 
588
        if ((err= resource_context->getTransactionalStorageEngine()->commit(session, normal_transaction)))
 
589
        {
 
590
          my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 
591
          error= 1;
 
592
        }
 
593
        else
 
594
        {
 
595
          status_var_increment(session->status_var.ha_commit_count);
 
596
        }
 
597
      }
 
598
      resource_context->reset(); /* keep it conveniently zero-filled */
538
599
    }
539
 
    trans->ha_list= 0;
540
 
    trans->no_2pc=0;
 
600
 
541
601
    if (is_real_trans)
542
602
      session->transaction.xid_state.xid.null();
543
 
    if (all)
 
603
 
 
604
    if (normal_transaction)
544
605
    {
545
 
      session->variables.tx_isolation=session->session_tx_isolation;
 
606
      session->variables.tx_isolation= session->session_tx_isolation;
546
607
      session->transaction.cleanup();
547
608
    }
548
609
  }
 
610
  trans->reset();
549
611
  if (error == 0)
550
612
  {
551
613
    if (is_real_trans)
552
614
    {
553
615
      /* 
554
 
        * We commit the normal transaction by finalizing the transaction message
555
 
        * and propogating the message to all registered replicators.
556
 
        */
 
616
       * We commit the normal transaction by finalizing the transaction message
 
617
       * and propogating the message to all registered replicators.
 
618
       */
557
619
      ReplicationServices &replication_services= ReplicationServices::singleton();
558
620
      replication_services.commitTransaction(session);
559
621
    }
561
623
  return error;
562
624
}
563
625
 
564
 
 
565
 
int TransactionServices::ha_rollback_trans(Session *session, bool all)
 
626
int TransactionServices::ha_rollback_trans(Session *session, bool normal_transaction)
566
627
{
567
 
  int error=0;
568
 
  Session_TRANS *trans=all ? &session->transaction.all : &session->transaction.stmt;
569
 
  Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
570
 
  bool is_real_trans=all || session->transaction.all.ha_list == 0;
 
628
  int error= 0;
 
629
  TransactionContext *trans= normal_transaction ? &session->transaction.all : &session->transaction.stmt;
 
630
  TransactionContext::ResourceContexts &resource_contexts= trans->getResourceContexts();
 
631
 
 
632
  bool is_real_trans= normal_transaction || session->transaction.all.getResourceContexts().empty();
571
633
 
572
634
  /*
573
635
    We must not rollback the normal transaction if a statement
574
636
    transaction is pending.
575
637
  */
576
 
  assert(session->transaction.stmt.ha_list == NULL ||
 
638
  assert(session->transaction.stmt.getResourceContexts().empty() ||
577
639
              trans == &session->transaction.stmt);
578
640
 
579
 
  if (ha_info)
 
641
  if (resource_contexts.empty() == false)
580
642
  {
581
 
    for (; ha_info; ha_info= ha_info_next)
 
643
    for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
644
         it != resource_contexts.end();
 
645
         ++it)
582
646
    {
583
647
      int err;
584
 
      plugin::StorageEngine *engine= ha_info->engine();
585
 
      if ((err= engine->rollback(session, all)))
586
 
      { // cannot happen
587
 
        my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
588
 
        error=1;
589
 
      }
590
 
      status_var_increment(session->status_var.ha_rollback_count);
591
 
      ha_info_next= ha_info->next();
592
 
      ha_info->reset(); /* keep it conveniently zero-filled */
 
648
      ResourceContext *resource_context= *it;
 
649
 
 
650
      plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
651
 
 
652
      if (resource->participatesInXaTransaction())
 
653
      {
 
654
        if ((err= resource_context->getXaResourceManager()->xaRollback(session, normal_transaction)))
 
655
        {
 
656
          my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 
657
          error= 1;
 
658
        }
 
659
        else
 
660
        {
 
661
          status_var_increment(session->status_var.ha_rollback_count);
 
662
        }
 
663
      }
 
664
      else if (resource->participatesInSqlTransaction())
 
665
      {
 
666
        if ((err= resource_context->getTransactionalStorageEngine()->rollback(session, normal_transaction)))
 
667
        {
 
668
          my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 
669
          error= 1;
 
670
        }
 
671
        else
 
672
        {
 
673
          status_var_increment(session->status_var.ha_rollback_count);
 
674
        }
 
675
      }
 
676
      resource_context->reset(); /* keep it conveniently zero-filled */
593
677
    }
594
 
    trans->ha_list= 0;
595
 
    trans->no_2pc=0;
596
678
    
597
679
    /* 
598
680
     * We need to signal the ROLLBACK to ReplicationServices here
606
688
 
607
689
    if (is_real_trans)
608
690
      session->transaction.xid_state.xid.null();
609
 
    if (all)
 
691
    if (normal_transaction)
610
692
    {
611
693
      session->variables.tx_isolation=session->session_tx_isolation;
612
694
      session->transaction.cleanup();
613
695
    }
614
696
  }
615
 
  if (all)
 
697
  if (normal_transaction)
616
698
    session->transaction_rollback_request= false;
617
699
 
618
700
  /*
619
 
    If a non-transactional table was updated, warn; don't warn if this is a
620
 
    slave thread (because when a slave thread executes a ROLLBACK, it has
621
 
    been read from the binary log, so it's 100% sure and normal to produce
622
 
    error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
623
 
    slave SQL thread, it would not stop the thread but just be printed in
624
 
    the error log; but we don't want users to wonder why they have this
625
 
    message in the error log, so we don't send it.
626
 
  */
627
 
  if (is_real_trans && session->transaction.all.modified_non_trans_table && session->killed != Session::KILL_CONNECTION)
 
701
   * If a non-transactional table was updated, warn the user
 
702
   */
 
703
  if (is_real_trans &&
 
704
      session->transaction.all.hasModifiedNonTransData() &&
 
705
      session->killed != Session::KILL_CONNECTION)
 
706
  {
628
707
    push_warning(session, DRIZZLE_ERROR::WARN_LEVEL_WARN,
629
708
                 ER_WARNING_NOT_COMPLETE_ROLLBACK,
630
709
                 ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
 
710
  }
 
711
  trans->reset();
631
712
  return error;
632
713
}
633
714
 
644
725
*/
645
726
int TransactionServices::ha_autocommit_or_rollback(Session *session, int error)
646
727
{
647
 
  if (session->transaction.stmt.ha_list)
 
728
  if (session->transaction.stmt.getResourceContexts().empty() == false)
648
729
  {
649
 
    if (!error)
 
730
    if (! error)
650
731
    {
651
732
      if (ha_commit_trans(session, false))
652
733
        error= 1;
658
739
        (void) ha_rollback_trans(session, true);
659
740
    }
660
741
 
661
 
    session->variables.tx_isolation=session->session_tx_isolation;
 
742
    session->variables.tx_isolation= session->session_tx_isolation;
662
743
  }
663
 
 
664
744
  return error;
665
745
}
666
746
 
709
789
  return 0;
710
790
}
711
791
 
 
792
struct ResourceContextCompare : public std::binary_function<ResourceContext *, ResourceContext *, bool>
 
793
{
 
794
  result_type operator()(const ResourceContext *lhs, const ResourceContext *rhs) const
 
795
  {
 
796
    /* The below is perfectly fine, since we're simply comparing addresses for the underlying
 
797
     * resources aren't the same... */
 
798
    return reinterpret_cast<uint64_t>(lhs->getMonitored()) < reinterpret_cast<uint64_t>(rhs->getMonitored());
 
799
  }
 
800
};
712
801
 
713
802
int TransactionServices::ha_rollback_to_savepoint(Session *session, NamedSavepoint &sv)
714
803
{
715
804
  int error= 0;
716
 
  Session_TRANS *trans= &session->transaction.all;
717
 
  Ha_trx_info *ha_info, *ha_info_next;
 
805
  TransactionContext *trans= &session->transaction.all;
 
806
  TransactionContext::ResourceContexts &tran_resource_contexts= trans->getResourceContexts();
 
807
  TransactionContext::ResourceContexts &sv_resource_contexts= sv.getResourceContexts();
718
808
 
719
 
  trans->no_2pc=0;
 
809
  trans->no_2pc= false;
720
810
  /*
721
811
    rolling back to savepoint in all storage engines that were part of the
722
812
    transaction when the savepoint was set
723
813
  */
724
 
  for (ha_info= sv.ha_list; ha_info; ha_info= ha_info->next())
 
814
  for (TransactionContext::ResourceContexts::iterator it= sv_resource_contexts.begin();
 
815
       it != sv_resource_contexts.end();
 
816
       ++it)
725
817
  {
726
818
    int err;
727
 
    plugin::StorageEngine *engine= ha_info->engine();
728
 
    assert(engine);
729
 
    if ((err= engine->savepoint_rollback(session, sv)))
730
 
    { // cannot happen
731
 
      my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
732
 
      error= 1;
 
819
    ResourceContext *resource_context= *it;
 
820
 
 
821
    plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
822
 
 
823
    if (resource->participatesInSqlTransaction())
 
824
    {
 
825
      if ((err= resource_context->getTransactionalStorageEngine()->rollbackToSavepoint(session, sv)))
 
826
      {
 
827
        my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 
828
        error= 1;
 
829
      }
 
830
      else
 
831
      {
 
832
        status_var_increment(session->status_var.ha_savepoint_rollback_count);
 
833
      }
733
834
    }
734
 
    status_var_increment(session->status_var.ha_savepoint_rollback_count);
735
 
    trans->no_2pc|= not engine->has_2pc();
 
835
    trans->no_2pc|= not resource->participatesInXaTransaction();
736
836
  }
737
837
  /*
738
838
    rolling back the transaction in all storage engines that were not part of
739
839
    the transaction when the savepoint was set
740
840
  */
741
 
  for (ha_info= trans->ha_list; ha_info != sv.ha_list;
742
 
       ha_info= ha_info_next)
743
841
  {
744
 
    int err;
745
 
    plugin::StorageEngine *engine= ha_info->engine();
746
 
    if ((err= engine->rollback(session, !(0))))
747
 
    { // cannot happen
748
 
      my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
749
 
      error= 1;
 
842
    TransactionContext::ResourceContexts sorted_tran_resource_contexts(tran_resource_contexts);
 
843
    TransactionContext::ResourceContexts sorted_sv_resource_contexts(sv_resource_contexts);
 
844
    TransactionContext::ResourceContexts set_difference_contexts;
 
845
 
 
846
    sort(sorted_tran_resource_contexts.begin(),
 
847
         sorted_tran_resource_contexts.end(),
 
848
         ResourceContextCompare());
 
849
    sort(sorted_sv_resource_contexts.begin(),
 
850
         sorted_sv_resource_contexts.end(),
 
851
         ResourceContextCompare());
 
852
    set_difference(sorted_tran_resource_contexts.begin(),
 
853
                   sorted_tran_resource_contexts.end(),
 
854
                   sorted_sv_resource_contexts.begin(),
 
855
                   sorted_sv_resource_contexts.end(),
 
856
                   set_difference_contexts.begin(),
 
857
                   ResourceContextCompare());
 
858
    /* 
 
859
     * set_difference_contexts now contains all resource contexts
 
860
     * which are in the transaction context but were NOT in the
 
861
     * savepoint's resource contexts.
 
862
     */
 
863
        
 
864
    for (TransactionContext::ResourceContexts::iterator it= set_difference_contexts.begin();
 
865
         it != set_difference_contexts.end();
 
866
         ++it)
 
867
    {
 
868
      ResourceContext *resource_context= *it;
 
869
      int err;
 
870
 
 
871
      plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
872
 
 
873
      if (resource->participatesInSqlTransaction())
 
874
      {
 
875
        if ((err= resource_context->getTransactionalStorageEngine()->rollback(session, !(0))))
 
876
        {
 
877
          my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 
878
          error= 1;
 
879
        }
 
880
        else
 
881
        {
 
882
          status_var_increment(session->status_var.ha_rollback_count);
 
883
        }
 
884
      }
 
885
      resource_context->reset(); /* keep it conveniently zero-filled */
750
886
    }
751
 
    status_var_increment(session->status_var.ha_rollback_count);
752
 
    ha_info_next= ha_info->next();
753
 
    ha_info->reset(); /* keep it conveniently zero-filled */
754
887
  }
755
 
  trans->ha_list= sv.ha_list;
 
888
  trans->setResourceContexts(sv_resource_contexts);
756
889
  return error;
757
890
}
758
891
 
765
898
int TransactionServices::ha_savepoint(Session *session, NamedSavepoint &sv)
766
899
{
767
900
  int error= 0;
768
 
  Session_TRANS *trans= &session->transaction.all;
769
 
  Ha_trx_info *ha_info= trans->ha_list;
770
 
  for (; ha_info; ha_info= ha_info->next())
 
901
  TransactionContext *trans= &session->transaction.all;
 
902
  TransactionContext::ResourceContexts &resource_contexts= trans->getResourceContexts();
 
903
 
 
904
  if (resource_contexts.empty() == false)
771
905
  {
772
 
    int err;
773
 
    plugin::StorageEngine *engine= ha_info->engine();
774
 
    assert(engine);
775
 
#ifdef NOT_IMPLEMENTED /*- TODO (examine this againt the original code base) */
776
 
    if (! engine->savepoint_set)
 
906
    for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
907
         it != resource_contexts.end();
 
908
         ++it)
777
909
    {
778
 
      my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "NamedSavepoint");
779
 
      error= 1;
780
 
      break;
781
 
    } 
782
 
#endif
783
 
    if ((err= engine->savepoint_set(session, sv)))
784
 
    { // cannot happen
785
 
      my_error(ER_GET_ERRNO, MYF(0), err);
786
 
      error= 1;
 
910
      ResourceContext *resource_context= *it;
 
911
      int err;
 
912
 
 
913
      plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
914
 
 
915
      if (resource->participatesInSqlTransaction())
 
916
      {
 
917
        if ((err= resource_context->getTransactionalStorageEngine()->setSavepoint(session, sv)))
 
918
        {
 
919
          my_error(ER_GET_ERRNO, MYF(0), err);
 
920
          error= 1;
 
921
        }
 
922
        else
 
923
        {
 
924
          status_var_increment(session->status_var.ha_savepoint_count);
 
925
        }
 
926
      }
787
927
    }
788
 
    status_var_increment(session->status_var.ha_savepoint_count);
789
928
  }
790
929
  /*
791
 
    Remember the list of registered storage engines. All new
792
 
    engines are prepended to the beginning of the list.
 
930
    Remember the list of registered storage engines.
793
931
  */
794
 
  sv.ha_list= trans->ha_list;
 
932
  sv.setResourceContexts(resource_contexts);
795
933
  return error;
796
934
}
797
935
 
798
936
int TransactionServices::ha_release_savepoint(Session *session, NamedSavepoint &sv)
799
937
{
800
938
  int error= 0;
801
 
  Ha_trx_info *ha_info= sv.ha_list;
802
 
 
803
 
  for (; ha_info; ha_info= ha_info->next())
 
939
 
 
940
  TransactionContext::ResourceContexts &resource_contexts= sv.getResourceContexts();
 
941
 
 
942
  for (TransactionContext::ResourceContexts::iterator it= resource_contexts.begin();
 
943
       it != resource_contexts.end();
 
944
       ++it)
804
945
  {
805
946
    int err;
806
 
    plugin::StorageEngine *engine= ha_info->engine();
807
 
    /* Savepoint life time is enclosed into transaction life time. */
808
 
    assert(engine);
809
 
    if ((err= engine->savepoint_release(session, sv)))
810
 
    { // cannot happen
811
 
      my_error(ER_GET_ERRNO, MYF(0), err);
812
 
      error= 1;
 
947
    ResourceContext *resource_context= *it;
 
948
 
 
949
    plugin::MonitoredInTransaction *resource= resource_context->getMonitored();
 
950
 
 
951
    if (resource->participatesInSqlTransaction())
 
952
    {
 
953
      if ((err= resource_context->getTransactionalStorageEngine()->releaseSavepoint(session, sv)))
 
954
      {
 
955
        my_error(ER_GET_ERRNO, MYF(0), err);
 
956
        error= 1;
 
957
      }
813
958
    }
814
959
  }
815
960
  return error;