1 | /*------------------------------------------------------------------------- |
2 | * txid.c |
3 | * |
4 | * Export internal transaction IDs to user level. |
5 | * |
6 | * Note that only top-level transaction IDs are ever converted to TXID. |
7 | * This is important because TXIDs 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 | * |
13 | * Copyright (c) 2003-2019, PostgreSQL Global Development Group |
14 | * Author: Jan Wieck, Afilias USA INC. |
15 | * 64-bit txids: Marko Kreen, Skype Technologies |
16 | * |
17 | * src/backend/utils/adt/txid.c |
18 | * |
19 | *------------------------------------------------------------------------- |
20 | */ |
21 | |
22 | #include "postgres.h" |
23 | |
24 | #include "access/clog.h" |
25 | #include "access/transam.h" |
26 | #include "access/xact.h" |
27 | #include "access/xlog.h" |
28 | #include "funcapi.h" |
29 | #include "miscadmin.h" |
30 | #include "libpq/pqformat.h" |
31 | #include "postmaster/postmaster.h" |
32 | #include "storage/lwlock.h" |
33 | #include "utils/builtins.h" |
34 | #include "utils/memutils.h" |
35 | #include "utils/snapmgr.h" |
36 | |
37 | |
38 | /* txid will be signed int8 in database, so must limit to 63 bits */ |
39 | #define MAX_TXID ((uint64) PG_INT64_MAX) |
40 | |
41 | /* Use unsigned variant internally */ |
42 | typedef uint64 txid; |
43 | |
44 | /* sprintf format code for uint64 */ |
45 | #define TXID_FMT UINT64_FORMAT |
46 | |
47 | /* |
48 | * If defined, use bsearch() function for searching for txids in snapshots |
49 | * that have more than the specified number of values. |
50 | */ |
51 | #define USE_BSEARCH_IF_NXIP_GREATER 30 |
52 | |
53 | |
54 | /* |
55 | * Snapshot containing 8byte txids. |
56 | */ |
57 | typedef struct |
58 | { |
59 | /* |
60 | * 4-byte length hdr, should not be touched directly. |
61 | * |
62 | * Explicit embedding is ok as we want always correct alignment anyway. |
63 | */ |
64 | int32 __varsz; |
65 | |
66 | uint32 nxip; /* number of txids in xip array */ |
67 | txid xmin; |
68 | txid xmax; |
69 | /* in-progress txids, xmin <= xip[i] < xmax: */ |
70 | txid xip[FLEXIBLE_ARRAY_MEMBER]; |
71 | } TxidSnapshot; |
72 | |
73 | #define TXID_SNAPSHOT_SIZE(nxip) \ |
74 | (offsetof(TxidSnapshot, xip) + sizeof(txid) * (nxip)) |
75 | #define TXID_SNAPSHOT_MAX_NXIP \ |
76 | ((MaxAllocSize - offsetof(TxidSnapshot, xip)) / sizeof(txid)) |
77 | |
78 | /* |
79 | * Epoch values from xact.c |
80 | */ |
81 | typedef struct |
82 | { |
83 | TransactionId last_xid; |
84 | uint32 epoch; |
85 | } TxidEpoch; |
86 | |
87 | |
88 | /* |
89 | * Fetch epoch data from xact.c. |
90 | */ |
91 | static void |
92 | load_xid_epoch(TxidEpoch *state) |
93 | { |
94 | FullTransactionId fullXid = ReadNextFullTransactionId(); |
95 | |
96 | state->last_xid = XidFromFullTransactionId(fullXid); |
97 | state->epoch = EpochFromFullTransactionId(fullXid); |
98 | } |
99 | |
100 | /* |
101 | * Helper to get a TransactionId from a 64-bit xid with wraparound detection. |
102 | * |
103 | * It is an ERROR if the xid is in the future. Otherwise, returns true if |
104 | * the transaction is still new enough that we can determine whether it |
105 | * committed and false otherwise. If *extracted_xid is not NULL, it is set |
106 | * to the low 32 bits of the transaction ID (i.e. the actual XID, without the |
107 | * epoch). |
108 | * |
109 | * The caller must hold CLogTruncationLock since it's dealing with arbitrary |
110 | * XIDs, and must continue to hold it until it's done with any clog lookups |
111 | * relating to those XIDs. |
112 | */ |
113 | static bool |
114 | TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *) |
115 | { |
116 | uint32 xid_epoch = (uint32) (xid_with_epoch >> 32); |
117 | TransactionId xid = (TransactionId) xid_with_epoch; |
118 | uint32 now_epoch; |
119 | TransactionId now_epoch_next_xid; |
120 | FullTransactionId now_fullxid; |
121 | |
122 | now_fullxid = ReadNextFullTransactionId(); |
123 | now_epoch_next_xid = XidFromFullTransactionId(now_fullxid); |
124 | now_epoch = EpochFromFullTransactionId(now_fullxid); |
125 | |
126 | if (extracted_xid != NULL) |
127 | *extracted_xid = xid; |
128 | |
129 | if (!TransactionIdIsValid(xid)) |
130 | return false; |
131 | |
132 | /* For non-normal transaction IDs, we can ignore the epoch. */ |
133 | if (!TransactionIdIsNormal(xid)) |
134 | return true; |
135 | |
136 | /* If the transaction ID is in the future, throw an error. */ |
137 | if (xid_with_epoch >= U64FromFullTransactionId(now_fullxid)) |
138 | ereport(ERROR, |
139 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
140 | errmsg("transaction ID %s is in the future" , |
141 | psprintf(UINT64_FORMAT, xid_with_epoch)))); |
142 | |
143 | /* |
144 | * ShmemVariableCache->oldestClogXid is protected by CLogTruncationLock, |
145 | * but we don't acquire that lock here. Instead, we require the caller to |
146 | * acquire it, because the caller is presumably going to look up the |
147 | * returned XID. If we took and released the lock within this function, a |
148 | * CLOG truncation could occur before the caller finished with the XID. |
149 | */ |
150 | Assert(LWLockHeldByMe(CLogTruncationLock)); |
151 | |
152 | /* |
153 | * If the transaction ID has wrapped around, it's definitely too old to |
154 | * determine the commit status. Otherwise, we can compare it to |
155 | * ShmemVariableCache->oldestClogXid to determine whether the relevant |
156 | * CLOG entry is guaranteed to still exist. |
157 | */ |
158 | if (xid_epoch + 1 < now_epoch |
159 | || (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid) |
160 | || TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid)) |
161 | return false; |
162 | |
163 | return true; |
164 | } |
165 | |
166 | /* |
167 | * do a TransactionId -> txid conversion for an XID near the given epoch |
168 | */ |
169 | static txid |
170 | convert_xid(TransactionId xid, const TxidEpoch *state) |
171 | { |
172 | uint64 epoch; |
173 | |
174 | /* return special xid's as-is */ |
175 | if (!TransactionIdIsNormal(xid)) |
176 | return (txid) xid; |
177 | |
178 | /* xid can be on either side when near wrap-around */ |
179 | epoch = (uint64) state->epoch; |
180 | if (xid > state->last_xid && |
181 | TransactionIdPrecedes(xid, state->last_xid)) |
182 | epoch--; |
183 | else if (xid < state->last_xid && |
184 | TransactionIdFollows(xid, state->last_xid)) |
185 | epoch++; |
186 | |
187 | return (epoch << 32) | xid; |
188 | } |
189 | |
190 | /* |
191 | * txid comparator for qsort/bsearch |
192 | */ |
193 | static int |
194 | cmp_txid(const void *aa, const void *bb) |
195 | { |
196 | txid a = *(const txid *) aa; |
197 | txid b = *(const txid *) bb; |
198 | |
199 | if (a < b) |
200 | return -1; |
201 | if (a > b) |
202 | return 1; |
203 | return 0; |
204 | } |
205 | |
206 | /* |
207 | * Sort a snapshot's txids, so we can use bsearch() later. Also remove |
208 | * any duplicates. |
209 | * |
210 | * For consistency of on-disk representation, we always sort even if bsearch |
211 | * will not be used. |
212 | */ |
213 | static void |
214 | sort_snapshot(TxidSnapshot *snap) |
215 | { |
216 | txid last = 0; |
217 | int nxip, |
218 | idx1, |
219 | idx2; |
220 | |
221 | if (snap->nxip > 1) |
222 | { |
223 | qsort(snap->xip, snap->nxip, sizeof(txid), cmp_txid); |
224 | |
225 | /* remove duplicates */ |
226 | nxip = snap->nxip; |
227 | idx1 = idx2 = 0; |
228 | while (idx1 < nxip) |
229 | { |
230 | if (snap->xip[idx1] != last) |
231 | last = snap->xip[idx2++] = snap->xip[idx1]; |
232 | else |
233 | snap->nxip--; |
234 | idx1++; |
235 | } |
236 | } |
237 | } |
238 | |
239 | /* |
240 | * check txid visibility. |
241 | */ |
242 | static bool |
243 | is_visible_txid(txid value, const TxidSnapshot *snap) |
244 | { |
245 | if (value < snap->xmin) |
246 | return true; |
247 | else if (value >= snap->xmax) |
248 | return false; |
249 | #ifdef USE_BSEARCH_IF_NXIP_GREATER |
250 | else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER) |
251 | { |
252 | void *res; |
253 | |
254 | res = bsearch(&value, snap->xip, snap->nxip, sizeof(txid), cmp_txid); |
255 | /* if found, transaction is still in progress */ |
256 | return (res) ? false : true; |
257 | } |
258 | #endif |
259 | else |
260 | { |
261 | uint32 i; |
262 | |
263 | for (i = 0; i < snap->nxip; i++) |
264 | { |
265 | if (value == snap->xip[i]) |
266 | return false; |
267 | } |
268 | return true; |
269 | } |
270 | } |
271 | |
272 | /* |
273 | * helper functions to use StringInfo for TxidSnapshot creation. |
274 | */ |
275 | |
276 | static StringInfo |
277 | buf_init(txid xmin, txid xmax) |
278 | { |
279 | TxidSnapshot snap; |
280 | StringInfo buf; |
281 | |
282 | snap.xmin = xmin; |
283 | snap.xmax = xmax; |
284 | snap.nxip = 0; |
285 | |
286 | buf = makeStringInfo(); |
287 | appendBinaryStringInfo(buf, (char *) &snap, TXID_SNAPSHOT_SIZE(0)); |
288 | return buf; |
289 | } |
290 | |
291 | static void |
292 | buf_add_txid(StringInfo buf, txid xid) |
293 | { |
294 | TxidSnapshot *snap = (TxidSnapshot *) buf->data; |
295 | |
296 | /* do this before possible realloc */ |
297 | snap->nxip++; |
298 | |
299 | appendBinaryStringInfo(buf, (char *) &xid, sizeof(xid)); |
300 | } |
301 | |
302 | static TxidSnapshot * |
303 | buf_finalize(StringInfo buf) |
304 | { |
305 | TxidSnapshot *snap = (TxidSnapshot *) buf->data; |
306 | |
307 | SET_VARSIZE(snap, buf->len); |
308 | |
309 | /* buf is not needed anymore */ |
310 | buf->data = NULL; |
311 | pfree(buf); |
312 | |
313 | return snap; |
314 | } |
315 | |
316 | /* |
317 | * simple number parser. |
318 | * |
319 | * We return 0 on error, which is invalid value for txid. |
320 | */ |
321 | static txid |
322 | str2txid(const char *s, const char **endp) |
323 | { |
324 | txid val = 0; |
325 | txid cutoff = MAX_TXID / 10; |
326 | txid cutlim = MAX_TXID % 10; |
327 | |
328 | for (; *s; s++) |
329 | { |
330 | unsigned d; |
331 | |
332 | if (*s < '0' || *s > '9') |
333 | break; |
334 | d = *s - '0'; |
335 | |
336 | /* |
337 | * check for overflow |
338 | */ |
339 | if (val > cutoff || (val == cutoff && d > cutlim)) |
340 | { |
341 | val = 0; |
342 | break; |
343 | } |
344 | |
345 | val = val * 10 + d; |
346 | } |
347 | if (endp) |
348 | *endp = s; |
349 | return val; |
350 | } |
351 | |
352 | /* |
353 | * parse snapshot from cstring |
354 | */ |
355 | static TxidSnapshot * |
356 | parse_snapshot(const char *str) |
357 | { |
358 | txid xmin; |
359 | txid xmax; |
360 | txid last_val = 0, |
361 | val; |
362 | const char *str_start = str; |
363 | const char *endp; |
364 | StringInfo buf; |
365 | |
366 | xmin = str2txid(str, &endp); |
367 | if (*endp != ':') |
368 | goto bad_format; |
369 | str = endp + 1; |
370 | |
371 | xmax = str2txid(str, &endp); |
372 | if (*endp != ':') |
373 | goto bad_format; |
374 | str = endp + 1; |
375 | |
376 | /* it should look sane */ |
377 | if (xmin == 0 || xmax == 0 || xmin > xmax) |
378 | goto bad_format; |
379 | |
380 | /* allocate buffer */ |
381 | buf = buf_init(xmin, xmax); |
382 | |
383 | /* loop over values */ |
384 | while (*str != '\0') |
385 | { |
386 | /* read next value */ |
387 | val = str2txid(str, &endp); |
388 | str = endp; |
389 | |
390 | /* require the input to be in order */ |
391 | if (val < xmin || val >= xmax || val < last_val) |
392 | goto bad_format; |
393 | |
394 | /* skip duplicates */ |
395 | if (val != last_val) |
396 | buf_add_txid(buf, val); |
397 | last_val = val; |
398 | |
399 | if (*str == ',') |
400 | str++; |
401 | else if (*str != '\0') |
402 | goto bad_format; |
403 | } |
404 | |
405 | return buf_finalize(buf); |
406 | |
407 | bad_format: |
408 | ereport(ERROR, |
409 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
410 | errmsg("invalid input syntax for type %s: \"%s\"" , |
411 | "txid_snapshot" , str_start))); |
412 | return NULL; /* keep compiler quiet */ |
413 | } |
414 | |
415 | /* |
416 | * Public functions. |
417 | * |
418 | * txid_current() and txid_current_snapshot() are the only ones that |
419 | * communicate with core xid machinery. All the others work on data |
420 | * returned by them. |
421 | */ |
422 | |
423 | /* |
424 | * txid_current() returns int8 |
425 | * |
426 | * Return the current toplevel transaction ID as TXID |
427 | * If the current transaction does not have one, one is assigned. |
428 | * |
429 | * This value has the epoch as the high 32 bits and the 32-bit xid |
430 | * as the low 32 bits. |
431 | */ |
432 | Datum |
433 | txid_current(PG_FUNCTION_ARGS) |
434 | { |
435 | txid val; |
436 | TxidEpoch state; |
437 | |
438 | /* |
439 | * Must prevent during recovery because if an xid is not assigned we try |
440 | * to assign one, which would fail. Programs already rely on this function |
441 | * to always return a valid current xid, so we should not change this to |
442 | * return NULL or similar invalid xid. |
443 | */ |
444 | PreventCommandDuringRecovery("txid_current()" ); |
445 | |
446 | load_xid_epoch(&state); |
447 | |
448 | val = convert_xid(GetTopTransactionId(), &state); |
449 | |
450 | PG_RETURN_INT64(val); |
451 | } |
452 | |
453 | /* |
454 | * Same as txid_current() but doesn't assign a new xid if there isn't one |
455 | * yet. |
456 | */ |
457 | Datum |
458 | txid_current_if_assigned(PG_FUNCTION_ARGS) |
459 | { |
460 | txid val; |
461 | TxidEpoch state; |
462 | TransactionId topxid = GetTopTransactionIdIfAny(); |
463 | |
464 | if (topxid == InvalidTransactionId) |
465 | PG_RETURN_NULL(); |
466 | |
467 | load_xid_epoch(&state); |
468 | |
469 | val = convert_xid(topxid, &state); |
470 | |
471 | PG_RETURN_INT64(val); |
472 | } |
473 | |
474 | /* |
475 | * txid_current_snapshot() returns txid_snapshot |
476 | * |
477 | * Return current snapshot in TXID format |
478 | * |
479 | * Note that only top-transaction XIDs are included in the snapshot. |
480 | */ |
481 | Datum |
482 | txid_current_snapshot(PG_FUNCTION_ARGS) |
483 | { |
484 | TxidSnapshot *snap; |
485 | uint32 nxip, |
486 | i; |
487 | TxidEpoch state; |
488 | Snapshot cur; |
489 | |
490 | cur = GetActiveSnapshot(); |
491 | if (cur == NULL) |
492 | elog(ERROR, "no active snapshot set" ); |
493 | |
494 | load_xid_epoch(&state); |
495 | |
496 | /* |
497 | * Compile-time limits on the procarray (MAX_BACKENDS processes plus |
498 | * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large. |
499 | */ |
500 | StaticAssertStmt(MAX_BACKENDS * 2 <= TXID_SNAPSHOT_MAX_NXIP, |
501 | "possible overflow in txid_current_snapshot()" ); |
502 | |
503 | /* allocate */ |
504 | nxip = cur->xcnt; |
505 | snap = palloc(TXID_SNAPSHOT_SIZE(nxip)); |
506 | |
507 | /* fill */ |
508 | snap->xmin = convert_xid(cur->xmin, &state); |
509 | snap->xmax = convert_xid(cur->xmax, &state); |
510 | snap->nxip = nxip; |
511 | for (i = 0; i < nxip; i++) |
512 | snap->xip[i] = convert_xid(cur->xip[i], &state); |
513 | |
514 | /* |
515 | * We want them guaranteed to be in ascending order. This also removes |
516 | * any duplicate xids. Normally, an XID can only be assigned to one |
517 | * backend, but when preparing a transaction for two-phase commit, there |
518 | * is a transient state when both the original backend and the dummy |
519 | * PGPROC entry reserved for the prepared transaction hold the same XID. |
520 | */ |
521 | sort_snapshot(snap); |
522 | |
523 | /* set size after sorting, because it may have removed duplicate xips */ |
524 | SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(snap->nxip)); |
525 | |
526 | PG_RETURN_POINTER(snap); |
527 | } |
528 | |
529 | /* |
530 | * txid_snapshot_in(cstring) returns txid_snapshot |
531 | * |
532 | * input function for type txid_snapshot |
533 | */ |
534 | Datum |
535 | txid_snapshot_in(PG_FUNCTION_ARGS) |
536 | { |
537 | char *str = PG_GETARG_CSTRING(0); |
538 | TxidSnapshot *snap; |
539 | |
540 | snap = parse_snapshot(str); |
541 | |
542 | PG_RETURN_POINTER(snap); |
543 | } |
544 | |
545 | /* |
546 | * txid_snapshot_out(txid_snapshot) returns cstring |
547 | * |
548 | * output function for type txid_snapshot |
549 | */ |
550 | Datum |
551 | txid_snapshot_out(PG_FUNCTION_ARGS) |
552 | { |
553 | TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0); |
554 | StringInfoData str; |
555 | uint32 i; |
556 | |
557 | initStringInfo(&str); |
558 | |
559 | appendStringInfo(&str, TXID_FMT ":" , snap->xmin); |
560 | appendStringInfo(&str, TXID_FMT ":" , snap->xmax); |
561 | |
562 | for (i = 0; i < snap->nxip; i++) |
563 | { |
564 | if (i > 0) |
565 | appendStringInfoChar(&str, ','); |
566 | appendStringInfo(&str, TXID_FMT, snap->xip[i]); |
567 | } |
568 | |
569 | PG_RETURN_CSTRING(str.data); |
570 | } |
571 | |
572 | /* |
573 | * txid_snapshot_recv(internal) returns txid_snapshot |
574 | * |
575 | * binary input function for type txid_snapshot |
576 | * |
577 | * format: int4 nxip, int8 xmin, int8 xmax, int8 xip |
578 | */ |
579 | Datum |
580 | txid_snapshot_recv(PG_FUNCTION_ARGS) |
581 | { |
582 | StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); |
583 | TxidSnapshot *snap; |
584 | txid last = 0; |
585 | int nxip; |
586 | int i; |
587 | txid xmin, |
588 | xmax; |
589 | |
590 | /* load and validate nxip */ |
591 | nxip = pq_getmsgint(buf, 4); |
592 | if (nxip < 0 || nxip > TXID_SNAPSHOT_MAX_NXIP) |
593 | goto bad_format; |
594 | |
595 | xmin = pq_getmsgint64(buf); |
596 | xmax = pq_getmsgint64(buf); |
597 | if (xmin == 0 || xmax == 0 || xmin > xmax || xmax > MAX_TXID) |
598 | goto bad_format; |
599 | |
600 | snap = palloc(TXID_SNAPSHOT_SIZE(nxip)); |
601 | snap->xmin = xmin; |
602 | snap->xmax = xmax; |
603 | |
604 | for (i = 0; i < nxip; i++) |
605 | { |
606 | txid cur = pq_getmsgint64(buf); |
607 | |
608 | if (cur < last || cur < xmin || cur >= xmax) |
609 | goto bad_format; |
610 | |
611 | /* skip duplicate xips */ |
612 | if (cur == last) |
613 | { |
614 | i--; |
615 | nxip--; |
616 | continue; |
617 | } |
618 | |
619 | snap->xip[i] = cur; |
620 | last = cur; |
621 | } |
622 | snap->nxip = nxip; |
623 | SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip)); |
624 | PG_RETURN_POINTER(snap); |
625 | |
626 | bad_format: |
627 | ereport(ERROR, |
628 | (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), |
629 | errmsg("invalid external txid_snapshot data" ))); |
630 | PG_RETURN_POINTER(NULL); /* keep compiler quiet */ |
631 | } |
632 | |
633 | /* |
634 | * txid_snapshot_send(txid_snapshot) returns bytea |
635 | * |
636 | * binary output function for type txid_snapshot |
637 | * |
638 | * format: int4 nxip, int8 xmin, int8 xmax, int8 xip |
639 | */ |
640 | Datum |
641 | txid_snapshot_send(PG_FUNCTION_ARGS) |
642 | { |
643 | TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0); |
644 | StringInfoData buf; |
645 | uint32 i; |
646 | |
647 | pq_begintypsend(&buf); |
648 | pq_sendint32(&buf, snap->nxip); |
649 | pq_sendint64(&buf, snap->xmin); |
650 | pq_sendint64(&buf, snap->xmax); |
651 | for (i = 0; i < snap->nxip; i++) |
652 | pq_sendint64(&buf, snap->xip[i]); |
653 | PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); |
654 | } |
655 | |
656 | /* |
657 | * txid_visible_in_snapshot(int8, txid_snapshot) returns bool |
658 | * |
659 | * is txid visible in snapshot ? |
660 | */ |
661 | Datum |
662 | txid_visible_in_snapshot(PG_FUNCTION_ARGS) |
663 | { |
664 | txid value = PG_GETARG_INT64(0); |
665 | TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(1); |
666 | |
667 | PG_RETURN_BOOL(is_visible_txid(value, snap)); |
668 | } |
669 | |
670 | /* |
671 | * txid_snapshot_xmin(txid_snapshot) returns int8 |
672 | * |
673 | * return snapshot's xmin |
674 | */ |
675 | Datum |
676 | txid_snapshot_xmin(PG_FUNCTION_ARGS) |
677 | { |
678 | TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0); |
679 | |
680 | PG_RETURN_INT64(snap->xmin); |
681 | } |
682 | |
683 | /* |
684 | * txid_snapshot_xmax(txid_snapshot) returns int8 |
685 | * |
686 | * return snapshot's xmax |
687 | */ |
688 | Datum |
689 | txid_snapshot_xmax(PG_FUNCTION_ARGS) |
690 | { |
691 | TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0); |
692 | |
693 | PG_RETURN_INT64(snap->xmax); |
694 | } |
695 | |
696 | /* |
697 | * txid_snapshot_xip(txid_snapshot) returns setof int8 |
698 | * |
699 | * return in-progress TXIDs in snapshot. |
700 | */ |
701 | Datum |
702 | txid_snapshot_xip(PG_FUNCTION_ARGS) |
703 | { |
704 | FuncCallContext *fctx; |
705 | TxidSnapshot *snap; |
706 | txid value; |
707 | |
708 | /* on first call initialize snap_state and get copy of snapshot */ |
709 | if (SRF_IS_FIRSTCALL()) |
710 | { |
711 | TxidSnapshot *arg = (TxidSnapshot *) PG_GETARG_VARLENA_P(0); |
712 | |
713 | fctx = SRF_FIRSTCALL_INIT(); |
714 | |
715 | /* make a copy of user snapshot */ |
716 | snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg)); |
717 | memcpy(snap, arg, VARSIZE(arg)); |
718 | |
719 | fctx->user_fctx = snap; |
720 | } |
721 | |
722 | /* return values one-by-one */ |
723 | fctx = SRF_PERCALL_SETUP(); |
724 | snap = fctx->user_fctx; |
725 | if (fctx->call_cntr < snap->nxip) |
726 | { |
727 | value = snap->xip[fctx->call_cntr]; |
728 | SRF_RETURN_NEXT(fctx, Int64GetDatum(value)); |
729 | } |
730 | else |
731 | { |
732 | SRF_RETURN_DONE(fctx); |
733 | } |
734 | } |
735 | |
736 | /* |
737 | * Report the status of a recent transaction ID, or null for wrapped, |
738 | * truncated away or otherwise too old XIDs. |
739 | * |
740 | * The passed epoch-qualified xid is treated as a normal xid, not a |
741 | * multixact id. |
742 | * |
743 | * If it points to a committed subxact the result is the subxact status even |
744 | * though the parent xact may still be in progress or may have aborted. |
745 | */ |
746 | Datum |
747 | txid_status(PG_FUNCTION_ARGS) |
748 | { |
749 | const char *status; |
750 | uint64 xid_with_epoch = PG_GETARG_INT64(0); |
751 | TransactionId xid; |
752 | |
753 | /* |
754 | * We must protect against concurrent truncation of clog entries to avoid |
755 | * an I/O error on SLRU lookup. |
756 | */ |
757 | LWLockAcquire(CLogTruncationLock, LW_SHARED); |
758 | if (TransactionIdInRecentPast(xid_with_epoch, &xid)) |
759 | { |
760 | Assert(TransactionIdIsValid(xid)); |
761 | |
762 | if (TransactionIdIsCurrentTransactionId(xid)) |
763 | status = "in progress" ; |
764 | else if (TransactionIdDidCommit(xid)) |
765 | status = "committed" ; |
766 | else if (TransactionIdDidAbort(xid)) |
767 | status = "aborted" ; |
768 | else |
769 | { |
770 | /* |
771 | * The xact is not marked as either committed or aborted in clog. |
772 | * |
773 | * It could be a transaction that ended without updating clog or |
774 | * writing an abort record due to a crash. We can safely assume |
775 | * it's aborted if it isn't committed and is older than our |
776 | * snapshot xmin. |
777 | * |
778 | * Otherwise it must be in-progress (or have been at the time we |
779 | * checked commit/abort status). |
780 | */ |
781 | if (TransactionIdPrecedes(xid, GetActiveSnapshot()->xmin)) |
782 | status = "aborted" ; |
783 | else |
784 | status = "in progress" ; |
785 | } |
786 | } |
787 | else |
788 | { |
789 | status = NULL; |
790 | } |
791 | LWLockRelease(CLogTruncationLock); |
792 | |
793 | if (status == NULL) |
794 | PG_RETURN_NULL(); |
795 | else |
796 | PG_RETURN_TEXT_P(cstring_to_text(status)); |
797 | } |
798 | |