Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * xid8funcs.c
3 : *
4 : * Export internal transaction IDs to user level.
5 : *
6 : * Note that only top-level transaction IDs are exposed to user sessions.
7 : * This is important because xid8s frequently persist beyond the global
8 : * xmin horizon, or may even be shipped to other machines, so we cannot
9 : * rely on being able to correlate subtransaction IDs with their parents
10 : * via functions such as SubTransGetTopmostTransaction().
11 : *
12 : * These functions are used to support the txid_XXX functions and the newer
13 : * pg_current_xact_id, pg_current_snapshot and related fmgr functions, since
14 : * the only difference between them is whether they expose xid8 or int8 values
15 : * to users. The txid_XXX variants should eventually be dropped.
16 : *
17 : *
18 : * Copyright (c) 2003-2024, PostgreSQL Global Development Group
19 : * Author: Jan Wieck, Afilias USA INC.
20 : * 64-bit txids: Marko Kreen, Skype Technologies
21 : *
22 : * src/backend/utils/adt/xid8funcs.c
23 : *
24 : *-------------------------------------------------------------------------
25 : */
26 :
27 : #include "postgres.h"
28 :
29 : #include "access/transam.h"
30 : #include "access/xact.h"
31 : #include "funcapi.h"
32 : #include "lib/qunique.h"
33 : #include "libpq/pqformat.h"
34 : #include "miscadmin.h"
35 : #include "postmaster/postmaster.h"
36 : #include "storage/lwlock.h"
37 : #include "storage/procarray.h"
38 : #include "utils/builtins.h"
39 : #include "utils/memutils.h"
40 : #include "utils/snapmgr.h"
41 : #include "utils/xid8.h"
42 :
43 :
44 : /*
45 : * If defined, use bsearch() function for searching for xid8s in snapshots
46 : * that have more than the specified number of values.
47 : */
48 : #define USE_BSEARCH_IF_NXIP_GREATER 30
49 :
50 :
51 : /*
52 : * Snapshot containing FullTransactionIds.
53 : */
54 : typedef struct
55 : {
56 : /*
57 : * 4-byte length hdr, should not be touched directly.
58 : *
59 : * Explicit embedding is ok as we want always correct alignment anyway.
60 : */
61 : int32 __varsz;
62 :
63 : uint32 nxip; /* number of fxids in xip array */
64 : FullTransactionId xmin;
65 : FullTransactionId xmax;
66 : /* in-progress fxids, xmin <= xip[i] < xmax: */
67 : FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
68 : } pg_snapshot;
69 :
70 : #define PG_SNAPSHOT_SIZE(nxip) \
71 : (offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip))
72 : #define PG_SNAPSHOT_MAX_NXIP \
73 : ((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId))
74 :
75 : /*
76 : * Compile-time limits on the procarray (MAX_BACKENDS processes plus
77 : * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large.
78 : */
79 : StaticAssertDecl(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
80 : "possible overflow in pg_current_snapshot()");
81 :
82 :
83 : /*
84 : * Helper to get a TransactionId from a 64-bit xid with wraparound detection.
85 : *
86 : * It is an ERROR if the xid is in the future. Otherwise, returns true if
87 : * the transaction is still new enough that we can determine whether it
88 : * committed and false otherwise. If *extracted_xid is not NULL, it is set
89 : * to the low 32 bits of the transaction ID (i.e. the actual XID, without the
90 : * epoch).
91 : *
92 : * The caller must hold XactTruncationLock since it's dealing with arbitrary
93 : * XIDs, and must continue to hold it until it's done with any clog lookups
94 : * relating to those XIDs.
95 : */
96 : static bool
97 84 : TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
98 : {
99 84 : TransactionId xid = XidFromFullTransactionId(fxid);
100 : uint32 now_epoch;
101 : TransactionId now_epoch_next_xid;
102 : FullTransactionId now_fullxid;
103 : TransactionId oldest_xid;
104 : FullTransactionId oldest_fxid;
105 :
106 84 : now_fullxid = ReadNextFullTransactionId();
107 84 : now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
108 84 : now_epoch = EpochFromFullTransactionId(now_fullxid);
109 :
110 84 : if (extracted_xid != NULL)
111 84 : *extracted_xid = xid;
112 :
113 84 : if (!TransactionIdIsValid(xid))
114 0 : return false;
115 :
116 : /* For non-normal transaction IDs, we can ignore the epoch. */
117 84 : if (!TransactionIdIsNormal(xid))
118 24 : return true;
119 :
120 : /* If the transaction ID is in the future, throw an error. */
121 60 : if (!FullTransactionIdPrecedes(fxid, now_fullxid))
122 12 : ereport(ERROR,
123 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
124 : errmsg("transaction ID %llu is in the future",
125 : (unsigned long long) U64FromFullTransactionId(fxid))));
126 :
127 : /*
128 : * TransamVariables->oldestClogXid is protected by XactTruncationLock, but
129 : * we don't acquire that lock here. Instead, we require the caller to
130 : * acquire it, because the caller is presumably going to look up the
131 : * returned XID. If we took and released the lock within this function, a
132 : * CLOG truncation could occur before the caller finished with the XID.
133 : */
134 : Assert(LWLockHeldByMe(XactTruncationLock));
135 :
136 : /*
137 : * If fxid is not older than TransamVariables->oldestClogXid, the relevant
138 : * CLOG entry is guaranteed to still exist. Convert
139 : * TransamVariables->oldestClogXid into a FullTransactionId to compare it
140 : * with fxid. Determine the right epoch knowing that oldest_fxid
141 : * shouldn't be more than 2^31 older than now_fullxid.
142 : */
143 48 : oldest_xid = TransamVariables->oldestClogXid;
144 : Assert(TransactionIdPrecedesOrEquals(oldest_xid, now_epoch_next_xid));
145 48 : if (oldest_xid <= now_epoch_next_xid)
146 : {
147 48 : oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch, oldest_xid);
148 : }
149 : else
150 : {
151 : Assert(now_epoch > 0);
152 0 : oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch - 1, oldest_xid);
153 : }
154 48 : return !FullTransactionIdPrecedes(fxid, oldest_fxid);
155 : }
156 :
157 : /*
158 : * Convert a TransactionId obtained from a snapshot held by the caller to a
159 : * FullTransactionId. Use next_fxid as a reference FullTransactionId, so that
160 : * we can compute the high order bits. It must have been obtained by the
161 : * caller with ReadNextFullTransactionId() after the snapshot was created.
162 : */
163 : static FullTransactionId
164 106 : widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
165 : {
166 106 : TransactionId next_xid = XidFromFullTransactionId(next_fxid);
167 106 : uint32 epoch = EpochFromFullTransactionId(next_fxid);
168 :
169 : /* Special transaction ID. */
170 106 : if (!TransactionIdIsNormal(xid))
171 0 : return FullTransactionIdFromEpochAndXid(0, xid);
172 :
173 : /*
174 : * The 64 bit result must be <= next_fxid, since next_fxid hadn't been
175 : * issued yet when the snapshot was created. Every TransactionId in the
176 : * snapshot must therefore be from the same epoch as next_fxid, or the
177 : * epoch before. We know this because next_fxid is never allow to get
178 : * more than one epoch ahead of the TransactionIds in any snapshot.
179 : */
180 106 : if (xid > next_xid)
181 0 : epoch--;
182 :
183 106 : return FullTransactionIdFromEpochAndXid(epoch, xid);
184 : }
185 :
186 : /*
187 : * txid comparator for qsort/bsearch
188 : */
189 : static int
190 2712 : cmp_fxid(const void *aa, const void *bb)
191 : {
192 2712 : FullTransactionId a = *(const FullTransactionId *) aa;
193 2712 : FullTransactionId b = *(const FullTransactionId *) bb;
194 :
195 2712 : if (FullTransactionIdPrecedes(a, b))
196 654 : return -1;
197 2058 : if (FullTransactionIdPrecedes(b, a))
198 1686 : return 1;
199 372 : return 0;
200 : }
201 :
202 : /*
203 : * Sort a snapshot's txids, so we can use bsearch() later. Also remove
204 : * any duplicates.
205 : *
206 : * For consistency of on-disk representation, we always sort even if bsearch
207 : * will not be used.
208 : */
209 : static void
210 40 : sort_snapshot(pg_snapshot *snap)
211 : {
212 40 : if (snap->nxip > 1)
213 : {
214 6 : qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
215 6 : snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
216 : cmp_fxid);
217 : }
218 40 : }
219 :
220 : /*
221 : * check fxid visibility.
222 : */
223 : static bool
224 1020 : is_visible_fxid(FullTransactionId value, const pg_snapshot *snap)
225 : {
226 1020 : if (FullTransactionIdPrecedes(value, snap->xmin))
227 132 : return true;
228 888 : else if (!FullTransactionIdPrecedes(value, snap->xmax))
229 168 : return false;
230 : #ifdef USE_BSEARCH_IF_NXIP_GREATER
231 720 : else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
232 : {
233 : void *res;
234 :
235 600 : res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
236 : cmp_fxid);
237 : /* if found, transaction is still in progress */
238 600 : return (res) ? false : true;
239 : }
240 : #endif
241 : else
242 : {
243 : uint32 i;
244 :
245 360 : for (i = 0; i < snap->nxip; i++)
246 : {
247 288 : if (FullTransactionIdEquals(value, snap->xip[i]))
248 48 : return false;
249 : }
250 72 : return true;
251 : }
252 : }
253 :
254 : /*
255 : * helper functions to use StringInfo for pg_snapshot creation.
256 : */
257 :
258 : static StringInfo
259 186 : buf_init(FullTransactionId xmin, FullTransactionId xmax)
260 : {
261 : pg_snapshot snap;
262 : StringInfo buf;
263 :
264 186 : snap.xmin = xmin;
265 186 : snap.xmax = xmax;
266 186 : snap.nxip = 0;
267 :
268 186 : buf = makeStringInfo();
269 186 : appendBinaryStringInfo(buf, &snap, PG_SNAPSHOT_SIZE(0));
270 186 : return buf;
271 : }
272 :
273 : static void
274 624 : buf_add_txid(StringInfo buf, FullTransactionId fxid)
275 : {
276 624 : pg_snapshot *snap = (pg_snapshot *) buf->data;
277 :
278 : /* do this before possible realloc */
279 624 : snap->nxip++;
280 :
281 624 : appendBinaryStringInfo(buf, &fxid, sizeof(fxid));
282 624 : }
283 :
284 : static pg_snapshot *
285 150 : buf_finalize(StringInfo buf)
286 : {
287 150 : pg_snapshot *snap = (pg_snapshot *) buf->data;
288 :
289 150 : SET_VARSIZE(snap, buf->len);
290 :
291 : /* buf is not needed anymore */
292 150 : buf->data = NULL;
293 150 : pfree(buf);
294 :
295 150 : return snap;
296 : }
297 :
298 : /*
299 : * parse snapshot from cstring
300 : */
301 : static pg_snapshot *
302 234 : parse_snapshot(const char *str, Node *escontext)
303 : {
304 : FullTransactionId xmin;
305 : FullTransactionId xmax;
306 234 : FullTransactionId last_val = InvalidFullTransactionId;
307 : FullTransactionId val;
308 234 : const char *str_start = str;
309 : char *endp;
310 : StringInfo buf;
311 :
312 234 : xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10));
313 234 : if (*endp != ':')
314 0 : goto bad_format;
315 234 : str = endp + 1;
316 :
317 234 : xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10));
318 234 : if (*endp != ':')
319 0 : goto bad_format;
320 234 : str = endp + 1;
321 :
322 : /* it should look sane */
323 234 : if (!FullTransactionIdIsValid(xmin) ||
324 222 : !FullTransactionIdIsValid(xmax) ||
325 210 : FullTransactionIdPrecedes(xmax, xmin))
326 48 : goto bad_format;
327 :
328 : /* allocate buffer */
329 186 : buf = buf_init(xmin, xmax);
330 :
331 : /* loop over values */
332 822 : while (*str != '\0')
333 : {
334 : /* read next value */
335 672 : val = FullTransactionIdFromU64(strtou64(str, &endp, 10));
336 672 : str = endp;
337 :
338 : /* require the input to be in order */
339 672 : if (FullTransactionIdPrecedes(val, xmin) ||
340 660 : FullTransactionIdFollowsOrEquals(val, xmax) ||
341 660 : FullTransactionIdPrecedes(val, last_val))
342 36 : goto bad_format;
343 :
344 : /* skip duplicates */
345 636 : if (!FullTransactionIdEquals(val, last_val))
346 624 : buf_add_txid(buf, val);
347 636 : last_val = val;
348 :
349 636 : if (*str == ',')
350 516 : str++;
351 120 : else if (*str != '\0')
352 0 : goto bad_format;
353 : }
354 :
355 150 : return buf_finalize(buf);
356 :
357 84 : bad_format:
358 84 : ereturn(escontext, NULL,
359 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
360 : errmsg("invalid input syntax for type %s: \"%s\"",
361 : "pg_snapshot", str_start)));
362 : }
363 :
364 : /*
365 : * pg_current_xact_id() returns xid8
366 : *
367 : * Return the current toplevel full transaction ID.
368 : * If the current transaction does not have one, one is assigned.
369 : */
370 : Datum
371 5714 : pg_current_xact_id(PG_FUNCTION_ARGS)
372 : {
373 : /*
374 : * Must prevent during recovery because if an xid is not assigned we try
375 : * to assign one, which would fail. Programs already rely on this function
376 : * to always return a valid current xid, so we should not change this to
377 : * return NULL or similar invalid xid.
378 : */
379 5714 : PreventCommandDuringRecovery("pg_current_xact_id()");
380 :
381 5714 : PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
382 : }
383 :
384 : /*
385 : * Same as pg_current_xact_id() but doesn't assign a new xid if there
386 : * isn't one yet.
387 : */
388 : Datum
389 24 : pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
390 : {
391 24 : FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
392 :
393 24 : if (!FullTransactionIdIsValid(topfxid))
394 12 : PG_RETURN_NULL();
395 :
396 12 : PG_RETURN_FULLTRANSACTIONID(topfxid);
397 : }
398 :
399 : /*
400 : * pg_current_snapshot() returns pg_snapshot
401 : *
402 : * Return current snapshot
403 : *
404 : * Note that only top-transaction XIDs are included in the snapshot.
405 : */
406 : Datum
407 40 : pg_current_snapshot(PG_FUNCTION_ARGS)
408 : {
409 : pg_snapshot *snap;
410 : uint32 nxip,
411 : i;
412 : Snapshot cur;
413 40 : FullTransactionId next_fxid = ReadNextFullTransactionId();
414 :
415 40 : cur = GetActiveSnapshot();
416 40 : if (cur == NULL)
417 0 : elog(ERROR, "no active snapshot set");
418 :
419 : /* allocate */
420 40 : nxip = cur->xcnt;
421 40 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
422 :
423 : /* fill */
424 40 : snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
425 40 : snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
426 40 : snap->nxip = nxip;
427 66 : for (i = 0; i < nxip; i++)
428 26 : snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid);
429 :
430 : /*
431 : * We want them guaranteed to be in ascending order. This also removes
432 : * any duplicate xids. Normally, an XID can only be assigned to one
433 : * backend, but when preparing a transaction for two-phase commit, there
434 : * is a transient state when both the original backend and the dummy
435 : * PGPROC entry reserved for the prepared transaction hold the same XID.
436 : */
437 40 : sort_snapshot(snap);
438 :
439 : /* set size after sorting, because it may have removed duplicate xips */
440 40 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip));
441 :
442 40 : PG_RETURN_POINTER(snap);
443 : }
444 :
445 : /*
446 : * pg_snapshot_in(cstring) returns pg_snapshot
447 : *
448 : * input function for type pg_snapshot
449 : */
450 : Datum
451 234 : pg_snapshot_in(PG_FUNCTION_ARGS)
452 : {
453 234 : char *str = PG_GETARG_CSTRING(0);
454 : pg_snapshot *snap;
455 :
456 234 : snap = parse_snapshot(str, fcinfo->context);
457 :
458 174 : PG_RETURN_POINTER(snap);
459 : }
460 :
461 : /*
462 : * pg_snapshot_out(pg_snapshot) returns cstring
463 : *
464 : * output function for type pg_snapshot
465 : */
466 : Datum
467 124 : pg_snapshot_out(PG_FUNCTION_ARGS)
468 : {
469 124 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
470 : StringInfoData str;
471 : uint32 i;
472 :
473 124 : initStringInfo(&str);
474 :
475 124 : appendStringInfo(&str, UINT64_FORMAT ":",
476 : U64FromFullTransactionId(snap->xmin));
477 124 : appendStringInfo(&str, UINT64_FORMAT ":",
478 : U64FromFullTransactionId(snap->xmax));
479 :
480 688 : for (i = 0; i < snap->nxip; i++)
481 : {
482 564 : if (i > 0)
483 464 : appendStringInfoChar(&str, ',');
484 564 : appendStringInfo(&str, UINT64_FORMAT,
485 : U64FromFullTransactionId(snap->xip[i]));
486 : }
487 :
488 124 : PG_RETURN_CSTRING(str.data);
489 : }
490 :
491 : /*
492 : * pg_snapshot_recv(internal) returns pg_snapshot
493 : *
494 : * binary input function for type pg_snapshot
495 : *
496 : * format: int4 nxip, int8 xmin, int8 xmax, int8 xip
497 : */
498 : Datum
499 0 : pg_snapshot_recv(PG_FUNCTION_ARGS)
500 : {
501 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
502 : pg_snapshot *snap;
503 0 : FullTransactionId last = InvalidFullTransactionId;
504 : int nxip;
505 : int i;
506 : FullTransactionId xmin;
507 : FullTransactionId xmax;
508 :
509 : /* load and validate nxip */
510 0 : nxip = pq_getmsgint(buf, 4);
511 0 : if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP)
512 0 : goto bad_format;
513 :
514 0 : xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
515 0 : xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
516 0 : if (!FullTransactionIdIsValid(xmin) ||
517 0 : !FullTransactionIdIsValid(xmax) ||
518 0 : FullTransactionIdPrecedes(xmax, xmin))
519 0 : goto bad_format;
520 :
521 0 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
522 0 : snap->xmin = xmin;
523 0 : snap->xmax = xmax;
524 :
525 0 : for (i = 0; i < nxip; i++)
526 : {
527 : FullTransactionId cur =
528 0 : FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
529 :
530 0 : if (FullTransactionIdPrecedes(cur, last) ||
531 0 : FullTransactionIdPrecedes(cur, xmin) ||
532 0 : FullTransactionIdPrecedes(xmax, cur))
533 0 : goto bad_format;
534 :
535 : /* skip duplicate xips */
536 0 : if (FullTransactionIdEquals(cur, last))
537 : {
538 0 : i--;
539 0 : nxip--;
540 0 : continue;
541 : }
542 :
543 0 : snap->xip[i] = cur;
544 0 : last = cur;
545 : }
546 0 : snap->nxip = nxip;
547 0 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip));
548 0 : PG_RETURN_POINTER(snap);
549 :
550 0 : bad_format:
551 0 : ereport(ERROR,
552 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
553 : errmsg("invalid external pg_snapshot data")));
554 : PG_RETURN_POINTER(NULL); /* keep compiler quiet */
555 : }
556 :
557 : /*
558 : * pg_snapshot_send(pg_snapshot) returns bytea
559 : *
560 : * binary output function for type pg_snapshot
561 : *
562 : * format: int4 nxip, u64 xmin, u64 xmax, u64 xip...
563 : */
564 : Datum
565 0 : pg_snapshot_send(PG_FUNCTION_ARGS)
566 : {
567 0 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
568 : StringInfoData buf;
569 : uint32 i;
570 :
571 0 : pq_begintypsend(&buf);
572 0 : pq_sendint32(&buf, snap->nxip);
573 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
574 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
575 0 : for (i = 0; i < snap->nxip; i++)
576 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
577 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
578 : }
579 :
580 : /*
581 : * pg_visible_in_snapshot(xid8, pg_snapshot) returns bool
582 : *
583 : * is txid visible in snapshot ?
584 : */
585 : Datum
586 1020 : pg_visible_in_snapshot(PG_FUNCTION_ARGS)
587 : {
588 1020 : FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
589 1020 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);
590 :
591 1020 : PG_RETURN_BOOL(is_visible_fxid(value, snap));
592 : }
593 :
594 : /*
595 : * pg_snapshot_xmin(pg_snapshot) returns xid8
596 : *
597 : * return snapshot's xmin
598 : */
599 : Datum
600 76 : pg_snapshot_xmin(PG_FUNCTION_ARGS)
601 : {
602 76 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
603 :
604 76 : PG_RETURN_FULLTRANSACTIONID(snap->xmin);
605 : }
606 :
607 : /*
608 : * pg_snapshot_xmax(pg_snapshot) returns xid8
609 : *
610 : * return snapshot's xmax
611 : */
612 : Datum
613 48 : pg_snapshot_xmax(PG_FUNCTION_ARGS)
614 : {
615 48 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
616 :
617 48 : PG_RETURN_FULLTRANSACTIONID(snap->xmax);
618 : }
619 :
620 : /*
621 : * pg_snapshot_xip(pg_snapshot) returns setof xid8
622 : *
623 : * return in-progress xid8s in snapshot.
624 : */
625 : Datum
626 492 : pg_snapshot_xip(PG_FUNCTION_ARGS)
627 : {
628 : FuncCallContext *fctx;
629 : pg_snapshot *snap;
630 : FullTransactionId value;
631 :
632 : /* on first call initialize fctx and get copy of snapshot */
633 492 : if (SRF_IS_FIRSTCALL())
634 : {
635 48 : pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
636 :
637 48 : fctx = SRF_FIRSTCALL_INIT();
638 :
639 : /* make a copy of user snapshot */
640 48 : snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg));
641 48 : memcpy(snap, arg, VARSIZE(arg));
642 :
643 48 : fctx->user_fctx = snap;
644 : }
645 :
646 : /* return values one-by-one */
647 492 : fctx = SRF_PERCALL_SETUP();
648 492 : snap = fctx->user_fctx;
649 492 : if (fctx->call_cntr < snap->nxip)
650 : {
651 444 : value = snap->xip[fctx->call_cntr];
652 444 : SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
653 : }
654 : else
655 : {
656 48 : SRF_RETURN_DONE(fctx);
657 : }
658 : }
659 :
660 : /*
661 : * Report the status of a recent transaction ID, or null for wrapped,
662 : * truncated away or otherwise too old XIDs.
663 : *
664 : * The passed epoch-qualified xid is treated as a normal xid, not a
665 : * multixact id.
666 : *
667 : * If it points to a committed subxact the result is the subxact status even
668 : * though the parent xact may still be in progress or may have aborted.
669 : */
670 : Datum
671 84 : pg_xact_status(PG_FUNCTION_ARGS)
672 : {
673 : const char *status;
674 84 : FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
675 : TransactionId xid;
676 :
677 : /*
678 : * We must protect against concurrent truncation of clog entries to avoid
679 : * an I/O error on SLRU lookup.
680 : */
681 84 : LWLockAcquire(XactTruncationLock, LW_SHARED);
682 84 : if (TransactionIdInRecentPast(fxid, &xid))
683 : {
684 : Assert(TransactionIdIsValid(xid));
685 :
686 : /*
687 : * Like when doing visibility checks on a row, check whether the
688 : * transaction is still in progress before looking into the CLOG.
689 : * Otherwise we would incorrectly return "committed" for a transaction
690 : * that is committing and has already updated the CLOG, but hasn't
691 : * removed its XID from the proc array yet. (See comment on that race
692 : * condition at the top of heapam_visibility.c)
693 : */
694 60 : if (TransactionIdIsInProgress(xid))
695 12 : status = "in progress";
696 48 : else if (TransactionIdDidCommit(xid))
697 36 : status = "committed";
698 : else
699 : {
700 : /* it must have aborted or crashed */
701 12 : status = "aborted";
702 : }
703 : }
704 : else
705 : {
706 12 : status = NULL;
707 : }
708 72 : LWLockRelease(XactTruncationLock);
709 :
710 72 : if (status == NULL)
711 12 : PG_RETURN_NULL();
712 : else
713 60 : PG_RETURN_TEXT_P(cstring_to_text(status));
714 : }
|