/src/postgres/src/backend/access/rmgrdesc/heapdesc.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * heapdesc.c |
4 | | * rmgr descriptor routines for access/heap/heapam.c |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/access/rmgrdesc/heapdesc.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/heapam_xlog.h" |
18 | | #include "access/rmgrdesc_utils.h" |
19 | | #include "storage/standbydefs.h" |
20 | | |
21 | | /* |
22 | | * NOTE: "keyname" argument cannot have trailing spaces or punctuation |
23 | | * characters |
24 | | */ |
25 | | static void |
26 | | infobits_desc(StringInfo buf, uint8 infobits, const char *keyname) |
27 | 0 | { |
28 | 0 | appendStringInfo(buf, "%s: [", keyname); |
29 | |
|
30 | 0 | Assert(buf->data[buf->len - 1] != ' '); |
31 | |
|
32 | 0 | if (infobits & XLHL_XMAX_IS_MULTI) |
33 | 0 | appendStringInfoString(buf, "IS_MULTI, "); |
34 | 0 | if (infobits & XLHL_XMAX_LOCK_ONLY) |
35 | 0 | appendStringInfoString(buf, "LOCK_ONLY, "); |
36 | 0 | if (infobits & XLHL_XMAX_EXCL_LOCK) |
37 | 0 | appendStringInfoString(buf, "EXCL_LOCK, "); |
38 | 0 | if (infobits & XLHL_XMAX_KEYSHR_LOCK) |
39 | 0 | appendStringInfoString(buf, "KEYSHR_LOCK, "); |
40 | 0 | if (infobits & XLHL_KEYS_UPDATED) |
41 | 0 | appendStringInfoString(buf, "KEYS_UPDATED, "); |
42 | |
|
43 | 0 | if (buf->data[buf->len - 1] == ' ') |
44 | 0 | { |
45 | | /* Truncate-away final unneeded ", " */ |
46 | 0 | Assert(buf->data[buf->len - 2] == ','); |
47 | 0 | buf->len -= 2; |
48 | 0 | buf->data[buf->len] = '\0'; |
49 | 0 | } |
50 | |
|
51 | 0 | appendStringInfoChar(buf, ']'); |
52 | 0 | } |
53 | | |
54 | | static void |
55 | | truncate_flags_desc(StringInfo buf, uint8 flags) |
56 | 0 | { |
57 | 0 | appendStringInfoString(buf, "flags: ["); |
58 | |
|
59 | 0 | if (flags & XLH_TRUNCATE_CASCADE) |
60 | 0 | appendStringInfoString(buf, "CASCADE, "); |
61 | 0 | if (flags & XLH_TRUNCATE_RESTART_SEQS) |
62 | 0 | appendStringInfoString(buf, "RESTART_SEQS, "); |
63 | |
|
64 | 0 | if (buf->data[buf->len - 1] == ' ') |
65 | 0 | { |
66 | | /* Truncate-away final unneeded ", " */ |
67 | 0 | Assert(buf->data[buf->len - 2] == ','); |
68 | 0 | buf->len -= 2; |
69 | 0 | buf->data[buf->len] = '\0'; |
70 | 0 | } |
71 | |
|
72 | 0 | appendStringInfoChar(buf, ']'); |
73 | 0 | } |
74 | | |
75 | | static void |
76 | | plan_elem_desc(StringInfo buf, void *plan, void *data) |
77 | 0 | { |
78 | 0 | xlhp_freeze_plan *new_plan = (xlhp_freeze_plan *) plan; |
79 | 0 | OffsetNumber **offsets = data; |
80 | |
|
81 | 0 | appendStringInfo(buf, "{ xmax: %u, infomask: %u, infomask2: %u, ntuples: %u", |
82 | 0 | new_plan->xmax, |
83 | 0 | new_plan->t_infomask, new_plan->t_infomask2, |
84 | 0 | new_plan->ntuples); |
85 | |
|
86 | 0 | appendStringInfoString(buf, ", offsets:"); |
87 | 0 | array_desc(buf, *offsets, sizeof(OffsetNumber), new_plan->ntuples, |
88 | 0 | &offset_elem_desc, NULL); |
89 | |
|
90 | 0 | *offsets += new_plan->ntuples; |
91 | |
|
92 | 0 | appendStringInfoString(buf, " }"); |
93 | 0 | } |
94 | | |
95 | | |
96 | | /* |
97 | | * Given a MAXALIGNed buffer returned by XLogRecGetBlockData() and pointed to |
98 | | * by cursor and any xl_heap_prune flags, deserialize the arrays of |
99 | | * OffsetNumbers contained in an XLOG_HEAP2_PRUNE_* record. |
100 | | * |
101 | | * This is in heapdesc.c so it can be shared between heap2_redo and heap2_desc |
102 | | * code, the latter of which is used in frontend (pg_waldump) code. |
103 | | */ |
104 | | void |
105 | | heap_xlog_deserialize_prune_and_freeze(char *cursor, uint8 flags, |
106 | | int *nplans, xlhp_freeze_plan **plans, |
107 | | OffsetNumber **frz_offsets, |
108 | | int *nredirected, OffsetNumber **redirected, |
109 | | int *ndead, OffsetNumber **nowdead, |
110 | | int *nunused, OffsetNumber **nowunused) |
111 | 0 | { |
112 | 0 | if (flags & XLHP_HAS_FREEZE_PLANS) |
113 | 0 | { |
114 | 0 | xlhp_freeze_plans *freeze_plans = (xlhp_freeze_plans *) cursor; |
115 | |
|
116 | 0 | *nplans = freeze_plans->nplans; |
117 | 0 | Assert(*nplans > 0); |
118 | 0 | *plans = freeze_plans->plans; |
119 | |
|
120 | 0 | cursor += offsetof(xlhp_freeze_plans, plans); |
121 | 0 | cursor += sizeof(xlhp_freeze_plan) * *nplans; |
122 | 0 | } |
123 | 0 | else |
124 | 0 | { |
125 | 0 | *nplans = 0; |
126 | 0 | *plans = NULL; |
127 | 0 | } |
128 | |
|
129 | 0 | if (flags & XLHP_HAS_REDIRECTIONS) |
130 | 0 | { |
131 | 0 | xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; |
132 | |
|
133 | 0 | *nredirected = subrecord->ntargets; |
134 | 0 | Assert(*nredirected > 0); |
135 | 0 | *redirected = &subrecord->data[0]; |
136 | |
|
137 | 0 | cursor += offsetof(xlhp_prune_items, data); |
138 | 0 | cursor += sizeof(OffsetNumber[2]) * *nredirected; |
139 | 0 | } |
140 | 0 | else |
141 | 0 | { |
142 | 0 | *nredirected = 0; |
143 | 0 | *redirected = NULL; |
144 | 0 | } |
145 | |
|
146 | 0 | if (flags & XLHP_HAS_DEAD_ITEMS) |
147 | 0 | { |
148 | 0 | xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; |
149 | |
|
150 | 0 | *ndead = subrecord->ntargets; |
151 | 0 | Assert(*ndead > 0); |
152 | 0 | *nowdead = subrecord->data; |
153 | |
|
154 | 0 | cursor += offsetof(xlhp_prune_items, data); |
155 | 0 | cursor += sizeof(OffsetNumber) * *ndead; |
156 | 0 | } |
157 | 0 | else |
158 | 0 | { |
159 | 0 | *ndead = 0; |
160 | 0 | *nowdead = NULL; |
161 | 0 | } |
162 | |
|
163 | 0 | if (flags & XLHP_HAS_NOW_UNUSED_ITEMS) |
164 | 0 | { |
165 | 0 | xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; |
166 | |
|
167 | 0 | *nunused = subrecord->ntargets; |
168 | 0 | Assert(*nunused > 0); |
169 | 0 | *nowunused = subrecord->data; |
170 | |
|
171 | 0 | cursor += offsetof(xlhp_prune_items, data); |
172 | 0 | cursor += sizeof(OffsetNumber) * *nunused; |
173 | 0 | } |
174 | 0 | else |
175 | 0 | { |
176 | 0 | *nunused = 0; |
177 | 0 | *nowunused = NULL; |
178 | 0 | } |
179 | |
|
180 | 0 | *frz_offsets = (OffsetNumber *) cursor; |
181 | 0 | } |
182 | | |
183 | | void |
184 | | heap_desc(StringInfo buf, XLogReaderState *record) |
185 | 0 | { |
186 | 0 | char *rec = XLogRecGetData(record); |
187 | 0 | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
188 | |
|
189 | 0 | info &= XLOG_HEAP_OPMASK; |
190 | 0 | if (info == XLOG_HEAP_INSERT) |
191 | 0 | { |
192 | 0 | xl_heap_insert *xlrec = (xl_heap_insert *) rec; |
193 | |
|
194 | 0 | appendStringInfo(buf, "off: %u, flags: 0x%02X", |
195 | 0 | xlrec->offnum, |
196 | 0 | xlrec->flags); |
197 | 0 | } |
198 | 0 | else if (info == XLOG_HEAP_DELETE) |
199 | 0 | { |
200 | 0 | xl_heap_delete *xlrec = (xl_heap_delete *) rec; |
201 | |
|
202 | 0 | appendStringInfo(buf, "xmax: %u, off: %u, ", |
203 | 0 | xlrec->xmax, xlrec->offnum); |
204 | 0 | infobits_desc(buf, xlrec->infobits_set, "infobits"); |
205 | 0 | appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); |
206 | 0 | } |
207 | 0 | else if (info == XLOG_HEAP_UPDATE) |
208 | 0 | { |
209 | 0 | xl_heap_update *xlrec = (xl_heap_update *) rec; |
210 | |
|
211 | 0 | appendStringInfo(buf, "old_xmax: %u, old_off: %u, ", |
212 | 0 | xlrec->old_xmax, xlrec->old_offnum); |
213 | 0 | infobits_desc(buf, xlrec->old_infobits_set, "old_infobits"); |
214 | 0 | appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u", |
215 | 0 | xlrec->flags, xlrec->new_xmax, xlrec->new_offnum); |
216 | 0 | } |
217 | 0 | else if (info == XLOG_HEAP_HOT_UPDATE) |
218 | 0 | { |
219 | 0 | xl_heap_update *xlrec = (xl_heap_update *) rec; |
220 | |
|
221 | 0 | appendStringInfo(buf, "old_xmax: %u, old_off: %u, ", |
222 | 0 | xlrec->old_xmax, xlrec->old_offnum); |
223 | 0 | infobits_desc(buf, xlrec->old_infobits_set, "old_infobits"); |
224 | 0 | appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u", |
225 | 0 | xlrec->flags, xlrec->new_xmax, xlrec->new_offnum); |
226 | 0 | } |
227 | 0 | else if (info == XLOG_HEAP_TRUNCATE) |
228 | 0 | { |
229 | 0 | xl_heap_truncate *xlrec = (xl_heap_truncate *) rec; |
230 | |
|
231 | 0 | truncate_flags_desc(buf, xlrec->flags); |
232 | 0 | appendStringInfo(buf, ", nrelids: %u", xlrec->nrelids); |
233 | 0 | appendStringInfoString(buf, ", relids:"); |
234 | 0 | array_desc(buf, xlrec->relids, sizeof(Oid), xlrec->nrelids, |
235 | 0 | &oid_elem_desc, NULL); |
236 | 0 | } |
237 | 0 | else if (info == XLOG_HEAP_CONFIRM) |
238 | 0 | { |
239 | 0 | xl_heap_confirm *xlrec = (xl_heap_confirm *) rec; |
240 | |
|
241 | 0 | appendStringInfo(buf, "off: %u", xlrec->offnum); |
242 | 0 | } |
243 | 0 | else if (info == XLOG_HEAP_LOCK) |
244 | 0 | { |
245 | 0 | xl_heap_lock *xlrec = (xl_heap_lock *) rec; |
246 | |
|
247 | 0 | appendStringInfo(buf, "xmax: %u, off: %u, ", |
248 | 0 | xlrec->xmax, xlrec->offnum); |
249 | 0 | infobits_desc(buf, xlrec->infobits_set, "infobits"); |
250 | 0 | appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); |
251 | 0 | } |
252 | 0 | else if (info == XLOG_HEAP_INPLACE) |
253 | 0 | { |
254 | 0 | xl_heap_inplace *xlrec = (xl_heap_inplace *) rec; |
255 | |
|
256 | 0 | appendStringInfo(buf, "off: %u", xlrec->offnum); |
257 | 0 | standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs, |
258 | 0 | xlrec->dbId, xlrec->tsId, |
259 | 0 | xlrec->relcacheInitFileInval); |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | | void |
264 | | heap2_desc(StringInfo buf, XLogReaderState *record) |
265 | 0 | { |
266 | 0 | char *rec = XLogRecGetData(record); |
267 | 0 | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
268 | |
|
269 | 0 | info &= XLOG_HEAP_OPMASK; |
270 | 0 | if (info == XLOG_HEAP2_PRUNE_ON_ACCESS || |
271 | 0 | info == XLOG_HEAP2_PRUNE_VACUUM_SCAN || |
272 | 0 | info == XLOG_HEAP2_PRUNE_VACUUM_CLEANUP) |
273 | 0 | { |
274 | 0 | xl_heap_prune *xlrec = (xl_heap_prune *) rec; |
275 | |
|
276 | 0 | if (xlrec->flags & XLHP_HAS_CONFLICT_HORIZON) |
277 | 0 | { |
278 | 0 | TransactionId conflict_xid; |
279 | |
|
280 | 0 | memcpy(&conflict_xid, rec + SizeOfHeapPrune, sizeof(TransactionId)); |
281 | |
|
282 | 0 | appendStringInfo(buf, "snapshotConflictHorizon: %u", |
283 | 0 | conflict_xid); |
284 | 0 | } |
285 | |
|
286 | 0 | appendStringInfo(buf, ", isCatalogRel: %c", |
287 | 0 | xlrec->flags & XLHP_IS_CATALOG_REL ? 'T' : 'F'); |
288 | |
|
289 | 0 | if (XLogRecHasBlockData(record, 0)) |
290 | 0 | { |
291 | 0 | Size datalen; |
292 | 0 | OffsetNumber *redirected; |
293 | 0 | OffsetNumber *nowdead; |
294 | 0 | OffsetNumber *nowunused; |
295 | 0 | int nredirected; |
296 | 0 | int nunused; |
297 | 0 | int ndead; |
298 | 0 | int nplans; |
299 | 0 | xlhp_freeze_plan *plans; |
300 | 0 | OffsetNumber *frz_offsets; |
301 | |
|
302 | 0 | char *cursor = XLogRecGetBlockData(record, 0, &datalen); |
303 | |
|
304 | 0 | heap_xlog_deserialize_prune_and_freeze(cursor, xlrec->flags, |
305 | 0 | &nplans, &plans, &frz_offsets, |
306 | 0 | &nredirected, &redirected, |
307 | 0 | &ndead, &nowdead, |
308 | 0 | &nunused, &nowunused); |
309 | |
|
310 | 0 | appendStringInfo(buf, ", nplans: %u, nredirected: %u, ndead: %u, nunused: %u", |
311 | 0 | nplans, nredirected, ndead, nunused); |
312 | |
|
313 | 0 | if (nplans > 0) |
314 | 0 | { |
315 | 0 | appendStringInfoString(buf, ", plans:"); |
316 | 0 | array_desc(buf, plans, sizeof(xlhp_freeze_plan), nplans, |
317 | 0 | &plan_elem_desc, &frz_offsets); |
318 | 0 | } |
319 | |
|
320 | 0 | if (nredirected > 0) |
321 | 0 | { |
322 | 0 | appendStringInfoString(buf, ", redirected:"); |
323 | 0 | array_desc(buf, redirected, sizeof(OffsetNumber) * 2, |
324 | 0 | nredirected, &redirect_elem_desc, NULL); |
325 | 0 | } |
326 | |
|
327 | 0 | if (ndead > 0) |
328 | 0 | { |
329 | 0 | appendStringInfoString(buf, ", dead:"); |
330 | 0 | array_desc(buf, nowdead, sizeof(OffsetNumber), ndead, |
331 | 0 | &offset_elem_desc, NULL); |
332 | 0 | } |
333 | |
|
334 | 0 | if (nunused > 0) |
335 | 0 | { |
336 | 0 | appendStringInfoString(buf, ", unused:"); |
337 | 0 | array_desc(buf, nowunused, sizeof(OffsetNumber), nunused, |
338 | 0 | &offset_elem_desc, NULL); |
339 | 0 | } |
340 | 0 | } |
341 | 0 | } |
342 | 0 | else if (info == XLOG_HEAP2_VISIBLE) |
343 | 0 | { |
344 | 0 | xl_heap_visible *xlrec = (xl_heap_visible *) rec; |
345 | |
|
346 | 0 | appendStringInfo(buf, "snapshotConflictHorizon: %u, flags: 0x%02X", |
347 | 0 | xlrec->snapshotConflictHorizon, xlrec->flags); |
348 | 0 | } |
349 | 0 | else if (info == XLOG_HEAP2_MULTI_INSERT) |
350 | 0 | { |
351 | 0 | xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec; |
352 | 0 | bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0; |
353 | |
|
354 | 0 | appendStringInfo(buf, "ntuples: %d, flags: 0x%02X", xlrec->ntuples, |
355 | 0 | xlrec->flags); |
356 | |
|
357 | 0 | if (XLogRecHasBlockData(record, 0) && !isinit) |
358 | 0 | { |
359 | 0 | appendStringInfoString(buf, ", offsets:"); |
360 | 0 | array_desc(buf, xlrec->offsets, sizeof(OffsetNumber), |
361 | 0 | xlrec->ntuples, &offset_elem_desc, NULL); |
362 | 0 | } |
363 | 0 | } |
364 | 0 | else if (info == XLOG_HEAP2_LOCK_UPDATED) |
365 | 0 | { |
366 | 0 | xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec; |
367 | |
|
368 | 0 | appendStringInfo(buf, "xmax: %u, off: %u, ", |
369 | 0 | xlrec->xmax, xlrec->offnum); |
370 | 0 | infobits_desc(buf, xlrec->infobits_set, "infobits"); |
371 | 0 | appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); |
372 | 0 | } |
373 | 0 | else if (info == XLOG_HEAP2_NEW_CID) |
374 | 0 | { |
375 | 0 | xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec; |
376 | |
|
377 | 0 | appendStringInfo(buf, "rel: %u/%u/%u, tid: %u/%u", |
378 | 0 | xlrec->target_locator.spcOid, |
379 | 0 | xlrec->target_locator.dbOid, |
380 | 0 | xlrec->target_locator.relNumber, |
381 | 0 | ItemPointerGetBlockNumber(&(xlrec->target_tid)), |
382 | 0 | ItemPointerGetOffsetNumber(&(xlrec->target_tid))); |
383 | 0 | appendStringInfo(buf, ", cmin: %u, cmax: %u, combo: %u", |
384 | 0 | xlrec->cmin, xlrec->cmax, xlrec->combocid); |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | | const char * |
389 | | heap_identify(uint8 info) |
390 | 0 | { |
391 | 0 | const char *id = NULL; |
392 | |
|
393 | 0 | switch (info & ~XLR_INFO_MASK) |
394 | 0 | { |
395 | 0 | case XLOG_HEAP_INSERT: |
396 | 0 | id = "INSERT"; |
397 | 0 | break; |
398 | 0 | case XLOG_HEAP_INSERT | XLOG_HEAP_INIT_PAGE: |
399 | 0 | id = "INSERT+INIT"; |
400 | 0 | break; |
401 | 0 | case XLOG_HEAP_DELETE: |
402 | 0 | id = "DELETE"; |
403 | 0 | break; |
404 | 0 | case XLOG_HEAP_UPDATE: |
405 | 0 | id = "UPDATE"; |
406 | 0 | break; |
407 | 0 | case XLOG_HEAP_UPDATE | XLOG_HEAP_INIT_PAGE: |
408 | 0 | id = "UPDATE+INIT"; |
409 | 0 | break; |
410 | 0 | case XLOG_HEAP_HOT_UPDATE: |
411 | 0 | id = "HOT_UPDATE"; |
412 | 0 | break; |
413 | 0 | case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE: |
414 | 0 | id = "HOT_UPDATE+INIT"; |
415 | 0 | break; |
416 | 0 | case XLOG_HEAP_TRUNCATE: |
417 | 0 | id = "TRUNCATE"; |
418 | 0 | break; |
419 | 0 | case XLOG_HEAP_CONFIRM: |
420 | 0 | id = "HEAP_CONFIRM"; |
421 | 0 | break; |
422 | 0 | case XLOG_HEAP_LOCK: |
423 | 0 | id = "LOCK"; |
424 | 0 | break; |
425 | 0 | case XLOG_HEAP_INPLACE: |
426 | 0 | id = "INPLACE"; |
427 | 0 | break; |
428 | 0 | } |
429 | | |
430 | 0 | return id; |
431 | 0 | } |
432 | | |
433 | | const char * |
434 | | heap2_identify(uint8 info) |
435 | 0 | { |
436 | 0 | const char *id = NULL; |
437 | |
|
438 | 0 | switch (info & ~XLR_INFO_MASK) |
439 | 0 | { |
440 | 0 | case XLOG_HEAP2_PRUNE_ON_ACCESS: |
441 | 0 | id = "PRUNE_ON_ACCESS"; |
442 | 0 | break; |
443 | 0 | case XLOG_HEAP2_PRUNE_VACUUM_SCAN: |
444 | 0 | id = "PRUNE_VACUUM_SCAN"; |
445 | 0 | break; |
446 | 0 | case XLOG_HEAP2_PRUNE_VACUUM_CLEANUP: |
447 | 0 | id = "PRUNE_VACUUM_CLEANUP"; |
448 | 0 | break; |
449 | 0 | case XLOG_HEAP2_VISIBLE: |
450 | 0 | id = "VISIBLE"; |
451 | 0 | break; |
452 | 0 | case XLOG_HEAP2_MULTI_INSERT: |
453 | 0 | id = "MULTI_INSERT"; |
454 | 0 | break; |
455 | 0 | case XLOG_HEAP2_MULTI_INSERT | XLOG_HEAP_INIT_PAGE: |
456 | 0 | id = "MULTI_INSERT+INIT"; |
457 | 0 | break; |
458 | 0 | case XLOG_HEAP2_LOCK_UPDATED: |
459 | 0 | id = "LOCK_UPDATED"; |
460 | 0 | break; |
461 | 0 | case XLOG_HEAP2_NEW_CID: |
462 | 0 | id = "NEW_CID"; |
463 | 0 | break; |
464 | 0 | case XLOG_HEAP2_REWRITE: |
465 | 0 | id = "REWRITE"; |
466 | 0 | break; |
467 | 0 | } |
468 | | |
469 | 0 | return id; |
470 | 0 | } |