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-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
1468 tmunro@postgresql.or 97 :CBC 42 : TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
98 : : {
99 : 42 : 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 : :
1844 106 : 42 : now_fullxid = ReadNextFullTransactionId();
107 : 42 : now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
108 : 42 : now_epoch = EpochFromFullTransactionId(now_fullxid);
109 : :
2578 rhaas@postgresql.org 110 [ + - ]: 42 : if (extracted_xid != NULL)
111 : 42 : *extracted_xid = xid;
112 : :
113 [ - + ]: 42 : if (!TransactionIdIsValid(xid))
2578 rhaas@postgresql.org 114 :UBC 0 : return false;
115 : :
116 : : /* For non-normal transaction IDs, we can ignore the epoch. */
2578 rhaas@postgresql.org 117 [ + + ]:CBC 42 : if (!TransactionIdIsNormal(xid))
118 : 12 : return true;
119 : :
120 : : /* If the transaction ID is in the future, throw an error. */
1468 tmunro@postgresql.or 121 [ + + ]: 30 : if (!FullTransactionIdPrecedes(fxid, now_fullxid))
2578 rhaas@postgresql.org 122 [ + - ]: 6 : 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 : : */
1430 tgl@sss.pgh.pa.us 134 [ - + ]: 24 : 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 : : */
66 akorotkov@postgresql 143 :GNC 24 : oldest_xid = TransamVariables->oldestClogXid;
66 akorotkov@postgresql 144 [ - + ]:CBC 24 : Assert(TransactionIdPrecedesOrEquals(oldest_xid, now_epoch_next_xid));
145 [ + - ]: 24 : if (oldest_xid <= now_epoch_next_xid)
146 : : {
147 : 24 : oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch, oldest_xid);
148 : : }
149 : : else
150 : : {
66 akorotkov@postgresql 151 [ # # ]:UBC 0 : Assert(now_epoch > 0);
152 : 0 : oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch - 1, oldest_xid);
153 : : }
66 akorotkov@postgresql 154 :CBC 24 : 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
1468 tmunro@postgresql.or 164 : 64 : widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
165 : : {
166 : 64 : TransactionId next_xid = XidFromFullTransactionId(next_fxid);
167 : 64 : uint32 epoch = EpochFromFullTransactionId(next_fxid);
168 : :
169 : : /* Special transaction ID. */
6028 tgl@sss.pgh.pa.us 170 [ - + ]: 64 : if (!TransactionIdIsNormal(xid))
1468 tmunro@postgresql.or 171 :UBC 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 : : */
1468 tmunro@postgresql.or 180 [ - + ]:CBC 64 : if (xid > next_xid)
6028 tgl@sss.pgh.pa.us 181 :UBC 0 : epoch--;
182 : :
1468 tmunro@postgresql.or 183 :CBC 64 : return FullTransactionIdFromEpochAndXid(epoch, xid);
184 : : }
185 : :
186 : : /*
187 : : * txid comparator for qsort/bsearch
188 : : */
189 : : static int
190 : 1373 : cmp_fxid(const void *aa, const void *bb)
191 : : {
192 : 1373 : FullTransactionId a = *(const FullTransactionId *) aa;
193 : 1373 : FullTransactionId b = *(const FullTransactionId *) bb;
194 : :
195 [ + + ]: 1373 : if (FullTransactionIdPrecedes(a, b))
6028 tgl@sss.pgh.pa.us 196 : 330 : return -1;
1468 tmunro@postgresql.or 197 [ + + ]: 1043 : if (FullTransactionIdPrecedes(b, a))
6028 tgl@sss.pgh.pa.us 198 : 857 : return 1;
199 : 186 : 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
1468 tmunro@postgresql.or 210 : 20 : sort_snapshot(pg_snapshot *snap)
211 : : {
6028 tgl@sss.pgh.pa.us 212 [ + + ]: 20 : if (snap->nxip > 1)
213 : : {
1468 tmunro@postgresql.or 214 : 8 : qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
215 : 8 : snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
216 : : cmp_fxid);
217 : : }
6028 tgl@sss.pgh.pa.us 218 : 20 : }
219 : :
220 : : /*
221 : : * check fxid visibility.
222 : : */
223 : : static bool
1468 tmunro@postgresql.or 224 : 510 : is_visible_fxid(FullTransactionId value, const pg_snapshot *snap)
225 : : {
226 [ + + ]: 510 : if (FullTransactionIdPrecedes(value, snap->xmin))
6028 tgl@sss.pgh.pa.us 227 : 66 : return true;
1468 tmunro@postgresql.or 228 [ + + ]: 444 : else if (!FullTransactionIdPrecedes(value, snap->xmax))
6028 tgl@sss.pgh.pa.us 229 : 84 : return false;
230 : : #ifdef USE_BSEARCH_IF_NXIP_GREATER
231 [ + + ]: 360 : else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
232 : : {
233 : : void *res;
234 : :
1468 tmunro@postgresql.or 235 : 300 : res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
236 : : cmp_fxid);
237 : : /* if found, transaction is still in progress */
6028 tgl@sss.pgh.pa.us 238 : 300 : return (res) ? false : true;
239 : : }
240 : : #endif
241 : : else
242 : : {
243 : : uint32 i;
244 : :
245 [ + + ]: 180 : for (i = 0; i < snap->nxip; i++)
246 : : {
1468 tmunro@postgresql.or 247 [ + + ]: 144 : if (FullTransactionIdEquals(value, snap->xip[i]))
6028 tgl@sss.pgh.pa.us 248 : 24 : return false;
249 : : }
250 : 36 : return true;
251 : : }
252 : : }
253 : :
254 : : /*
255 : : * helper functions to use StringInfo for pg_snapshot creation.
256 : : */
257 : :
258 : : static StringInfo
1468 tmunro@postgresql.or 259 : 93 : buf_init(FullTransactionId xmin, FullTransactionId xmax)
260 : : {
261 : : pg_snapshot snap;
262 : : StringInfo buf;
263 : :
6028 tgl@sss.pgh.pa.us 264 : 93 : snap.xmin = xmin;
265 : 93 : snap.xmax = xmax;
266 : 93 : snap.nxip = 0;
267 : :
268 : 93 : buf = makeStringInfo();
471 peter@eisentraut.org 269 : 93 : appendBinaryStringInfo(buf, &snap, PG_SNAPSHOT_SIZE(0));
6028 tgl@sss.pgh.pa.us 270 : 93 : return buf;
271 : : }
272 : :
273 : : static void
1468 tmunro@postgresql.or 274 : 312 : buf_add_txid(StringInfo buf, FullTransactionId fxid)
275 : : {
276 : 312 : pg_snapshot *snap = (pg_snapshot *) buf->data;
277 : :
278 : : /* do this before possible realloc */
6028 tgl@sss.pgh.pa.us 279 : 312 : snap->nxip++;
280 : :
471 peter@eisentraut.org 281 : 312 : appendBinaryStringInfo(buf, &fxid, sizeof(fxid));
6028 tgl@sss.pgh.pa.us 282 : 312 : }
283 : :
284 : : static pg_snapshot *
285 : 75 : buf_finalize(StringInfo buf)
286 : : {
1468 tmunro@postgresql.or 287 : 75 : pg_snapshot *snap = (pg_snapshot *) buf->data;
288 : :
6028 tgl@sss.pgh.pa.us 289 : 75 : SET_VARSIZE(snap, buf->len);
290 : :
291 : : /* buf is not needed anymore */
292 : 75 : buf->data = NULL;
293 : 75 : pfree(buf);
294 : :
295 : 75 : return snap;
296 : : }
297 : :
298 : : /*
299 : : * parse snapshot from cstring
300 : : */
301 : : static pg_snapshot *
487 302 : 117 : parse_snapshot(const char *str, Node *escontext)
303 : : {
304 : : FullTransactionId xmin;
305 : : FullTransactionId xmax;
1468 tmunro@postgresql.or 306 : 117 : FullTransactionId last_val = InvalidFullTransactionId;
307 : : FullTransactionId val;
6028 tgl@sss.pgh.pa.us 308 : 117 : const char *str_start = str;
309 : : char *endp;
310 : : StringInfo buf;
311 : :
849 peter@eisentraut.org 312 : 117 : xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10));
6028 tgl@sss.pgh.pa.us 313 [ - + ]: 117 : if (*endp != ':')
6028 tgl@sss.pgh.pa.us 314 :UBC 0 : goto bad_format;
6028 tgl@sss.pgh.pa.us 315 :CBC 117 : str = endp + 1;
316 : :
849 peter@eisentraut.org 317 : 117 : xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10));
6028 tgl@sss.pgh.pa.us 318 [ - + ]: 117 : if (*endp != ':')
6028 tgl@sss.pgh.pa.us 319 :UBC 0 : goto bad_format;
6028 tgl@sss.pgh.pa.us 320 :CBC 117 : str = endp + 1;
321 : :
322 : : /* it should look sane */
1468 tmunro@postgresql.or 323 [ + + ]: 117 : if (!FullTransactionIdIsValid(xmin) ||
324 [ + + ]: 111 : !FullTransactionIdIsValid(xmax) ||
325 [ + + ]: 105 : FullTransactionIdPrecedes(xmax, xmin))
6028 tgl@sss.pgh.pa.us 326 : 24 : goto bad_format;
327 : :
328 : : /* allocate buffer */
329 : 93 : buf = buf_init(xmin, xmax);
330 : :
331 : : /* loop over values */
332 [ + + ]: 411 : while (*str != '\0')
333 : : {
334 : : /* read next value */
849 peter@eisentraut.org 335 : 336 : val = FullTransactionIdFromU64(strtou64(str, &endp, 10));
6028 tgl@sss.pgh.pa.us 336 : 336 : str = endp;
337 : :
338 : : /* require the input to be in order */
1468 tmunro@postgresql.or 339 [ + + ]: 336 : if (FullTransactionIdPrecedes(val, xmin) ||
340 [ + - ]: 330 : FullTransactionIdFollowsOrEquals(val, xmax) ||
341 [ + + ]: 330 : FullTransactionIdPrecedes(val, last_val))
6028 tgl@sss.pgh.pa.us 342 : 18 : goto bad_format;
343 : :
344 : : /* skip duplicates */
1468 tmunro@postgresql.or 345 [ + + ]: 318 : if (!FullTransactionIdEquals(val, last_val))
3622 heikki.linnakangas@i 346 : 312 : buf_add_txid(buf, val);
6028 tgl@sss.pgh.pa.us 347 : 318 : last_val = val;
348 : :
349 [ + + ]: 318 : if (*str == ',')
350 : 258 : str++;
351 [ - + ]: 60 : else if (*str != '\0')
6028 tgl@sss.pgh.pa.us 352 :UBC 0 : goto bad_format;
353 : : }
354 : :
6028 tgl@sss.pgh.pa.us 355 :CBC 75 : return buf_finalize(buf);
356 : :
357 : 42 : bad_format:
487 358 [ + + ]: 42 : 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
1468 tmunro@postgresql.or 371 : 3098 : 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 : 3098 : PreventCommandDuringRecovery("pg_current_xact_id()");
380 : :
381 : 3098 : 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 : 12 : pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
390 : : {
391 : 12 : FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
392 : :
393 [ + + ]: 12 : if (!FullTransactionIdIsValid(topfxid))
2791 rhaas@postgresql.org 394 : 6 : PG_RETURN_NULL();
395 : :
1468 tmunro@postgresql.or 396 : 6 : 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 : 20 : pg_current_snapshot(PG_FUNCTION_ARGS)
408 : : {
409 : : pg_snapshot *snap;
410 : : uint32 nxip,
411 : : i;
412 : : Snapshot cur;
413 : 20 : FullTransactionId next_fxid = ReadNextFullTransactionId();
414 : :
5816 alvherre@alvh.no-ip. 415 : 20 : cur = GetActiveSnapshot();
6028 tgl@sss.pgh.pa.us 416 [ - + ]: 20 : if (cur == NULL)
5816 alvherre@alvh.no-ip. 417 [ # # ]:UBC 0 : elog(ERROR, "no active snapshot set");
418 : :
419 : : /* allocate */
6028 tgl@sss.pgh.pa.us 420 :CBC 20 : nxip = cur->xcnt;
1468 tmunro@postgresql.or 421 : 20 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
422 : :
423 : : /* fill */
424 : 20 : snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
425 : 20 : snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
6028 tgl@sss.pgh.pa.us 426 : 20 : snap->nxip = nxip;
427 [ + + ]: 44 : for (i = 0; i < nxip; i++)
1468 tmunro@postgresql.or 428 : 24 : 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 : : */
6028 tgl@sss.pgh.pa.us 437 : 20 : sort_snapshot(snap);
438 : :
439 : : /* set size after sorting, because it may have removed duplicate xips */
1468 tmunro@postgresql.or 440 : 20 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip));
441 : :
6028 tgl@sss.pgh.pa.us 442 : 20 : 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
1468 tmunro@postgresql.or 451 : 117 : pg_snapshot_in(PG_FUNCTION_ARGS)
452 : : {
6028 tgl@sss.pgh.pa.us 453 : 117 : char *str = PG_GETARG_CSTRING(0);
454 : : pg_snapshot *snap;
455 : :
487 456 : 117 : snap = parse_snapshot(str, fcinfo->context);
457 : :
6028 458 : 87 : 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
1468 tmunro@postgresql.or 467 : 62 : pg_snapshot_out(PG_FUNCTION_ARGS)
468 : : {
469 : 62 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
470 : : StringInfoData str;
471 : : uint32 i;
472 : :
6028 tgl@sss.pgh.pa.us 473 : 62 : initStringInfo(&str);
474 : :
1468 tmunro@postgresql.or 475 : 62 : appendStringInfo(&str, UINT64_FORMAT ":",
476 : : U64FromFullTransactionId(snap->xmin));
477 : 62 : appendStringInfo(&str, UINT64_FORMAT ":",
478 : : U64FromFullTransactionId(snap->xmax));
479 : :
6028 tgl@sss.pgh.pa.us 480 [ + + ]: 344 : for (i = 0; i < snap->nxip; i++)
481 : : {
482 [ + + ]: 282 : if (i > 0)
483 : 232 : appendStringInfoChar(&str, ',');
1468 tmunro@postgresql.or 484 : 282 : appendStringInfo(&str, UINT64_FORMAT,
485 : : U64FromFullTransactionId(snap->xip[i]));
486 : : }
487 : :
6028 tgl@sss.pgh.pa.us 488 : 62 : 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
1468 tmunro@postgresql.or 499 :UBC 0 : pg_snapshot_recv(PG_FUNCTION_ARGS)
500 : : {
5995 bruce@momjian.us 501 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
502 : : pg_snapshot *snap;
1468 tmunro@postgresql.or 503 : 0 : FullTransactionId last = InvalidFullTransactionId;
504 : : int nxip;
505 : : int i;
506 : : FullTransactionId xmin;
507 : : FullTransactionId xmax;
508 : :
509 : : /* load and validate nxip */
6028 tgl@sss.pgh.pa.us 510 : 0 : nxip = pq_getmsgint(buf, 4);
1468 tmunro@postgresql.or 511 [ # # # # ]: 0 : if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP)
6028 tgl@sss.pgh.pa.us 512 : 0 : goto bad_format;
513 : :
1468 tmunro@postgresql.or 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))
6028 tgl@sss.pgh.pa.us 519 : 0 : goto bad_format;
520 : :
1468 tmunro@postgresql.or 521 : 0 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
6028 tgl@sss.pgh.pa.us 522 : 0 : snap->xmin = xmin;
523 : 0 : snap->xmax = xmax;
524 : :
525 [ # # ]: 0 : for (i = 0; i < nxip; i++)
526 : : {
527 : : FullTransactionId cur =
331 528 : 0 : FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
529 : :
1468 tmunro@postgresql.or 530 [ # # ]: 0 : if (FullTransactionIdPrecedes(cur, last) ||
531 [ # # ]: 0 : FullTransactionIdPrecedes(cur, xmin) ||
532 [ # # ]: 0 : FullTransactionIdPrecedes(xmax, cur))
6028 tgl@sss.pgh.pa.us 533 : 0 : goto bad_format;
534 : :
535 : : /* skip duplicate xips */
1468 tmunro@postgresql.or 536 [ # # ]: 0 : if (FullTransactionIdEquals(cur, last))
537 : : {
3622 heikki.linnakangas@i 538 : 0 : i--;
539 : 0 : nxip--;
540 : 0 : continue;
541 : : }
542 : :
6028 tgl@sss.pgh.pa.us 543 : 0 : snap->xip[i] = cur;
544 : 0 : last = cur;
545 : : }
3622 heikki.linnakangas@i 546 : 0 : snap->nxip = nxip;
1468 tmunro@postgresql.or 547 : 0 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip));
6028 tgl@sss.pgh.pa.us 548 : 0 : PG_RETURN_POINTER(snap);
549 : :
550 : 0 : bad_format:
3178 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
1468 tmunro@postgresql.or 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 : :
6028 tgl@sss.pgh.pa.us 571 : 0 : pq_begintypsend(&buf);
2377 andres@anarazel.de 572 : 0 : pq_sendint32(&buf, snap->nxip);
1468 tmunro@postgresql.or 573 : 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
574 : 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
6028 tgl@sss.pgh.pa.us 575 [ # # ]: 0 : for (i = 0; i < snap->nxip; i++)
1468 tmunro@postgresql.or 576 : 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
6028 tgl@sss.pgh.pa.us 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
1468 tmunro@postgresql.or 586 :CBC 510 : pg_visible_in_snapshot(PG_FUNCTION_ARGS)
587 : : {
588 : 510 : FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
589 : 510 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);
590 : :
591 : 510 : 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 : 38 : pg_snapshot_xmin(PG_FUNCTION_ARGS)
601 : : {
602 : 38 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
603 : :
604 : 38 : 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 : 24 : pg_snapshot_xmax(PG_FUNCTION_ARGS)
614 : : {
615 : 24 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
616 : :
617 : 24 : 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 : 246 : 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 */
5995 bruce@momjian.us 633 [ + + ]: 246 : if (SRF_IS_FIRSTCALL())
634 : : {
1468 tmunro@postgresql.or 635 : 24 : pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
636 : :
6028 tgl@sss.pgh.pa.us 637 : 24 : fctx = SRF_FIRSTCALL_INIT();
638 : :
639 : : /* make a copy of user snapshot */
640 : 24 : snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg));
641 : 24 : memcpy(snap, arg, VARSIZE(arg));
642 : :
643 : 24 : fctx->user_fctx = snap;
644 : : }
645 : :
646 : : /* return values one-by-one */
647 : 246 : fctx = SRF_PERCALL_SETUP();
648 : 246 : snap = fctx->user_fctx;
5995 bruce@momjian.us 649 [ + + ]: 246 : if (fctx->call_cntr < snap->nxip)
650 : : {
6028 tgl@sss.pgh.pa.us 651 : 222 : value = snap->xip[fctx->call_cntr];
1468 tmunro@postgresql.or 652 : 222 : SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
653 : : }
654 : : else
655 : : {
6028 tgl@sss.pgh.pa.us 656 : 24 : 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
1468 tmunro@postgresql.or 671 : 42 : pg_xact_status(PG_FUNCTION_ARGS)
672 : : {
673 : : const char *status;
674 : 42 : 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 : : */
1430 tgl@sss.pgh.pa.us 681 : 42 : LWLockAcquire(XactTruncationLock, LW_SHARED);
1468 tmunro@postgresql.or 682 [ + + ]: 42 : if (TransactionIdInRecentPast(fxid, &xid))
683 : : {
2578 rhaas@postgresql.org 684 [ - + ]: 30 : 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 : : */
657 heikki.linnakangas@i 694 [ + + ]: 30 : if (TransactionIdIsInProgress(xid))
2407 peter_e@gmx.net 695 : 6 : status = "in progress";
2578 rhaas@postgresql.org 696 [ + + ]: 24 : else if (TransactionIdDidCommit(xid))
2407 peter_e@gmx.net 697 : 18 : status = "committed";
698 : : else
699 : : {
700 : : /* it must have aborted or crashed */
657 heikki.linnakangas@i 701 : 6 : status = "aborted";
702 : : }
703 : : }
704 : : else
705 : : {
2578 rhaas@postgresql.org 706 : 6 : status = NULL;
707 : : }
1430 tgl@sss.pgh.pa.us 708 : 36 : LWLockRelease(XactTruncationLock);
709 : :
2578 rhaas@postgresql.org 710 [ + + ]: 36 : if (status == NULL)
711 : 6 : PG_RETURN_NULL();
712 : : else
713 : 30 : PG_RETURN_TEXT_P(cstring_to_text(status));
714 : : }
|