Merge lp:~jaypipes/drizzle/explicit-transaction into lp:~drizzle-trunk/drizzle/development
- explicit-transaction
- Merge into 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 |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Aker | Pending | ||
Drizzle Developers | Pending | ||
Review via email: mp+20102@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote : | # |
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
1 | === modified file 'drizzled/plugin/transactional_storage_engine.cc' |
2 | --- drizzled/plugin/transactional_storage_engine.cc 2010-02-14 19:27:57 +0000 |
3 | +++ drizzled/plugin/transactional_storage_engine.cc 2010-02-25 04:43:15 +0000 |
4 | @@ -2,6 +2,7 @@ |
5 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: |
6 | * |
7 | * Copyright (C) 2008 Sun Microsystems |
8 | + * Copyright (c) 2009-2010 Jay Pipes <jaypipes@gmail.com> |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License as published by |
12 | @@ -92,12 +93,35 @@ |
13 | return 0; |
14 | } |
15 | |
16 | -int TransactionalStorageEngine::startConsistentSnapshot(Session *session) |
17 | -{ |
18 | - for_each(vector_of_transactional_engines.begin(), vector_of_transactional_engines.end(), |
19 | - bind2nd(mem_fun(&TransactionalStorageEngine::doStartConsistentSnapshot), |
20 | - session)); |
21 | - return 0; |
22 | +struct StartTransactionFunc :public unary_function<TransactionalStorageEngine *, int> |
23 | +{ |
24 | + Session *session; |
25 | + start_transaction_option_t options; |
26 | + StartTransactionFunc(Session *in_session, start_transaction_option_t in_options) : |
27 | + session(in_session), |
28 | + options(in_options) |
29 | + {} |
30 | + result_type operator()(argument_type engine) const |
31 | + { |
32 | + return engine->startTransaction(session, options); |
33 | + } |
34 | +}; |
35 | + |
36 | +int TransactionalStorageEngine::notifyStartTransaction(Session *session, start_transaction_option_t options) |
37 | +{ |
38 | + if (vector_of_transactional_engines.empty()) |
39 | + return 0; |
40 | + else |
41 | + { |
42 | + StartTransactionFunc functor(session, options); |
43 | + vector<int> results; |
44 | + results.reserve(vector_of_transactional_engines.size()); |
45 | + transform(vector_of_transactional_engines.begin(), |
46 | + vector_of_transactional_engines.end(), |
47 | + results.begin(), |
48 | + functor); |
49 | + return *max_element(results.begin(), results.end()); |
50 | + } |
51 | } |
52 | |
53 | bool TransactionalStorageEngine::addPlugin(TransactionalStorageEngine *engine) |
54 | |
55 | === modified file 'drizzled/plugin/transactional_storage_engine.h' |
56 | --- drizzled/plugin/transactional_storage_engine.h 2010-02-25 04:43:14 +0000 |
57 | +++ drizzled/plugin/transactional_storage_engine.h 2010-02-25 04:43:15 +0000 |
58 | @@ -2,6 +2,7 @@ |
59 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: |
60 | * |
61 | * Copyright (C) 2008 Sun Microsystems |
62 | + * Copyright (c) 2009-2010 Jay Pipes <jaypipes@gmail.com> |
63 | * |
64 | * This program is free software; you can redistribute it and/or modify |
65 | * it under the terms of the GNU General Public License as published by |
66 | @@ -20,6 +21,7 @@ |
67 | #ifndef DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H |
68 | #define DRIZZLED_PLUGIN_TRANSACTIONAL_STORAGE_ENGINE_H |
69 | |
70 | +#include "drizzled/definitions.h" /* for start_transaction_option_t */ |
71 | #include "drizzled/plugin/storage_engine.h" |
72 | #include "drizzled/transaction_services.h" |
73 | |
74 | @@ -63,6 +65,13 @@ |
75 | |
76 | virtual ~TransactionalStorageEngine(); |
77 | |
78 | + int startTransaction(Session *session, start_transaction_option_t options) |
79 | + { |
80 | + TransactionServices &transaction_services= TransactionServices::singleton(); |
81 | + transaction_services.registerResourceForTransaction(session, this); |
82 | + return doStartTransaction(session, options); |
83 | + } |
84 | + |
85 | void startStatement(Session *session) |
86 | { |
87 | TransactionServices &transaction_services= TransactionServices::singleton(); |
88 | @@ -103,14 +112,12 @@ |
89 | /** |
90 | * The below static class methods wrap the interaction |
91 | * of the vector of transactional storage engines. |
92 | - * |
93 | - * @todo kill these. they belong in TransactionServices. |
94 | */ |
95 | + static int notifyStartTransaction(Session *session, start_transaction_option_t options); |
96 | /** |
97 | * @todo Kill this one entirely. It's implementation, not interface... |
98 | */ |
99 | static int releaseTemporaryLatches(Session *session); |
100 | - static int startConsistentSnapshot(Session *session); |
101 | |
102 | /* Class Methods for operating on plugin */ |
103 | static bool addPlugin(plugin::TransactionalStorageEngine *engine); |
104 | @@ -121,6 +128,30 @@ |
105 | |
106 | /* |
107 | * Indicates to a storage engine the start of a |
108 | + * new SQL transaction. This is called ONLY in the following |
109 | + * scenarios: |
110 | + * |
111 | + * 1) An explicit BEGIN WORK/START TRANSACTION is called |
112 | + * 2) After an explicit COMMIT AND CHAIN is called |
113 | + * 3) After an explicit ROLLBACK AND RELEASE is called |
114 | + * 4) When in AUTOCOMMIT mode and directly before a new |
115 | + * SQL statement is started. |
116 | + * |
117 | + * Engines should typically use the doStartStatement() |
118 | + * and doEndStatement() methods to manage transaction state, |
119 | + * since the kernel ALWAYS notifies engines at the start |
120 | + * and end of statement transactions and at the end of the |
121 | + * normal transaction by calling doCommit() or doRollback(). |
122 | + */ |
123 | + virtual int doStartTransaction(Session *session, start_transaction_option_t options) |
124 | + { |
125 | + (void) session; |
126 | + (void) options; |
127 | + return 0; |
128 | + } |
129 | + |
130 | + /* |
131 | + * Indicates to a storage engine the start of a |
132 | * new SQL statement. |
133 | */ |
134 | virtual void doStartStatement(Session *session) |
135 | |
136 | === modified file 'drizzled/session.cc' |
137 | --- drizzled/session.cc 2010-02-22 16:14:47 +0000 |
138 | +++ drizzled/session.cc 2010-02-25 04:43:15 +0000 |
139 | @@ -849,12 +849,9 @@ |
140 | options|= OPTION_BEGIN; |
141 | server_status|= SERVER_STATUS_IN_TRANS; |
142 | |
143 | - if (opt == START_TRANS_OPT_WITH_CONS_SNAPSHOT) |
144 | + if (plugin::TransactionalStorageEngine::notifyStartTransaction(this, opt)) |
145 | { |
146 | - if (plugin::TransactionalStorageEngine::startConsistentSnapshot(this)) |
147 | - { |
148 | - result= false; |
149 | - } |
150 | + result= false; |
151 | } |
152 | } |
153 | |
154 | |
155 | === modified file 'drizzled/session.h' |
156 | --- drizzled/session.h 2010-02-22 16:14:47 +0000 |
157 | +++ drizzled/session.h 2010-02-25 04:43:15 +0000 |
158 | @@ -312,15 +312,23 @@ |
159 | */ |
160 | void *ha_ptr; |
161 | /** |
162 | - 0: Life time: one statement within a transaction. If @@autocommit is |
163 | - on, also represents the entire transaction. |
164 | - @sa trans_register_ha() |
165 | - |
166 | - 1: Life time: one transaction within a connection. |
167 | - If the storage engine does not participate in a transaction, |
168 | - this should not be used. |
169 | - @sa trans_register_ha() |
170 | - */ |
171 | + * Resource contexts for both the "statement" and "normal" |
172 | + * transactions. |
173 | + * |
174 | + * Resource context at index 0: |
175 | + * |
176 | + * Life time: one statement within a transaction. If @@autocommit is |
177 | + * on, also represents the entire transaction. |
178 | + * |
179 | + * Resource context at index 1: |
180 | + * |
181 | + * Life time: one transaction within a connection. |
182 | + * |
183 | + * @note |
184 | + * |
185 | + * If the storage engine does not participate in a transaction, |
186 | + * there will not be a resource context. |
187 | + */ |
188 | drizzled::ResourceContext resource_context[2]; |
189 | |
190 | Ha_data() :ha_ptr(NULL) {} |
191 | |
192 | === modified file 'drizzled/transaction_services.cc' |
193 | --- drizzled/transaction_services.cc 2010-02-25 04:43:14 +0000 |
194 | +++ drizzled/transaction_services.cc 2010-02-25 04:43:15 +0000 |
195 | @@ -2,6 +2,7 @@ |
196 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: |
197 | * |
198 | * Copyright (C) 2008 Sun Microsystems |
199 | + * Copyright (c) Jay Pipes <jaypipes@gmail.com> |
200 | * |
201 | * This program is free software; you can redistribute it and/or modify |
202 | * it under the terms of the GNU General Public License as published by |
203 | @@ -245,39 +246,80 @@ |
204 | session->transaction.all list is cleared. |
205 | |
206 | When a connection is closed, the current normal transaction, if |
207 | - any, is rolled back. |
208 | + any is currently active, is rolled back. |
209 | |
210 | Roles and responsibilities |
211 | -------------------------- |
212 | |
213 | - The server has no way to know that an engine participates in |
214 | - the statement and a transaction has been started |
215 | - in it unless the engine says so. Thus, in order to be |
216 | - a part of a transaction, the engine must "register" itself. |
217 | - This is done by invoking trans_register_ha() server call. |
218 | - Normally the engine registers itself whenever Cursor::external_lock() |
219 | - is called. trans_register_ha() can be invoked many times: if |
220 | - an engine is already registered, the call does nothing. |
221 | - In case autocommit is not set, the engine must register itself |
222 | - twice -- both in the statement list and in the normal transaction |
223 | - list. |
224 | - In which list to register is a parameter of trans_register_ha(). |
225 | - |
226 | - Note, that although the registration interface in itself is |
227 | - fairly clear, the current usage practice often leads to undesired |
228 | - effects. E.g. since a call to trans_register_ha() in most engines |
229 | - is embedded into implementation of Cursor::external_lock(), some |
230 | - DDL statements start a transaction (at least from the server |
231 | - point of view) even though they are not expected to. E.g. |
232 | - CREATE TABLE does not start a transaction, since |
233 | - Cursor::external_lock() is never called during CREATE TABLE. But |
234 | - CREATE TABLE ... SELECT does, since Cursor::external_lock() is |
235 | - called for the table that is being selected from. This has no |
236 | - practical effects currently, but must be kept in mind |
237 | - nevertheless. |
238 | - |
239 | - Once an engine is registered, the server will do the rest |
240 | - of the work. |
241 | + Beginning of SQL Statement (and Statement Transaction) |
242 | + ------------------------------------------------------ |
243 | + |
244 | + At the start of each SQL statement, for each storage engine |
245 | + <strong>that is involved in the SQL statement</strong>, the kernel |
246 | + calls the engine's plugin::StoragEngine::startStatement() method. If the |
247 | + engine needs to track some data for the statement, it should use |
248 | + this method invocation to initialize this data. This is the |
249 | + beginning of what is called the "statement transaction". |
250 | + |
251 | + <strong>For transaction storage engines (those storage engines |
252 | + that inherit from plugin::TransactionalStorageEngine)</strong>, the |
253 | + kernel automatically determines if the start of the SQL statement |
254 | + transaction should <em>also</em> begin the normal SQL transaction. |
255 | + This occurs when the connection is in NOT in autocommit mode. If |
256 | + the kernel detects this, then the kernel automatically starts the |
257 | + normal transaction w/ plugin::TransactionalStorageEngine::startTransaction() |
258 | + method and then calls plugin::StorageEngine::startStatement() |
259 | + afterwards. |
260 | + |
261 | + Beginning of an SQL "Normal" Transaction |
262 | + ---------------------------------------- |
263 | + |
264 | + As noted above, a "normal SQL transaction" may be started when |
265 | + an SQL statement is started in a connection and the connection is |
266 | + NOT in AUTOCOMMIT mode. This is automatically done by the kernel. |
267 | + |
268 | + In addition, when a user executes a START TRANSACTION or |
269 | + BEGIN WORK statement in a connection, the kernel explicitly |
270 | + calls each transactional storage engine's startTransaction() method. |
271 | + |
272 | + Ending of an SQL Statement (and Statement Transaction) |
273 | + ------------------------------------------------------ |
274 | + |
275 | + At the end of each SQL statement, for each of the aforementioned |
276 | + involved storage engines, the kernel calls the engine's |
277 | + plugin::StorageEngine::endStatement() method. If the engine |
278 | + has initialized or modified some internal data about the |
279 | + statement transaction, it should use this method to reset or destroy |
280 | + this data appropriately. |
281 | + |
282 | + Ending of an SQL "Normal" Transaction |
283 | + ------------------------------------- |
284 | + |
285 | + The end of a normal transaction is either a ROLLBACK or a COMMIT, |
286 | + depending on the success or failure of the statement transaction(s) |
287 | + it encloses. |
288 | + |
289 | + The end of a "normal transaction" occurs when any of the following |
290 | + occurs: |
291 | + |
292 | + 1) If a statement transaction has completed and AUTOCOMMIT is ON, |
293 | + then the normal transaction which encloses the statement |
294 | + transaction ends |
295 | + 2) If a COMMIT or ROLLBACK statement occurs on the connection |
296 | + 3) Just before a DDL operation occurs, the kernel will implicitly |
297 | + commit the active normal transaction |
298 | + |
299 | + Transactions and Non-transactional Storage Engines |
300 | + -------------------------------------------------- |
301 | + |
302 | + For non-transactional engines, this call can be safely ignored, and |
303 | + the kernel tracks whether a non-transactional engine has changed |
304 | + any data state, and warns the user appropriately if a transaction |
305 | + (statement or normal) is rolled back after such non-transactional |
306 | + data changes have been made. |
307 | + |
308 | + XA Two-phase Commit Protocol |
309 | + ---------------------------- |
310 | |
311 | During statement execution, whenever any of data-modifying |
312 | PSEA API methods is used, e.g. Cursor::write_row() or |
313 | @@ -305,47 +347,49 @@ |
314 | Additional notes on DDL and the normal transaction. |
315 | --------------------------------------------------- |
316 | |
317 | - DDLs and operations with non-transactional engines |
318 | - do not "register" in session->transaction lists, and thus do not |
319 | - modify the transaction state. Besides, each DDL in |
320 | - MySQL is prefixed with an implicit normal transaction commit |
321 | - (a call to Session::endActiveTransaction()), and thus leaves nothing |
322 | - to modify. |
323 | - However, as it has been pointed out with CREATE TABLE .. SELECT, |
324 | - some DDL statements can start a *new* transaction. |
325 | + CREATE TABLE .. SELECT can start a *new* normal transaction |
326 | + because of the fact that SELECTs on a transactional storage |
327 | + engine participate in the normal SQL transaction (due to |
328 | + isolation level issues and consistent read views). |
329 | |
330 | Behaviour of the server in this case is currently badly |
331 | defined. |
332 | + |
333 | DDL statements use a form of "semantic" logging |
334 | to maintain atomicity: if CREATE TABLE .. SELECT failed, |
335 | the newly created table is deleted. |
336 | + |
337 | In addition, some DDL statements issue interim transaction |
338 | - commits: e.g. ALTER Table issues a commit after data is copied |
339 | + commits: e.g. ALTER TABLE issues a COMMIT after data is copied |
340 | from the original table to the internal temporary table. Other |
341 | statements, e.g. CREATE TABLE ... SELECT do not always commit |
342 | after itself. |
343 | + |
344 | And finally there is a group of DDL statements such as |
345 | - RENAME/DROP Table that doesn't start a new transaction |
346 | + RENAME/DROP TABLE that doesn't start a new transaction |
347 | and doesn't commit. |
348 | |
349 | - This diversity makes it hard to say what will happen if |
350 | - by chance a stored function is invoked during a DDL -- |
351 | - whether any modifications it makes will be committed or not |
352 | - is not clear. Fortunately, SQL grammar of few DDLs allows |
353 | - invocation of a stored function. |
354 | - |
355 | A consistent behaviour is perhaps to always commit the normal |
356 | transaction after all DDLs, just like the statement transaction |
357 | is always committed at the end of all statements. |
358 | */ |
359 | - |
360 | void TransactionServices::registerResourceForStatement(Session *session, |
361 | plugin::TransactionalStorageEngine *engine) |
362 | { |
363 | + if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) |
364 | + { |
365 | + /* |
366 | + * Now we automatically register this resource manager for the |
367 | + * normal transaction. This is fine because a statement |
368 | + * transaction registration should always enlist the resource |
369 | + * in the normal transaction which contains the statement |
370 | + * transaction. |
371 | + */ |
372 | + registerResourceForTransaction(session, engine); |
373 | + } |
374 | + |
375 | TransactionContext *trans= &session->transaction.stmt; |
376 | - ResourceContext *resource_context; |
377 | - |
378 | - resource_context= session->getResourceContext(engine, 0); |
379 | + ResourceContext *resource_context= session->getResourceContext(engine, 0); |
380 | |
381 | if (resource_context->isStarted()) |
382 | return; /* already registered, return */ |
383 | @@ -356,38 +400,28 @@ |
384 | trans->no_2pc|= not engine->hasTwoPhaseCommit(); |
385 | } |
386 | |
387 | -/** |
388 | - Register a storage engine for a transaction. |
389 | - |
390 | - Every storage engine MUST call this function when it starts |
391 | - a transaction or a statement (that is it must be called both for the |
392 | - "beginning of transaction" and "beginning of statement"). |
393 | - Only storage engines registered for the transaction/statement |
394 | - will know when to commit/rollback it. |
395 | - |
396 | - @note |
397 | - trans_register_ha is idempotent - storage engine may register many |
398 | - times per transaction. |
399 | - |
400 | -*/ |
401 | -void TransactionServices::trans_register_ha(Session *session, plugin::TransactionalStorageEngine *engine) |
402 | +void TransactionServices::registerResourceForTransaction(Session *session, |
403 | + plugin::TransactionalStorageEngine *engine) |
404 | { |
405 | TransactionContext *trans= &session->transaction.all; |
406 | - ResourceContext *resource_context; |
407 | + ResourceContext *resource_context= session->getResourceContext(engine, 1); |
408 | + |
409 | + if (resource_context->isStarted()) |
410 | + return; /* already registered, return */ |
411 | |
412 | session->server_status|= SERVER_STATUS_IN_TRANS; |
413 | |
414 | - resource_context= session->getResourceContext(engine, 1); |
415 | - |
416 | - if (resource_context->isStarted()) |
417 | - return; /* already registered, return */ |
418 | - |
419 | resource_context->setResource(engine); |
420 | trans->registerResource(resource_context); |
421 | |
422 | trans->no_2pc|= not engine->hasTwoPhaseCommit(); |
423 | + |
424 | if (session->transaction.xid_state.xid.is_null()) |
425 | session->transaction.xid_state.xid.set(session->getQueryId()); |
426 | + |
427 | + /* Only true if user is executing a BEGIN WORK/START TRANSACTION */ |
428 | + if (! session->getResourceContext(engine, 0)->isStarted()) |
429 | + registerResourceForStatement(session, engine); |
430 | } |
431 | |
432 | /** |
433 | @@ -555,6 +589,7 @@ |
434 | { |
435 | int err; |
436 | ResourceContext *resource_context= *it; |
437 | + |
438 | plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource()); |
439 | if ((err= engine->commit(session, normal_transaction))) |
440 | { |
441 | @@ -564,7 +599,6 @@ |
442 | status_var_increment(session->status_var.ha_commit_count); |
443 | resource_context->reset(); /* keep it conveniently zero-filled */ |
444 | } |
445 | - trans->reset(); |
446 | |
447 | if (is_real_trans) |
448 | session->transaction.xid_state.xid.null(); |
449 | @@ -575,6 +609,7 @@ |
450 | session->transaction.cleanup(); |
451 | } |
452 | } |
453 | + trans->reset(); |
454 | if (error == 0) |
455 | { |
456 | if (is_real_trans) |
457 | @@ -613,16 +648,16 @@ |
458 | { |
459 | int err; |
460 | ResourceContext *resource_context= *it; |
461 | + |
462 | plugin::TransactionalStorageEngine *engine= static_cast<plugin::TransactionalStorageEngine *>(resource_context->getResource()); |
463 | if ((err= engine->rollback(session, normal_transaction))) |
464 | - { // cannot happen |
465 | + { |
466 | my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); |
467 | error=1; |
468 | } |
469 | status_var_increment(session->status_var.ha_rollback_count); |
470 | resource_context->reset(); /* keep it conveniently zero-filled */ |
471 | } |
472 | - trans->reset(); |
473 | |
474 | /* |
475 | * We need to signal the ROLLBACK to ReplicationServices here |
476 | @@ -646,14 +681,8 @@ |
477 | session->transaction_rollback_request= false; |
478 | |
479 | /* |
480 | - If a non-transactional table was updated, warn; don't warn if this is a |
481 | - slave thread (because when a slave thread executes a ROLLBACK, it has |
482 | - been read from the binary log, so it's 100% sure and normal to produce |
483 | - error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the |
484 | - slave SQL thread, it would not stop the thread but just be printed in |
485 | - the error log; but we don't want users to wonder why they have this |
486 | - message in the error log, so we don't send it. |
487 | - */ |
488 | + * If a non-transactional table was updated, warn the user |
489 | + */ |
490 | if (is_real_trans && |
491 | session->transaction.all.hasModifiedNonTransData() && |
492 | session->killed != Session::KILL_CONNECTION) |
493 | @@ -662,6 +691,7 @@ |
494 | ER_WARNING_NOT_COMPLETE_ROLLBACK, |
495 | ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); |
496 | } |
497 | + trans->reset(); |
498 | return error; |
499 | } |
500 | |
501 | |
502 | === modified file 'drizzled/transaction_services.h' |
503 | --- drizzled/transaction_services.h 2010-02-25 04:43:14 +0000 |
504 | +++ drizzled/transaction_services.h 2010-02-25 04:43:15 +0000 |
505 | @@ -2,6 +2,7 @@ |
506 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: |
507 | * |
508 | * Copyright (C) 2008 Sun Microsystems |
509 | + * Copyright (c) Jay Pipes <jaypipes@gmail.com> |
510 | * |
511 | * This program is free software; you can redistribute it and/or modify |
512 | * it under the terms of the GNU General Public License as published by |
513 | @@ -91,8 +92,32 @@ |
514 | void registerResourceForStatement(Session *session, |
515 | plugin::TransactionalStorageEngine *engine); |
516 | |
517 | - /* these are called by storage engines */ |
518 | - void trans_register_ha(Session *session, plugin::TransactionalStorageEngine *engine); |
519 | + /** |
520 | + * Registers a resource manager in the "normal" transaction. |
521 | + * |
522 | + * @note |
523 | + * |
524 | + * This method is idempotent and must be idempotent |
525 | + * because it can be called both by the above |
526 | + * TransactionServices::registerResourceForStatement(), |
527 | + * which occurs at the beginning of each SQL statement, |
528 | + * and also manually when a BEGIN WORK/START TRANSACTION |
529 | + * statement is executed. If the latter case (BEGIN WORK) |
530 | + * is called, then subsequent contained statement transactions |
531 | + * will call this method as well. |
532 | + * |
533 | + * @note |
534 | + * |
535 | + * This method checks to see if the supplied resource |
536 | + * is also registered in the statement transaction, and |
537 | + * if not, registers the resource in the statement |
538 | + * transaction. This happens ONLY when the user has |
539 | + * called BEGIN WORK/START TRANSACTION, which is the only |
540 | + * time when this method is called except from the |
541 | + * TransactionServices::registerResourceForStatement method. |
542 | + */ |
543 | + void registerResourceForTransaction(Session *session, |
544 | + plugin::TransactionalStorageEngine *engine); |
545 | }; |
546 | |
547 | } /* namespace drizzled */ |
548 | |
549 | === modified file 'plugin/innobase/handler/ha_innodb.cc' |
550 | --- plugin/innobase/handler/ha_innodb.cc 2010-02-25 04:43:14 +0000 |
551 | +++ plugin/innobase/handler/ha_innodb.cc 2010-02-25 04:43:15 +0000 |
552 | @@ -270,6 +270,7 @@ |
553 | addAlias("INNOBASE"); |
554 | } |
555 | private: |
556 | + virtual int doStartTransaction(Session *session, start_transaction_option_t options); |
557 | virtual void doStartStatement(Session *session); |
558 | virtual void doEndStatement(Session *session); |
559 | public: |
560 | @@ -348,18 +349,6 @@ |
561 | the database name: for example, in 'mysql/data/test' |
562 | the database name is 'test' */ |
563 | |
564 | - /********************************************************************* |
565 | - Creates an InnoDB transaction struct for the session if it does not yet have one. |
566 | - Starts a new InnoDB transaction if a transaction is not yet started. And |
567 | - assigns a new snapshot for a consistent read if the transaction does not yet |
568 | - have one. */ |
569 | - virtual |
570 | - int |
571 | - doStartConsistentSnapshot( |
572 | - /*====================================*/ |
573 | - /* out: 0 */ |
574 | - Session* session); /* in: MySQL thread handle of the user for whom |
575 | - the transaction should be committed */ |
576 | /******************************************************************** |
577 | Flushes InnoDB logs to disk and makes a checkpoint. Really, a commit flushes |
578 | the logs, and the name of this function should be innobase_checkpoint. */ |
579 | @@ -1506,27 +1495,6 @@ |
580 | update_session(session); |
581 | } |
582 | |
583 | -/*********************************************************************//** |
584 | -Registers an InnoDB transaction in MySQL, so that the MySQL XA code knows |
585 | -to call the InnoDB prepare and commit, or rollback for the transaction. This |
586 | -MUST be called for every transaction for which the user may call commit or |
587 | -rollback. Calling this several times to register the same transaction is |
588 | -allowed, too. |
589 | -This function also registers the current SQL statement. */ |
590 | -static inline |
591 | -void |
592 | -innobase_register_trx_and_stmt( |
593 | -/*===========================*/ |
594 | - plugin::TransactionalStorageEngine *engine, /*!< in: Innobase StorageEngine */ |
595 | - Session* session) /*!< in: MySQL thd (connection) object */ |
596 | -{ |
597 | - if (session_test_options(session, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { |
598 | - /* No autocommit mode, register for a transaction */ |
599 | - TransactionServices &transaction_services= TransactionServices::singleton(); |
600 | - transaction_services.trans_register_ha(session, engine); |
601 | - } |
602 | -} |
603 | - |
604 | /*****************************************************************//** |
605 | Convert an SQL identifier to the MySQL system_charset_info (UTF-8) |
606 | and quote it if needed. |
607 | @@ -2089,41 +2057,35 @@ |
608 | have one. |
609 | @return 0 */ |
610 | int |
611 | -InnobaseEngine::doStartConsistentSnapshot( |
612 | +InnobaseEngine::doStartTransaction( |
613 | /*====================================*/ |
614 | - Session* session) /*!< in: MySQL thread handle of the user for whom |
615 | - the transaction should be committed */ |
616 | + Session* session, /*!< in: MySQL thread handle of the user for whom |
617 | + the transaction should be committed */ |
618 | + start_transaction_option_t options) |
619 | { |
620 | - trx_t* trx; |
621 | - |
622 | assert(this == innodb_engine_ptr); |
623 | |
624 | /* Create a new trx struct for session, if it does not yet have one */ |
625 | - |
626 | - trx = check_trx_exists(session); |
627 | + trx_t *trx = check_trx_exists(session); |
628 | |
629 | /* This is just to play safe: release a possible FIFO ticket and |
630 | search latch. Since we will reserve the kernel mutex, we have to |
631 | release the search system latch first to obey the latching order. */ |
632 | - |
633 | innobase_release_stat_resources(trx); |
634 | |
635 | /* If the transaction is not started yet, start it */ |
636 | - |
637 | trx_start_if_not_started(trx); |
638 | |
639 | /* Assign a read view if the transaction does not have it yet */ |
640 | - |
641 | - trx_assign_read_view(trx); |
642 | - |
643 | - /* Set the MySQL flag to mark that there is an active transaction */ |
644 | - |
645 | + if (options == START_TRANS_OPT_WITH_CONS_SNAPSHOT) |
646 | + trx_assign_read_view(trx); |
647 | + |
648 | + /* Set the Drizzle flag to mark that there is an active transaction */ |
649 | if (trx->active_trans == 0) { |
650 | - innobase_register_trx_and_stmt(this, current_session); |
651 | - trx->active_trans = 1; |
652 | + trx->active_trans= 1; |
653 | } |
654 | |
655 | - return(0); |
656 | + return 0; |
657 | } |
658 | |
659 | /*****************************************************************//** |
660 | @@ -2153,9 +2115,10 @@ |
661 | |
662 | /* The flag trx->active_trans is set to 1 in |
663 | |
664 | - 1. ::external_lock(), |
665 | - 4. InnobaseEngine::setSavepoint(), |
666 | - 6. InnobaseEngine::doStartConsistentSnapshot(), |
667 | + 1. ::external_lock() |
668 | + 2 InnobaseEngine::doStartStatement() |
669 | + 3. InnobaseEngine::setSavepoint() |
670 | + 4. InnobaseEngine::doStartTransaction() |
671 | |
672 | and it is only set to 0 in a commit or a rollback. If it is 0 we know |
673 | there cannot be resources to be freed and we could return immediately. |
674 | @@ -8031,7 +7994,6 @@ |
675 | * @todo this should go away |
676 | */ |
677 | if (trx->active_trans == 0) { |
678 | - innobase_register_trx_and_stmt(innodb_engine_ptr, session); |
679 | trx->active_trans= 1; |
680 | } |
681 | } |
682 | @@ -8053,6 +8015,7 @@ |
683 | if (trx->active_trans != 0) |
684 | { |
685 | commit(session, TRUE); |
686 | + trx->active_trans= 0; |
687 | } |
688 | } |
689 | else |
690 | @@ -8065,8 +8028,6 @@ |
691 | read_view_close_for_mysql(trx); |
692 | } |
693 | } |
694 | - |
695 | - trx->active_trans= 0; |
696 | } |
697 | |
698 | /*******************************************************************//** |
699 | |
700 | === added file 'tests/r/transaction.result' |
701 | --- tests/r/transaction.result 1970-01-01 00:00:00 +0000 |
702 | +++ tests/r/transaction.result 2010-02-25 04:43:15 +0000 |
703 | @@ -0,0 +1,43 @@ |
704 | +DROP TABLE IF EXISTS t1_trx, t1_non_trx; |
705 | +SET AUTOCOMMIT= 0; |
706 | +CREATE TABLE t1_trx ( |
707 | +k VARCHAR(10) NOT NULL |
708 | +, v VARCHAR(10) NOT NULL |
709 | +, PRIMARY KEY (k) |
710 | +) ENGINE=InnoDB; |
711 | +CREATE TEMPORARY TABLE t1_non_trx ( |
712 | +k VARCHAR(10) NOT NULL |
713 | +, v VARCHAR(10) NOT NULL |
714 | +, PRIMARY KEY (k) |
715 | +) ENGINE=MyISAM; |
716 | +START TRANSACTION; |
717 | +INSERT INTO t1_trx VALUES ('key1','value1'); |
718 | +INSERT INTO t1_trx VALUES ('key2','value2'); |
719 | +INSERT INTO t1_non_trx VALUES ('key1','value1'); |
720 | +INSERT INTO t1_non_trx VALUES ('key2','value2'); |
721 | +ROLLBACK; |
722 | +Warnings: |
723 | +Warning 1196 Some non-transactional changed tables couldn't be rolled back |
724 | +Expected warning about non-trx data changes not being rolled back |
725 | +SELECT * FROM t1_trx; |
726 | +k v |
727 | +SELECT * FROM t1_non_trx; |
728 | +k v |
729 | +key1 value1 |
730 | +key2 value2 |
731 | +START TRANSACTION; |
732 | +INSERT INTO t1_trx VALUES ('key1','value1'); |
733 | +INSERT INTO t1_trx VALUES ('key2','value2'); |
734 | +SELECT t1_trx.k, t1_trx.v |
735 | +FROM t1_trx |
736 | +INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k; |
737 | +k v |
738 | +key1 value1 |
739 | +key2 value2 |
740 | +ROLLBACK; |
741 | +SELECT t1_trx.k, t1_trx.v |
742 | +FROM t1_trx |
743 | +INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k; |
744 | +k v |
745 | +DROP TABLE t1_trx; |
746 | +DROP TABLE t1_non_trx; |
747 | |
748 | === added file 'tests/t/transaction.test' |
749 | --- tests/t/transaction.test 1970-01-01 00:00:00 +0000 |
750 | +++ tests/t/transaction.test 2010-02-25 04:43:15 +0000 |
751 | @@ -0,0 +1,56 @@ |
752 | +# Tests a number of things related to transactions: |
753 | +# |
754 | +# 1. Interaction of more than one engine in a transaction |
755 | +# 2. Correct commit and rollback behaviour |
756 | +# 3. XA protocol communication and recovery |
757 | + |
758 | +--disable_warnings |
759 | +DROP TABLE IF EXISTS t1_trx, t1_non_trx; |
760 | +--enable_warnings |
761 | + |
762 | +SET AUTOCOMMIT= 0; |
763 | + |
764 | +CREATE TABLE t1_trx ( |
765 | + k VARCHAR(10) NOT NULL |
766 | +, v VARCHAR(10) NOT NULL |
767 | +, PRIMARY KEY (k) |
768 | +) ENGINE=InnoDB; |
769 | + |
770 | +CREATE TEMPORARY TABLE t1_non_trx ( |
771 | + k VARCHAR(10) NOT NULL |
772 | +, v VARCHAR(10) NOT NULL |
773 | +, PRIMARY KEY (k) |
774 | +) ENGINE=MyISAM; |
775 | + |
776 | +START TRANSACTION; |
777 | + |
778 | +INSERT INTO t1_trx VALUES ('key1','value1'); |
779 | +INSERT INTO t1_trx VALUES ('key2','value2'); |
780 | + |
781 | +INSERT INTO t1_non_trx VALUES ('key1','value1'); |
782 | +INSERT INTO t1_non_trx VALUES ('key2','value2'); |
783 | + |
784 | +ROLLBACK; |
785 | + |
786 | +--echo Expected warning about non-trx data changes not being rolled back |
787 | + |
788 | +SELECT * FROM t1_trx; |
789 | +SELECT * FROM t1_non_trx; |
790 | + |
791 | +START TRANSACTION; |
792 | + |
793 | +INSERT INTO t1_trx VALUES ('key1','value1'); |
794 | +INSERT INTO t1_trx VALUES ('key2','value2'); |
795 | + |
796 | +SELECT t1_trx.k, t1_trx.v |
797 | +FROM t1_trx |
798 | +INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k; |
799 | + |
800 | +ROLLBACK; |
801 | + |
802 | +SELECT t1_trx.k, t1_trx.v |
803 | +FROM t1_trx |
804 | +INNER JOIN t1_non_trx ON t1_trx.k = t1_non_trx.k; |
805 | + |
806 | +DROP TABLE t1_trx; |
807 | +DROP TABLE t1_non_trx; |
Completes the work of removing the weirdness around transaction
boundaries in the storage engine API.
* Transactional storage engines are now all explicitly notified :TransactionalS torageEngine: :doStartTransac tion() on_option_ t as one of its t_snapshot( ) method.
of the start of a new "normal" transaction in the new PSE API
method plugin:
This new method takes a start_transacti
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_consisten
* 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.