]> granicus.if.org Git - postgresql/blob - src/backend/access/transam/xact.c
Replace implementation of pg_log as a relation accessed through the
[postgresql] / src / backend / access / transam / xact.c
1 /*-------------------------------------------------------------------------
2  *
3  * xact.c
4  *        top level transaction system support routines
5  *
6  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.109 2001/08/25 18:52:41 tgl Exp $
12  *
13  * NOTES
14  *              Transaction aborts can now occur two ways:
15  *
16  *              1)      system dies from some internal cause  (Assert, etc..)
17  *              2)      user types abort
18  *
19  *              These two cases used to be treated identically, but now
20  *              we need to distinguish them.  Why?      consider the following
21  *              two situations:
22  *
23  *                              case 1                                                  case 2
24  *                              ------                                                  ------
25  *              1) user types BEGIN                             1) user types BEGIN
26  *              2) user does something                  2) user does something
27  *              3) user does not like what              3) system aborts for some reason
28  *                 she sees and types ABORT
29  *
30  *              In case 1, we want to abort the transaction and return to the
31  *              default state.  In case 2, there may be more commands coming
32  *              our way which are part of the same transaction block and we have
33  *              to ignore these commands until we see an END transaction.
34  *              (or an ABORT! --djm)
35  *
36  *              Internal aborts are now handled by AbortTransactionBlock(), just as
37  *              they always have been, and user aborts are now handled by
38  *              UserAbortTransactionBlock().  Both of them rely on AbortTransaction()
39  *              to do all the real work.  The only difference is what state we
40  *              enter after AbortTransaction() does its work:
41  *
42  *              * AbortTransactionBlock() leaves us in TBLOCK_ABORT and
43  *              * UserAbortTransactionBlock() leaves us in TBLOCK_ENDABORT
44  *
45  *              Low-level transaction abort handling is divided into two phases:
46  *              * AbortTransaction() executes as soon as we realize the transaction
47  *                has failed.  It should release all shared resources (locks etc)
48  *                so that we do not delay other backends unnecessarily.
49  *              * CleanupTransaction() executes when we finally see a user COMMIT
50  *                or ROLLBACK command; it cleans things up and gets us out of
51  *                the transaction internally.  In particular, we mustn't destroy
52  *                TransactionCommandContext until this point.
53  *
54  *       NOTES
55  *              This file is an attempt at a redesign of the upper layer
56  *              of the V1 transaction system which was too poorly thought
57  *              out to describe.  This new system hopes to be both simpler
58  *              in design, simpler to extend and needs to contain added
59  *              functionality to solve problems beyond the scope of the V1
60  *              system.  (In particuler, communication of transaction
61  *              information between parallel backends has to be supported)
62  *
63  *              The essential aspects of the transaction system are:
64  *
65  *                              o  transaction id generation
66  *                              o  transaction log updating
67  *                              o  memory cleanup
68  *                              o  cache invalidation
69  *                              o  lock cleanup
70  *
71  *              Hence, the functional division of the transaction code is
72  *              based on what of the above things need to be done during
73  *              a start/commit/abort transaction.  For instance, the
74  *              routine AtCommit_Memory() takes care of all the memory
75  *              cleanup stuff done at commit time.
76  *
77  *              The code is layered as follows:
78  *
79  *                              StartTransaction
80  *                              CommitTransaction
81  *                              AbortTransaction
82  *                              CleanupTransaction
83  *
84  *              are provided to do the lower level work like recording
85  *              the transaction status in the log and doing memory cleanup.
86  *              above these routines are another set of functions:
87  *
88  *                              StartTransactionCommand
89  *                              CommitTransactionCommand
90  *                              AbortCurrentTransaction
91  *
92  *              These are the routines used in the postgres main processing
93  *              loop.  They are sensitive to the current transaction block state
94  *              and make calls to the lower level routines appropriately.
95  *
96  *              Support for transaction blocks is provided via the functions:
97  *
98  *                              StartTransactionBlock
99  *                              CommitTransactionBlock
100  *                              AbortTransactionBlock
101  *
102  *              These are invoked only in responce to a user "BEGIN", "END",
103  *              or "ABORT" command.  The tricky part about these functions
104  *              is that they are called within the postgres main loop, in between
105  *              the StartTransactionCommand() and CommitTransactionCommand().
106  *
107  *              For example, consider the following sequence of user commands:
108  *
109  *              1)              begin
110  *              2)              retrieve (foo.all)
111  *              3)              append foo (bar = baz)
112  *              4)              end
113  *
114  *              in the main processing loop, this results in the following
115  *              transaction sequence:
116  *
117  *                      /       StartTransactionCommand();
118  *              1) /    ProcessUtility();                               << begin
119  *                 \            StartTransactionBlock();
120  *                      \       CommitTransactionCommand();
121  *
122  *                      /       StartTransactionCommand();
123  *              2) <    ProcessQuery();                                 << retrieve (foo.all)
124  *                      \       CommitTransactionCommand();
125  *
126  *                      /       StartTransactionCommand();
127  *              3) <    ProcessQuery();                                 << append foo (bar = baz)
128  *                      \       CommitTransactionCommand();
129  *
130  *                      /       StartTransactionCommand();
131  *              4) /    ProcessUtility();                               << end
132  *                 \            CommitTransactionBlock();
133  *                      \       CommitTransactionCommand();
134  *
135  *              The point of this example is to demonstrate the need for
136  *              StartTransactionCommand() and CommitTransactionCommand() to
137  *              be state smart -- they should do nothing in between the calls
138  *              to StartTransactionBlock() and EndTransactionBlock() and
139  *              outside these calls they need to do normal start/commit
140  *              processing.
141  *
142  *              Furthermore, suppose the "retrieve (foo.all)" caused an abort
143  *              condition.      We would then want to abort the transaction and
144  *              ignore all subsequent commands up to the "end".
145  *              -cim 3/23/90
146  *
147  *-------------------------------------------------------------------------
148  */
149
150 /*
151  * Large object clean up added in CommitTransaction() to prevent buffer leaks.
152  * [PA, 7/17/98]
153  * [PA] is Pascal AndrĂ© <andre@via.ecp.fr>
154  */
155 #include "postgres.h"
156
157 #include <sys/time.h>
158
159 #include "access/gistscan.h"
160 #include "access/hash.h"
161 #include "access/nbtree.h"
162 #include "access/rtree.h"
163 #include "access/xact.h"
164 #include "catalog/heap.h"
165 #include "catalog/index.h"
166 #include "commands/async.h"
167 #include "commands/sequence.h"
168 #include "commands/trigger.h"
169 #include "executor/spi.h"
170 #include "libpq/be-fsstubs.h"
171 #include "miscadmin.h"
172 #include "storage/proc.h"
173 #include "storage/sinval.h"
174 #include "storage/smgr.h"
175 #include "utils/inval.h"
176 #include "utils/memutils.h"
177 #include "utils/portal.h"
178 #include "utils/catcache.h"
179 #include "utils/relcache.h"
180 #include "utils/temprel.h"
181
182 #include "pgstat.h"
183
184 extern bool SharedBufferChanged;
185
186 static void AbortTransaction(void);
187 static void AtAbort_Cache(void);
188 static void AtAbort_Locks(void);
189 static void AtAbort_Memory(void);
190 static void AtCleanup_Memory(void);
191 static void AtCommit_Cache(void);
192 static void AtCommit_LocalCache(void);
193 static void AtCommit_Locks(void);
194 static void AtCommit_Memory(void);
195 static void AtStart_Cache(void);
196 static void AtStart_Locks(void);
197 static void AtStart_Memory(void);
198 static void CleanupTransaction(void);
199 static void CommitTransaction(void);
200 static void RecordTransactionAbort(void);
201 static void StartTransaction(void);
202
203 /* ----------------
204  *              global variables holding the current transaction state.
205  * ----------------
206  */
207 static TransactionStateData CurrentTransactionStateData = {
208         0,                                                      /* transaction id */
209         FirstCommandId,                         /* command id */
210         0,                                                      /* scan command id */
211         0x0,                                            /* start time */
212         TRANS_DEFAULT,                          /* transaction state */
213         TBLOCK_DEFAULT                          /* transaction block state */
214 };
215
216 TransactionState CurrentTransactionState = &CurrentTransactionStateData;
217
218 /*
219  * User-tweakable parameters
220  */
221 int                     DefaultXactIsoLevel = XACT_READ_COMMITTED;
222 int                     XactIsoLevel;
223
224 int                     CommitDelay = 0;        /* precommit delay in microseconds */
225 int                     CommitSiblings = 5; /* number of concurrent xacts needed to
226                                                                  * sleep */
227
228 static void (*_RollbackFunc) (void *) = NULL;
229 static void *_RollbackData = NULL;
230
231 /* ----------------
232  *              catalog creation transaction bootstrapping flag.
233  *              This should be eliminated and added to the transaction
234  *              state stuff.  -cim 3/19/90
235  * ----------------
236  */
237 bool            AMI_OVERRIDE = false;
238
239 /* ----------------------------------------------------------------
240  *                                       transaction state accessors
241  * ----------------------------------------------------------------
242  */
243
244 /* --------------------------------
245  *              TranactionFlushEnabled()
246  *              SetTransactionFlushEnabled()
247  *
248  *              These are used to test and set the "TransactionFlushState"
249  *              varable.  If this variable is true (the default), then
250  *              the system will flush all dirty buffers to disk at the end
251  *              of each transaction.   If false then we are assuming the
252  *              buffer pool resides in stable main memory, in which case we
253  *              only do writes as necessary.
254  * --------------------------------
255  */
256 static int      TransactionFlushState = 1;
257
258 int
259 TransactionFlushEnabled(void)
260 {
261         return TransactionFlushState;
262 }
263
264 #ifdef NOT_USED
265 void
266 SetTransactionFlushEnabled(bool state)
267 {
268         TransactionFlushState = (state == true);
269 }
270
271
272 /* --------------------------------
273  *              IsTransactionState
274  *
275  *              This returns true if we are currently running a query
276  *              within an executing transaction.
277  * --------------------------------
278  */
279 bool
280 IsTransactionState(void)
281 {
282         TransactionState s = CurrentTransactionState;
283
284         switch (s->state)
285         {
286                 case TRANS_DEFAULT:
287                         return false;
288                 case TRANS_START:
289                         return true;
290                 case TRANS_INPROGRESS:
291                         return true;
292                 case TRANS_COMMIT:
293                         return true;
294                 case TRANS_ABORT:
295                         return true;
296         }
297
298         /*
299          * Shouldn't get here, but lint is not happy with this...
300          */
301         return false;
302 }
303
304 #endif
305
306 /* --------------------------------
307  *              IsAbortedTransactionBlockState
308  *
309  *              This returns true if we are currently running a query
310  *              within an aborted transaction block.
311  * --------------------------------
312  */
313 bool
314 IsAbortedTransactionBlockState(void)
315 {
316         TransactionState s = CurrentTransactionState;
317
318         if (s->blockState == TBLOCK_ABORT)
319                 return true;
320
321         return false;
322 }
323
324
325 /* --------------------------------
326  *              GetCurrentTransactionId
327  * --------------------------------
328  */
329 TransactionId
330 GetCurrentTransactionId(void)
331 {
332         TransactionState s = CurrentTransactionState;
333
334         return s->transactionIdData;
335 }
336
337
338 /* --------------------------------
339  *              GetCurrentCommandId
340  * --------------------------------
341  */
342 CommandId
343 GetCurrentCommandId(void)
344 {
345         TransactionState s = CurrentTransactionState;
346
347         return s->commandId;
348 }
349
350 CommandId
351 GetScanCommandId(void)
352 {
353         TransactionState s = CurrentTransactionState;
354
355         return s->scanCommandId;
356 }
357
358
359 /* --------------------------------
360  *              GetCurrentTransactionStartTime
361  * --------------------------------
362  */
363 AbsoluteTime
364 GetCurrentTransactionStartTime(void)
365 {
366         TransactionState s = CurrentTransactionState;
367
368         return s->startTime;
369 }
370
371
372 /* --------------------------------
373  *              TransactionIdIsCurrentTransactionId
374  * --------------------------------
375  */
376 bool
377 TransactionIdIsCurrentTransactionId(TransactionId xid)
378 {
379         TransactionState s = CurrentTransactionState;
380
381         if (AMI_OVERRIDE)
382                 return false;
383
384         return TransactionIdEquals(xid, s->transactionIdData);
385 }
386
387
388 /* --------------------------------
389  *              CommandIdIsCurrentCommandId
390  * --------------------------------
391  */
392 bool
393 CommandIdIsCurrentCommandId(CommandId cid)
394 {
395         TransactionState s = CurrentTransactionState;
396
397         if (AMI_OVERRIDE)
398                 return false;
399
400         return (cid == s->commandId) ? true : false;
401 }
402
403 bool
404 CommandIdGEScanCommandId(CommandId cid)
405 {
406         TransactionState s = CurrentTransactionState;
407
408         if (AMI_OVERRIDE)
409                 return false;
410
411         return (cid >= s->scanCommandId) ? true : false;
412 }
413
414
415 /* --------------------------------
416  *              CommandCounterIncrement
417  * --------------------------------
418  */
419 void
420 CommandCounterIncrement(void)
421 {
422         CurrentTransactionStateData.commandId += 1;
423         if (CurrentTransactionStateData.commandId == FirstCommandId)
424                 elog(ERROR, "You may only have 2^32-1 commands per transaction");
425
426         CurrentTransactionStateData.scanCommandId = CurrentTransactionStateData.commandId;
427
428         /*
429          * make cache changes visible to me.  AtCommit_LocalCache() instead of
430          * AtCommit_Cache() is called here.
431          */
432         AtCommit_LocalCache();
433         AtStart_Cache();
434 }
435
436 void
437 SetScanCommandId(CommandId savedId)
438 {
439         CurrentTransactionStateData.scanCommandId = savedId;
440 }
441
442 /* ----------------------------------------------------------------
443  *                                              StartTransaction stuff
444  * ----------------------------------------------------------------
445  */
446
447 /* --------------------------------
448  *              AtStart_Cache
449  * --------------------------------
450  */
451 static void
452 AtStart_Cache(void)
453 {
454         AcceptInvalidationMessages();
455 }
456
457 /* --------------------------------
458  *              AtStart_Locks
459  * --------------------------------
460  */
461 static void
462 AtStart_Locks(void)
463 {
464
465         /*
466          * at present, it is unknown to me what belongs here -cim 3/18/90
467          *
468          * There isn't anything to do at the start of a xact for locks. -mer
469          * 5/24/92
470          */
471 }
472
473 /* --------------------------------
474  *              AtStart_Memory
475  * --------------------------------
476  */
477 static void
478 AtStart_Memory(void)
479 {
480
481         /*
482          * We shouldn't have any transaction contexts already.
483          */
484         Assert(TopTransactionContext == NULL);
485         Assert(TransactionCommandContext == NULL);
486
487         /*
488          * Create a toplevel context for the transaction.
489          */
490         TopTransactionContext =
491                 AllocSetContextCreate(TopMemoryContext,
492                                                           "TopTransactionContext",
493                                                           ALLOCSET_DEFAULT_MINSIZE,
494                                                           ALLOCSET_DEFAULT_INITSIZE,
495                                                           ALLOCSET_DEFAULT_MAXSIZE);
496
497         /*
498          * Create a statement-level context and make it active.
499          */
500         TransactionCommandContext =
501                 AllocSetContextCreate(TopTransactionContext,
502                                                           "TransactionCommandContext",
503                                                           ALLOCSET_DEFAULT_MINSIZE,
504                                                           ALLOCSET_DEFAULT_INITSIZE,
505                                                           ALLOCSET_DEFAULT_MAXSIZE);
506         MemoryContextSwitchTo(TransactionCommandContext);
507 }
508
509
510 /* ----------------------------------------------------------------
511  *                                              CommitTransaction stuff
512  * ----------------------------------------------------------------
513  */
514
515 /* --------------------------------
516  *              RecordTransactionCommit
517  *
518  *              Note: the two calls to BufferManagerFlush() exist to ensure
519  *                        that data pages are written before log pages.  These
520  *                        explicit calls should be replaced by a more efficient
521  *                        ordered page write scheme in the buffer manager
522  *                        -cim 3/18/90
523  * --------------------------------
524  */
525 void
526 RecordTransactionCommit(void)
527 {
528         TransactionId xid;
529         bool            leak;
530
531         leak = BufferPoolCheckLeak();
532
533         xid = GetCurrentTransactionId();
534
535         /*
536          * We needn't write anything in xlog or clog if the transaction was
537          * read-only, which we check by testing if it made any xlog entries.
538          */
539         if (MyLastRecPtr.xrecoff != 0)
540         {
541                 XLogRecData rdata;
542                 xl_xact_commit xlrec;
543                 XLogRecPtr      recptr;
544
545                 BufmgrCommit();
546
547                 xlrec.xtime = time(NULL);
548                 rdata.buffer = InvalidBuffer;
549                 rdata.data = (char *) (&xlrec);
550                 rdata.len = SizeOfXactCommit;
551                 rdata.next = NULL;
552
553                 START_CRIT_SECTION();
554
555                 /*
556                  * SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP
557                  */
558                 recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, &rdata);
559
560                 /*
561                  * Sleep before commit! So we can flush more than one commit
562                  * records per single fsync.  (The idea is some other backend may
563                  * do the XLogFlush while we're sleeping.  This needs work still,
564                  * because on most Unixen, the minimum select() delay is 10msec or
565                  * more, which is way too long.)
566                  *
567                  * We do not sleep if enableFsync is not turned on, nor if there are
568                  * fewer than CommitSiblings other backends with active
569                  * transactions.
570                  */
571                 if (CommitDelay > 0 && enableFsync &&
572                         CountActiveBackends() >= CommitSiblings)
573                 {
574                         struct timeval delay;
575
576                         delay.tv_sec = 0;
577                         delay.tv_usec = CommitDelay;
578                         (void) select(0, NULL, NULL, NULL, &delay);
579                 }
580
581                 XLogFlush(recptr);
582
583                 /* Break the chain of back-links in the XLOG records I output */
584                 MyLastRecPtr.xrecoff = 0;
585
586                 /* Mark the transaction committed in clog */
587                 TransactionIdCommit(xid);
588
589                 END_CRIT_SECTION();
590         }
591
592         /* Show myself as out of the transaction in PROC array */
593         MyProc->logRec.xrecoff = 0;
594
595         if (leak)
596                 ResetBufferPool(true);
597 }
598
599
600 /* --------------------------------
601  *              AtCommit_Cache
602  * --------------------------------
603  */
604 static void
605 AtCommit_Cache(void)
606 {
607         /*
608          * Make catalog changes visible to all backends.
609          */
610         AtEOXactInvalidationMessages(true);
611 }
612
613 /* --------------------------------
614  *              AtCommit_LocalCache
615  * --------------------------------
616  */
617 static void
618 AtCommit_LocalCache(void)
619 {
620         /*
621          * Make catalog changes visible to me for the next command.
622          */
623         CommandEndInvalidationMessages(true);
624 }
625
626 /* --------------------------------
627  *              AtCommit_Locks
628  * --------------------------------
629  */
630 static void
631 AtCommit_Locks(void)
632 {
633         /*
634          * XXX What if ProcReleaseLocks fails?  (race condition?)
635          *
636          * Then you're up a creek! -mer 5/24/92
637          */
638         ProcReleaseLocks(true);
639 }
640
641 /* --------------------------------
642  *              AtCommit_Memory
643  * --------------------------------
644  */
645 static void
646 AtCommit_Memory(void)
647 {
648         /*
649          * Now that we're "out" of a transaction, have the system allocate
650          * things in the top memory context instead of per-transaction
651          * contexts.
652          */
653         MemoryContextSwitchTo(TopMemoryContext);
654
655         /*
656          * Release all transaction-local memory.
657          */
658         Assert(TopTransactionContext != NULL);
659         MemoryContextDelete(TopTransactionContext);
660         TopTransactionContext = NULL;
661         TransactionCommandContext = NULL;
662 }
663
664 /* ----------------------------------------------------------------
665  *                                              AbortTransaction stuff
666  * ----------------------------------------------------------------
667  */
668
669 /* --------------------------------
670  *              RecordTransactionAbort
671  * --------------------------------
672  */
673 static void
674 RecordTransactionAbort(void)
675 {
676         TransactionId xid = GetCurrentTransactionId();
677
678         /*
679          * We needn't write anything in xlog or clog if the transaction was
680          * read-only, which we check by testing if it made any xlog entries.
681          *
682          * Extra check here is to catch case that we aborted partway through
683          * RecordTransactionCommit ...
684          */
685         if (MyLastRecPtr.xrecoff != 0 && !TransactionIdDidCommit(xid))
686         {
687                 XLogRecData rdata;
688                 xl_xact_abort xlrec;
689                 XLogRecPtr      recptr;
690
691                 xlrec.xtime = time(NULL);
692                 rdata.buffer = InvalidBuffer;
693                 rdata.data = (char *) (&xlrec);
694                 rdata.len = SizeOfXactAbort;
695                 rdata.next = NULL;
696
697                 START_CRIT_SECTION();
698
699                 /*
700                  * SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP
701                  */
702                 recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, &rdata);
703
704                 /*
705                  * There's no need for XLogFlush here, since the default assumption
706                  * would be that we aborted, anyway.
707                  */
708
709                 /* Mark the transaction aborted in clog */
710                 TransactionIdAbort(xid);
711
712                 END_CRIT_SECTION();
713         }
714
715         /* Break the chain of back-links in the XLOG records I output */
716         MyLastRecPtr.xrecoff = 0;
717         /* Show myself as out of the transaction in PROC array */
718         MyProc->logRec.xrecoff = 0;
719
720         /*
721          * Tell bufmgr and smgr to release resources.
722          */
723         ResetBufferPool(false);         /* false -> is abort */
724 }
725
726 /* --------------------------------
727  *              AtAbort_Cache
728  * --------------------------------
729  */
730 static void
731 AtAbort_Cache(void)
732 {
733         RelationCacheAbort();
734         AtEOXactInvalidationMessages(false);
735 }
736
737 /* --------------------------------
738  *              AtAbort_Locks
739  * --------------------------------
740  */
741 static void
742 AtAbort_Locks(void)
743 {
744
745         /*
746          * XXX What if ProcReleaseLocks() fails?  (race condition?)
747          *
748          * Then you're up a creek without a paddle! -mer
749          */
750         ProcReleaseLocks(false);
751 }
752
753
754 /* --------------------------------
755  *              AtAbort_Memory
756  * --------------------------------
757  */
758 static void
759 AtAbort_Memory(void)
760 {
761
762         /*
763          * Make sure we are in a valid context (not a child of
764          * TransactionCommandContext...).  Note that it is possible for this
765          * code to be called when we aren't in a transaction at all; go
766          * directly to TopMemoryContext in that case.
767          */
768         if (TransactionCommandContext != NULL)
769         {
770                 MemoryContextSwitchTo(TransactionCommandContext);
771
772                 /*
773                  * We do not want to destroy transaction contexts yet, but it
774                  * should be OK to delete any command-local memory.
775                  */
776                 MemoryContextResetAndDeleteChildren(TransactionCommandContext);
777         }
778         else
779                 MemoryContextSwitchTo(TopMemoryContext);
780 }
781
782
783 /* ----------------------------------------------------------------
784  *                                              CleanupTransaction stuff
785  * ----------------------------------------------------------------
786  */
787
788 /* --------------------------------
789  *              AtCleanup_Memory
790  * --------------------------------
791  */
792 static void
793 AtCleanup_Memory(void)
794 {
795
796         /*
797          * Now that we're "out" of a transaction, have the system allocate
798          * things in the top memory context instead of per-transaction
799          * contexts.
800          */
801         MemoryContextSwitchTo(TopMemoryContext);
802
803         /*
804          * Release all transaction-local memory.
805          */
806         if (TopTransactionContext != NULL)
807                 MemoryContextDelete(TopTransactionContext);
808         TopTransactionContext = NULL;
809         TransactionCommandContext = NULL;
810 }
811
812
813 /* ----------------------------------------------------------------
814  *                                              interface routines
815  * ----------------------------------------------------------------
816  */
817
818 /* --------------------------------
819  *              StartTransaction
820  *
821  * --------------------------------
822  */
823 static void
824 StartTransaction(void)
825 {
826         TransactionState s = CurrentTransactionState;
827
828         FreeXactSnapshot();
829         XactIsoLevel = DefaultXactIsoLevel;
830
831         /*
832          * Check the current transaction state.  If the transaction system is
833          * switched off, or if we're already in a transaction, do nothing.
834          * We're already in a transaction when the monitor sends a null
835          * command to the backend to flush the comm channel.  This is a hacky
836          * fix to a communications problem, and we keep having to deal with it
837          * here.  We should fix the comm channel code.  mao 080891
838          */
839         if (s->state == TRANS_INPROGRESS)
840                 return;
841
842         /*
843          * set the current transaction state information appropriately during
844          * start processing
845          */
846         s->state = TRANS_START;
847
848         SetReindexProcessing(false);
849
850         /*
851          * generate a new transaction id
852          */
853         s->transactionIdData = GetNewTransactionId();
854
855         XactLockTableInsert(s->transactionIdData);
856
857         /*
858          * initialize current transaction state fields
859          */
860         s->commandId = FirstCommandId;
861         s->scanCommandId = FirstCommandId;
862         s->startTime = GetCurrentAbsoluteTime();
863
864         /*
865          * initialize the various transaction subsystems
866          */
867         AtStart_Memory();
868         AtStart_Cache();
869         AtStart_Locks();
870
871         /*
872          * Tell the trigger manager to we're starting a transaction
873          */
874         DeferredTriggerBeginXact();
875
876         /*
877          * done with start processing, set current transaction state to "in
878          * progress"
879          */
880         s->state = TRANS_INPROGRESS;
881
882 }
883
884 #ifdef NOT_USED
885 /* ---------------
886  * Tell me if we are currently in progress
887  * ---------------
888  */
889 bool
890 CurrentXactInProgress(void)
891 {
892         return CurrentTransactionState->state == TRANS_INPROGRESS;
893 }
894
895 #endif
896
897 /* --------------------------------
898  *              CommitTransaction
899  *
900  * --------------------------------
901  */
902 static void
903 CommitTransaction(void)
904 {
905         TransactionState s = CurrentTransactionState;
906
907         /*
908          * check the current transaction state
909          */
910         if (s->state != TRANS_INPROGRESS)
911                 elog(NOTICE, "CommitTransaction and not in in-progress state");
912
913         /*
914          * Tell the trigger manager that this transaction is about to be
915          * committed. He'll invoke all trigger deferred until XACT before we
916          * really start on committing the transaction.
917          */
918         DeferredTriggerEndXact();
919
920         /* Prevent cancel/die interrupt while cleaning up */
921         HOLD_INTERRUPTS();
922
923         /*
924          * set the current transaction state information appropriately during
925          * the abort processing
926          */
927         s->state = TRANS_COMMIT;
928
929         /*
930          * do commit processing
931          */
932
933         /* handle commit for large objects [ PA, 7/17/98 ] */
934         lo_commit(true);
935
936         /* NOTIFY commit must also come before lower-level cleanup */
937         AtCommit_Notify();
938
939         CloseSequences();
940         AtEOXact_portals();
941
942         /* Here is where we really truly commit. */
943         RecordTransactionCommit();
944
945         /*
946          * Let others know about no transaction in progress by me. Note that
947          * this must be done _before_ releasing locks we hold and _after_
948          * RecordTransactionCommit.
949          *
950          * SpinAcquire(SInvalLock) is required: UPDATE with xid 0 is blocked
951          * by xid 1' UPDATE, xid 1 is doing commit while xid 2 gets snapshot -
952          * if xid 2' GetSnapshotData sees xid 1 as running then it must see
953          * xid 0 as running as well or it will see two tuple versions - one
954          * deleted by xid 1 and one inserted by xid 0.  See notes in
955          * GetSnapshotData.
956          */
957         if (MyProc != (PROC *) NULL)
958         {
959                 /* Lock SInvalLock because that's what GetSnapshotData uses. */
960                 SpinAcquire(SInvalLock);
961                 MyProc->xid = InvalidTransactionId;
962                 MyProc->xmin = InvalidTransactionId;
963                 SpinRelease(SInvalLock);
964         }
965
966         /*
967          * This is all post-commit cleanup.  Note that if an error is raised
968          * here, it's too late to abort the transaction.  This should be just
969          * noncritical resource releasing.
970          */
971
972         RelationPurgeLocalRelation(true);
973         AtEOXact_temp_relations(true);
974         smgrDoPendingDeletes(true);
975
976         AtEOXact_SPI();
977         AtEOXact_gist();
978         AtEOXact_hash();
979         AtEOXact_nbtree();
980         AtEOXact_rtree();
981         AtCommit_Cache();
982         AtCommit_Locks();
983         AtEOXact_CatCache(true);
984         AtCommit_Memory();
985         AtEOXact_Files();
986
987         SharedBufferChanged = false;/* safest place to do it */
988
989         /* Count transaction commit in statistics collector */
990         pgstat_count_xact_commit();
991
992         /*
993          * done with commit processing, set current transaction state back to
994          * default
995          */
996         s->state = TRANS_DEFAULT;
997
998         RESUME_INTERRUPTS();
999 }
1000
1001 /* --------------------------------
1002  *              AbortTransaction
1003  *
1004  * --------------------------------
1005  */
1006 static void
1007 AbortTransaction(void)
1008 {
1009         TransactionState s = CurrentTransactionState;
1010
1011         /* Prevent cancel/die interrupt while cleaning up */
1012         HOLD_INTERRUPTS();
1013
1014         /*
1015          * Release any spinlocks or buffer context locks we might be holding
1016          * as quickly as possible.      (Real locks, however, must be held till we
1017          * finish aborting.)  Releasing spinlocks is critical since we might
1018          * try to grab them again while cleaning up!
1019          */
1020         ProcReleaseSpins(NULL);
1021         UnlockBuffers();
1022
1023         /*
1024          * Also clean up any open wait for lock, since the lock manager will
1025          * choke if we try to wait for another lock before doing this.
1026          */
1027         LockWaitCancel();
1028
1029         /*
1030          * check the current transaction state
1031          */
1032         if (s->state != TRANS_INPROGRESS)
1033                 elog(NOTICE, "AbortTransaction and not in in-progress state");
1034
1035         /*
1036          * set the current transaction state information appropriately during
1037          * the abort processing
1038          */
1039         s->state = TRANS_ABORT;
1040
1041         /*
1042          * Reset user id which might have been changed transiently
1043          */
1044         SetUserId(GetSessionUserId());
1045
1046         /*
1047          * do abort processing
1048          */
1049         DeferredTriggerAbortXact();
1050         lo_commit(false);                       /* 'false' means it's abort */
1051         AtAbort_Notify();
1052         CloseSequences();
1053         AtEOXact_portals();
1054
1055         /* Advertise the fact that we aborted in pg_clog. */
1056         RecordTransactionAbort();
1057
1058         /*
1059          * Let others know about no transaction in progress by me. Note that
1060          * this must be done _before_ releasing locks we hold and _after_
1061          * RecordTransactionAbort.
1062          */
1063         if (MyProc != (PROC *) NULL)
1064         {
1065                 /* Lock SInvalLock because that's what GetSnapshotData uses. */
1066                 SpinAcquire(SInvalLock);
1067                 MyProc->xid = InvalidTransactionId;
1068                 MyProc->xmin = InvalidTransactionId;
1069                 SpinRelease(SInvalLock);
1070         }
1071
1072         RelationPurgeLocalRelation(false);
1073         AtEOXact_temp_relations(false);
1074         smgrDoPendingDeletes(false);
1075
1076         AtEOXact_SPI();
1077         AtEOXact_gist();
1078         AtEOXact_hash();
1079         AtEOXact_nbtree();
1080         AtEOXact_rtree();
1081         AtAbort_Cache();
1082         AtEOXact_CatCache(false);
1083         AtAbort_Memory();
1084         AtEOXact_Files();
1085         AtAbort_Locks();
1086
1087         SharedBufferChanged = false;/* safest place to do it */
1088
1089         /* Count transaction abort in statistics collector */
1090         pgstat_count_xact_rollback();
1091
1092         /*
1093          * State remains TRANS_ABORT until CleanupTransaction().
1094          */
1095         RESUME_INTERRUPTS();
1096 }
1097
1098 /* --------------------------------
1099  *              CleanupTransaction
1100  *
1101  * --------------------------------
1102  */
1103 static void
1104 CleanupTransaction(void)
1105 {
1106         TransactionState s = CurrentTransactionState;
1107
1108         /*
1109          * State should still be TRANS_ABORT from AbortTransaction().
1110          */
1111         if (s->state != TRANS_ABORT)
1112                 elog(FATAL, "CleanupTransaction and not in abort state");
1113
1114         /*
1115          * do abort cleanup processing
1116          */
1117         AtCleanup_Memory();
1118
1119         /*
1120          * done with abort processing, set current transaction state back to
1121          * default
1122          */
1123         s->state = TRANS_DEFAULT;
1124 }
1125
1126 /* --------------------------------
1127  *              StartTransactionCommand
1128  * --------------------------------
1129  */
1130 void
1131 StartTransactionCommand(void)
1132 {
1133         TransactionState s = CurrentTransactionState;
1134
1135         switch (s->blockState)
1136         {
1137
1138                         /*
1139                          * if we aren't in a transaction block, we just do our usual
1140                          * start transaction.
1141                          */
1142                 case TBLOCK_DEFAULT:
1143                         StartTransaction();
1144                         break;
1145
1146                         /*
1147                          * We should never experience this -- if we do it means the
1148                          * BEGIN state was not changed in the previous
1149                          * CommitTransactionCommand().  If we get it, we print a
1150                          * warning and change to the in-progress state.
1151                          */
1152                 case TBLOCK_BEGIN:
1153                         elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_BEGIN");
1154                         s->blockState = TBLOCK_INPROGRESS;
1155                         break;
1156
1157                         /*
1158                          * This is the case when are somewhere in a transaction block
1159                          * and about to start a new command.  For now we do nothing
1160                          * but someday we may do command-local resource
1161                          * initialization.
1162                          */
1163                 case TBLOCK_INPROGRESS:
1164                         break;
1165
1166                         /*
1167                          * As with BEGIN, we should never experience this if we do it
1168                          * means the END state was not changed in the previous
1169                          * CommitTransactionCommand().  If we get it, we print a
1170                          * warning, commit the transaction, start a new transaction
1171                          * and change to the default state.
1172                          */
1173                 case TBLOCK_END:
1174                         elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_END");
1175                         s->blockState = TBLOCK_DEFAULT;
1176                         CommitTransaction();
1177                         StartTransaction();
1178                         break;
1179
1180                         /*
1181                          * Here we are in the middle of a transaction block but one of
1182                          * the commands caused an abort so we do nothing but remain in
1183                          * the abort state.  Eventually we will get to the "END
1184                          * TRANSACTION" which will set things straight.
1185                          */
1186                 case TBLOCK_ABORT:
1187                         break;
1188
1189                         /*
1190                          * This means we somehow aborted and the last call to
1191                          * CommitTransactionCommand() didn't clear the state so we
1192                          * remain in the ENDABORT state and maybe next time we get to
1193                          * CommitTransactionCommand() the state will get reset to
1194                          * default.
1195                          */
1196                 case TBLOCK_ENDABORT:
1197                         elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_ENDABORT");
1198                         break;
1199         }
1200
1201         /*
1202          * We must switch to TransactionCommandContext before returning. This
1203          * is already done if we called StartTransaction, otherwise not.
1204          */
1205         Assert(TransactionCommandContext != NULL);
1206         MemoryContextSwitchTo(TransactionCommandContext);
1207 }
1208
1209 /* --------------------------------
1210  *              CommitTransactionCommand
1211  * --------------------------------
1212  */
1213 void
1214 CommitTransactionCommand(void)
1215 {
1216         TransactionState s = CurrentTransactionState;
1217
1218         switch (s->blockState)
1219         {
1220
1221                         /*
1222                          * if we aren't in a transaction block, we just do our usual
1223                          * transaction commit
1224                          */
1225                 case TBLOCK_DEFAULT:
1226                         CommitTransaction();
1227                         break;
1228
1229                         /*
1230                          * This is the case right after we get a "BEGIN TRANSACTION"
1231                          * command, but the user hasn't done anything else yet, so we
1232                          * change to the "transaction block in progress" state and
1233                          * return.
1234                          */
1235                 case TBLOCK_BEGIN:
1236                         s->blockState = TBLOCK_INPROGRESS;
1237                         break;
1238
1239                         /*
1240                          * This is the case when we have finished executing a command
1241                          * someplace within a transaction block.  We increment the
1242                          * command counter and return.  Someday we may free resources
1243                          * local to the command.
1244                          *
1245                          * That someday is today, at least for memory allocated in
1246                          * TransactionCommandContext. - vadim 03/25/97
1247                          */
1248                 case TBLOCK_INPROGRESS:
1249                         CommandCounterIncrement();
1250                         MemoryContextResetAndDeleteChildren(TransactionCommandContext);
1251                         break;
1252
1253                         /*
1254                          * This is the case when we just got the "END TRANSACTION"
1255                          * statement, so we commit the transaction and go back to the
1256                          * default state.
1257                          */
1258                 case TBLOCK_END:
1259                         CommitTransaction();
1260                         s->blockState = TBLOCK_DEFAULT;
1261                         break;
1262
1263                         /*
1264                          * Here we are in the middle of a transaction block but one of
1265                          * the commands caused an abort so we do nothing but remain in
1266                          * the abort state.  Eventually we will get to the "END
1267                          * TRANSACTION" which will set things straight.
1268                          */
1269                 case TBLOCK_ABORT:
1270                         break;
1271
1272                         /*
1273                          * Here we were in an aborted transaction block which just
1274                          * processed the "END TRANSACTION" command from the user, so
1275                          * clean up and return to the default state.
1276                          */
1277                 case TBLOCK_ENDABORT:
1278                         CleanupTransaction();
1279                         s->blockState = TBLOCK_DEFAULT;
1280                         break;
1281         }
1282 }
1283
1284 /* --------------------------------
1285  *              AbortCurrentTransaction
1286  * --------------------------------
1287  */
1288 void
1289 AbortCurrentTransaction(void)
1290 {
1291         TransactionState s = CurrentTransactionState;
1292
1293         switch (s->blockState)
1294         {
1295
1296                         /*
1297                          * if we aren't in a transaction block, we just do the basic
1298                          * abort & cleanup transaction.
1299                          */
1300                 case TBLOCK_DEFAULT:
1301                         AbortTransaction();
1302                         CleanupTransaction();
1303                         break;
1304
1305                         /*
1306                          * If we are in the TBLOCK_BEGIN it means something screwed up
1307                          * right after reading "BEGIN TRANSACTION" so we enter the
1308                          * abort state.  Eventually an "END TRANSACTION" will fix
1309                          * things.
1310                          */
1311                 case TBLOCK_BEGIN:
1312                         s->blockState = TBLOCK_ABORT;
1313                         AbortTransaction();
1314                         /* CleanupTransaction happens when we exit TBLOCK_ABORT */
1315                         break;
1316
1317                         /*
1318                          * This is the case when are somewhere in a transaction block
1319                          * which aborted so we abort the transaction and set the ABORT
1320                          * state.  Eventually an "END TRANSACTION" will fix things and
1321                          * restore us to a normal state.
1322                          */
1323                 case TBLOCK_INPROGRESS:
1324                         s->blockState = TBLOCK_ABORT;
1325                         AbortTransaction();
1326                         /* CleanupTransaction happens when we exit TBLOCK_ABORT */
1327                         break;
1328
1329                         /*
1330                          * Here, the system was fouled up just after the user wanted
1331                          * to end the transaction block so we abort the transaction
1332                          * and put us back into the default state.
1333                          */
1334                 case TBLOCK_END:
1335                         s->blockState = TBLOCK_DEFAULT;
1336                         AbortTransaction();
1337                         CleanupTransaction();
1338                         break;
1339
1340                         /*
1341                          * Here, we are already in an aborted transaction state and
1342                          * are waiting for an "END TRANSACTION" to come along and lo
1343                          * and behold, we abort again! So we just remain in the abort
1344                          * state.
1345                          */
1346                 case TBLOCK_ABORT:
1347                         break;
1348
1349                         /*
1350                          * Here we were in an aborted transaction block which just
1351                          * processed the "END TRANSACTION" command but somehow aborted
1352                          * again.. since we must have done the abort processing, we
1353                          * clean up and return to the default state.
1354                          */
1355                 case TBLOCK_ENDABORT:
1356                         CleanupTransaction();
1357                         s->blockState = TBLOCK_DEFAULT;
1358                         break;
1359         }
1360 }
1361
1362 /* ----------------------------------------------------------------
1363  *                                         transaction block support
1364  * ----------------------------------------------------------------
1365  */
1366 /* --------------------------------
1367  *              BeginTransactionBlock
1368  * --------------------------------
1369  */
1370 void
1371 BeginTransactionBlock(void)
1372 {
1373         TransactionState s = CurrentTransactionState;
1374
1375         /*
1376          * check the current transaction state
1377          */
1378         if (s->blockState != TBLOCK_DEFAULT)
1379                 elog(NOTICE, "BEGIN: already a transaction in progress");
1380
1381         /*
1382          * set the current transaction block state information appropriately
1383          * during begin processing
1384          */
1385         s->blockState = TBLOCK_BEGIN;
1386
1387         /*
1388          * do begin processing
1389          */
1390
1391         /*
1392          * done with begin processing, set block state to inprogress
1393          */
1394         s->blockState = TBLOCK_INPROGRESS;
1395 }
1396
1397 /* --------------------------------
1398  *              EndTransactionBlock
1399  * --------------------------------
1400  */
1401 void
1402 EndTransactionBlock(void)
1403 {
1404         TransactionState s = CurrentTransactionState;
1405
1406         /*
1407          * check the current transaction state
1408          */
1409         if (s->blockState == TBLOCK_INPROGRESS)
1410         {
1411
1412                 /*
1413                  * here we are in a transaction block which should commit when we
1414                  * get to the upcoming CommitTransactionCommand() so we set the
1415                  * state to "END".      CommitTransactionCommand() will recognize this
1416                  * and commit the transaction and return us to the default state
1417                  */
1418                 s->blockState = TBLOCK_END;
1419                 return;
1420         }
1421
1422         if (s->blockState == TBLOCK_ABORT)
1423         {
1424
1425                 /*
1426                  * here, we are in a transaction block which aborted and since the
1427                  * AbortTransaction() was already done, we do whatever is needed
1428                  * and change to the special "END ABORT" state.  The upcoming
1429                  * CommitTransactionCommand() will recognise this and then put us
1430                  * back in the default state.
1431                  */
1432                 s->blockState = TBLOCK_ENDABORT;
1433                 return;
1434         }
1435
1436         /*
1437          * here, the user issued COMMIT when not inside a transaction. Issue a
1438          * notice and go to abort state.  The upcoming call to
1439          * CommitTransactionCommand() will then put us back into the default
1440          * state.
1441          */
1442         elog(NOTICE, "COMMIT: no transaction in progress");
1443         AbortTransaction();
1444         s->blockState = TBLOCK_ENDABORT;
1445 }
1446
1447 /* --------------------------------
1448  *              AbortTransactionBlock
1449  * --------------------------------
1450  */
1451 #ifdef NOT_USED
1452 static void
1453 AbortTransactionBlock(void)
1454 {
1455         TransactionState s = CurrentTransactionState;
1456
1457         /*
1458          * check the current transaction state
1459          */
1460         if (s->blockState == TBLOCK_INPROGRESS)
1461         {
1462
1463                 /*
1464                  * here we were inside a transaction block something screwed up
1465                  * inside the system so we enter the abort state, do the abort
1466                  * processing and then return. We remain in the abort state until
1467                  * we see an END TRANSACTION command.
1468                  */
1469                 s->blockState = TBLOCK_ABORT;
1470                 AbortTransaction();
1471                 return;
1472         }
1473
1474         /*
1475          * here, the user issued ABORT when not inside a transaction. Issue a
1476          * notice and go to abort state.  The upcoming call to
1477          * CommitTransactionCommand() will then put us back into the default
1478          * state.
1479          */
1480         elog(NOTICE, "ROLLBACK: no transaction in progress");
1481         AbortTransaction();
1482         s->blockState = TBLOCK_ENDABORT;
1483 }
1484
1485 #endif
1486
1487 /* --------------------------------
1488  *              UserAbortTransactionBlock
1489  * --------------------------------
1490  */
1491 void
1492 UserAbortTransactionBlock(void)
1493 {
1494         TransactionState s = CurrentTransactionState;
1495
1496         /*
1497          * if the transaction has already been automatically aborted with an
1498          * error, and the user subsequently types 'abort', allow it.  (the
1499          * behavior is the same as if they had typed 'end'.)
1500          */
1501         if (s->blockState == TBLOCK_ABORT)
1502         {
1503                 s->blockState = TBLOCK_ENDABORT;
1504                 return;
1505         }
1506
1507         if (s->blockState == TBLOCK_INPROGRESS)
1508         {
1509
1510                 /*
1511                  * here we were inside a transaction block and we got an abort
1512                  * command from the user, so we move to the abort state, do the
1513                  * abort processing and then change to the ENDABORT state so we
1514                  * will end up in the default state after the upcoming
1515                  * CommitTransactionCommand().
1516                  */
1517                 s->blockState = TBLOCK_ABORT;
1518                 AbortTransaction();
1519                 s->blockState = TBLOCK_ENDABORT;
1520                 return;
1521         }
1522
1523         /*
1524          * here, the user issued ABORT when not inside a transaction. Issue a
1525          * notice and go to abort state.  The upcoming call to
1526          * CommitTransactionCommand() will then put us back into the default
1527          * state.
1528          */
1529         elog(NOTICE, "ROLLBACK: no transaction in progress");
1530         AbortTransaction();
1531         s->blockState = TBLOCK_ENDABORT;
1532 }
1533
1534 /* --------------------------------
1535  *              AbortOutOfAnyTransaction
1536  *
1537  * This routine is provided for error recovery purposes.  It aborts any
1538  * active transaction or transaction block, leaving the system in a known
1539  * idle state.
1540  * --------------------------------
1541  */
1542 void
1543 AbortOutOfAnyTransaction(void)
1544 {
1545         TransactionState s = CurrentTransactionState;
1546
1547         /*
1548          * Get out of any low-level transaction
1549          */
1550         switch (s->state)
1551         {
1552                 case TRANS_START:
1553                 case TRANS_INPROGRESS:
1554                 case TRANS_COMMIT:
1555                         /* In a transaction, so clean up */
1556                         AbortTransaction();
1557                         CleanupTransaction();
1558                         break;
1559                 case TRANS_ABORT:
1560                         /* AbortTransaction already done, still need Cleanup */
1561                         CleanupTransaction();
1562                         break;
1563                 case TRANS_DEFAULT:
1564                         /* Not in a transaction, do nothing */
1565                         break;
1566         }
1567
1568         /*
1569          * Now reset the high-level state
1570          */
1571         s->blockState = TBLOCK_DEFAULT;
1572 }
1573
1574 bool
1575 IsTransactionBlock(void)
1576 {
1577         TransactionState s = CurrentTransactionState;
1578
1579         if (s->blockState == TBLOCK_INPROGRESS
1580                 || s->blockState == TBLOCK_ABORT
1581                 || s->blockState == TBLOCK_ENDABORT)
1582                 return true;
1583
1584         return false;
1585 }
1586
1587 void
1588 xact_redo(XLogRecPtr lsn, XLogRecord *record)
1589 {
1590         uint8           info = record->xl_info & ~XLR_INFO_MASK;
1591
1592         if (info == XLOG_XACT_COMMIT)
1593         {
1594                 TransactionIdCommit(record->xl_xid);
1595                 /* SHOULD REMOVE FILES OF ALL DROPPED RELATIONS */
1596         }
1597         else if (info == XLOG_XACT_ABORT)
1598         {
1599                 TransactionIdAbort(record->xl_xid);
1600                 /* SHOULD REMOVE FILES OF ALL FAILED-TO-BE-CREATED RELATIONS */
1601         }
1602         else
1603                 elog(STOP, "xact_redo: unknown op code %u", info);
1604 }
1605
1606 void
1607 xact_undo(XLogRecPtr lsn, XLogRecord *record)
1608 {
1609         uint8           info = record->xl_info & ~XLR_INFO_MASK;
1610
1611         if (info == XLOG_XACT_COMMIT)           /* shouldn't be called by XLOG */
1612                 elog(STOP, "xact_undo: can't undo committed xaction");
1613         else if (info != XLOG_XACT_ABORT)
1614                 elog(STOP, "xact_redo: unknown op code %u", info);
1615 }
1616
1617 void
1618 xact_desc(char *buf, uint8 xl_info, char *rec)
1619 {
1620         uint8           info = xl_info & ~XLR_INFO_MASK;
1621
1622         if (info == XLOG_XACT_COMMIT)
1623         {
1624                 xl_xact_commit *xlrec = (xl_xact_commit *) rec;
1625                 struct tm  *tm = localtime(&xlrec->xtime);
1626
1627                 sprintf(buf + strlen(buf), "commit: %04u-%02u-%02u %02u:%02u:%02u",
1628                                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
1629                                 tm->tm_hour, tm->tm_min, tm->tm_sec);
1630         }
1631         else if (info == XLOG_XACT_ABORT)
1632         {
1633                 xl_xact_abort *xlrec = (xl_xact_abort *) rec;
1634                 struct tm  *tm = localtime(&xlrec->xtime);
1635
1636                 sprintf(buf + strlen(buf), "abort: %04u-%02u-%02u %02u:%02u:%02u",
1637                                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
1638                                 tm->tm_hour, tm->tm_min, tm->tm_sec);
1639         }
1640         else
1641                 strcat(buf, "UNKNOWN");
1642 }
1643
1644 void
1645                         XactPushRollback(void (*func) (void *), void *data)
1646 {
1647 #ifdef XLOG_II
1648         if (_RollbackFunc != NULL)
1649                 elog(STOP, "XactPushRollback: already installed");
1650 #endif
1651
1652         _RollbackFunc = func;
1653         _RollbackData = data;
1654 }
1655
1656 void
1657 XactPopRollback(void)
1658 {
1659         _RollbackFunc = NULL;
1660 }