Merge lp:~jaypipes/drizzle/explicit-transaction into lp:~drizzle-trunk/drizzle/development

Proposed by Jay Pipes
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jaypipes/drizzle/explicit-transaction
Merge into: lp:~drizzle-trunk/drizzle/development
Prerequisite: lp:~jaypipes/drizzle/explicit-statement
Diff against target: 807 lines (+337/-162)
9 files modified
drizzled/plugin/transactional_storage_engine.cc (+30/-6)
drizzled/plugin/transactional_storage_engine.h (+34/-3)
drizzled/session.cc (+2/-5)
drizzled/session.h (+17/-9)
drizzled/transaction_services.cc (+111/-81)
drizzled/transaction_services.h (+27/-2)
plugin/innobase/handler/ha_innodb.cc (+17/-56)
tests/r/transaction.result (+43/-0)
tests/t/transaction.test (+56/-0)
To merge this branch: bzr merge lp:~jaypipes/drizzle/explicit-transaction
Reviewer Review Type Date Requested Status
Brian Aker Pending
Drizzle Developers Pending
Review via email: mp+20102@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

    Completes the work of removing the weirdness around transaction
    boundaries in the storage engine API.

    * Transactional storage engines are now all explicitly notified
      of the start of a new "normal" transaction in the new PSE API
      method plugin::TransactionalStorageEngine::doStartTransaction()
      This new method takes a start_transaction_option_t as one of its
      parameters, and passing this option allows the storage engine
      API to cleanly signal the start of a consistent snapshot (and in
      the future additional transaction attributes). This meant the
      removal of the old start_consistent_snapshot() method.

    * The TransactionServices component now fully manages the transaction
      boundaries, notification of transaction boundaries to participating
      resource managers (transactional storage engines)

    Adds a simple test case (to be expanded with future XA work) for
    transaction behaviour.

Revision history for this message
Stewart Smith (stewart) wrote :

explicit transaction start++

(i should look at the code too, but love explicit)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'drizzled/plugin/transactional_storage_engine.cc'
--- drizzled/plugin/transactional_storage_engine.cc 2010-02-14 19:27:57 +0000
+++ drizzled/plugin/transactional_storage_engine.cc 2010-02-25 04:43:15 +0000
@@ -2,6 +2,7 @@
2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3 *3 *
4 * Copyright (C) 2008 Sun Microsystems4 * Copyright (C) 2008 Sun Microsystems
5 * Copyright (c) 2009-2010 Jay Pipes <jaypipes@gmail.com>
5 *6 *
6 * This program is free software; you can redistribute it and/or modify7 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by8 * it under the terms of the GNU General Public License as published by
@@ -92,12 +93,35 @@
92 return 0;93 return 0;
93}94}
9495
95int TransactionalStorageEngine::startConsistentSnapshot(Session *session)96struct StartTransactionFunc :public unary_function<TransactionalStorageEngine *, int>
96{97{
97 for_each(vector_of_transactional_engines.begin(), vector_of_transactional_engines.end(),98 Session *session;
98 bind2nd(mem_fun(&TransactionalStorageEngine::doStartConsistentSnapshot),99 start_transaction_option_t options;
99 session));100 StartTransactionFunc(Session *in_session, start_transaction_option_t in_options) :
100 return 0;101 session(in_session),
102 options(in_options)
103 {}
104 result_type operator()(argument_type engine) const
105 {
106 return engine->startTransaction(session, options);
107 }
108};
109
110int TransactionalStorageEngine::notifyStartTransaction(Session *session, start_transaction_option_t options)
111{
112 if (vector_of_transactional_engines.empty())
113 return 0;
114 else
115 {
116 StartTransactionFunc functor(session, options);
117 vector<int> results;
118 results.reserve(vector_of_transactional_engines.size());
119 transform(vector_of_transactional_engines.begin(),
120 vector_of_transactional_engines.end(),
121 results.begin(),
122 functor);
123 return *max_element(results.begin(), results.end());
124 }
101}125}
102126
103bool TransactionalStorageEngine::addPlugin(TransactionalStorageEngine *engine)127bool TransactionalStorageEngine::addPlugin(TransactionalStorageEngine *engine)
104128
=== modified file 'drizzled/plugin/transactional_storage_engine.h'
--- drizzled/plugin/transactional_storage_engine.h 2010-02-25 04:43:14 +0000
+++ drizzled/plugin/transactional_storage_engine.h 2010-02-25 04:43:15 +0000
@@ -2,6 +2,7 @@
2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3 *3 *
4 * Copyright (C) 2008 Sun Microsystems4 * Copyright (C) 2008 Sun Microsystems
5 * Copyright (c) 2009-2010 Jay Pipes <jaypipes@gmail.com>
5 *6 *
6 * This program is free software; you can redistribute it and/or modify7 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by8 * it under the terms of the GNU General Public License as published by
@@ -20,6 +21,7 @@
20#ifndef DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H21#ifndef DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H
21#define DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H22#define DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H
2223
24#include "drizzled/definitions.h" /* for start_transaction_option_t */
23#include "drizzled/plugin/storage_engine.h"25#include "drizzled/plugin/storage_engine.h"
24#include "drizzled/transaction_services.h"26#include "drizzled/transaction_services.h"
2527
@@ -63,6 +65,13 @@
6365
64 virtual ~TransactionalStorageEngine();66 virtual ~TransactionalStorageEngine();
6567
68 int startTransaction(Session *session, start_transaction_option_t options)
69 {
70 TransactionServices &transaction_services= TransactionServices::singleton();
71 transaction_services.registerResourceForTransaction(session, this);
72 return doStartTransaction(session, options);
73 }
74
66 void startStatement(Session *session)75 void startStatement(Session *session)
67 {76 {
68 TransactionServices &transaction_services= TransactionServices::singleton();77 TransactionServices &transaction_services= TransactionServices::singleton();
@@ -103,14 +112,12 @@
103 /** 112 /**
104 * The below static class methods wrap the interaction113 * The below static class methods wrap the interaction
105 * of the vector of transactional storage engines.114 * of the vector of transactional storage engines.
106 *
107 * @todo kill these. they belong in TransactionServices.
108 */115 */
116 static int notifyStartTransaction(Session *session, start_transaction_option_t options);
109 /**117 /**
110 * @todo Kill this one entirely. It's implementation, not interface...118 * @todo Kill this one entirely. It's implementation, not interface...
111 */119 */
112 static int releaseTemporaryLatches(Session *session);120 static int releaseTemporaryLatches(Session *session);
113 static int startConsistentSnapshot(Session *session);
114121
115 /* Class Methods for operating on plugin */122 /* Class Methods for operating on plugin */
116 static bool addPlugin(plugin::TransactionalStorageEngine *engine);123 static bool addPlugin(plugin::TransactionalStorageEngine *engine);
@@ -121,6 +128,30 @@
121128
122 /*129 /*
123 * Indicates to a storage engine the start of a130 * Indicates to a storage engine the start of a
131 * new SQL transaction. This is called ONLY in the following
132 * scenarios:
133 *
134 * 1) An explicit BEGIN WORK/START TRANSACTION is called
135 * 2) After an explicit COMMIT AND CHAIN is called
136 * 3) After an explicit ROLLBACK AND RELEASE is called
137 * 4) When in AUTOCOMMIT mode and directly before a new
138 * SQL statement is started.
139 *
140 * Engines should typically use the doStartStatement()
141 * and doEndStatement() methods to manage transaction state,
142 * since the kernel ALWAYS notifies engines at the start
143 * and end of statement transactions and at the end of the
144 * normal transaction by calling doCommit() or doRollback().
145 */
146 virtual int doStartTransaction(Session *session, start_transaction_option_t options)
147 {
148 (void) session;
149 (void) options;
150 return 0;
151 }
152
153 /*
154 * Indicates to a storage engine the start of a
124 * new SQL statement.155 * new SQL statement.
125 */156 */
126 virtual void doStartStatement(Session *session)157 virtual void doStartStatement(Session *session)
127158
=== modified file 'drizzled/session.cc'
--- drizzled/session.cc 2010-02-22 16:14:47 +0000
+++ drizzled/session.cc 2010-02-25 04:43:15 +0000
@@ -849,12 +849,9 @@
849 options|= OPTION_BEGIN;849 options|= OPTION_BEGIN;
850 server_status|= SERVER_STATUS_IN_TRANS;850 server_status|= SERVER_STATUS_IN_TRANS;
851851
852 if (opt == START_TRANS_OPT_WITH_CONS_SNAPSHOT)852 if (plugin::TransactionalStorageEngine::notifyStartTransaction(this, opt))
853 {853 {
854 if (plugin::TransactionalStorageEngine::startConsistentSnapshot(this))854 result= false;
855 {
856 result= false;
857 }
858 }855 }
859 }856 }
860857
861858
=== modified file 'drizzled/session.h'
--- drizzled/session.h 2010-02-22 16:14:47 +0000
+++ drizzled/session.h 2010-02-25 04:43:15 +0000
@@ -312,15 +312,23 @@
312 */312 */
313 void *ha_ptr;313 void *ha_ptr;
314 /**314 /**
315 0: Life time: one statement within a transaction. If @@autocommit is315 * Resource contexts for both the "statement" and "normal"
316 on, also represents the entire transaction.316 * transactions.
317 @sa trans_register_ha()317 *
318318 * Resource context at index 0:
319 1: Life time: one transaction within a connection.319 *
320 If the storage engine does not participate in a transaction,320 * Life time: one statement within a transaction. If @@autocommit is
321 this should not be used.321 * on, also represents the entire transaction.
322 @sa trans_register_ha()322 *
323 */323 * Resource context at index 1:
324 *
325 * Life time: one transaction within a connection.
326 *
327 * @note
328 *
329 * If the storage engine does not participate in a transaction,
330 * there will not be a resource context.
331 */
324 drizzled::ResourceContext resource_context[2];332 drizzled::ResourceContext resource_context[2];
325333
326 Ha_data() :ha_ptr(NULL) {}334 Ha_data() :ha_ptr(NULL) {}
327335
=== modified file 'drizzled/transaction_services.cc'
--- drizzled/transaction_services.cc 2010-02-25 04:43:14 +0000
+++ drizzled/transaction_services.cc 2010-02-25 04:43:15 +0000
@@ -2,6 +2,7 @@
2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3 *3 *
4 * Copyright (C) 2008 Sun Microsystems4 * Copyright (C) 2008 Sun Microsystems
5 * Copyright (c) Jay Pipes <jaypipes@gmail.com>
5 *6 *
6 * This program is free software; you can redistribute it and/or modify7 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by8 * it under the terms of the GNU General Public License as published by
@@ -245,39 +246,80 @@
245 session->transaction.all list is cleared.246 session->transaction.all list is cleared.
246247
247 When a connection is closed, the current normal transaction, if248 When a connection is closed, the current normal transaction, if
248 any, is rolled back.249 any is currently active, is rolled back.
249250
250 Roles and responsibilities251 Roles and responsibilities
251 --------------------------252 --------------------------
252253
253 The server has no way to know that an engine participates in254 Beginning of SQL Statement (and Statement Transaction)
254 the statement and a transaction has been started255 ------------------------------------------------------
255 in it unless the engine says so. Thus, in order to be256
256 a part of a transaction, the engine must "register" itself.257 At the start of each SQL statement, for each storage engine
257 This is done by invoking trans_register_ha() server call.258 <strong>that is involved in the SQL statement</strong>, the kernel
258 Normally the engine registers itself whenever Cursor::external_lock()259 calls the engine's plugin::StoragEngine::startStatement() method. If the
259 is called. trans_register_ha() can be invoked many times: if260 engine needs to track some data for the statement, it should use
260 an engine is already registered, the call does nothing.261 this method invocation to initialize this data. This is the
261 In case autocommit is not set, the engine must register itself262 beginning of what is called the "statement transaction".
262 twice -- both in the statement list and in the normal transaction263
263 list.264 <strong>For transaction storage engines (those storage engines
264 In which list to register is a parameter of trans_register_ha().265 that inherit from plugin::TransactionalStorageEngine)</strong>, the
265266 kernel automatically determines if the start of the SQL statement
266 Note, that although the registration interface in itself is267 transaction should <em>also</em> begin the normal SQL transaction.
267 fairly clear, the current usage practice often leads to undesired268 This occurs when the connection is in NOT in autocommit mode. If
268 effects. E.g. since a call to trans_register_ha() in most engines269 the kernel detects this, then the kernel automatically starts the
269 is embedded into implementation of Cursor::external_lock(), some270 normal transaction w/ plugin::TransactionalStorageEngine::startTransaction()
270 DDL statements start a transaction (at least from the server271 method and then calls plugin::StorageEngine::startStatement()
271 point of view) even though they are not expected to. E.g.272 afterwards.
272 CREATE TABLE does not start a transaction, since273
273 Cursor::external_lock() is never called during CREATE TABLE. But274 Beginning of an SQL "Normal" Transaction
274 CREATE TABLE ... SELECT does, since Cursor::external_lock() is275 ----------------------------------------
275 called for the table that is being selected from. This has no276
276 practical effects currently, but must be kept in mind277 As noted above, a "normal SQL transaction" may be started when
277 nevertheless.278 an SQL statement is started in a connection and the connection is
278279 NOT in AUTOCOMMIT mode. This is automatically done by the kernel.
279 Once an engine is registered, the server will do the rest280
280 of the work.281 In addition, when a user executes a START TRANSACTION or
282 BEGIN WORK statement in a connection, the kernel explicitly
283 calls each transactional storage engine's startTransaction() method.
284
285 Ending of an SQL Statement (and Statement Transaction)
286 ------------------------------------------------------
287
288 At the end of each SQL statement, for each of the aforementioned
289 involved storage engines, the kernel calls the engine's
290 plugin::StorageEngine::endStatement() method. If the engine
291 has initialized or modified some internal data about the
292 statement transaction, it should use this method to reset or destroy
293 this data appropriately.
294
295 Ending of an SQL "Normal" Transaction
296 -------------------------------------
297
298 The end of a normal transaction is either a ROLLBACK or a COMMIT,
299 depending on the success or failure of the statement transaction(s)
300 it encloses.
301
302 The end of a "normal transaction" occurs when any of the following
303 occurs:
304
305 1) If a statement transaction has completed and AUTOCOMMIT is ON,
306 then the normal transaction which encloses the statement
307 transaction ends
308 2) If a COMMIT or ROLLBACK statement occurs on the connection
309 3) Just before a DDL operation occurs, the kernel will implicitly
310 commit the active normal transaction
311
312 Transactions and Non-transactional Storage Engines
313 --------------------------------------------------
314
315 For non-transactional engines, this call can be safely ignored, and
316 the kernel tracks whether a non-transactional engine has changed
317 any data state, and warns the user appropriately if a transaction
318 (statement or normal) is rolled back after such non-transactional
319 data changes have been made.
320
321 XA Two-phase Commit Protocol
322 ----------------------------
281323
282 During statement execution, whenever any of data-modifying324 During statement execution, whenever any of data-modifying
283 PSEA API methods is used, e.g. Cursor::write_row() or325 PSEA API methods is used, e.g. Cursor::write_row() or
@@ -305,47 +347,49 @@
305 Additional notes on DDL and the normal transaction.347 Additional notes on DDL and the normal transaction.
306 ---------------------------------------------------348 ---------------------------------------------------
307349
308 DDLs and operations with non-transactional engines350 CREATE TABLE .. SELECT can start a *new* normal transaction
309 do not "register" in session->transaction lists, and thus do not351 because of the fact that SELECTs on a transactional storage
310 modify the transaction state. Besides, each DDL in352 engine participate in the normal SQL transaction (due to
311 MySQL is prefixed with an implicit normal transaction commit353 isolation level issues and consistent read views).
312 (a call to Session::endActiveTransaction()), and thus leaves nothing
313 to modify.
314 However, as it has been pointed out with CREATE TABLE .. SELECT,
315 some DDL statements can start a *new* transaction.
316354
317 Behaviour of the server in this case is currently badly355 Behaviour of the server in this case is currently badly
318 defined.356 defined.
357
319 DDL statements use a form of "semantic" logging358 DDL statements use a form of "semantic" logging
320 to maintain atomicity: if CREATE TABLE .. SELECT failed,359 to maintain atomicity: if CREATE TABLE .. SELECT failed,
321 the newly created table is deleted.360 the newly created table is deleted.
361
322 In addition, some DDL statements issue interim transaction362 In addition, some DDL statements issue interim transaction
323 commits: e.g. ALTER Table issues a commit after data is copied363 commits: e.g. ALTER TABLE issues a COMMIT after data is copied
324 from the original table to the internal temporary table. Other364 from the original table to the internal temporary table. Other
325 statements, e.g. CREATE TABLE ... SELECT do not always commit365 statements, e.g. CREATE TABLE ... SELECT do not always commit
326 after itself.366 after itself.
367
327 And finally there is a group of DDL statements such as368 And finally there is a group of DDL statements such as
328 RENAME/DROP Table that doesn't start a new transaction369 RENAME/DROP TABLE that doesn't start a new transaction
329 and doesn't commit.370 and doesn't commit.
330371
331 This diversity makes it hard to say what will happen if
332 by chance a stored function is invoked during a DDL --
333 whether any modifications it makes will be committed or not
334 is not clear. Fortunately, SQL grammar of few DDLs allows
335 invocation of a stored function.
336
337 A consistent behaviour is perhaps to always commit the normal372 A consistent behaviour is perhaps to always commit the normal
338 transaction after all DDLs, just like the statement transaction373 transaction after all DDLs, just like the statement transaction
339 is always committed at the end of all statements.374 is always committed at the end of all statements.
340*/375*/
341
342void TransactionServices::registerResourceForStatement(Session *session,376void TransactionServices::registerResourceForStatement(Session *session,
343 plugin::TransactionalStorageEngine *engine)377 plugin::TransactionalStorageEngine *engine)
344{378{
379 if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
380 {
381 /*
382 * Now we automatically register this resource manager for the
383 * normal transaction. This is fine because a statement
384 * transaction registration should always enlist the resource
385 * in the normal transaction which contains the statement
386 * transaction.
387 */
388 registerResourceForTransaction(session, engine);
389 }
390
345 TransactionContext *trans= &session->transaction.stmt;391 TransactionContext *trans= &session->transaction.stmt;
346 ResourceContext *resource_context;392 ResourceContext *resource_context= session->getResourceContext(engine, 0);
347
348 resource_context= session->getResourceContext(engine, 0);
349393
350 if (resource_context->isStarted())394 if (resource_context->isStarted())
351 return; /* already registered, return */395 return; /* already registered, return */
@@ -356,38 +400,28 @@
356 trans->no_2pc|= not engine->hasTwoPhaseCommit();400 trans->no_2pc|= not engine->hasTwoPhaseCommit();
357}401}
358402
359/**403void TransactionServices::registerResourceForTransaction(Session *session,
360 Register a storage engine for a transaction.404 plugin::TransactionalStorageEngine *engine)
361
362 Every storage engine MUST call this function when it starts
363 a transaction or a statement (that is it must be called both for the
364 "beginning of transaction" and "beginning of statement").
365 Only storage engines registered for the transaction/statement
366 will know when to commit/rollback it.
367
368 @note
369 trans_register_ha is idempotent - storage engine may register many
370 times per transaction.
371
372*/
373void TransactionServices::trans_register_ha(Session *session, plugin::TransactionalStorageEngine *engine)
374{405{
375 TransactionContext *trans= &session->transaction.all;406 TransactionContext *trans= &session->transaction.all;
376 ResourceContext *resource_context;407 ResourceContext *resource_context= session->getResourceContext(engine, 1);
408
409 if (resource_context->isStarted())
410 return; /* already registered, return */
377411
378 session->server_status|= SERVER_STATUS_IN_TRANS;412 session->server_status|= SERVER_STATUS_IN_TRANS;
379413
380 resource_context= session->getResourceContext(engine, 1);
381
382 if (resource_context->isStarted())
383 return; /* already registered, return */
384
385 resource_context->setResource(engine);414 resource_context->setResource(engine);
386 trans->registerResource(resource_context);415 trans->registerResource(resource_context);
387416
388 trans->no_2pc|= not engine->hasTwoPhaseCommit();417 trans->no_2pc|= not engine->hasTwoPhaseCommit();
418
389 if (session->transaction.xid_state.xid.is_null())419 if (session->transaction.xid_state.xid.is_null())
390 session->transaction.xid_state.xid.set(session->getQueryId());420 session->transaction.xid_state.xid.set(session->getQueryId());
421
422 /* Only true if user is executing a BEGIN WORK/START TRANSACTION */
423 if (! session->getResourceContext(engine, 0)->isStarted())
424 registerResourceForStatement(session, engine);
391}425}
392426
393/**427/**
@@ -555,6 +589,7 @@
555 {589 {
556 int err;590 int err;
557 ResourceContext *resource_context= *it;591 ResourceContext *resource_context= *it;
592
558 plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource());593 plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource());
559 if ((err= engine->commit(session, normal_transaction)))594 if ((err= engine->commit(session, normal_transaction)))
560 {595 {
@@ -564,7 +599,6 @@
564 status_var_increment(session->status_var.ha_commit_count);599 status_var_increment(session->status_var.ha_commit_count);
565 resource_context->reset(); /* keep it conveniently zero-filled */600 resource_context->reset(); /* keep it conveniently zero-filled */
566 }601 }
567 trans->reset();
568602
569 if (is_real_trans)603 if (is_real_trans)
570 session->transaction.xid_state.xid.null();604 session->transaction.xid_state.xid.null();
@@ -575,6 +609,7 @@
575 session->transaction.cleanup();609 session->transaction.cleanup();
576 }610 }
577 }611 }
612 trans->reset();
578 if (error == 0)613 if (error == 0)
579 {614 {
580 if (is_real_trans)615 if (is_real_trans)
@@ -613,16 +648,16 @@
613 {648 {
614 int err;649 int err;
615 ResourceContext *resource_context= *it;650 ResourceContext *resource_context= *it;
651
616 plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource());652 plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource());
617 if ((err= engine->rollback(session, normal_transaction)))653 if ((err= engine->rollback(session, normal_transaction)))
618 { // cannot happen654 {
619 my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);655 my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
620 error=1;656 error=1;
621 }657 }
622 status_var_increment(session->status_var.ha_rollback_count);658 status_var_increment(session->status_var.ha_rollback_count);
623 resource_context->reset(); /* keep it conveniently zero-filled */659 resource_context->reset(); /* keep it conveniently zero-filled */
624 }660 }
625 trans->reset();
626 661
627 /* 662 /*
628 * We need to signal the ROLLBACK to ReplicationServices here663 * We need to signal the ROLLBACK to ReplicationServices here
@@ -646,14 +681,8 @@
646 session->transaction_rollback_request= false;681 session->transaction_rollback_request= false;
647682
648 /*683 /*
649 If a non-transactional table was updated, warn; don't warn if this is a684 * If a non-transactional table was updated, warn the user
650 slave thread (because when a slave thread executes a ROLLBACK, it has685 */
651 been read from the binary log, so it's 100% sure and normal to produce
652 error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
653 slave SQL thread, it would not stop the thread but just be printed in
654 the error log; but we don't want users to wonder why they have this
655 message in the error log, so we don't send it.
656 */
657 if (is_real_trans &&686 if (is_real_trans &&
658 session->transaction.all.hasModifiedNonTransData() &&687 session->transaction.all.hasModifiedNonTransData() &&
659 session->killed != Session::KILL_CONNECTION)688 session->killed != Session::KILL_CONNECTION)
@@ -662,6 +691,7 @@
662 ER_WARNING_NOT_COMPLETE_ROLLBACK,691 ER_WARNING_NOT_COMPLETE_ROLLBACK,
663 ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));692 ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
664 }693 }
694 trans->reset();
665 return error;695 return error;
666}696}
667697
668698
=== modified file 'drizzled/transaction_services.h'
--- drizzled/transaction_services.h 2010-02-25 04:43:14 +0000
+++ drizzled/transaction_services.h 2010-02-25 04:43:15 +0000
@@ -2,6 +2,7 @@
2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:2 * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3 *3 *
4 * Copyright (C) 2008 Sun Microsystems4 * Copyright (C) 2008 Sun Microsystems
5 * Copyright (c) Jay Pipes <jaypipes@gmail.com>
5 *6 *
6 * This program is free software; you can redistribute it and/or modify7 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by8 * it under the terms of the GNU General Public License as published by
@@ -91,8 +92,32 @@
91 void registerResourceForStatement(Session *session,92 void registerResourceForStatement(Session *session,
92 plugin::TransactionalStorageEngine *engine);93 plugin::TransactionalStorageEngine *engine);
9394
94 /* these are called by storage engines */95 /**
95 void trans_register_ha(Session *session, plugin::TransactionalStorageEngine *engine);96 * Registers a resource manager in the "normal" transaction.
97 *
98 * @note
99 *
100 * This method is idempotent and must be idempotent
101 * because it can be called both by the above
102 * TransactionServices::registerResourceForStatement(),
103 * which occurs at the beginning of each SQL statement,
104 * and also manually when a BEGIN WORK/START TRANSACTION
105 * statement is executed. If the latter case (BEGIN WORK)
106 * is called, then subsequent contained statement transactions
107 * will call this method as well.
108 *
109 * @note
110 *
111 * This method checks to see if the supplied resource
112 * is also registered in the statement transaction, and
113 * if not, registers the resource in the statement
114 * transaction. This happens ONLY when the user has
115 * called BEGIN WORK/START TRANSACTION, which is the only
116 * time when this method is called except from the
117 * TransactionServices::registerResourceForStatement method.
118 */
119 void registerResourceForTransaction(Session *session,
120 plugin::TransactionalStorageEngine *engine);
96};121};
97122
98} /* namespace drizzled */123} /* namespace drizzled */
99124
=== modified file 'plugin/innobase/handler/ha_innodb.cc'
--- plugin/innobase/handler/ha_innodb.cc 2010-02-25 04:43:14 +0000
+++ plugin/innobase/handler/ha_innodb.cc 2010-02-25 04:43:15 +0000
@@ -270,6 +270,7 @@
270 addAlias("INNOBASE");270 addAlias("INNOBASE");
271 }271 }
272private:272private:
273 virtual int doStartTransaction(Session *session, start_transaction_option_t options);
273 virtual void doStartStatement(Session *session);274 virtual void doStartStatement(Session *session);
274 virtual void doEndStatement(Session *session);275 virtual void doEndStatement(Session *session);
275public:276public:
@@ -348,18 +349,6 @@
348 the database name: for example, in 'mysql/data/test'349 the database name: for example, in 'mysql/data/test'
349 the database name is 'test' */350 the database name is 'test' */
350351
351 /*********************************************************************
352 Creates an InnoDB transaction struct for the session if it does not yet have one.
353 Starts a new InnoDB transaction if a transaction is not yet started. And
354 assigns a new snapshot for a consistent read if the transaction does not yet
355 have one. */
356 virtual
357 int
358 doStartConsistentSnapshot(
359 /*====================================*/
360 /* out: 0 */
361 Session* session); /* in: MySQL thread handle of the user for whom
362 the transaction should be committed */
363 /********************************************************************352 /********************************************************************
364 Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes353 Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes
365 the logs, and the name of this function should be innobase_checkpoint. */354 the logs, and the name of this function should be innobase_checkpoint. */
@@ -1506,27 +1495,6 @@
1506 update_session(session);1495 update_session(session);
1507}1496}
15081497
1509/*********************************************************************//**
1510Registers an InnoDB transaction in MySQL, so that the MySQL XA code knows
1511to call the InnoDB prepare and commit, or rollback for the transaction. This
1512MUST be called for every transaction for which the user may call commit or
1513rollback. Calling this several times to register the same transaction is
1514allowed, too.
1515This function also registers the current SQL statement. */
1516static inline
1517void
1518innobase_register_trx_and_stmt(
1519/*===========================*/
1520 plugin::TransactionalStorageEngine *engine, /*!< in: Innobase StorageEngine */
1521 Session* session) /*!< in: MySQL thd (connection) object */
1522{
1523 if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
1524 /* No autocommit mode, register for a transaction */
1525 TransactionServices &transaction_services= TransactionServices::singleton();
1526 transaction_services.trans_register_ha(session, engine);
1527 }
1528}
1529
1530/*****************************************************************//**1498/*****************************************************************//**
1531Convert an SQL identifier to the MySQL system_charset_info (UTF-8)1499Convert an SQL identifier to the MySQL system_charset_info (UTF-8)
1532and quote it if needed.1500and quote it if needed.
@@ -2089,41 +2057,35 @@
2089have one.2057have one.
2090@return 0 */2058@return 0 */
2091int2059int
2092InnobaseEngine::doStartConsistentSnapshot(2060InnobaseEngine::doStartTransaction(
2093/*====================================*/2061/*====================================*/
2094 Session* session) /*!< in: MySQL thread handle of the user for whom2062 Session* session, /*!< in: MySQL thread handle of the user for whom
2095 the transaction should be committed */2063 the transaction should be committed */
2064 start_transaction_option_t options)
2096{2065{
2097 trx_t* trx;
2098
2099 assert(this == innodb_engine_ptr);2066 assert(this == innodb_engine_ptr);
21002067
2101 /* Create a new trx struct for session, if it does not yet have one */2068 /* Create a new trx struct for session, if it does not yet have one */
21022069 trx_t *trx = check_trx_exists(session);
2103 trx = check_trx_exists(session);
21042070
2105 /* This is just to play safe: release a possible FIFO ticket and2071 /* This is just to play safe: release a possible FIFO ticket and
2106 search latch. Since we will reserve the kernel mutex, we have to2072 search latch. Since we will reserve the kernel mutex, we have to
2107 release the search system latch first to obey the latching order. */2073 release the search system latch first to obey the latching order. */
2108
2109 innobase_release_stat_resources(trx);2074 innobase_release_stat_resources(trx);
21102075
2111 /* If the transaction is not started yet, start it */2076 /* If the transaction is not started yet, start it */
2112
2113 trx_start_if_not_started(trx);2077 trx_start_if_not_started(trx);
21142078
2115 /* Assign a read view if the transaction does not have it yet */2079 /* Assign a read view if the transaction does not have it yet */
21162080 if (options == START_TRANS_OPT_WITH_CONS_SNAPSHOT)
2117 trx_assign_read_view(trx);2081 trx_assign_read_view(trx);
21182082
2119 /* Set the MySQL flag to mark that there is an active transaction */2083 /* Set the Drizzle flag to mark that there is an active transaction */
2120
2121 if (trx->active_trans == 0) {2084 if (trx->active_trans == 0) {
2122 innobase_register_trx_and_stmt(this, current_session);2085 trx->active_trans= 1;
2123 trx->active_trans = 1;
2124 }2086 }
21252087
2126 return(0);2088 return 0;
2127}2089}
21282090
2129/*****************************************************************//**2091/*****************************************************************//**
@@ -2153,9 +2115,10 @@
21532115
2154 /* The flag trx->active_trans is set to 1 in2116 /* The flag trx->active_trans is set to 1 in
21552117
2156 1. ::external_lock(),2118 1. ::external_lock()
2157 4. InnobaseEngine::setSavepoint(),2119 2 InnobaseEngine::doStartStatement()
2158 6. InnobaseEngine::doStartConsistentSnapshot(),2120 3. InnobaseEngine::setSavepoint()
2121 4. InnobaseEngine::doStartTransaction()
21592122
2160 and it is only set to 0 in a commit or a rollback. If it is 0 we know2123 and it is only set to 0 in a commit or a rollback. If it is 0 we know
2161 there cannot be resources to be freed and we could return immediately.2124 there cannot be resources to be freed and we could return immediately.
@@ -8031,7 +7994,6 @@
8031 * @todo this should go away7994 * @todo this should go away
8032 */7995 */
8033 if (trx->active_trans == 0) {7996 if (trx->active_trans == 0) {
8034 innobase_register_trx_and_stmt(innodb_engine_ptr, session);
8035 trx->active_trans= 1;7997 trx->active_trans= 1;
8036 }7998 }
8037}7999}
@@ -8053,6 +8015,7 @@
8053 if (trx->active_trans != 0)8015 if (trx->active_trans != 0)
8054 {8016 {
8055 commit(session, TRUE);8017 commit(session, TRUE);
8018 trx->active_trans= 0;
8056 }8019 }
8057 }8020 }
8058 else8021 else
@@ -8065,8 +8028,6 @@
8065 read_view_close_for_mysql(trx);8028 read_view_close_for_mysql(trx);
8066 }8029 }
8067 }8030 }
8068
8069 trx->active_trans= 0;
8070}8031}
80718032
8072/*******************************************************************//**8033/*******************************************************************//**
80738034
=== added file 'tests/r/transaction.result'
--- tests/r/transaction.result 1970-01-01 00:00:00 +0000
+++ tests/r/transaction.result 2010-02-25 04:43:15 +0000
@@ -0,0 +1,43 @@
1DROP TABLE IF EXISTS t1_trx, t1_non_trx;
2SET AUTOCOMMIT= 0;
3CREATE TABLE t1_trx (
4k VARCHAR(10) NOT NULL
5, v VARCHAR(10) NOT NULL
6, PRIMARY KEY (k)
7) ENGINE=InnoDB;
8CREATE TEMPORARY TABLE t1_non_trx (
9k VARCHAR(10) NOT NULL
10, v VARCHAR(10) NOT NULL
11, PRIMARY KEY (k)
12) ENGINE=MyISAM;
13START TRANSACTION;
14INSERT INTO t1_trx VALUES ('key1','value1');
15INSERT INTO t1_trx VALUES ('key2','value2');
16INSERT INTO t1_non_trx VALUES ('key1','value1');
17INSERT INTO t1_non_trx VALUES ('key2','value2');
18ROLLBACK;
19Warnings:
20Warning 1196 Some non-transactional changed tables couldn't be rolled back
21Expected warning about non-trx data changes not being rolled back
22SELECT * FROM t1_trx;
23k v
24SELECT * FROM t1_non_trx;
25k v
26key1 value1
27key2 value2
28START TRANSACTION;
29INSERT INTO t1_trx VALUES ('key1','value1');
30INSERT INTO t1_trx VALUES ('key2','value2');
31SELECT t1_trx.k, t1_trx.v
32FROM t1_trx
33INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k;
34k v
35key1 value1
36key2 value2
37ROLLBACK;
38SELECT t1_trx.k, t1_trx.v
39FROM t1_trx
40INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k;
41k v
42DROP TABLE t1_trx;
43DROP TABLE t1_non_trx;
044
=== added file 'tests/t/transaction.test'
--- tests/t/transaction.test 1970-01-01 00:00:00 +0000
+++ tests/t/transaction.test 2010-02-25 04:43:15 +0000
@@ -0,0 +1,56 @@
1# Tests a number of things related to transactions:
2#
3# 1. Interaction of more than one engine in a transaction
4# 2. Correct commit and rollback behaviour
5# 3. XA protocol communication and recovery
6
7--disable_warnings
8DROP TABLE IF EXISTS t1_trx, t1_non_trx;
9--enable_warnings
10
11SET AUTOCOMMIT= 0;
12
13CREATE TABLE t1_trx (
14 k VARCHAR(10) NOT NULL
15, v VARCHAR(10) NOT NULL
16, PRIMARY KEY (k)
17) ENGINE=InnoDB;
18
19CREATE TEMPORARY TABLE t1_non_trx (
20 k VARCHAR(10) NOT NULL
21, v VARCHAR(10) NOT NULL
22, PRIMARY KEY (k)
23) ENGINE=MyISAM;
24
25START TRANSACTION;
26
27INSERT INTO t1_trx VALUES ('key1','value1');
28INSERT INTO t1_trx VALUES ('key2','value2');
29
30INSERT INTO t1_non_trx VALUES ('key1','value1');
31INSERT INTO t1_non_trx VALUES ('key2','value2');
32
33ROLLBACK;
34
35--echo Expected warning about non-trx data changes not being rolled back
36
37SELECT * FROM t1_trx;
38SELECT * FROM t1_non_trx;
39
40START TRANSACTION;
41
42INSERT INTO t1_trx VALUES ('key1','value1');
43INSERT INTO t1_trx VALUES ('key2','value2');
44
45SELECT t1_trx.k, t1_trx.v
46FROM t1_trx
47INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k;
48
49ROLLBACK;
50
51SELECT t1_trx.k, t1_trx.v
52FROM t1_trx
53INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k;
54
55DROP TABLE t1_trx;
56DROP TABLE t1_non_trx;