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*/
32static 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*/
49void 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. */
66static 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*/
99static 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
130static 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
162bool 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
294bool 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
353bool 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
407bool 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
454bool 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
502bool 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*/
565bool 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. */
598static SAVEPOINT **
599find_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
625bool 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
698bool 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
771bool 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
801bool 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
852bool 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
881bool 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
913bool 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
1015bool 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