| /*------------------------------------------------------------------------- |
| * |
| * time_mapping.c |
| * time to XID mapping information |
| * |
| * Copyright (c) 2020-2023, PostgreSQL Global Development Group |
| * |
| * contrib/old_snapshot/time_mapping.c |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "funcapi.h" |
| #include "storage/lwlock.h" |
| #include "utils/old_snapshot.h" |
| #include "utils/snapmgr.h" |
| #include "utils/timestamp.h" |
| |
| /* |
| * Backend-private copy of the information from oldSnapshotControl which relates |
| * to the time to XID mapping, plus an index so that we can iterate. |
| * |
| * Note that the length of the xid_by_minute array is given by |
| * OLD_SNAPSHOT_TIME_MAP_ENTRIES (which is not a compile-time constant). |
| */ |
| typedef struct |
| { |
| int current_index; |
| int head_offset; |
| TimestampTz head_timestamp; |
| int count_used; |
| TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER]; |
| } OldSnapshotTimeMapping; |
| |
| #define NUM_TIME_MAPPING_COLUMNS 3 |
| |
| PG_MODULE_MAGIC; |
| PG_FUNCTION_INFO_V1(pg_old_snapshot_time_mapping); |
| |
| static OldSnapshotTimeMapping *GetOldSnapshotTimeMapping(void); |
| static HeapTuple MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, |
| OldSnapshotTimeMapping *mapping); |
| |
| /* |
| * SQL-callable set-returning function. |
| */ |
| Datum |
| pg_old_snapshot_time_mapping(PG_FUNCTION_ARGS) |
| { |
| FuncCallContext *funcctx; |
| OldSnapshotTimeMapping *mapping; |
| |
| if (SRF_IS_FIRSTCALL()) |
| { |
| MemoryContext oldcontext; |
| TupleDesc tupdesc; |
| |
| funcctx = SRF_FIRSTCALL_INIT(); |
| oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
| mapping = GetOldSnapshotTimeMapping(); |
| funcctx->user_fctx = mapping; |
| if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
| elog(ERROR, "return type must be a row type"); |
| funcctx->tuple_desc = tupdesc; |
| MemoryContextSwitchTo(oldcontext); |
| } |
| |
| funcctx = SRF_PERCALL_SETUP(); |
| mapping = (OldSnapshotTimeMapping *) funcctx->user_fctx; |
| |
| while (mapping->current_index < mapping->count_used) |
| { |
| HeapTuple tuple; |
| |
| tuple = MakeOldSnapshotTimeMappingTuple(funcctx->tuple_desc, mapping); |
| ++mapping->current_index; |
| SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); |
| } |
| |
| SRF_RETURN_DONE(funcctx); |
| } |
| |
| /* |
| * Get the old snapshot time mapping data from shared memory. |
| */ |
| static OldSnapshotTimeMapping * |
| GetOldSnapshotTimeMapping(void) |
| { |
| OldSnapshotTimeMapping *mapping; |
| |
| mapping = palloc(offsetof(OldSnapshotTimeMapping, xid_by_minute) |
| + sizeof(TransactionId) * OLD_SNAPSHOT_TIME_MAP_ENTRIES); |
| mapping->current_index = 0; |
| |
| LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED); |
| mapping->head_offset = oldSnapshotControl->head_offset; |
| mapping->head_timestamp = oldSnapshotControl->head_timestamp; |
| mapping->count_used = oldSnapshotControl->count_used; |
| for (int i = 0; i < OLD_SNAPSHOT_TIME_MAP_ENTRIES; ++i) |
| mapping->xid_by_minute[i] = oldSnapshotControl->xid_by_minute[i]; |
| LWLockRelease(OldSnapshotTimeMapLock); |
| |
| return mapping; |
| } |
| |
| /* |
| * Convert one entry from the old snapshot time mapping to a HeapTuple. |
| */ |
| static HeapTuple |
| MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, OldSnapshotTimeMapping *mapping) |
| { |
| Datum values[NUM_TIME_MAPPING_COLUMNS]; |
| bool nulls[NUM_TIME_MAPPING_COLUMNS]; |
| int array_position; |
| TimestampTz timestamp; |
| |
| /* |
| * Figure out the array position corresponding to the current index. |
| * |
| * Index 0 means the oldest entry in the mapping, which is stored at |
| * mapping->head_offset. Index 1 means the next-oldest entry, which is a |
| * the following index, and so on. We wrap around when we reach the end of |
| * the array. |
| */ |
| array_position = (mapping->head_offset + mapping->current_index) |
| % OLD_SNAPSHOT_TIME_MAP_ENTRIES; |
| |
| /* |
| * No explicit timestamp is stored for any entry other than the oldest |
| * one, but each entry corresponds to 1-minute period, so we can just add. |
| */ |
| timestamp = TimestampTzPlusMilliseconds(mapping->head_timestamp, |
| mapping->current_index * 60000); |
| |
| /* Initialize nulls and values arrays. */ |
| memset(nulls, 0, sizeof(nulls)); |
| values[0] = Int32GetDatum(array_position); |
| values[1] = TimestampTzGetDatum(timestamp); |
| values[2] = TransactionIdGetDatum(mapping->xid_by_minute[array_position]); |
| |
| return heap_form_tuple(tupdesc, values, nulls); |
| } |