Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * heap_surgery.c
4 : : * Functions to perform surgery on the damaged heap table.
5 : : *
6 : : * Copyright (c) 2020-2024, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * contrib/pg_surgery/heap_surgery.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include "access/heapam.h"
16 : : #include "access/visibilitymap.h"
17 : : #include "access/xloginsert.h"
18 : : #include "catalog/pg_am_d.h"
19 : : #include "catalog/pg_proc_d.h"
20 : : #include "miscadmin.h"
21 : : #include "storage/bufmgr.h"
22 : : #include "utils/acl.h"
23 : : #include "utils/array.h"
24 : : #include "utils/rel.h"
25 : :
1312 rhaas@postgresql.org 26 :CBC 1 : PG_MODULE_MAGIC;
27 : :
28 : : /* Options to forcefully change the state of a heap tuple. */
29 : : typedef enum HeapTupleForceOption
30 : : {
31 : : HEAP_FORCE_KILL,
32 : : HEAP_FORCE_FREEZE,
33 : : } HeapTupleForceOption;
34 : :
35 : 2 : PG_FUNCTION_INFO_V1(heap_force_kill);
36 : 2 : PG_FUNCTION_INFO_V1(heap_force_freeze);
37 : :
38 : : static int32 tidcmp(const void *a, const void *b);
39 : : static Datum heap_force_common(FunctionCallInfo fcinfo,
40 : : HeapTupleForceOption heap_force_opt);
41 : : static void sanity_check_tid_array(ArrayType *ta, int *ntids);
42 : : static BlockNumber find_tids_one_page(ItemPointer tids, int ntids,
43 : : OffsetNumber *next_start_ptr);
44 : :
45 : : /*-------------------------------------------------------------------------
46 : : * heap_force_kill()
47 : : *
48 : : * Force kill the tuple(s) pointed to by the item pointer(s) stored in the
49 : : * given TID array.
50 : : *
51 : : * Usage: SELECT heap_force_kill(regclass, tid[]);
52 : : *-------------------------------------------------------------------------
53 : : */
54 : : Datum
55 : 9 : heap_force_kill(PG_FUNCTION_ARGS)
56 : : {
57 : 9 : PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_KILL));
58 : : }
59 : :
60 : : /*-------------------------------------------------------------------------
61 : : * heap_force_freeze()
62 : : *
63 : : * Force freeze the tuple(s) pointed to by the item pointer(s) stored in the
64 : : * given TID array.
65 : : *
66 : : * Usage: SELECT heap_force_freeze(regclass, tid[]);
67 : : *-------------------------------------------------------------------------
68 : : */
69 : : Datum
70 : 7 : heap_force_freeze(PG_FUNCTION_ARGS)
71 : : {
72 : 7 : PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_FREEZE));
73 : : }
74 : :
75 : : /*-------------------------------------------------------------------------
76 : : * heap_force_common()
77 : : *
78 : : * Common code for heap_force_kill and heap_force_freeze
79 : : *-------------------------------------------------------------------------
80 : : */
81 : : static Datum
82 : 16 : heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
83 : : {
84 : 16 : Oid relid = PG_GETARG_OID(0);
85 : 16 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
86 : : ItemPointer tids;
87 : : int ntids,
88 : : nblocks;
89 : : Relation rel;
90 : : OffsetNumber curr_start_ptr,
91 : : next_start_ptr;
92 : : bool include_this_tid[MaxHeapTuplesPerPage];
93 : :
94 [ - + ]: 16 : if (RecoveryInProgress())
1312 rhaas@postgresql.org 95 [ # # ]:UBC 0 : ereport(ERROR,
96 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
97 : : errmsg("recovery is in progress"),
98 : : errhint("Heap surgery functions cannot be executed during recovery.")));
99 : :
100 : : /* Check inputs. */
1312 rhaas@postgresql.org 101 :CBC 16 : sanity_check_tid_array(ta, &ntids);
102 : :
103 : 14 : rel = relation_open(relid, RowExclusiveLock);
104 : :
105 : : /*
106 : : * Check target relation.
107 : : */
863 peter@eisentraut.org 108 [ + + + - : 14 : if (!RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
+ + ]
1011 109 [ + - ]: 2 : ereport(ERROR,
110 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
111 : : errmsg("cannot operate on relation \"%s\"",
112 : : RelationGetRelationName(rel)),
113 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
114 : :
115 [ - + ]: 12 : if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
1011 peter@eisentraut.org 116 [ # # ]:UBC 0 : ereport(ERROR,
117 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
118 : : errmsg("only heap AM is supported")));
119 : :
120 : : /* Must be owner of the table or superuser. */
518 peter@eisentraut.org 121 [ - + ]:CBC 12 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
1011 peter@eisentraut.org 122 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER,
123 : 0 : get_relkind_objtype(rel->rd_rel->relkind),
124 : 0 : RelationGetRelationName(rel));
125 : :
1312 rhaas@postgresql.org 126 [ - + ]:CBC 12 : tids = ((ItemPointer) ARR_DATA_PTR(ta));
127 : :
128 : : /*
129 : : * If there is more than one TID in the array, sort them so that we can
130 : : * easily fetch all the TIDs belonging to one particular page from the
131 : : * array.
132 : : */
133 [ + + ]: 12 : if (ntids > 1)
432 peter@eisentraut.org 134 : 2 : qsort(tids, ntids, sizeof(ItemPointerData), tidcmp);
135 : :
1312 rhaas@postgresql.org 136 : 12 : curr_start_ptr = next_start_ptr = 0;
137 : 12 : nblocks = RelationGetNumberOfBlocks(rel);
138 : :
139 : : /*
140 : : * Loop, performing the necessary actions for each block.
141 : : */
142 [ + + ]: 24 : while (next_start_ptr != ntids)
143 : : {
144 : : Buffer buf;
145 : 12 : Buffer vmbuf = InvalidBuffer;
146 : : Page page;
147 : : BlockNumber blkno;
148 : : OffsetNumber curoff;
149 : : OffsetNumber maxoffset;
150 : : int i;
151 : 12 : bool did_modify_page = false;
152 : 12 : bool did_modify_vm = false;
153 : :
154 [ - + ]: 12 : CHECK_FOR_INTERRUPTS();
155 : :
156 : : /*
157 : : * Find all the TIDs belonging to one particular page starting from
158 : : * next_start_ptr and process them one by one.
159 : : */
160 : 12 : blkno = find_tids_one_page(tids, ntids, &next_start_ptr);
161 : :
162 : : /* Check whether the block number is valid. */
163 [ + + ]: 12 : if (blkno >= nblocks)
164 : : {
165 : : /* Update the current_start_ptr before moving to the next page. */
166 : 1 : curr_start_ptr = next_start_ptr;
167 : :
168 [ + - ]: 1 : ereport(NOTICE,
169 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
170 : : errmsg("skipping block %u for relation \"%s\" because the block number is out of range",
171 : : blkno, RelationGetRelationName(rel))));
172 : 1 : continue;
173 : : }
174 : :
175 : 11 : buf = ReadBuffer(rel, blkno);
176 : 11 : LockBufferForCleanup(buf);
177 : :
178 : 11 : page = BufferGetPage(buf);
179 : :
180 : 11 : maxoffset = PageGetMaxOffsetNumber(page);
181 : :
182 : : /*
183 : : * Figure out which TIDs we are going to process and which ones we are
184 : : * going to skip.
185 : : */
186 : 11 : memset(include_this_tid, 0, sizeof(include_this_tid));
187 [ + + ]: 24 : for (i = curr_start_ptr; i < next_start_ptr; i++)
188 : : {
189 : 13 : OffsetNumber offno = ItemPointerGetOffsetNumberNoCheck(&tids[i]);
190 : : ItemId itemid;
191 : :
192 : : /* Check whether the offset number is valid. */
193 [ + + + + ]: 13 : if (offno == InvalidOffsetNumber || offno > maxoffset)
194 : : {
195 [ + - ]: 2 : ereport(NOTICE,
196 : : errmsg("skipping tid (%u, %u) for relation \"%s\" because the item number is out of range",
197 : : blkno, offno, RelationGetRelationName(rel)));
198 : 2 : continue;
199 : : }
200 : :
201 : 11 : itemid = PageGetItemId(page, offno);
202 : :
203 : : /* Only accept an item ID that is used. */
204 [ + + ]: 11 : if (ItemIdIsRedirected(itemid))
205 : : {
206 [ + - ]: 1 : ereport(NOTICE,
207 : : errmsg("skipping tid (%u, %u) for relation \"%s\" because it redirects to item %u",
208 : : blkno, offno, RelationGetRelationName(rel),
209 : : ItemIdGetRedirect(itemid)));
210 : 1 : continue;
211 : : }
212 [ + + ]: 10 : else if (ItemIdIsDead(itemid))
213 : : {
214 [ + - ]: 2 : ereport(NOTICE,
215 : : (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked dead",
216 : : blkno, offno, RelationGetRelationName(rel))));
217 : 2 : continue;
218 : : }
219 [ + + ]: 8 : else if (!ItemIdIsUsed(itemid))
220 : : {
221 [ + - ]: 1 : ereport(NOTICE,
222 : : (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked unused",
223 : : blkno, offno, RelationGetRelationName(rel))));
224 : 1 : continue;
225 : : }
226 : :
227 : : /* Mark it for processing. */
228 [ - + ]: 7 : Assert(offno < MaxHeapTuplesPerPage);
229 : 7 : include_this_tid[offno] = true;
230 : : }
231 : :
232 : : /*
233 : : * Before entering the critical section, pin the visibility map page
234 : : * if it appears to be necessary.
235 : : */
236 [ + + + + ]: 11 : if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page))
237 : 3 : visibilitymap_pin(rel, blkno, &vmbuf);
238 : :
239 : : /* No ereport(ERROR) from here until all the changes are logged. */
240 : 11 : START_CRIT_SECTION();
241 : :
242 [ + + ]: 55 : for (curoff = FirstOffsetNumber; curoff <= maxoffset;
243 : 44 : curoff = OffsetNumberNext(curoff))
244 : : {
245 : : ItemId itemid;
246 : :
247 [ + + ]: 44 : if (!include_this_tid[curoff])
248 : 37 : continue;
249 : :
250 : 7 : itemid = PageGetItemId(page, curoff);
251 [ - + ]: 7 : Assert(ItemIdIsNormal(itemid));
252 : :
253 : 7 : did_modify_page = true;
254 : :
255 [ + + ]: 7 : if (heap_force_opt == HEAP_FORCE_KILL)
256 : : {
257 : 3 : ItemIdSetDead(itemid);
258 : :
259 : : /*
260 : : * If the page is marked all-visible, we must clear
261 : : * PD_ALL_VISIBLE flag on the page header and an all-visible
262 : : * bit on the visibility map corresponding to the page.
263 : : */
264 [ + + ]: 3 : if (PageIsAllVisible(page))
265 : : {
266 : 1 : PageClearAllVisible(page);
267 : 1 : visibilitymap_clear(rel, blkno, vmbuf,
268 : : VISIBILITYMAP_VALID_BITS);
269 : 1 : did_modify_vm = true;
270 : : }
271 : : }
272 : : else
273 : : {
274 : : HeapTupleHeader htup;
275 : :
276 [ - + ]: 4 : Assert(heap_force_opt == HEAP_FORCE_FREEZE);
277 : :
278 : 4 : htup = (HeapTupleHeader) PageGetItem(page, itemid);
279 : :
280 : : /*
281 : : * Reset all visibility-related fields of the tuple. This
282 : : * logic should mimic heap_execute_freeze_tuple(), but we
283 : : * choose to reset xmin and ctid just to be sure that no
284 : : * potentially-garbled data is left behind.
285 : : */
286 : 4 : ItemPointerSet(&htup->t_ctid, blkno, curoff);
287 : 4 : HeapTupleHeaderSetXmin(htup, FrozenTransactionId);
288 : 4 : HeapTupleHeaderSetXmax(htup, InvalidTransactionId);
289 [ - + ]: 4 : if (htup->t_infomask & HEAP_MOVED)
290 : : {
1312 rhaas@postgresql.org 291 [ # # ]:UBC 0 : if (htup->t_infomask & HEAP_MOVED_OFF)
292 [ # # ]: 0 : HeapTupleHeaderSetXvac(htup, InvalidTransactionId);
293 : : else
294 [ # # ]: 0 : HeapTupleHeaderSetXvac(htup, FrozenTransactionId);
295 : : }
296 : :
297 : : /*
298 : : * Clear all the visibility-related bits of this tuple and
299 : : * mark it as frozen. Also, get rid of HOT_UPDATED and
300 : : * KEYS_UPDATES bits.
301 : : */
1312 rhaas@postgresql.org 302 :CBC 4 : htup->t_infomask &= ~HEAP_XACT_MASK;
303 : 4 : htup->t_infomask |= (HEAP_XMIN_FROZEN | HEAP_XMAX_INVALID);
304 : 4 : htup->t_infomask2 &= ~HEAP_HOT_UPDATED;
305 : 4 : htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
306 : : }
307 : : }
308 : :
309 : : /*
310 : : * If the page was modified, only then, we mark the buffer dirty or do
311 : : * the WAL logging.
312 : : */
313 [ + + ]: 11 : if (did_modify_page)
314 : : {
315 : : /* Mark buffer dirty before we write WAL. */
316 : 6 : MarkBufferDirty(buf);
317 : :
318 : : /* XLOG stuff */
319 [ + + - + : 6 : if (RelationNeedsWAL(rel))
- - - - ]
320 : 2 : log_newpage_buffer(buf, true);
321 : : }
322 : :
323 : : /* WAL log the VM page if it was modified. */
324 [ + + - + : 11 : if (did_modify_vm && RelationNeedsWAL(rel))
- - - - -
- ]
1312 rhaas@postgresql.org 325 :UBC 0 : log_newpage_buffer(vmbuf, false);
326 : :
1312 rhaas@postgresql.org 327 [ - + ]:CBC 11 : END_CRIT_SECTION();
328 : :
329 : 11 : UnlockReleaseBuffer(buf);
330 : :
331 [ + + ]: 11 : if (vmbuf != InvalidBuffer)
332 : 3 : ReleaseBuffer(vmbuf);
333 : :
334 : : /* Update the current_start_ptr before moving to the next page. */
335 : 11 : curr_start_ptr = next_start_ptr;
336 : : }
337 : :
338 : 12 : relation_close(rel, RowExclusiveLock);
339 : :
340 : 12 : pfree(ta);
341 : :
342 : 12 : PG_RETURN_VOID();
343 : : }
344 : :
345 : : /*-------------------------------------------------------------------------
346 : : * tidcmp()
347 : : *
348 : : * Compare two item pointers, return -1, 0, or +1.
349 : : *
350 : : * See ItemPointerCompare for details.
351 : : * ------------------------------------------------------------------------
352 : : */
353 : : static int32
354 : 3 : tidcmp(const void *a, const void *b)
355 : : {
356 : 3 : ItemPointer iptr1 = ((const ItemPointer) a);
357 : 3 : ItemPointer iptr2 = ((const ItemPointer) b);
358 : :
359 : 3 : return ItemPointerCompare(iptr1, iptr2);
360 : : }
361 : :
362 : : /*-------------------------------------------------------------------------
363 : : * sanity_check_tid_array()
364 : : *
365 : : * Perform sanity checks on the given tid array, and set *ntids to the
366 : : * number of items in the array.
367 : : * ------------------------------------------------------------------------
368 : : */
369 : : static void
370 : 16 : sanity_check_tid_array(ArrayType *ta, int *ntids)
371 : : {
372 [ + + + - ]: 16 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
373 [ + - ]: 1 : ereport(ERROR,
374 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
375 : : errmsg("array must not contain nulls")));
376 : :
377 [ + + ]: 15 : if (ARR_NDIM(ta) > 1)
378 [ + - ]: 1 : ereport(ERROR,
379 : : (errcode(ERRCODE_DATA_EXCEPTION),
380 : : errmsg("argument must be empty or one-dimensional array")));
381 : :
382 : 14 : *ntids = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
383 : 14 : }
384 : :
385 : : /*-------------------------------------------------------------------------
386 : : * find_tids_one_page()
387 : : *
388 : : * Find all the tids residing in the same page as tids[next_start_ptr], and
389 : : * update next_start_ptr so that it points to the first tid in the next page.
390 : : *
391 : : * NOTE: The input tids[] array must be sorted.
392 : : * ------------------------------------------------------------------------
393 : : */
394 : : static BlockNumber
395 : 12 : find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr)
396 : : {
397 : : int i;
398 : : BlockNumber prev_blkno,
399 : : blkno;
400 : :
401 : 12 : prev_blkno = blkno = InvalidBlockNumber;
402 : :
403 [ + + ]: 26 : for (i = *next_start_ptr; i < ntids; i++)
404 : : {
405 : 15 : ItemPointerData tid = tids[i];
406 : :
407 : 15 : blkno = ItemPointerGetBlockNumberNoCheck(&tid);
408 : :
409 [ + + ]: 15 : if (i == *next_start_ptr)
410 : 12 : prev_blkno = blkno;
411 : :
412 [ + + ]: 15 : if (prev_blkno != blkno)
413 : 1 : break;
414 : : }
415 : :
416 : 12 : *next_start_ptr = i;
417 : 12 : return prev_blkno;
418 : : }
|