1 | /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. |
2 | |
3 | This program is free software; you can redistribute it and/or modify |
4 | it under the terms of the GNU General Public License as published by |
5 | the Free Software Foundation; version 2 of the License. |
6 | |
7 | This program is distributed in the hope that it will be useful, |
8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | GNU General Public License for more details. |
11 | |
12 | You should have received a copy of the GNU General Public License |
13 | along with this program; if not, write to the Free Software |
14 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ |
15 | |
16 | |
17 | #ifdef USE_PRAGMA_IMPLEMENTATION |
18 | #pragma implementation // gcc: Class implementation |
19 | #endif |
20 | |
21 | #include "mariadb.h" |
22 | #include "sql_priv.h" |
23 | #include "transaction.h" |
24 | #include "debug_sync.h" // DEBUG_SYNC |
25 | #include "sql_acl.h" |
26 | #include "semisync_master.h" |
27 | |
28 | #ifndef EMBEDDED_LIBRARY |
29 | /** |
30 | Helper: Tell tracker (if any) that transaction ended. |
31 | */ |
32 | static void trans_track_end_trx(THD *thd) |
33 | { |
34 | if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) |
35 | { |
36 | ((Transaction_state_tracker *) |
37 | thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER))->end_trx(thd); |
38 | } |
39 | } |
40 | #else |
41 | #define trans_track_end_trx(A) do{}while(0) |
42 | #endif //EMBEDDED_LIBRARY |
43 | |
44 | |
45 | /** |
46 | Helper: transaction ended, SET TRANSACTION one-shot variables |
47 | revert to session values. Let the transaction state tracker know. |
48 | */ |
49 | void trans_reset_one_shot_chistics(THD *thd) |
50 | { |
51 | #ifndef EMBEDDED_LIBRARY |
52 | if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) |
53 | { |
54 | Transaction_state_tracker *tst= (Transaction_state_tracker *) |
55 | thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); |
56 | |
57 | tst->set_read_flags(thd, TX_READ_INHERIT); |
58 | tst->set_isol_level(thd, TX_ISOL_INHERIT); |
59 | } |
60 | #endif //EMBEDDED_LIBRARY |
61 | thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; |
62 | thd->tx_read_only= thd->variables.tx_read_only; |
63 | } |
64 | |
65 | /* Conditions under which the transaction state must not change. */ |
66 | static bool trans_check(THD *thd) |
67 | { |
68 | enum xa_states xa_state= thd->transaction.xid_state.xa_state; |
69 | DBUG_ENTER("trans_check" ); |
70 | |
71 | /* |
72 | Always commit statement transaction before manipulating with |
73 | the normal one. |
74 | */ |
75 | DBUG_ASSERT(thd->transaction.stmt.is_empty()); |
76 | |
77 | if (unlikely(thd->in_sub_stmt)) |
78 | my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); |
79 | if (xa_state != XA_NOTR) |
80 | my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); |
81 | else |
82 | DBUG_RETURN(FALSE); |
83 | |
84 | DBUG_RETURN(TRUE); |
85 | } |
86 | |
87 | |
88 | /** |
89 | Mark a XA transaction as rollback-only if the RM unilaterally |
90 | rolled back the transaction branch. |
91 | |
92 | @note If a rollback was requested by the RM, this function sets |
93 | the appropriate rollback error code and transits the state |
94 | to XA_ROLLBACK_ONLY. |
95 | |
96 | @return TRUE if transaction was rolled back or if the transaction |
97 | state is XA_ROLLBACK_ONLY. FALSE otherwise. |
98 | */ |
99 | static bool xa_trans_rolled_back(XID_STATE *xid_state) |
100 | { |
101 | if (xid_state->rm_error) |
102 | { |
103 | switch (xid_state->rm_error) { |
104 | case ER_LOCK_WAIT_TIMEOUT: |
105 | my_error(ER_XA_RBTIMEOUT, MYF(0)); |
106 | break; |
107 | case ER_LOCK_DEADLOCK: |
108 | my_error(ER_XA_RBDEADLOCK, MYF(0)); |
109 | break; |
110 | default: |
111 | my_error(ER_XA_RBROLLBACK, MYF(0)); |
112 | } |
113 | xid_state->xa_state= XA_ROLLBACK_ONLY; |
114 | } |
115 | |
116 | return (xid_state->xa_state == XA_ROLLBACK_ONLY); |
117 | } |
118 | |
119 | |
120 | /** |
121 | Rollback the active XA transaction. |
122 | |
123 | @note Resets rm_error before calling ha_rollback(), so |
124 | the thd->transaction.xid structure gets reset |
125 | by ha_rollback() / THD::transaction::cleanup(). |
126 | |
127 | @return TRUE if the rollback failed, FALSE otherwise. |
128 | */ |
129 | |
130 | static bool xa_trans_force_rollback(THD *thd) |
131 | { |
132 | /* |
133 | We must reset rm_error before calling ha_rollback(), |
134 | so thd->transaction.xid structure gets reset |
135 | by ha_rollback()/THD::transaction::cleanup(). |
136 | */ |
137 | thd->transaction.xid_state.rm_error= 0; |
138 | if (WSREP_ON) |
139 | wsrep_register_hton(thd, TRUE); |
140 | if (ha_rollback_trans(thd, true)) |
141 | { |
142 | my_error(ER_XAER_RMERR, MYF(0)); |
143 | return true; |
144 | } |
145 | return false; |
146 | } |
147 | |
148 | |
149 | /** |
150 | Begin a new transaction. |
151 | |
152 | @note Beginning a transaction implicitly commits any current |
153 | transaction and releases existing locks. |
154 | |
155 | @param thd Current thread |
156 | @param flags Transaction flags |
157 | |
158 | @retval FALSE Success |
159 | @retval TRUE Failure |
160 | */ |
161 | |
162 | bool trans_begin(THD *thd, uint flags) |
163 | { |
164 | int res= FALSE; |
165 | #ifndef EMBEDDED_LIBRARY |
166 | Transaction_state_tracker *tst= NULL; |
167 | #endif //EMBEDDED_LIBRARY |
168 | DBUG_ENTER("trans_begin" ); |
169 | |
170 | if (trans_check(thd)) |
171 | DBUG_RETURN(TRUE); |
172 | |
173 | #ifndef EMBEDDED_LIBRARY |
174 | if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) |
175 | tst= (Transaction_state_tracker *) |
176 | thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); |
177 | #endif //EMBEDDED_LIBRARY |
178 | |
179 | thd->locked_tables_list.unlock_locked_tables(thd); |
180 | |
181 | DBUG_ASSERT(!thd->locked_tables_mode); |
182 | |
183 | if (thd->in_multi_stmt_transaction_mode() || |
184 | (thd->variables.option_bits & OPTION_TABLE_LOCK)) |
185 | { |
186 | thd->variables.option_bits&= ~OPTION_TABLE_LOCK; |
187 | if (WSREP_ON) |
188 | wsrep_register_hton(thd, TRUE); |
189 | thd->server_status&= |
190 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
191 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
192 | res= MY_TEST(ha_commit_trans(thd, TRUE)); |
193 | if (WSREP_ON) |
194 | wsrep_post_commit(thd, TRUE); |
195 | } |
196 | |
197 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
198 | |
199 | /* |
200 | The following set should not be needed as transaction state should |
201 | already be reset. We should at some point change this to an assert. |
202 | */ |
203 | thd->transaction.all.reset(); |
204 | thd->has_waiter= false; |
205 | thd->waiting_on_group_commit= false; |
206 | thd->transaction.start_time.reset(thd); |
207 | |
208 | if (res) |
209 | DBUG_RETURN(TRUE); |
210 | |
211 | /* |
212 | Release transactional metadata locks only after the |
213 | transaction has been committed. |
214 | */ |
215 | thd->mdl_context.release_transactional_locks(); |
216 | |
217 | // The RO/RW options are mutually exclusive. |
218 | DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) && |
219 | (flags & MYSQL_START_TRANS_OPT_READ_WRITE))); |
220 | if (flags & MYSQL_START_TRANS_OPT_READ_ONLY) |
221 | { |
222 | thd->tx_read_only= true; |
223 | #ifndef EMBEDDED_LIBRARY |
224 | if (tst) |
225 | tst->set_read_flags(thd, TX_READ_ONLY); |
226 | #endif //EMBEDDED_LIBRARY |
227 | } |
228 | else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE) |
229 | { |
230 | /* |
231 | Explicitly starting a RW transaction when the server is in |
232 | read-only mode, is not allowed unless the user has SUPER priv. |
233 | Implicitly starting a RW transaction is allowed for backward |
234 | compatibility. |
235 | */ |
236 | const bool user_is_super= |
237 | MY_TEST(thd->security_ctx->master_access & SUPER_ACL); |
238 | if (opt_readonly && !user_is_super) |
239 | { |
240 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only" ); |
241 | DBUG_RETURN(true); |
242 | } |
243 | thd->tx_read_only= false; |
244 | /* |
245 | This flags that tx_read_only was set explicitly, rather than |
246 | just from the session's default. |
247 | */ |
248 | #ifndef EMBEDDED_LIBRARY |
249 | if (tst) |
250 | tst->set_read_flags(thd, TX_READ_WRITE); |
251 | #endif //EMBEDDED_LIBRARY |
252 | } |
253 | |
254 | #ifdef WITH_WSREP |
255 | thd->wsrep_PA_safe= true; |
256 | if (WSREP_CLIENT(thd) && wsrep_sync_wait(thd)) |
257 | DBUG_RETURN(TRUE); |
258 | #endif /* WITH_WSREP */ |
259 | |
260 | thd->variables.option_bits|= OPTION_BEGIN; |
261 | thd->server_status|= SERVER_STATUS_IN_TRANS; |
262 | if (thd->tx_read_only) |
263 | thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; |
264 | DBUG_PRINT("info" , ("setting SERVER_STATUS_IN_TRANS" )); |
265 | |
266 | #ifndef EMBEDDED_LIBRARY |
267 | if (tst) |
268 | tst->add_trx_state(thd, TX_EXPLICIT); |
269 | #endif //EMBEDDED_LIBRARY |
270 | |
271 | /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */ |
272 | if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) |
273 | { |
274 | #ifndef EMBEDDED_LIBRARY |
275 | if (tst) |
276 | tst->add_trx_state(thd, TX_WITH_SNAPSHOT); |
277 | #endif //EMBEDDED_LIBRARY |
278 | res= ha_start_consistent_snapshot(thd); |
279 | } |
280 | |
281 | DBUG_RETURN(MY_TEST(res)); |
282 | } |
283 | |
284 | |
285 | /** |
286 | Commit the current transaction, making its changes permanent. |
287 | |
288 | @param thd Current thread |
289 | |
290 | @retval FALSE Success |
291 | @retval TRUE Failure |
292 | */ |
293 | |
294 | bool trans_commit(THD *thd) |
295 | { |
296 | int res; |
297 | DBUG_ENTER("trans_commit" ); |
298 | |
299 | if (trans_check(thd)) |
300 | DBUG_RETURN(TRUE); |
301 | |
302 | if (WSREP_ON) |
303 | wsrep_register_hton(thd, TRUE); |
304 | thd->server_status&= |
305 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
306 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
307 | res= ha_commit_trans(thd, TRUE); |
308 | |
309 | mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); |
310 | mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); |
311 | mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); |
312 | mysql_mutex_assert_not_owner(&LOCK_commit_ordered); |
313 | |
314 | if (WSREP_ON) |
315 | wsrep_post_commit(thd, TRUE); |
316 | /* |
317 | if res is non-zero, then ha_commit_trans has rolled back the |
318 | transaction, so the hooks for rollback will be called. |
319 | */ |
320 | if (res) |
321 | { |
322 | #ifdef HAVE_REPLICATION |
323 | repl_semisync_master.wait_after_rollback(thd, FALSE); |
324 | #endif |
325 | } |
326 | else |
327 | { |
328 | #ifdef HAVE_REPLICATION |
329 | repl_semisync_master.wait_after_commit(thd, FALSE); |
330 | #endif |
331 | } |
332 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
333 | thd->transaction.all.reset(); |
334 | thd->lex->start_transaction_opt= 0; |
335 | |
336 | trans_track_end_trx(thd); |
337 | |
338 | DBUG_RETURN(MY_TEST(res)); |
339 | } |
340 | |
341 | |
342 | /** |
343 | Implicitly commit the current transaction. |
344 | |
345 | @note A implicit commit does not releases existing table locks. |
346 | |
347 | @param thd Current thread |
348 | |
349 | @retval FALSE Success |
350 | @retval TRUE Failure |
351 | */ |
352 | |
353 | bool trans_commit_implicit(THD *thd) |
354 | { |
355 | bool res= FALSE; |
356 | DBUG_ENTER("trans_commit_implicit" ); |
357 | |
358 | if (trans_check(thd)) |
359 | DBUG_RETURN(TRUE); |
360 | |
361 | if (thd->variables.option_bits & OPTION_GTID_BEGIN) |
362 | DBUG_PRINT("error" , ("OPTION_GTID_BEGIN is set. " |
363 | "Master and slave will have different GTID values" )); |
364 | |
365 | if (thd->in_multi_stmt_transaction_mode() || |
366 | (thd->variables.option_bits & OPTION_TABLE_LOCK)) |
367 | { |
368 | /* Safety if one did "drop table" on locked tables */ |
369 | if (!thd->locked_tables_mode) |
370 | thd->variables.option_bits&= ~OPTION_TABLE_LOCK; |
371 | if (WSREP_ON) |
372 | wsrep_register_hton(thd, TRUE); |
373 | thd->server_status&= |
374 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
375 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
376 | res= MY_TEST(ha_commit_trans(thd, TRUE)); |
377 | if (WSREP_ON) |
378 | wsrep_post_commit(thd, TRUE); |
379 | } |
380 | |
381 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
382 | thd->transaction.all.reset(); |
383 | |
384 | /* |
385 | Upon implicit commit, reset the current transaction |
386 | isolation level and access mode. We do not care about |
387 | @@session.completion_type since it's documented |
388 | to not have any effect on implicit commit. |
389 | */ |
390 | trans_reset_one_shot_chistics(thd); |
391 | |
392 | trans_track_end_trx(thd); |
393 | |
394 | DBUG_RETURN(res); |
395 | } |
396 | |
397 | |
398 | /** |
399 | Rollback the current transaction, canceling its changes. |
400 | |
401 | @param thd Current thread |
402 | |
403 | @retval FALSE Success |
404 | @retval TRUE Failure |
405 | */ |
406 | |
407 | bool trans_rollback(THD *thd) |
408 | { |
409 | int res; |
410 | DBUG_ENTER("trans_rollback" ); |
411 | |
412 | #ifdef WITH_WSREP |
413 | thd->wsrep_PA_safe= true; |
414 | #endif /* WITH_WSREP */ |
415 | if (trans_check(thd)) |
416 | DBUG_RETURN(TRUE); |
417 | |
418 | if (WSREP_ON) |
419 | wsrep_register_hton(thd, TRUE); |
420 | thd->server_status&= |
421 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
422 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
423 | res= ha_rollback_trans(thd, TRUE); |
424 | #ifdef HAVE_REPLICATION |
425 | repl_semisync_master.wait_after_rollback(thd, FALSE); |
426 | #endif |
427 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
428 | /* Reset the binlog transaction marker */ |
429 | thd->variables.option_bits&= ~OPTION_GTID_BEGIN; |
430 | thd->transaction.all.reset(); |
431 | thd->lex->start_transaction_opt= 0; |
432 | |
433 | trans_track_end_trx(thd); |
434 | |
435 | DBUG_RETURN(MY_TEST(res)); |
436 | } |
437 | |
438 | |
439 | /** |
440 | Implicitly rollback the current transaction, typically |
441 | after deadlock was discovered. |
442 | |
443 | @param thd Current thread |
444 | |
445 | @retval False Success |
446 | @retval True Failure |
447 | |
448 | @note ha_rollback_low() which is indirectly called by this |
449 | function will mark XA transaction for rollback by |
450 | setting appropriate RM error status if there was |
451 | transaction rollback request. |
452 | */ |
453 | |
454 | bool trans_rollback_implicit(THD *thd) |
455 | { |
456 | int res; |
457 | DBUG_ENTER("trans_rollback_implict" ); |
458 | |
459 | /* |
460 | Always commit/rollback statement transaction before manipulating |
461 | with the normal one. |
462 | Don't perform rollback in the middle of sub-statement, wait till |
463 | its end. |
464 | */ |
465 | DBUG_ASSERT(thd->transaction.stmt.is_empty() && !thd->in_sub_stmt); |
466 | |
467 | thd->server_status&= ~SERVER_STATUS_IN_TRANS; |
468 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
469 | res= ha_rollback_trans(thd, true); |
470 | /* |
471 | We don't reset OPTION_BEGIN flag below to simulate implicit start |
472 | of new transacton in @@autocommit=1 mode. This is necessary to |
473 | preserve backward compatibility. |
474 | */ |
475 | thd->variables.option_bits&= ~(OPTION_KEEP_LOG); |
476 | thd->transaction.all.reset(); |
477 | |
478 | /* Rollback should clear transaction_rollback_request flag. */ |
479 | DBUG_ASSERT(! thd->transaction_rollback_request); |
480 | |
481 | trans_track_end_trx(thd); |
482 | |
483 | DBUG_RETURN(MY_TEST(res)); |
484 | } |
485 | |
486 | |
487 | /** |
488 | Commit the single statement transaction. |
489 | |
490 | @note Note that if the autocommit is on, then the following call |
491 | inside InnoDB will commit or rollback the whole transaction |
492 | (= the statement). The autocommit mechanism built into InnoDB |
493 | is based on counting locks, but if the user has used LOCK |
494 | TABLES then that mechanism does not know to do the commit. |
495 | |
496 | @param thd Current thread |
497 | |
498 | @retval FALSE Success |
499 | @retval TRUE Failure |
500 | */ |
501 | |
502 | bool trans_commit_stmt(THD *thd) |
503 | { |
504 | DBUG_ENTER("trans_commit_stmt" ); |
505 | int res= FALSE; |
506 | /* |
507 | We currently don't invoke commit/rollback at end of |
508 | a sub-statement. In future, we perhaps should take |
509 | a savepoint for each nested statement, and release the |
510 | savepoint when statement has succeeded. |
511 | */ |
512 | DBUG_ASSERT(! thd->in_sub_stmt); |
513 | |
514 | thd->merge_unsafe_rollback_flags(); |
515 | |
516 | if (thd->transaction.stmt.ha_list) |
517 | { |
518 | if (WSREP_ON) |
519 | wsrep_register_hton(thd, FALSE); |
520 | res= ha_commit_trans(thd, FALSE); |
521 | if (! thd->in_active_multi_stmt_transaction()) |
522 | { |
523 | trans_reset_one_shot_chistics(thd); |
524 | if (WSREP_ON) |
525 | wsrep_post_commit(thd, FALSE); |
526 | } |
527 | } |
528 | |
529 | mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); |
530 | mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); |
531 | mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); |
532 | mysql_mutex_assert_not_owner(&LOCK_commit_ordered); |
533 | |
534 | /* |
535 | if res is non-zero, then ha_commit_trans has rolled back the |
536 | transaction, so the hooks for rollback will be called. |
537 | */ |
538 | if (res) |
539 | { |
540 | #ifdef HAVE_REPLICATION |
541 | repl_semisync_master.wait_after_rollback(thd, FALSE); |
542 | #endif |
543 | } |
544 | else |
545 | { |
546 | #ifdef HAVE_REPLICATION |
547 | repl_semisync_master.wait_after_commit(thd, FALSE); |
548 | #endif |
549 | } |
550 | |
551 | thd->transaction.stmt.reset(); |
552 | |
553 | DBUG_RETURN(MY_TEST(res)); |
554 | } |
555 | |
556 | |
557 | /** |
558 | Rollback the single statement transaction. |
559 | |
560 | @param thd Current thread |
561 | |
562 | @retval FALSE Success |
563 | @retval TRUE Failure |
564 | */ |
565 | bool trans_rollback_stmt(THD *thd) |
566 | { |
567 | DBUG_ENTER("trans_rollback_stmt" ); |
568 | |
569 | /* |
570 | We currently don't invoke commit/rollback at end of |
571 | a sub-statement. In future, we perhaps should take |
572 | a savepoint for each nested statement, and release the |
573 | savepoint when statement has succeeded. |
574 | */ |
575 | DBUG_ASSERT(! thd->in_sub_stmt); |
576 | |
577 | thd->merge_unsafe_rollback_flags(); |
578 | |
579 | if (thd->transaction.stmt.ha_list) |
580 | { |
581 | if (WSREP_ON) |
582 | wsrep_register_hton(thd, FALSE); |
583 | ha_rollback_trans(thd, FALSE); |
584 | if (! thd->in_active_multi_stmt_transaction()) |
585 | trans_reset_one_shot_chistics(thd); |
586 | } |
587 | |
588 | #ifdef HAVE_REPLICATION |
589 | repl_semisync_master.wait_after_rollback(thd, FALSE); |
590 | #endif |
591 | |
592 | thd->transaction.stmt.reset(); |
593 | |
594 | DBUG_RETURN(FALSE); |
595 | } |
596 | |
597 | /* Find a named savepoint in the current transaction. */ |
598 | static SAVEPOINT ** |
599 | find_savepoint(THD *thd, LEX_CSTRING name) |
600 | { |
601 | SAVEPOINT **sv= &thd->transaction.savepoints; |
602 | |
603 | while (*sv) |
604 | { |
605 | if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length, |
606 | (uchar *) (*sv)->name, (*sv)->length) == 0) |
607 | break; |
608 | sv= &(*sv)->prev; |
609 | } |
610 | |
611 | return sv; |
612 | } |
613 | |
614 | |
615 | /** |
616 | Set a named transaction savepoint. |
617 | |
618 | @param thd Current thread |
619 | @param name Savepoint name |
620 | |
621 | @retval FALSE Success |
622 | @retval TRUE Failure |
623 | */ |
624 | |
625 | bool trans_savepoint(THD *thd, LEX_CSTRING name) |
626 | { |
627 | SAVEPOINT **sv, *newsv; |
628 | DBUG_ENTER("trans_savepoint" ); |
629 | |
630 | if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) || |
631 | !opt_using_transactions) |
632 | DBUG_RETURN(FALSE); |
633 | |
634 | if (thd->transaction.xid_state.check_has_uncommitted_xa()) |
635 | DBUG_RETURN(TRUE); |
636 | |
637 | sv= find_savepoint(thd, name); |
638 | |
639 | if (*sv) /* old savepoint of the same name exists */ |
640 | { |
641 | newsv= *sv; |
642 | ha_release_savepoint(thd, *sv); |
643 | *sv= (*sv)->prev; |
644 | } |
645 | else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root, |
646 | savepoint_alloc_size)) == NULL) |
647 | { |
648 | my_error(ER_OUT_OF_RESOURCES, MYF(0)); |
649 | DBUG_RETURN(TRUE); |
650 | } |
651 | |
652 | newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length); |
653 | newsv->length= (uint)name.length; |
654 | |
655 | /* |
656 | if we'll get an error here, don't add new savepoint to the list. |
657 | we'll lose a little bit of memory in transaction mem_root, but it'll |
658 | be free'd when transaction ends anyway |
659 | */ |
660 | if (unlikely(ha_savepoint(thd, newsv))) |
661 | DBUG_RETURN(TRUE); |
662 | |
663 | newsv->prev= thd->transaction.savepoints; |
664 | thd->transaction.savepoints= newsv; |
665 | |
666 | /* |
667 | Remember locks acquired before the savepoint was set. |
668 | They are used as a marker to only release locks acquired after |
669 | the setting of this savepoint. |
670 | Note: this works just fine if we're under LOCK TABLES, |
671 | since mdl_savepoint() is guaranteed to be beyond |
672 | the last locked table. This allows to release some |
673 | locks acquired during LOCK TABLES. |
674 | */ |
675 | newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint(); |
676 | |
677 | DBUG_RETURN(FALSE); |
678 | } |
679 | |
680 | |
681 | /** |
682 | Rollback a transaction to the named savepoint. |
683 | |
684 | @note Modifications that the current transaction made to |
685 | rows after the savepoint was set are undone in the |
686 | rollback. |
687 | |
688 | @note Savepoints that were set at a later time than the |
689 | named savepoint are deleted. |
690 | |
691 | @param thd Current thread |
692 | @param name Savepoint name |
693 | |
694 | @retval FALSE Success |
695 | @retval TRUE Failure |
696 | */ |
697 | |
698 | bool trans_rollback_to_savepoint(THD *thd, LEX_CSTRING name) |
699 | { |
700 | int res= FALSE; |
701 | SAVEPOINT *sv= *find_savepoint(thd, name); |
702 | DBUG_ENTER("trans_rollback_to_savepoint" ); |
703 | |
704 | if (sv == NULL) |
705 | { |
706 | my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT" , name.str); |
707 | DBUG_RETURN(TRUE); |
708 | } |
709 | |
710 | if (thd->transaction.xid_state.check_has_uncommitted_xa()) |
711 | DBUG_RETURN(TRUE); |
712 | |
713 | /** |
714 | Checking whether it is safe to release metadata locks acquired after |
715 | savepoint, if rollback to savepoint is successful. |
716 | |
717 | Whether it is safe to release MDL after rollback to savepoint depends |
718 | on storage engines participating in transaction: |
719 | |
720 | - InnoDB doesn't release any row-locks on rollback to savepoint so it |
721 | is probably a bad idea to release MDL as well. |
722 | - Binary log implementation in some cases (e.g when non-transactional |
723 | tables involved) may choose not to remove events added after savepoint |
724 | from transactional cache, but instead will write them to binary |
725 | log accompanied with ROLLBACK TO SAVEPOINT statement. Since the real |
726 | write happens at the end of transaction releasing MDL on tables |
727 | mentioned in these events (i.e. acquired after savepoint and before |
728 | rollback ot it) can break replication, as concurrent DROP TABLES |
729 | statements will be able to drop these tables before events will get |
730 | into binary log, |
731 | |
732 | For backward-compatibility reasons we always release MDL if binary |
733 | logging is off. |
734 | */ |
735 | bool mdl_can_safely_rollback_to_savepoint= |
736 | (!(mysql_bin_log.is_open() && thd->variables.sql_log_bin) || |
737 | ha_rollback_to_savepoint_can_release_mdl(thd)); |
738 | |
739 | if (ha_rollback_to_savepoint(thd, sv)) |
740 | res= TRUE; |
741 | else if (((thd->variables.option_bits & OPTION_KEEP_LOG) || |
742 | thd->transaction.all.modified_non_trans_table) && |
743 | !thd->slave_thread) |
744 | push_warning(thd, Sql_condition::WARN_LEVEL_WARN, |
745 | ER_WARNING_NOT_COMPLETE_ROLLBACK, |
746 | ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK)); |
747 | |
748 | thd->transaction.savepoints= sv; |
749 | |
750 | if (!res && mdl_can_safely_rollback_to_savepoint) |
751 | thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint); |
752 | |
753 | DBUG_RETURN(MY_TEST(res)); |
754 | } |
755 | |
756 | |
757 | /** |
758 | Remove the named savepoint from the set of savepoints of |
759 | the current transaction. |
760 | |
761 | @note No commit or rollback occurs. It is an error if the |
762 | savepoint does not exist. |
763 | |
764 | @param thd Current thread |
765 | @param name Savepoint name |
766 | |
767 | @retval FALSE Success |
768 | @retval TRUE Failure |
769 | */ |
770 | |
771 | bool trans_release_savepoint(THD *thd, LEX_CSTRING name) |
772 | { |
773 | int res= FALSE; |
774 | SAVEPOINT *sv= *find_savepoint(thd, name); |
775 | DBUG_ENTER("trans_release_savepoint" ); |
776 | |
777 | if (sv == NULL) |
778 | { |
779 | my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT" , name.str); |
780 | DBUG_RETURN(TRUE); |
781 | } |
782 | |
783 | if (ha_release_savepoint(thd, sv)) |
784 | res= TRUE; |
785 | |
786 | thd->transaction.savepoints= sv->prev; |
787 | |
788 | DBUG_RETURN(MY_TEST(res)); |
789 | } |
790 | |
791 | |
792 | /** |
793 | Starts an XA transaction with the given xid value. |
794 | |
795 | @param thd Current thread |
796 | |
797 | @retval FALSE Success |
798 | @retval TRUE Failure |
799 | */ |
800 | |
801 | bool trans_xa_start(THD *thd) |
802 | { |
803 | enum xa_states xa_state= thd->transaction.xid_state.xa_state; |
804 | DBUG_ENTER("trans_xa_start" ); |
805 | |
806 | if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME) |
807 | { |
808 | bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid); |
809 | if (not_equal) |
810 | my_error(ER_XAER_NOTA, MYF(0)); |
811 | else |
812 | thd->transaction.xid_state.xa_state= XA_ACTIVE; |
813 | DBUG_RETURN(not_equal); |
814 | } |
815 | |
816 | /* TODO: JOIN is not supported yet. */ |
817 | if (thd->lex->xa_opt != XA_NONE) |
818 | my_error(ER_XAER_INVAL, MYF(0)); |
819 | else if (xa_state != XA_NOTR) |
820 | my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); |
821 | else if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction()) |
822 | my_error(ER_XAER_OUTSIDE, MYF(0)); |
823 | else if (!trans_begin(thd)) |
824 | { |
825 | DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); |
826 | thd->transaction.xid_state.xa_state= XA_ACTIVE; |
827 | thd->transaction.xid_state.rm_error= 0; |
828 | thd->transaction.xid_state.xid.set(thd->lex->xid); |
829 | if (xid_cache_insert(thd, &thd->transaction.xid_state)) |
830 | { |
831 | thd->transaction.xid_state.xa_state= XA_NOTR; |
832 | thd->transaction.xid_state.xid.null(); |
833 | trans_rollback(thd); |
834 | DBUG_RETURN(true); |
835 | } |
836 | DBUG_RETURN(FALSE); |
837 | } |
838 | |
839 | DBUG_RETURN(TRUE); |
840 | } |
841 | |
842 | |
843 | /** |
844 | Put a XA transaction in the IDLE state. |
845 | |
846 | @param thd Current thread |
847 | |
848 | @retval FALSE Success |
849 | @retval TRUE Failure |
850 | */ |
851 | |
852 | bool trans_xa_end(THD *thd) |
853 | { |
854 | DBUG_ENTER("trans_xa_end" ); |
855 | |
856 | /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */ |
857 | if (thd->lex->xa_opt != XA_NONE) |
858 | my_error(ER_XAER_INVAL, MYF(0)); |
859 | else if (thd->transaction.xid_state.xa_state != XA_ACTIVE) |
860 | my_error(ER_XAER_RMFAIL, MYF(0), |
861 | xa_state_names[thd->transaction.xid_state.xa_state]); |
862 | else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) |
863 | my_error(ER_XAER_NOTA, MYF(0)); |
864 | else if (!xa_trans_rolled_back(&thd->transaction.xid_state)) |
865 | thd->transaction.xid_state.xa_state= XA_IDLE; |
866 | |
867 | DBUG_RETURN(thd->is_error() || |
868 | thd->transaction.xid_state.xa_state != XA_IDLE); |
869 | } |
870 | |
871 | |
872 | /** |
873 | Put a XA transaction in the PREPARED state. |
874 | |
875 | @param thd Current thread |
876 | |
877 | @retval FALSE Success |
878 | @retval TRUE Failure |
879 | */ |
880 | |
881 | bool trans_xa_prepare(THD *thd) |
882 | { |
883 | DBUG_ENTER("trans_xa_prepare" ); |
884 | |
885 | if (thd->transaction.xid_state.xa_state != XA_IDLE) |
886 | my_error(ER_XAER_RMFAIL, MYF(0), |
887 | xa_state_names[thd->transaction.xid_state.xa_state]); |
888 | else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) |
889 | my_error(ER_XAER_NOTA, MYF(0)); |
890 | else if (ha_prepare(thd)) |
891 | { |
892 | xid_cache_delete(thd, &thd->transaction.xid_state); |
893 | thd->transaction.xid_state.xa_state= XA_NOTR; |
894 | my_error(ER_XA_RBROLLBACK, MYF(0)); |
895 | } |
896 | else |
897 | thd->transaction.xid_state.xa_state= XA_PREPARED; |
898 | |
899 | DBUG_RETURN(thd->is_error() || |
900 | thd->transaction.xid_state.xa_state != XA_PREPARED); |
901 | } |
902 | |
903 | |
904 | /** |
905 | Commit and terminate the a XA transaction. |
906 | |
907 | @param thd Current thread |
908 | |
909 | @retval FALSE Success |
910 | @retval TRUE Failure |
911 | */ |
912 | |
913 | bool trans_xa_commit(THD *thd) |
914 | { |
915 | bool res= TRUE; |
916 | enum xa_states xa_state= thd->transaction.xid_state.xa_state; |
917 | DBUG_ENTER("trans_xa_commit" ); |
918 | |
919 | if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) |
920 | { |
921 | if (thd->fix_xid_hash_pins()) |
922 | { |
923 | my_error(ER_OUT_OF_RESOURCES, MYF(0)); |
924 | DBUG_RETURN(TRUE); |
925 | } |
926 | |
927 | XID_STATE *xs= xid_cache_search(thd, thd->lex->xid); |
928 | res= !xs; |
929 | if (res) |
930 | my_error(ER_XAER_NOTA, MYF(0)); |
931 | else |
932 | { |
933 | res= xa_trans_rolled_back(xs); |
934 | ha_commit_or_rollback_by_xid(thd->lex->xid, !res); |
935 | xid_cache_delete(thd, xs); |
936 | } |
937 | DBUG_RETURN(res); |
938 | } |
939 | |
940 | if (xa_trans_rolled_back(&thd->transaction.xid_state)) |
941 | { |
942 | xa_trans_force_rollback(thd); |
943 | res= thd->is_error(); |
944 | } |
945 | else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE) |
946 | { |
947 | if (WSREP_ON) |
948 | wsrep_register_hton(thd, TRUE); |
949 | int r= ha_commit_trans(thd, TRUE); |
950 | if ((res= MY_TEST(r))) |
951 | my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); |
952 | if (WSREP_ON) |
953 | wsrep_post_commit(thd, TRUE); |
954 | } |
955 | else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE) |
956 | { |
957 | MDL_request mdl_request; |
958 | |
959 | /* |
960 | Acquire metadata lock which will ensure that COMMIT is blocked |
961 | by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in |
962 | progress blocks FTWRL). |
963 | |
964 | We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. |
965 | */ |
966 | mdl_request.init(MDL_key::COMMIT, "" , "" , MDL_INTENTION_EXCLUSIVE, |
967 | MDL_TRANSACTION); |
968 | |
969 | if (thd->mdl_context.acquire_lock(&mdl_request, |
970 | thd->variables.lock_wait_timeout)) |
971 | { |
972 | if (WSREP_ON) |
973 | wsrep_register_hton(thd, TRUE); |
974 | ha_rollback_trans(thd, TRUE); |
975 | my_error(ER_XAER_RMERR, MYF(0)); |
976 | } |
977 | else |
978 | { |
979 | DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock" ); |
980 | |
981 | res= MY_TEST(ha_commit_one_phase(thd, 1)); |
982 | if (res) |
983 | my_error(ER_XAER_RMERR, MYF(0)); |
984 | } |
985 | } |
986 | else |
987 | { |
988 | my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); |
989 | DBUG_RETURN(TRUE); |
990 | } |
991 | |
992 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
993 | thd->transaction.all.reset(); |
994 | thd->server_status&= |
995 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
996 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
997 | xid_cache_delete(thd, &thd->transaction.xid_state); |
998 | thd->transaction.xid_state.xa_state= XA_NOTR; |
999 | |
1000 | trans_track_end_trx(thd); |
1001 | |
1002 | DBUG_RETURN(res); |
1003 | } |
1004 | |
1005 | |
1006 | /** |
1007 | Roll back and terminate a XA transaction. |
1008 | |
1009 | @param thd Current thread |
1010 | |
1011 | @retval FALSE Success |
1012 | @retval TRUE Failure |
1013 | */ |
1014 | |
1015 | bool trans_xa_rollback(THD *thd) |
1016 | { |
1017 | bool res= TRUE; |
1018 | enum xa_states xa_state= thd->transaction.xid_state.xa_state; |
1019 | DBUG_ENTER("trans_xa_rollback" ); |
1020 | |
1021 | if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) |
1022 | { |
1023 | if (thd->fix_xid_hash_pins()) |
1024 | { |
1025 | my_error(ER_OUT_OF_RESOURCES, MYF(0)); |
1026 | DBUG_RETURN(TRUE); |
1027 | } |
1028 | |
1029 | XID_STATE *xs= xid_cache_search(thd, thd->lex->xid); |
1030 | if (!xs) |
1031 | my_error(ER_XAER_NOTA, MYF(0)); |
1032 | else |
1033 | { |
1034 | xa_trans_rolled_back(xs); |
1035 | ha_commit_or_rollback_by_xid(thd->lex->xid, 0); |
1036 | xid_cache_delete(thd, xs); |
1037 | } |
1038 | DBUG_RETURN(thd->get_stmt_da()->is_error()); |
1039 | } |
1040 | |
1041 | if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY) |
1042 | { |
1043 | my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); |
1044 | DBUG_RETURN(TRUE); |
1045 | } |
1046 | |
1047 | res= xa_trans_force_rollback(thd); |
1048 | |
1049 | thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); |
1050 | thd->transaction.all.reset(); |
1051 | thd->server_status&= |
1052 | ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); |
1053 | DBUG_PRINT("info" , ("clearing SERVER_STATUS_IN_TRANS" )); |
1054 | xid_cache_delete(thd, &thd->transaction.xid_state); |
1055 | thd->transaction.xid_state.xa_state= XA_NOTR; |
1056 | |
1057 | trans_track_end_trx(thd); |
1058 | |
1059 | DBUG_RETURN(res); |
1060 | } |
1061 | |