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