Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_logicalinspect.c
4 : : * Functions to inspect contents of PostgreSQL logical snapshots
5 : : *
6 : : * Copyright (c) 2024-2025, 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 : :
164 tgl@sss.pgh.pa.us 21 :CBC 2 : PG_MODULE_MAGIC_EXT(
22 : : .name = "pg_logicalinspect",
23 : : .version = PG_VERSION
24 : : );
25 : :
327 msawada@postgresql.o 26 : 2 : PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_meta);
27 : 2 : 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 : : {
327 msawada@postgresql.o 37 :UBC 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;
327 msawada@postgresql.o 46 :CBC 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
179 58 : 2 : 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 [ - + ]: 2 : if (sscanf(filename, "%X-%X.snap", &hi, &lo) != 2)
179 msawada@postgresql.o 73 :UBC 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 : : */
179 msawada@postgresql.o 80 :CBC 2 : sprintf(tmpfname, "%X-%X.snap", hi, lo);
81 [ - + ]: 2 : if (strcmp(tmpfname, filename) != 0)
179 msawada@postgresql.o 82 :UBC 0 : goto parse_error;
83 : :
179 msawada@postgresql.o 84 :CBC 2 : lsn = ((uint64) hi) << 32 | lo;
85 : :
86 : 2 : return lsn;
87 : :
179 msawada@postgresql.o 88 :UBC 0 : parse_error:
89 [ # # ]: 0 : 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
327 msawada@postgresql.o 99 :CBC 1 : 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 : 1 : Datum values[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
105 : 1 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
106 : : TupleDesc tupdesc;
107 : : XLogRecPtr lsn;
108 : 1 : int i = 0;
109 : 1 : text *filename_t = PG_GETARG_TEXT_PP(0);
110 : :
111 : : /* Build a tuple descriptor for our result type */
112 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
327 msawada@postgresql.o 113 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
114 : :
179 msawada@postgresql.o 115 :CBC 1 : 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 : :
327 120 : 1 : values[i++] = UInt32GetDatum(ondisk.magic);
121 : 1 : values[i++] = Int64GetDatum((int64) ondisk.checksum);
122 : 1 : values[i++] = UInt32GetDatum(ondisk.version);
123 : :
124 [ - + ]: 1 : 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 : 1 : 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 : 1 : Datum values[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
140 : 1 : bool nulls[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
141 : : TupleDesc tupdesc;
142 : : XLogRecPtr lsn;
143 : 1 : int i = 0;
144 : 1 : text *filename_t = PG_GETARG_TEXT_PP(0);
145 : :
146 : : /* Build a tuple descriptor for our result type */
147 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
327 msawada@postgresql.o 148 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
149 : :
179 msawada@postgresql.o 150 :CBC 1 : 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 : :
327 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
327 msawada@postgresql.o 181 :UBC 0 : nulls[i++] = true;
182 : :
327 msawada@postgresql.o 183 :CBC 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
327 msawada@postgresql.o 198 :UBC 0 : nulls[i++] = true;
199 : :
327 msawada@postgresql.o 200 [ - + ]:CBC 1 : 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 : : }
|