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