Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * pg_logicalinspect.c
4 : * Functions to inspect contents of PostgreSQL logical snapshots
5 : *
6 : * Copyright (c) 2024-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/pg_logicalinspect/pg_logicalinspect.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "funcapi.h"
16 : #include "replication/snapbuild_internal.h"
17 : #include "utils/array.h"
18 : #include "utils/builtins.h"
19 : #include "utils/pg_lsn.h"
20 :
21 3 : PG_MODULE_MAGIC_EXT(
22 : .name = "pg_logicalinspect",
23 : .version = PG_VERSION
24 : );
25 :
26 4 : PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_meta);
27 4 : PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_info);
28 :
29 : /* Return the description of SnapBuildState */
30 : static const char *
31 1 : get_snapbuild_state_desc(SnapBuildState state)
32 : {
33 1 : const char *stateDesc = "unknown state";
34 :
35 1 : switch (state)
36 : {
37 0 : case SNAPBUILD_START:
38 0 : stateDesc = "start";
39 0 : break;
40 0 : case SNAPBUILD_BUILDING_SNAPSHOT:
41 0 : stateDesc = "building";
42 0 : break;
43 0 : case SNAPBUILD_FULL_SNAPSHOT:
44 0 : stateDesc = "full";
45 0 : break;
46 1 : case SNAPBUILD_CONSISTENT:
47 1 : stateDesc = "consistent";
48 1 : break;
49 : }
50 :
51 1 : return stateDesc;
52 : }
53 :
54 : /*
55 : * Extract the LSN from the given serialized snapshot file name.
56 : */
57 : static XLogRecPtr
58 15 : parse_snapshot_filename(const char *filename)
59 : {
60 : uint32 hi;
61 : uint32 lo;
62 : XLogRecPtr lsn;
63 : char tmpfname[MAXPGPATH];
64 :
65 : /*
66 : * Extract the values to build the LSN.
67 : *
68 : * Note: Including ".snap" doesn't mean that sscanf() also does the format
69 : * check including the suffix. The subsequent check validates if the given
70 : * filename has the expected suffix.
71 : */
72 15 : if (sscanf(filename, "%X-%X.snap", &hi, &lo) != 2)
73 8 : goto parse_error;
74 :
75 : /*
76 : * Bring back the extracted LSN to the snapshot file format and compare it
77 : * to the given filename. This check strictly checks if the given filename
78 : * follows the format of the snapshot filename.
79 : */
80 7 : sprintf(tmpfname, "%X-%X.snap", hi, lo);
81 7 : if (strcmp(tmpfname, filename) != 0)
82 5 : goto parse_error;
83 :
84 2 : lsn = ((uint64) hi) << 32 | lo;
85 :
86 2 : return lsn;
87 :
88 13 : parse_error:
89 13 : ereport(ERROR,
90 : errmsg("invalid snapshot file name \"%s\"", filename));
91 :
92 : return InvalidXLogRecPtr; /* keep compiler quiet */
93 : }
94 :
95 : /*
96 : * Retrieve the logical snapshot file metadata.
97 : */
98 : Datum
99 6 : pg_get_logical_snapshot_meta(PG_FUNCTION_ARGS)
100 : {
101 : #define PG_GET_LOGICAL_SNAPSHOT_META_COLS 3
102 : SnapBuildOnDisk ondisk;
103 : HeapTuple tuple;
104 6 : Datum values[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
105 6 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
106 : TupleDesc tupdesc;
107 : XLogRecPtr lsn;
108 6 : int i = 0;
109 6 : text *filename_t = PG_GETARG_TEXT_PP(0);
110 :
111 : /* Build a tuple descriptor for our result type */
112 6 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
113 0 : elog(ERROR, "return type must be a row type");
114 :
115 6 : lsn = parse_snapshot_filename(text_to_cstring(filename_t));
116 :
117 : /* Validate and restore the snapshot to 'ondisk' */
118 1 : SnapBuildRestoreSnapshot(&ondisk, lsn, CurrentMemoryContext, false);
119 :
120 1 : values[i++] = UInt32GetDatum(ondisk.magic);
121 1 : values[i++] = Int64GetDatum((int64) ondisk.checksum);
122 1 : values[i++] = UInt32GetDatum(ondisk.version);
123 :
124 : Assert(i == PG_GET_LOGICAL_SNAPSHOT_META_COLS);
125 :
126 1 : tuple = heap_form_tuple(tupdesc, values, nulls);
127 :
128 1 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
129 :
130 : #undef PG_GET_LOGICAL_SNAPSHOT_META_COLS
131 : }
132 :
133 : Datum
134 9 : pg_get_logical_snapshot_info(PG_FUNCTION_ARGS)
135 : {
136 : #define PG_GET_LOGICAL_SNAPSHOT_INFO_COLS 14
137 : SnapBuildOnDisk ondisk;
138 : HeapTuple tuple;
139 9 : Datum values[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
140 9 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
141 : TupleDesc tupdesc;
142 : XLogRecPtr lsn;
143 9 : int i = 0;
144 9 : text *filename_t = PG_GETARG_TEXT_PP(0);
145 :
146 : /* Build a tuple descriptor for our result type */
147 9 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
148 0 : elog(ERROR, "return type must be a row type");
149 :
150 9 : lsn = parse_snapshot_filename(text_to_cstring(filename_t));
151 :
152 : /* Validate and restore the snapshot to 'ondisk' */
153 1 : SnapBuildRestoreSnapshot(&ondisk, lsn, CurrentMemoryContext, false);
154 :
155 1 : values[i++] = CStringGetTextDatum(get_snapbuild_state_desc(ondisk.builder.state));
156 1 : values[i++] = TransactionIdGetDatum(ondisk.builder.xmin);
157 1 : values[i++] = TransactionIdGetDatum(ondisk.builder.xmax);
158 1 : values[i++] = LSNGetDatum(ondisk.builder.start_decoding_at);
159 1 : values[i++] = LSNGetDatum(ondisk.builder.two_phase_at);
160 1 : values[i++] = TransactionIdGetDatum(ondisk.builder.initial_xmin_horizon);
161 1 : values[i++] = BoolGetDatum(ondisk.builder.building_full_snapshot);
162 1 : values[i++] = BoolGetDatum(ondisk.builder.in_slot_creation);
163 1 : values[i++] = LSNGetDatum(ondisk.builder.last_serialized_snapshot);
164 1 : values[i++] = TransactionIdGetDatum(ondisk.builder.next_phase_at);
165 :
166 1 : values[i++] = UInt32GetDatum(ondisk.builder.committed.xcnt);
167 1 : if (ondisk.builder.committed.xcnt > 0)
168 : {
169 : Datum *arrayelems;
170 :
171 1 : arrayelems = (Datum *) palloc(ondisk.builder.committed.xcnt * sizeof(Datum));
172 :
173 2 : for (int j = 0; j < ondisk.builder.committed.xcnt; j++)
174 1 : arrayelems[j] = TransactionIdGetDatum(ondisk.builder.committed.xip[j]);
175 :
176 1 : values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
177 1 : ondisk.builder.committed.xcnt,
178 : XIDOID));
179 : }
180 : else
181 0 : nulls[i++] = true;
182 :
183 1 : values[i++] = UInt32GetDatum(ondisk.builder.catchange.xcnt);
184 1 : if (ondisk.builder.catchange.xcnt > 0)
185 : {
186 : Datum *arrayelems;
187 :
188 1 : arrayelems = (Datum *) palloc(ondisk.builder.catchange.xcnt * sizeof(Datum));
189 :
190 3 : for (int j = 0; j < ondisk.builder.catchange.xcnt; j++)
191 2 : arrayelems[j] = TransactionIdGetDatum(ondisk.builder.catchange.xip[j]);
192 :
193 1 : values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
194 1 : ondisk.builder.catchange.xcnt,
195 : XIDOID));
196 : }
197 : else
198 0 : nulls[i++] = true;
199 :
200 : Assert(i == PG_GET_LOGICAL_SNAPSHOT_INFO_COLS);
201 :
202 1 : tuple = heap_form_tuple(tupdesc, values, nulls);
203 :
204 1 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
205 :
206 : #undef PG_GET_LOGICAL_SNAPSHOT_INFO_COLS
207 : }
|