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