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