1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
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, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software Foundation,
21 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22
23
24 #include "transaction.h"
25 #include "rpl_handler.h"
26 #include "debug_sync.h" // DEBUG_SYNC
27 #include "auth_common.h" // SUPER_ACL
28 #include <pfs_transaction_provider.h>
29 #include <mysql/psi/mysql_transaction.h>
30 #include "rpl_context.h"
31 #include "sql_class.h"
32 #include "log.h"
33 #include "binlog.h"
34
35 /**
36 Helper: Tell tracker (if any) that transaction ended.
37 */
trans_track_end_trx(THD * thd)38 void trans_track_end_trx(THD *thd)
39 {
40 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
41 {
42 ((Transaction_state_tracker *)
43 thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER))->end_trx(thd);
44 }
45 }
46
47 /**
48 Helper: transaction ended, SET TRANSACTION one-shot variables
49 revert to session values. Let the transaction state tracker know.
50 */
trans_reset_one_shot_chistics(THD * thd)51 void trans_reset_one_shot_chistics(THD *thd)
52 {
53 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
54 {
55 Transaction_state_tracker *tst= (Transaction_state_tracker *)
56 thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
57
58 tst->set_read_flags(thd, TX_READ_INHERIT);
59 tst->set_isol_level(thd, TX_ISOL_INHERIT);
60 }
61
62 thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
63 thd->tx_read_only= thd->variables.tx_read_only;
64 }
65
66 /**
67 Check if we have a condition where the transaction state must
68 not be changed (committed or rolled back). Currently we check
69 that we are not executing a stored program and that we don't
70 have an active XA transaction.
71
72 @return TRUE if the commit/rollback cannot be executed,
73 FALSE otherwise.
74 */
75
trans_check_state(THD * thd)76 bool trans_check_state(THD *thd)
77 {
78 DBUG_ENTER("trans_check");
79
80 /*
81 Always commit statement transaction before manipulating with
82 the normal one.
83 */
84 assert(thd->get_transaction()->is_empty(Transaction_ctx::STMT));
85
86 if (unlikely(thd->in_sub_stmt))
87 {
88 my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
89 DBUG_RETURN(true);
90 }
91
92 if (thd->get_transaction()->xid_state()->check_in_xa(true))
93 DBUG_RETURN(true);
94
95 DBUG_RETURN(false);
96 }
97
98
99 /**
100 Begin a new transaction.
101
102 @note Beginning a transaction implicitly commits any current
103 transaction and releases existing locks.
104
105 @param thd Current thread
106 @param flags Transaction flags
107
108 @retval FALSE Success
109 @retval TRUE Failure
110 */
111
trans_begin(THD * thd,uint flags)112 bool trans_begin(THD *thd, uint flags)
113 {
114 int res= FALSE;
115 Transaction_state_tracker *tst= NULL;
116
117 DBUG_ENTER("trans_begin");
118
119 if (trans_check_state(thd))
120 DBUG_RETURN(TRUE);
121
122 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
123 tst= (Transaction_state_tracker *)
124 thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
125
126 thd->locked_tables_list.unlock_locked_tables(thd);
127
128 assert(!thd->locked_tables_mode);
129
130 if (thd->in_multi_stmt_transaction_mode() ||
131 (thd->variables.option_bits & OPTION_TABLE_LOCK))
132 {
133 thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
134 #ifdef WITH_WSREP
135 wsrep_register_hton(thd, TRUE);
136 #endif /* WITH_WSREP */
137 thd->server_status&=
138 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
139 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
140 res= MY_TEST(ha_commit_trans(thd, TRUE));
141 #ifdef WITH_WSREP
142 wsrep_post_commit(thd, TRUE);
143 #endif /* WITH_WSREP */
144 }
145
146 thd->variables.option_bits&= ~OPTION_BEGIN;
147 thd->get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::SESSION);
148
149 if (res)
150 DBUG_RETURN(TRUE);
151
152 /*
153 Release transactional metadata locks only after the
154 transaction has been committed.
155 */
156 thd->mdl_context.release_transactional_locks();
157
158 // The RO/RW options are mutually exclusive.
159 assert(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) &&
160 (flags & MYSQL_START_TRANS_OPT_READ_WRITE)));
161 if (flags & MYSQL_START_TRANS_OPT_READ_ONLY)
162 {
163 thd->tx_read_only= true;
164 if (tst)
165 tst->set_read_flags(thd, TX_READ_ONLY);
166 }
167 else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE)
168 {
169 /*
170 Explicitly starting a RW transaction when the server is in
171 read-only mode, is not allowed unless the user has SUPER priv.
172 Implicitly starting a RW transaction is allowed for backward
173 compatibility.
174 */
175 if (check_readonly(thd, true))
176 DBUG_RETURN(true);
177 thd->tx_read_only= false;
178 /*
179 This flags that tx_read_only was set explicitly, rather than
180 just from the session's default.
181 */
182 if (tst)
183 tst->set_read_flags(thd, TX_READ_WRITE);
184 }
185
186 DBUG_EXECUTE_IF("dbug_set_high_prio_trx", {
187 assert(thd->tx_priority==0);
188 #ifdef WITH_WSREP
189 WSREP_WARN("InnoDB High Priority being used: %d -> %d", thd->tx_priority, 1);
190 #endif /* WITH_WSREP */
191 thd->tx_priority= 1;
192 });
193
194 #ifdef WITH_WSREP
195 thd->wsrep_PA_safe= true;
196 if (WSREP_CLIENT(thd) && wsrep_sync_wait(thd))
197 DBUG_RETURN(TRUE);
198 #endif /* WITH_WSREP */
199
200 thd->variables.option_bits|= OPTION_BEGIN;
201 thd->server_status|= SERVER_STATUS_IN_TRANS;
202 if (thd->tx_read_only)
203 thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
204 DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
205
206 if (tst)
207 tst->add_trx_state(thd, TX_EXPLICIT);
208
209 /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */
210 if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
211 {
212 if (tst)
213 tst->add_trx_state(thd, TX_WITH_SNAPSHOT);
214 res= ha_start_consistent_snapshot(thd);
215 }
216
217 /*
218 Register transaction start in performance schema if not done already.
219 We handle explicitly started transactions here, implicitly started
220 transactions (and single-statement transactions in autocommit=1 mode)
221 are handled in trans_register_ha().
222 We can't handle explicit transactions in the same way as implicit
223 because we want to correctly attribute statements which follow
224 BEGIN but do not touch any transactional tables.
225 */
226 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
227 if (thd->m_transaction_psi == NULL)
228 {
229 thd->m_transaction_psi= MYSQL_START_TRANSACTION(&thd->m_transaction_state,
230 NULL, NULL, thd->tx_isolation,
231 thd->tx_read_only, false);
232 DEBUG_SYNC(thd, "after_set_transaction_psi_before_set_transaction_gtid");
233 gtid_set_performance_schema_values(thd);
234 }
235 #endif
236
237 DBUG_RETURN(MY_TEST(res));
238 }
239
240
241 /**
242 Commit the current transaction, making its changes permanent.
243
244 @param thd Current thread
245
246 @retval FALSE Success
247 @retval TRUE Failure
248 */
249
trans_commit(THD * thd)250 bool trans_commit(THD *thd)
251 {
252 int res;
253 DBUG_ENTER("trans_commit");
254
255 if (trans_check_state(thd))
256 DBUG_RETURN(TRUE);
257
258 #ifdef WITH_WSREP
259 wsrep_register_hton(thd, TRUE);
260 #endif /* WITH_WSREP */
261 thd->server_status&=
262 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
263 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
264 res= ha_commit_trans(thd, TRUE);
265 if (res == FALSE)
266 if (thd->rpl_thd_ctx.session_gtids_ctx().
267 notify_after_transaction_commit(thd))
268 sql_print_warning("Failed to collect GTID to send in the response packet!");
269 /*
270 When gtid mode is enabled, a transaction may cause binlog
271 rotation, which inserts a record into the gtid system table
272 (which is probably a transactional table). Thence, the flag
273 SERVER_STATUS_IN_TRANS may be set again while calling
274 ha_commit_trans(...) Consequently, we need to reset it back,
275 much like we are doing before calling ha_commit_trans(...).
276
277 We would really only need to do this when gtid_mode=on. However,
278 checking gtid_mode requires holding a lock, which is costly. So
279 we clear the bit unconditionally. This has no side effects since
280 if gtid_mode=off the bit is already cleared.
281 */
282 thd->server_status&= ~SERVER_STATUS_IN_TRANS;
283 thd->variables.option_bits&= ~OPTION_BEGIN;
284 thd->get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::SESSION);
285 thd->lex->start_transaction_opt= 0;
286
287 /* The transaction should be marked as complete in P_S. */
288 assert(thd->m_transaction_psi == NULL);
289
290 thd->tx_priority= 0;
291
292 #ifdef WITH_WSREP
293 wsrep_post_commit(thd, TRUE);
294 #endif /* WITH_WSREP */
295 trans_track_end_trx(thd);
296
297 DBUG_RETURN(MY_TEST(res));
298 }
299
300
301 /**
302 Implicitly commit the current transaction.
303
304 @note A implicit commit does not releases existing table locks.
305
306 @param thd Current thread
307
308 @retval FALSE Success
309 @retval TRUE Failure
310 */
311
trans_commit_implicit(THD * thd)312 bool trans_commit_implicit(THD *thd)
313 {
314 bool res= FALSE;
315 DBUG_ENTER("trans_commit_implicit");
316
317 /*
318 Ensure that trans_check_state() was called before trans_commit_implicit()
319 by asserting that conditions that are checked in the former function are
320 true.
321 */
322 assert(thd->get_transaction()->is_empty(Transaction_ctx::STMT) &&
323 !thd->in_sub_stmt &&
324 !thd->get_transaction()->xid_state()->check_in_xa(false));
325
326 if (thd->in_multi_stmt_transaction_mode() ||
327 (thd->variables.option_bits & OPTION_TABLE_LOCK))
328 {
329 /* Safety if one did "drop table" on locked tables */
330 if (!thd->locked_tables_mode)
331 thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
332 #ifdef WITH_WSREP
333 wsrep_register_hton(thd, TRUE);
334 #endif /* WITH_WSREP */
335 thd->server_status&=
336 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
337 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
338 res= MY_TEST(ha_commit_trans(thd, TRUE));
339 #ifdef WITH_WSREP
340 wsrep_post_commit(thd, TRUE);
341 #endif /* WITH_WSREP */
342 }
343 else if (tc_log)
344 res= tc_log->commit(thd, true);
345
346 if (res == FALSE)
347 if (thd->rpl_thd_ctx.session_gtids_ctx().
348 notify_after_transaction_commit(thd))
349 sql_print_warning("Failed to collect GTID to send in the response packet!");
350 thd->variables.option_bits&= ~OPTION_BEGIN;
351 thd->get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::SESSION);
352
353 /* The transaction should be marked as complete in P_S. */
354 assert(thd->m_transaction_psi == NULL);
355
356 /*
357 Upon implicit commit, reset the current transaction
358 isolation level and access mode. We do not care about
359 @@session.completion_type since it's documented
360 to not have any effect on implicit commit.
361 */
362 trans_reset_one_shot_chistics(thd);
363
364 trans_track_end_trx(thd);
365
366 DBUG_RETURN(res);
367 }
368
369
370 /**
371 Rollback the current transaction, canceling its changes.
372
373 @param thd Current thread
374
375 @retval FALSE Success
376 @retval TRUE Failure
377 */
378
trans_rollback(THD * thd)379 bool trans_rollback(THD *thd)
380 {
381 int res;
382 DBUG_ENTER("trans_rollback");
383 #ifdef WITH_WSREP
384 thd->wsrep_PA_safe= true;
385 #endif /* WITH_WSREP */
386
387 if (trans_check_state(thd))
388 DBUG_RETURN(TRUE);
389
390 #ifdef WITH_WSREP
391 wsrep_register_hton(thd, TRUE);
392 #endif /* WITH_WSREP */
393 thd->server_status&=
394 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
395 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
396 res= ha_rollback_trans(thd, TRUE);
397 thd->variables.option_bits&= ~OPTION_BEGIN;
398 thd->get_transaction()->reset_unsafe_rollback_flags(
399 Transaction_ctx::SESSION);
400 thd->lex->start_transaction_opt= 0;
401
402 /* The transaction should be marked as complete in P_S. */
403 assert(thd->m_transaction_psi == NULL);
404
405 thd->tx_priority= 0;
406
407 trans_track_end_trx(thd);
408
409 DBUG_RETURN(MY_TEST(res));
410 }
411
412
413 /**
414 Implicitly rollback the current transaction, typically
415 after deadlock was discovered.
416
417 @param thd Current thread
418
419 @retval False Success
420 @retval True Failure
421
422 @note ha_rollback_low() which is indirectly called by this
423 function will mark XA transaction for rollback by
424 setting appropriate RM error status if there was
425 transaction rollback request.
426 */
427
trans_rollback_implicit(THD * thd)428 bool trans_rollback_implicit(THD *thd)
429 {
430 int res;
431 DBUG_ENTER("trans_rollback_implict");
432
433 /*
434 Always commit/rollback statement transaction before manipulating
435 with the normal one.
436 Don't perform rollback in the middle of sub-statement, wait till
437 its end.
438 */
439 assert(thd->get_transaction()->is_empty(Transaction_ctx::STMT) &&
440 !thd->in_sub_stmt);
441
442 #ifdef WITH_WSREP
443 wsrep_register_hton(thd, true);
444 #endif /* WITH_WSREP */
445 thd->server_status&=
446 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
447 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
448 res= ha_rollback_trans(thd, true);
449 thd->variables.option_bits&= ~OPTION_BEGIN;
450 thd->get_transaction()->reset_unsafe_rollback_flags(
451 Transaction_ctx::SESSION);
452
453 /* Rollback should clear transaction_rollback_request flag. */
454 assert(!thd->transaction_rollback_request);
455 /* The transaction should be marked as complete in P_S. */
456 assert(thd->m_transaction_psi == NULL);
457
458 trans_track_end_trx(thd);
459
460 DBUG_RETURN(MY_TEST(res));
461 }
462
463
464 /**
465 Commit the single statement transaction.
466
467 @note Note that if the autocommit is on, then the following call
468 inside InnoDB will commit or rollback the whole transaction
469 (= the statement). The autocommit mechanism built into InnoDB
470 is based on counting locks, but if the user has used LOCK
471 TABLES then that mechanism does not know to do the commit.
472
473 @param thd Current thread
474
475 @retval FALSE Success
476 @retval TRUE Failure
477 */
478
trans_commit_stmt(THD * thd)479 bool trans_commit_stmt(THD *thd)
480 {
481 DBUG_ENTER("trans_commit_stmt");
482 int res= FALSE;
483 /*
484 We currently don't invoke commit/rollback at end of
485 a sub-statement. In future, we perhaps should take
486 a savepoint for each nested statement, and release the
487 savepoint when statement has succeeded.
488 */
489 assert(! thd->in_sub_stmt);
490
491 /*
492 Some code in MYSQL_BIN_LOG::commit and ha_commit_low() is not safe
493 for attachable transactions.
494 */
495 assert(!thd->is_attachable_ro_transaction_active());
496
497 thd->get_transaction()->merge_unsafe_rollback_flags();
498
499 if (thd->get_transaction()->is_active(Transaction_ctx::STMT))
500 {
501 #ifdef WITH_WSREP
502 wsrep_register_hton(thd, FALSE);
503 #endif /* WITH_WSREP */
504 res= ha_commit_trans(thd, FALSE);
505 if (! thd->in_active_multi_stmt_transaction())
506 #ifdef WITH_WSREP
507 {
508 #endif /* WITH_WSREP */
509 trans_reset_one_shot_chistics(thd);
510 #ifdef WITH_WSREP
511 wsrep_post_commit(thd, FALSE);
512 }
513 #endif /* WITH_WSREP */
514 }
515 else if (tc_log)
516 res= tc_log->commit(thd, false);
517 if (res == FALSE && !thd->in_active_multi_stmt_transaction())
518 if (thd->rpl_thd_ctx.session_gtids_ctx().
519 notify_after_transaction_commit(thd))
520 sql_print_warning("Failed to collect GTID to send in the response packet!");
521 /* In autocommit=1 mode the transaction should be marked as complete in P_S */
522 assert(thd->in_active_multi_stmt_transaction() ||
523 thd->m_transaction_psi == NULL);
524
525 thd->get_transaction()->reset(Transaction_ctx::STMT);
526
527 DBUG_RETURN(MY_TEST(res));
528 }
529
530
531 /**
532 Rollback the single statement transaction.
533
534 @param thd Current thread
535
536 @retval FALSE Success
537 @retval TRUE Failure
538 */
trans_rollback_stmt(THD * thd)539 bool trans_rollback_stmt(THD *thd)
540 {
541 DBUG_ENTER("trans_rollback_stmt");
542
543 /*
544 We currently don't invoke commit/rollback at end of
545 a sub-statement. In future, we perhaps should take
546 a savepoint for each nested statement, and release the
547 savepoint when statement has succeeded.
548 */
549 assert(! thd->in_sub_stmt);
550
551 /*
552 Some code in MYSQL_BIN_LOG::rollback and ha_rollback_low() is not safe
553 for attachable transactions.
554 */
555 assert(!thd->is_attachable_ro_transaction_active());
556
557 thd->get_transaction()->merge_unsafe_rollback_flags();
558
559 if (thd->get_transaction()->is_active(Transaction_ctx::STMT))
560 {
561 #ifdef WITH_WSREP
562 wsrep_register_hton(thd, FALSE);
563 #endif /* WITH_WSREP */
564 ha_rollback_trans(thd, FALSE);
565 if (! thd->in_active_multi_stmt_transaction())
566 trans_reset_one_shot_chistics(thd);
567 }
568 else if (tc_log)
569 tc_log->rollback(thd, false);
570
571 if (!thd->owned_gtid.is_empty() &&
572 !thd->in_active_multi_stmt_transaction())
573 {
574 /*
575 To a failed single statement transaction on auto-commit mode,
576 we roll back its owned gtid if it does not modify
577 non-transational table or commit its owned gtid if it has modified
578 non-transactional table when rolling back it if binlog is disabled,
579 as we did when binlog is enabled.
580 We do not need to check if binlog is enabled here, since we already
581 released its owned gtid in MYSQL_BIN_LOG::rollback(...) right before
582 this if binlog is enabled.
583 */
584 if (thd->get_transaction()->has_modified_non_trans_table(
585 Transaction_ctx::STMT))
586 gtid_state->update_on_commit(thd);
587 else
588 gtid_state->update_on_rollback(thd);
589 }
590
591 /* In autocommit=1 mode the transaction should be marked as complete in P_S */
592 assert(thd->in_active_multi_stmt_transaction() ||
593 thd->m_transaction_psi == NULL ||
594 /* Todo: BUG#20488921 is in the way. */
595 DBUG_EVALUATE_IF("simulate_xa_commit_log_failure", true, false));
596
597 thd->get_transaction()->reset(Transaction_ctx::STMT);
598
599 DBUG_RETURN(FALSE);
600 }
601
602
603 /**
604 Commit the attachable transaction.
605
606 @note This is slimmed down version of trans_commit_stmt() which commits
607 attachable transaction but skips code which is unnecessary and
608 unsafe for them (like dealing with GTIDs).
609
610 @param thd Current thread
611
612 @retval False - Success
613 @retval True - Failure
614 */
trans_commit_attachable(THD * thd)615 bool trans_commit_attachable(THD *thd)
616 {
617 DBUG_ENTER("trans_commit_attachable");
618 int res= 0;
619
620 /* This function only handles attachable transactions. */
621 assert(thd->is_attachable_ro_transaction_active());
622
623 /*
624 Since the attachable transaction is AUTOCOMMIT we only need to commit
625 statement transaction.
626 */
627 assert(! thd->get_transaction()->is_active(Transaction_ctx::SESSION));
628
629 /* Attachable transactions should not do anything unsafe. */
630 assert(!thd->get_transaction()->
631 cannot_safely_rollback(Transaction_ctx::STMT));
632
633
634 if (thd->get_transaction()->is_active(Transaction_ctx::STMT))
635 {
636 res= ha_commit_attachable(thd);
637 }
638
639 assert(thd->m_transaction_psi == NULL);
640
641 thd->get_transaction()->reset(Transaction_ctx::STMT);
642
643 DBUG_RETURN(MY_TEST(res));
644 }
645
646
647 /* Find a named savepoint in the current transaction. */
648 static SAVEPOINT **
find_savepoint(THD * thd,LEX_STRING name)649 find_savepoint(THD *thd, LEX_STRING name)
650 {
651 SAVEPOINT **sv= &thd->get_transaction()->m_savepoints;
652
653 while (*sv)
654 {
655 if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length,
656 (uchar *) (*sv)->name, (*sv)->length) == 0)
657 break;
658 sv= &(*sv)->prev;
659 }
660
661 return sv;
662 }
663
664
665 /**
666 Set a named transaction savepoint.
667
668 @param thd Current thread
669 @param name Savepoint name
670
671 @retval FALSE Success
672 @retval TRUE Failure
673 */
674
trans_savepoint(THD * thd,LEX_STRING name)675 bool trans_savepoint(THD *thd, LEX_STRING name)
676 {
677 SAVEPOINT **sv, *newsv;
678 DBUG_ENTER("trans_savepoint");
679
680 if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) ||
681 !opt_using_transactions)
682 DBUG_RETURN(FALSE);
683
684 if (thd->get_transaction()->xid_state()->check_has_uncommitted_xa())
685 DBUG_RETURN(true);
686
687 sv= find_savepoint(thd, name);
688
689 if (*sv) /* old savepoint of the same name exists */
690 {
691 newsv= *sv;
692 ha_release_savepoint(thd, *sv);
693 *sv= (*sv)->prev;
694 }
695 else if ((newsv= (SAVEPOINT *) thd->get_transaction()->allocate_memory(
696 savepoint_alloc_size)) == NULL)
697 {
698 my_error(ER_OUT_OF_RESOURCES, MYF(0));
699 DBUG_RETURN(TRUE);
700 }
701
702 newsv->name= thd->get_transaction()->strmake(name.str, name.length);
703 newsv->length= name.length;
704
705 /*
706 if we'll get an error here, don't add new savepoint to the list.
707 we'll lose a little bit of memory in transaction mem_root, but it'll
708 be free'd when transaction ends anyway
709 */
710 if (ha_savepoint(thd, newsv))
711 DBUG_RETURN(TRUE);
712
713 newsv->prev= thd->get_transaction()->m_savepoints;
714 thd->get_transaction()->m_savepoints= newsv;
715
716 /*
717 Remember locks acquired before the savepoint was set.
718 They are used as a marker to only release locks acquired after
719 the setting of this savepoint.
720 Note: this works just fine if we're under LOCK TABLES,
721 since mdl_savepoint() is guaranteed to be beyond
722 the last locked table. This allows to release some
723 locks acquired during LOCK TABLES.
724 */
725 newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint();
726
727 if (thd->is_current_stmt_binlog_row_enabled_with_write_set_extraction())
728 {
729 thd->get_transaction()->get_transaction_write_set_ctx()
730 ->add_savepoint(name.str);
731 }
732
733 DBUG_RETURN(FALSE);
734 }
735
736
737 /**
738 Rollback a transaction to the named savepoint.
739
740 @note Modifications that the current transaction made to
741 rows after the savepoint was set are undone in the
742 rollback.
743
744 @note Savepoints that were set at a later time than the
745 named savepoint are deleted.
746
747 @param thd Current thread
748 @param name Savepoint name
749
750 @retval FALSE Success
751 @retval TRUE Failure
752 */
753
trans_rollback_to_savepoint(THD * thd,LEX_STRING name)754 bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name)
755 {
756 int res= FALSE;
757 SAVEPOINT *sv= *find_savepoint(thd, name);
758 DBUG_ENTER("trans_rollback_to_savepoint");
759
760 if (sv == NULL)
761 {
762 my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
763 DBUG_RETURN(TRUE);
764 }
765
766 if (thd->get_transaction()->xid_state()->check_has_uncommitted_xa())
767 DBUG_RETURN(true);
768
769 /**
770 Checking whether it is safe to release metadata locks acquired after
771 savepoint, if rollback to savepoint is successful.
772
773 Whether it is safe to release MDL after rollback to savepoint depends
774 on storage engines participating in transaction:
775
776 - InnoDB doesn't release any row-locks on rollback to savepoint so it
777 is probably a bad idea to release MDL as well.
778 - Binary log implementation in some cases (e.g when non-transactional
779 tables involved) may choose not to remove events added after savepoint
780 from transactional cache, but instead will write them to binary
781 log accompanied with ROLLBACK TO SAVEPOINT statement. Since the real
782 write happens at the end of transaction releasing MDL on tables
783 mentioned in these events (i.e. acquired after savepoint and before
784 rollback ot it) can break replication, as concurrent DROP TABLES
785 statements will be able to drop these tables before events will get
786 into binary log,
787
788 For backward-compatibility reasons we always release MDL if binary
789 logging is off.
790 */
791 #ifdef WITH_WSREP
792 bool mdl_can_safely_rollback_to_savepoint=
793 (!((WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open())
794 && thd->variables.sql_log_bin) ||
795 ha_rollback_to_savepoint_can_release_mdl(thd));
796 #else
797 bool mdl_can_safely_rollback_to_savepoint=
798 (!(mysql_bin_log.is_open() && thd->variables.sql_log_bin) ||
799 ha_rollback_to_savepoint_can_release_mdl(thd));
800 #endif /* WITH_WSREP */
801
802 if (ha_rollback_to_savepoint(thd, sv))
803 res= TRUE;
804 else if (thd->get_transaction()->cannot_safely_rollback(
805 Transaction_ctx::SESSION) &&
806 !thd->slave_thread)
807 thd->get_transaction()->push_unsafe_rollback_warnings(thd);
808
809 thd->get_transaction()->m_savepoints= sv;
810
811 if (!res && mdl_can_safely_rollback_to_savepoint)
812 thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
813
814 if (thd->is_current_stmt_binlog_row_enabled_with_write_set_extraction())
815 {
816 thd->get_transaction()->get_transaction_write_set_ctx()
817 ->rollback_to_savepoint(name.str);
818 }
819
820 DBUG_RETURN(MY_TEST(res));
821 }
822
823
824 /**
825 Remove the named savepoint from the set of savepoints of
826 the current transaction.
827
828 @note No commit or rollback occurs. It is an error if the
829 savepoint does not exist.
830
831 @param thd Current thread
832 @param name Savepoint name
833
834 @retval FALSE Success
835 @retval TRUE Failure
836 */
837
trans_release_savepoint(THD * thd,LEX_STRING name)838 bool trans_release_savepoint(THD *thd, LEX_STRING name)
839 {
840 int res= FALSE;
841 SAVEPOINT *sv= *find_savepoint(thd, name);
842 DBUG_ENTER("trans_release_savepoint");
843
844 if (sv == NULL)
845 {
846 my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
847 DBUG_RETURN(TRUE);
848 }
849
850 if (thd->get_transaction()->xid_state()->check_has_uncommitted_xa())
851 DBUG_RETURN(true);
852
853 if (ha_release_savepoint(thd, sv))
854 res= TRUE;
855
856 thd->get_transaction()->m_savepoints= sv->prev;
857
858 if (thd->is_current_stmt_binlog_row_enabled_with_write_set_extraction())
859 {
860 thd->get_transaction()->get_transaction_write_set_ctx()
861 ->del_savepoint(name.str);
862 }
863
864 DBUG_RETURN(MY_TEST(res));
865 }
866