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