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 0 : PG_MODULE_MAGIC_EXT(
22 : .name = "pg_logicalinspect",
23 : .version = PG_VERSION
24 : );
25 :
26 0 : PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_meta);
27 0 : PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_info);
28 :
29 : /* Return the description of SnapBuildState */
30 : static const char *
31 0 : get_snapbuild_state_desc(SnapBuildState state)
32 : {
33 0 : const char *stateDesc = "unknown state";
34 :
35 0 : switch (state)
36 : {
37 : case SNAPBUILD_START:
38 0 : stateDesc = "start";
39 0 : break;
40 : case SNAPBUILD_BUILDING_SNAPSHOT:
41 0 : stateDesc = "building";
42 0 : break;
43 : case SNAPBUILD_FULL_SNAPSHOT:
44 0 : stateDesc = "full";
45 0 : break;
46 : case SNAPBUILD_CONSISTENT:
47 0 : stateDesc = "consistent";
48 0 : break;
49 : }
50 :
51 0 : return stateDesc;
52 0 : }
53 :
54 : /*
55 : * Extract the LSN from the given serialized snapshot file name.
56 : */
57 : static XLogRecPtr
58 0 : parse_snapshot_filename(const char *filename)
59 : {
60 0 : uint32 hi;
61 0 : uint32 lo;
62 0 : XLogRecPtr lsn;
63 0 : 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 0 : if (sscanf(filename, "%X-%X.snap", &hi, &lo) != 2)
73 0 : 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 0 : sprintf(tmpfname, "%X-%X.snap", hi, lo);
81 0 : if (strcmp(tmpfname, filename) != 0)
82 0 : goto parse_error;
83 :
84 0 : lsn = ((uint64) hi) << 32 | lo;
85 :
86 0 : return lsn;
87 :
88 : parse_error:
89 0 : ereport(ERROR,
90 : errmsg("invalid snapshot file name \"%s\"", filename));
91 :
92 0 : return InvalidXLogRecPtr; /* keep compiler quiet */
93 0 : }
94 :
95 : /*
96 : * Retrieve the logical snapshot file metadata.
97 : */
98 : Datum
99 0 : pg_get_logical_snapshot_meta(PG_FUNCTION_ARGS)
100 : {
101 : #define PG_GET_LOGICAL_SNAPSHOT_META_COLS 3
102 0 : SnapBuildOnDisk ondisk;
103 0 : HeapTuple tuple;
104 0 : Datum values[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
105 0 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
106 0 : TupleDesc tupdesc;
107 0 : XLogRecPtr lsn;
108 0 : int i = 0;
109 0 : text *filename_t = PG_GETARG_TEXT_PP(0);
110 :
111 : /* Build a tuple descriptor for our result type */
112 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
113 0 : elog(ERROR, "return type must be a row type");
114 :
115 0 : lsn = parse_snapshot_filename(text_to_cstring(filename_t));
116 :
117 : /* Validate and restore the snapshot to 'ondisk' */
118 0 : SnapBuildRestoreSnapshot(&ondisk, lsn, CurrentMemoryContext, false);
119 :
120 0 : values[i++] = UInt32GetDatum(ondisk.magic);
121 0 : values[i++] = Int64GetDatum((int64) ondisk.checksum);
122 0 : values[i++] = UInt32GetDatum(ondisk.version);
123 :
124 0 : Assert(i == PG_GET_LOGICAL_SNAPSHOT_META_COLS);
125 :
126 0 : tuple = heap_form_tuple(tupdesc, values, nulls);
127 :
128 0 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
129 :
130 : #undef PG_GET_LOGICAL_SNAPSHOT_META_COLS
131 0 : }
132 :
133 : Datum
134 0 : pg_get_logical_snapshot_info(PG_FUNCTION_ARGS)
135 : {
136 : #define PG_GET_LOGICAL_SNAPSHOT_INFO_COLS 14
137 0 : SnapBuildOnDisk ondisk;
138 0 : HeapTuple tuple;
139 0 : Datum values[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
140 0 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
141 0 : TupleDesc tupdesc;
142 0 : XLogRecPtr lsn;
143 0 : int i = 0;
144 0 : text *filename_t = PG_GETARG_TEXT_PP(0);
145 :
146 : /* Build a tuple descriptor for our result type */
147 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
148 0 : elog(ERROR, "return type must be a row type");
149 :
150 0 : lsn = parse_snapshot_filename(text_to_cstring(filename_t));
151 :
152 : /* Validate and restore the snapshot to 'ondisk' */
153 0 : SnapBuildRestoreSnapshot(&ondisk, lsn, CurrentMemoryContext, false);
154 :
155 0 : values[i++] = CStringGetTextDatum(get_snapbuild_state_desc(ondisk.builder.state));
156 0 : values[i++] = TransactionIdGetDatum(ondisk.builder.xmin);
157 0 : values[i++] = TransactionIdGetDatum(ondisk.builder.xmax);
158 0 : values[i++] = LSNGetDatum(ondisk.builder.start_decoding_at);
159 0 : values[i++] = LSNGetDatum(ondisk.builder.two_phase_at);
160 0 : values[i++] = TransactionIdGetDatum(ondisk.builder.initial_xmin_horizon);
161 0 : values[i++] = BoolGetDatum(ondisk.builder.building_full_snapshot);
162 0 : values[i++] = BoolGetDatum(ondisk.builder.in_slot_creation);
163 0 : values[i++] = LSNGetDatum(ondisk.builder.last_serialized_snapshot);
164 0 : values[i++] = TransactionIdGetDatum(ondisk.builder.next_phase_at);
165 :
166 0 : values[i++] = UInt32GetDatum(ondisk.builder.committed.xcnt);
167 0 : if (ondisk.builder.committed.xcnt > 0)
168 : {
169 0 : Datum *arrayelems;
170 :
171 0 : arrayelems = (Datum *) palloc(ondisk.builder.committed.xcnt * sizeof(Datum));
172 :
173 0 : for (int j = 0; j < ondisk.builder.committed.xcnt; j++)
174 0 : arrayelems[j] = TransactionIdGetDatum(ondisk.builder.committed.xip[j]);
175 :
176 0 : values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
177 0 : ondisk.builder.committed.xcnt,
178 : XIDOID));
179 0 : }
180 : else
181 0 : nulls[i++] = true;
182 :
183 0 : values[i++] = UInt32GetDatum(ondisk.builder.catchange.xcnt);
184 0 : if (ondisk.builder.catchange.xcnt > 0)
185 : {
186 0 : Datum *arrayelems;
187 :
188 0 : arrayelems = (Datum *) palloc(ondisk.builder.catchange.xcnt * sizeof(Datum));
189 :
190 0 : for (int j = 0; j < ondisk.builder.catchange.xcnt; j++)
191 0 : arrayelems[j] = TransactionIdGetDatum(ondisk.builder.catchange.xip[j]);
192 :
193 0 : values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
194 0 : ondisk.builder.catchange.xcnt,
195 : XIDOID));
196 0 : }
197 : else
198 0 : nulls[i++] = true;
199 :
200 0 : Assert(i == PG_GET_LOGICAL_SNAPSHOT_INFO_COLS);
201 :
202 0 : tuple = heap_form_tuple(tupdesc, values, nulls);
203 :
204 0 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
205 :
206 : #undef PG_GET_LOGICAL_SNAPSHOT_INFO_COLS
207 0 : }
|