Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * heapam_handler.c
4 : : * heap table access method code
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/heap/heapam_handler.c
12 : : *
13 : : *
14 : : * NOTES
15 : : * This file wires up the lower level heapam.c et al routines with the
16 : : * tableam abstraction.
17 : : *
18 : : *-------------------------------------------------------------------------
19 : : */
20 : : #include "postgres.h"
21 : :
22 : : #include "access/genam.h"
23 : : #include "access/heapam.h"
24 : : #include "access/heaptoast.h"
25 : : #include "access/multixact.h"
26 : : #include "access/rewriteheap.h"
27 : : #include "access/syncscan.h"
28 : : #include "access/tableam.h"
29 : : #include "access/tsmapi.h"
30 : : #include "access/visibilitymap.h"
31 : : #include "access/xact.h"
32 : : #include "catalog/catalog.h"
33 : : #include "catalog/index.h"
34 : : #include "catalog/storage.h"
35 : : #include "catalog/storage_xlog.h"
36 : : #include "commands/progress.h"
37 : : #include "executor/executor.h"
38 : : #include "miscadmin.h"
39 : : #include "pgstat.h"
40 : : #include "storage/bufmgr.h"
41 : : #include "storage/bufpage.h"
42 : : #include "storage/lmgr.h"
43 : : #include "storage/lock.h"
44 : : #include "storage/predicate.h"
45 : : #include "storage/procarray.h"
46 : : #include "storage/smgr.h"
47 : : #include "utils/builtins.h"
48 : : #include "utils/rel.h"
49 : : #include "utils/tuplesort.h"
50 : :
51 : : static void reform_and_rewrite_tuple(HeapTuple tuple,
52 : : Relation OldHeap, Relation NewHeap,
53 : : Datum *values, bool *isnull, RewriteState rwstate);
54 : : static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
55 : : Relation NewHeap, Datum *values, bool *isnull,
56 : : BulkInsertState bistate);
57 : : static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
58 : : Relation NewHeap, Datum *values, bool *isnull);
59 : :
60 : : static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
61 : : HeapTuple tuple,
62 : : OffsetNumber tupoffset);
63 : :
64 : : static BlockNumber heapam_scan_get_blocks_done(HeapScanDesc hscan);
65 : :
66 : : static bool BitmapHeapScanNextBlock(TableScanDesc scan,
67 : : bool *recheck,
68 : : uint64 *lossy_pages, uint64 *exact_pages);
69 : :
70 : :
71 : : /* ------------------------------------------------------------------------
72 : : * Slot related callbacks for heap AM
73 : : * ------------------------------------------------------------------------
74 : : */
75 : :
76 : : static const TupleTableSlotOps *
2750 andres@anarazel.de 77 :CBC 9284590 : heapam_slot_callbacks(Relation relation)
78 : : {
79 : 9284590 : return &TTSOpsBufferHeapTuple;
80 : : }
81 : :
82 : :
83 : : /* ------------------------------------------------------------------------
84 : : * Callbacks for non-modifying operations on individual tuples for heap AM
85 : : * ------------------------------------------------------------------------
86 : : */
87 : :
88 : : static bool
2736 89 : 2859892 : heapam_fetch_row_version(Relation relation,
90 : : ItemPointer tid,
91 : : Snapshot snapshot,
92 : : TupleTableSlot *slot)
93 : : {
94 : 2859892 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
95 : : Buffer buffer;
96 : :
97 [ - + ]: 2859892 : Assert(TTS_IS_BUFFERTUPLE(slot));
98 : :
99 : 2859892 : bslot->base.tupdata.t_self = *tid;
1621 tgl@sss.pgh.pa.us 100 [ + + ]: 2859892 : if (heap_fetch(relation, snapshot, &bslot->base.tupdata, &buffer, false))
101 : : {
102 : : /* store in slot, transferring existing pin */
2736 andres@anarazel.de 103 : 2859502 : ExecStorePinnedBufferHeapTuple(&bslot->base.tupdata, slot, buffer);
104 : 2859502 : slot->tts_tableOid = RelationGetRelid(relation);
105 : :
106 : 2859502 : return true;
107 : : }
108 : :
109 : 382 : return false;
110 : : }
111 : :
112 : : static bool
2683 113 : 8134 : heapam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
114 : : {
115 : 8134 : HeapScanDesc hscan = (HeapScanDesc) scan;
116 : :
117 [ + + ]: 16256 : return ItemPointerIsValid(tid) &&
118 [ + + ]: 8122 : ItemPointerGetBlockNumber(tid) < hscan->rs_nblocks;
119 : : }
120 : :
121 : : static bool
2750 122 : 761605 : heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
123 : : Snapshot snapshot)
124 : : {
125 : 761605 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
126 : : bool res;
127 : :
128 [ - + ]: 761605 : Assert(TTS_IS_BUFFERTUPLE(slot));
129 [ - + ]: 761605 : Assert(BufferIsValid(bslot->buffer));
130 : :
131 : : /*
132 : : * We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
133 : : * Caller should be holding pin, but not lock.
134 : : */
135 : 761605 : LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE);
136 : 761605 : res = HeapTupleSatisfiesVisibility(bslot->base.tuple, snapshot,
137 : : bslot->buffer);
138 : 761605 : LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);
139 : :
140 : 761605 : return res;
141 : : }
142 : :
143 : :
144 : : /* ----------------------------------------------------------------------------
145 : : * Functions for manipulations of physical tuples for heap AM.
146 : : * ----------------------------------------------------------------------------
147 : : */
148 : :
149 : : static void
2738 150 : 10771080 : heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
151 : : uint32 options, BulkInsertState bistate)
152 : : {
153 : 10771080 : bool shouldFree = true;
154 : 10771080 : HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
155 : :
156 : : /* Update the tuple with table oid */
157 : 10771080 : slot->tts_tableOid = RelationGetRelid(relation);
158 : 10771080 : tuple->t_tableOid = slot->tts_tableOid;
159 : :
160 : : /* Perform the insertion, and copy the resulting ItemPointer */
161 : 10771080 : heap_insert(relation, tuple, cid, options, bistate);
162 : 10771061 : ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
163 : :
164 [ + + ]: 10771061 : if (shouldFree)
165 : 2980316 : pfree(tuple);
166 : 10771061 : }
167 : :
168 : : static void
2731 169 : 2280 : heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
170 : : CommandId cid, uint32 options,
171 : : BulkInsertState bistate, uint32 specToken)
172 : : {
2738 173 : 2280 : bool shouldFree = true;
174 : 2280 : HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
175 : :
176 : : /* Update the tuple with table oid */
177 : 2280 : slot->tts_tableOid = RelationGetRelid(relation);
178 : 2280 : tuple->t_tableOid = slot->tts_tableOid;
179 : :
180 : 2280 : HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
181 : 2280 : options |= HEAP_INSERT_SPECULATIVE;
182 : :
183 : : /* Perform the insertion, and copy the resulting ItemPointer */
184 : 2280 : heap_insert(relation, tuple, cid, options, bistate);
185 : 2280 : ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
186 : :
187 [ + + ]: 2280 : if (shouldFree)
188 : 62 : pfree(tuple);
189 : 2280 : }
190 : :
191 : : static void
2731 192 : 2248 : heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
193 : : uint32 specToken, bool succeeded)
194 : : {
195 : : /* adjust the tuple's state accordingly */
2686 196 [ + + ]: 2248 : if (succeeded)
2738 197 : 2236 : heap_finish_speculative(relation, &slot->tts_tid);
198 : : else
199 : 12 : heap_abort_speculative(relation, &slot->tts_tid);
200 : 2248 : }
201 : :
202 : : static TM_Result
203 : 1052441 : heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
204 : : uint32 options, Snapshot snapshot, Snapshot crosscheck,
205 : : bool wait, TM_FailureData *tmfd)
206 : : {
207 : : /*
208 : : * Currently Deleting of index tuples are handled at vacuum, in case if
209 : : * the storage itself is cleaning the dead tuples by itself, it is the
210 : : * time to call the index tuple deletion also.
211 : : */
172 alvherre@kurilemu.de 212 : 1052441 : return heap_delete(relation, tid, cid, options, crosscheck, wait,
213 : : tmfd);
214 : : }
215 : :
216 : :
217 : : static TM_Result
2738 andres@anarazel.de 218 : 2257819 : heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
219 : : CommandId cid, uint32 options,
220 : : Snapshot snapshot, Snapshot crosscheck,
221 : : bool wait, TM_FailureData *tmfd,
222 : : LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
223 : : {
224 : 2257819 : bool shouldFree = true;
225 : 2257819 : HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
226 : : TM_Result result;
227 : :
228 : : /* Update the tuple with table oid */
229 : 2257819 : slot->tts_tableOid = RelationGetRelid(relation);
230 : 2257819 : tuple->t_tableOid = slot->tts_tableOid;
231 : :
172 alvherre@kurilemu.de 232 : 2257819 : result = heap_update(relation, otid, tuple, cid, options,
233 : : crosscheck, wait,
234 : : tmfd, lockmode, update_indexes);
2738 andres@anarazel.de 235 : 2257806 : ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
236 : :
237 : : /*
238 : : * Decide whether new index entries are needed for the tuple
239 : : *
240 : : * Note: heap_update returns the tid (location) of the new tuple in the
241 : : * t_self field.
242 : : *
243 : : * If the update is not HOT, we must update all indexes. If the update is
244 : : * HOT, it could be that we updated summarized columns, so we either
245 : : * update only summarized indexes, or none at all.
246 : : */
1280 tomas.vondra@postgre 247 [ + + ]: 2257806 : if (result != TM_Ok)
248 : : {
249 [ - + ]: 182 : Assert(*update_indexes == TU_None);
250 : 182 : *update_indexes = TU_None;
251 : : }
252 [ + + ]: 2257624 : else if (!HeapTupleIsHeapOnly(tuple))
253 [ - + ]: 2186401 : Assert(*update_indexes == TU_All);
254 : : else
255 [ + + - + ]: 71223 : Assert((*update_indexes == TU_Summarizing) ||
256 : : (*update_indexes == TU_None));
257 : :
2738 andres@anarazel.de 258 [ + + ]: 2257806 : if (shouldFree)
259 : 31991 : pfree(tuple);
260 : :
261 : 2257806 : return result;
262 : : }
263 : :
264 : : static TM_Result
265 : 571548 : heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
266 : : TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
267 : : LockWaitPolicy wait_policy, uint8 flags,
268 : : TM_FailureData *tmfd)
269 : : {
270 : 571548 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
271 : : TM_Result result;
272 : : Buffer buffer;
273 : 571548 : HeapTuple tuple = &bslot->base.tupdata;
274 : : bool follow_updates;
275 : :
276 : 571548 : follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) != 0;
277 : 571548 : tmfd->traversed = false;
278 : :
279 [ + - ]: 571548 : Assert(TTS_IS_BUFFERTUPLE(slot));
280 : :
281 : 571548 : tuple_lock_retry:
892 akorotkov@postgresql 282 : 571733 : tuple->t_self = *tid;
283 : 571733 : result = heap_lock_tuple(relation, tuple, cid, mode, wait_policy,
284 : : follow_updates, &buffer, tmfd);
285 : :
2738 andres@anarazel.de 286 [ + + ]: 571720 : if (result == TM_Updated &&
287 [ + + ]: 228 : (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION))
288 : : {
289 : : /* Should not encounter speculative tuple on recheck */
1266 akorotkov@postgresql 290 [ - + ]: 208 : Assert(!HeapTupleHeaderIsSpeculative(tuple->t_data));
291 : :
892 292 : 208 : ReleaseBuffer(buffer);
293 : :
2738 andres@anarazel.de 294 [ + - ]: 208 : if (!ItemPointerEquals(&tmfd->ctid, &tuple->t_self))
295 : : {
296 : : SnapshotData SnapshotDirty;
297 : : TransactionId priorXmax;
298 : :
299 : : /* it was updated, so look at the updated version */
300 : 208 : *tid = tmfd->ctid;
301 : : /* updated row should have xmin matching this xmax */
302 : 208 : priorXmax = tmfd->xmax;
303 : :
304 : : /* signal that a tuple later in the chain is getting locked */
305 : 208 : tmfd->traversed = true;
306 : :
307 : : /*
308 : : * fetch target tuple
309 : : *
310 : : * Loop here to deal with updated or busy tuples
311 : : */
312 : 208 : InitDirtySnapshot(SnapshotDirty);
313 : : for (;;)
314 : : {
315 [ + + ]: 245 : if (ItemPointerIndicatesMovedPartitions(tid))
316 [ + - ]: 11 : ereport(ERROR,
317 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
318 : : errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
319 : :
320 : 234 : tuple->t_self = *tid;
1621 tgl@sss.pgh.pa.us 321 [ + + ]: 234 : if (heap_fetch(relation, &SnapshotDirty, tuple, &buffer, true))
322 : : {
323 : : /*
324 : : * If xmin isn't what we're expecting, the slot must have
325 : : * been recycled and reused for an unrelated tuple. This
326 : : * implies that the latest version of the row was deleted,
327 : : * so we need do nothing. (Should be safe to examine xmin
328 : : * without getting buffer's content lock. We assume
329 : : * reading a TransactionId to be atomic, and Xmin never
330 : : * changes in an existing tuple, except to invalid or
331 : : * frozen, and neither of those can match priorXmax.)
332 : : */
2738 andres@anarazel.de 333 [ - + ]: 194 : if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
334 : : priorXmax))
335 : : {
2738 andres@anarazel.de 336 :UBC 0 : ReleaseBuffer(buffer);
2738 andres@anarazel.de 337 :CBC 11 : return TM_Deleted;
338 : : }
339 : :
340 : : /* otherwise xmin should not be dirty... */
341 [ - + ]: 194 : if (TransactionIdIsValid(SnapshotDirty.xmin))
2607 peter@eisentraut.org 342 [ # # ]:UBC 0 : ereport(ERROR,
343 : : (errcode(ERRCODE_DATA_CORRUPTED),
344 : : errmsg_internal("t_xmin %u is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
345 : : SnapshotDirty.xmin,
346 : : ItemPointerGetBlockNumber(&tuple->t_self),
347 : : ItemPointerGetOffsetNumber(&tuple->t_self),
348 : : RelationGetRelationName(relation))));
349 : :
350 : : /*
351 : : * If tuple is being updated by other transaction then we
352 : : * have to wait for its commit/abort, or die trying.
353 : : */
2738 andres@anarazel.de 354 [ + + ]:CBC 194 : if (TransactionIdIsValid(SnapshotDirty.xmax))
355 : : {
356 : 2 : ReleaseBuffer(buffer);
357 [ - + + - ]: 2 : switch (wait_policy)
358 : : {
2738 andres@anarazel.de 359 :UBC 0 : case LockWaitBlock:
360 : 0 : XactLockTableWait(SnapshotDirty.xmax,
361 : 0 : relation, &tuple->t_self,
362 : : XLTW_FetchUpdated);
363 : 0 : break;
2738 andres@anarazel.de 364 :CBC 1 : case LockWaitSkip:
555 fujii@postgresql.org 365 [ + - ]: 1 : if (!ConditionalXactLockTableWait(SnapshotDirty.xmax, false))
366 : : /* skip instead of waiting */
2738 andres@anarazel.de 367 : 1 : return TM_WouldBlock;
2738 andres@anarazel.de 368 :UBC 0 : break;
2738 andres@anarazel.de 369 :CBC 1 : case LockWaitError:
474 fujii@postgresql.org 370 [ + - ]: 1 : if (!ConditionalXactLockTableWait(SnapshotDirty.xmax, log_lock_failures))
2738 andres@anarazel.de 371 [ + - ]: 1 : ereport(ERROR,
372 : : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
373 : : errmsg("could not obtain lock on row in relation \"%s\"",
374 : : RelationGetRelationName(relation))));
2738 andres@anarazel.de 375 :UBC 0 : break;
376 : : }
377 : 0 : continue; /* loop back to repeat heap_fetch */
378 : : }
379 : :
380 : : /*
381 : : * If tuple was inserted by our own transaction, we have
382 : : * to check cmin against cid: cmin >= current CID means
383 : : * our command cannot see the tuple, so we should ignore
384 : : * it. Otherwise heap_lock_tuple() will throw an error,
385 : : * and so would any later attempt to update or delete the
386 : : * tuple. (We need not check cmax because
387 : : * HeapTupleSatisfiesDirty will consider a tuple deleted
388 : : * by our transaction dead, regardless of cmax.) We just
389 : : * checked that priorXmax == xmin, so we can test that
390 : : * variable instead of doing HeapTupleHeaderGetXmin again.
391 : : */
2738 andres@anarazel.de 392 [ + + + - ]:CBC 199 : if (TransactionIdIsCurrentTransactionId(priorXmax) &&
393 : 7 : HeapTupleHeaderGetCmin(tuple->t_data) >= cid)
394 : : {
2723 395 : 7 : tmfd->xmax = priorXmax;
396 : :
397 : : /*
398 : : * Cmin is the problematic value, so store that. See
399 : : * above.
400 : : */
401 : 7 : tmfd->cmax = HeapTupleHeaderGetCmin(tuple->t_data);
2738 402 : 7 : ReleaseBuffer(buffer);
2723 403 : 7 : return TM_SelfModified;
404 : : }
405 : :
406 : : /*
407 : : * This is a live tuple, so try to lock it again.
408 : : */
892 akorotkov@postgresql 409 : 185 : ReleaseBuffer(buffer);
2738 andres@anarazel.de 410 : 185 : goto tuple_lock_retry;
411 : : }
412 : :
413 : : /*
414 : : * If the referenced slot was actually empty, the latest
415 : : * version of the row must have been deleted, so we need do
416 : : * nothing.
417 : : */
418 [ - + ]: 40 : if (tuple->t_data == NULL)
419 : : {
892 akorotkov@postgresql 420 [ # # ]:UBC 0 : Assert(!BufferIsValid(buffer));
2738 andres@anarazel.de 421 : 0 : return TM_Deleted;
422 : : }
423 : :
424 : : /*
425 : : * As above, if xmin isn't what we're expecting, do nothing.
426 : : */
2738 andres@anarazel.de 427 [ - + ]:CBC 40 : if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
428 : : priorXmax))
429 : : {
1621 tgl@sss.pgh.pa.us 430 :UBC 0 : ReleaseBuffer(buffer);
2738 andres@anarazel.de 431 : 0 : return TM_Deleted;
432 : : }
433 : :
434 : : /*
435 : : * If we get here, the tuple was found but failed
436 : : * SnapshotDirty. Assuming the xmin is either a committed xact
437 : : * or our own xact (as it certainly should be if we're trying
438 : : * to modify the tuple), this must mean that the row was
439 : : * updated or deleted by either a committed xact or our own
440 : : * xact. If it was deleted, we can ignore it; if it was
441 : : * updated then chain up to the next version and repeat the
442 : : * whole process.
443 : : *
444 : : * As above, it should be safe to examine xmax and t_ctid
445 : : * without the buffer content lock, because they can't be
446 : : * changing. We'd better hold a buffer pin though.
447 : : */
2738 andres@anarazel.de 448 [ + + ]:CBC 40 : if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
449 : : {
450 : : /* deleted, so forget about it */
1621 tgl@sss.pgh.pa.us 451 : 3 : ReleaseBuffer(buffer);
2738 andres@anarazel.de 452 : 3 : return TM_Deleted;
453 : : }
454 : :
455 : : /* updated, so look at the updated row */
456 : 37 : *tid = tuple->t_data->t_ctid;
457 : : /* updated row should have xmin matching this xmax */
458 : 37 : priorXmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
1621 tgl@sss.pgh.pa.us 459 : 37 : ReleaseBuffer(buffer);
460 : : /* loop back to fetch next in chain */
461 : : }
462 : : }
463 : : else
464 : : {
465 : : /* tuple was deleted, so give up */
2738 andres@anarazel.de 466 :UBC 0 : return TM_Deleted;
467 : : }
468 : : }
469 : :
2738 andres@anarazel.de 470 :CBC 571512 : slot->tts_tableOid = RelationGetRelid(relation);
471 : 571512 : tuple->t_tableOid = slot->tts_tableOid;
472 : :
473 : : /* store in slot, transferring existing pin */
892 akorotkov@postgresql 474 : 571512 : ExecStorePinnedBufferHeapTuple(tuple, slot, buffer);
475 : :
2738 andres@anarazel.de 476 : 571512 : return result;
477 : : }
478 : :
479 : :
480 : : /* ------------------------------------------------------------------------
481 : : * DDL related callbacks for heap AM.
482 : : * ------------------------------------------------------------------------
483 : : */
484 : :
485 : : static void
1537 rhaas@postgresql.org 486 : 42208 : heapam_relation_set_new_filelocator(Relation rel,
487 : : const RelFileLocator *newrlocator,
488 : : char persistence,
489 : : TransactionId *freezeXid,
490 : : MultiXactId *minmulti)
491 : : {
492 : : SMgrRelation srel;
493 : :
494 : : /*
495 : : * Initialize to the minimum XID that could put tuples in the table. We
496 : : * know that no xacts older than RecentXmin are still running, so that
497 : : * will do.
498 : : */
2733 andres@anarazel.de 499 : 42208 : *freezeXid = RecentXmin;
500 : :
501 : : /*
502 : : * Similarly, initialize the minimum Multixact to the first value that
503 : : * could possibly be stored in tuples in the table. Running transactions
504 : : * could reuse values from their local cache, so we are careful to
505 : : * consider all currently running multis.
506 : : *
507 : : * XXX this could be refined further, but is it worth the hassle?
508 : : */
509 : 42208 : *minmulti = GetOldestMultiXactId();
510 : :
1537 rhaas@postgresql.org 511 : 42208 : srel = RelationCreateStorage(*newrlocator, persistence, true);
512 : :
513 : : /*
514 : : * If required, set up an init fork for an unlogged table so that it can
515 : : * be correctly reinitialized on restart.
516 : : */
2701 andres@anarazel.de 517 [ + + ]: 42208 : if (persistence == RELPERSISTENCE_UNLOGGED)
518 : : {
2733 519 [ + + - + ]: 166 : Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
520 : : rel->rd_rel->relkind == RELKIND_TOASTVALUE);
2701 521 : 166 : smgrcreate(srel, INIT_FORKNUM, false);
1537 rhaas@postgresql.org 522 : 166 : log_smgrcreate(newrlocator, INIT_FORKNUM);
523 : : }
524 : :
2701 andres@anarazel.de 525 : 42208 : smgrclose(srel);
2733 526 : 42208 : }
527 : :
528 : : static void
529 : 372 : heapam_relation_nontransactional_truncate(Relation rel)
530 : : {
531 : 372 : RelationTruncate(rel, 0);
532 : 372 : }
533 : :
534 : : static void
1537 rhaas@postgresql.org 535 : 67 : heapam_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
536 : : {
537 : : SMgrRelation dstrel;
538 : :
539 : : /*
540 : : * Since we copy the file directly without looking at the shared buffers,
541 : : * we'd better first flush out any pages of the source relation that are
542 : : * in shared buffers. We assume no new changes will be made while we are
543 : : * holding exclusive lock on the rel.
544 : : */
2701 andres@anarazel.de 545 : 67 : FlushRelationBuffers(rel);
546 : :
547 : : /*
548 : : * Create and copy all forks of the relation, and schedule unlinking of
549 : : * old physical files.
550 : : *
551 : : * NOTE: any conflict in relfilenumber value will be caught in
552 : : * RelationCreateStorage().
553 : : */
951 heikki.linnakangas@i 554 : 67 : dstrel = RelationCreateStorage(*newrlocator, rel->rd_rel->relpersistence, true);
555 : :
556 : : /* copy main fork */
1896 tgl@sss.pgh.pa.us 557 : 67 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
2733 andres@anarazel.de 558 : 67 : rel->rd_rel->relpersistence);
559 : :
560 : : /* copy those extra forks that exist */
561 : 67 : for (ForkNumber forkNum = MAIN_FORKNUM + 1;
562 [ + + ]: 268 : forkNum <= MAX_FORKNUM; forkNum++)
563 : : {
1896 tgl@sss.pgh.pa.us 564 [ + + ]: 201 : if (smgrexists(RelationGetSmgr(rel), forkNum))
565 : : {
2733 andres@anarazel.de 566 : 23 : smgrcreate(dstrel, forkNum, false);
567 : :
568 : : /*
569 : : * WAL log creation if the relation is persistent, or this is the
570 : : * init fork of an unlogged relation.
571 : : */
2008 bruce@momjian.us 572 [ + + ]: 23 : if (RelationIsPermanent(rel) ||
2733 andres@anarazel.de 573 [ - + - - ]: 8 : (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
574 : : forkNum == INIT_FORKNUM))
1537 rhaas@postgresql.org 575 : 15 : log_smgrcreate(newrlocator, forkNum);
1896 tgl@sss.pgh.pa.us 576 : 23 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
2733 andres@anarazel.de 577 : 23 : rel->rd_rel->relpersistence);
578 : : }
579 : : }
580 : :
581 : :
582 : : /* drop old relation, and close new one */
583 : 67 : RelationDropStorage(rel);
584 : 67 : smgrclose(dstrel);
585 : 67 : }
586 : :
587 : : static void
588 : 438 : heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
589 : : Relation OldIndex, bool use_sort,
590 : : TransactionId OldestXmin,
591 : : Snapshot snapshot,
592 : : TransactionId *xid_cutoff,
593 : : MultiXactId *multi_cutoff,
594 : : double *num_tuples,
595 : : double *tups_vacuumed,
596 : : double *tups_recently_dead)
597 : : {
598 : : RewriteState rwstate;
599 : : BulkInsertState bistate;
600 : : IndexScanDesc indexScan;
601 : : TableScanDesc tableScan;
602 : : HeapScanDesc heapScan;
603 : : bool is_system_catalog;
604 : : Tuplesortstate *tuplesort;
605 : 438 : TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
606 : 438 : TupleDesc newTupDesc = RelationGetDescr(NewHeap);
607 : : TupleTableSlot *slot;
608 : : int natts;
609 : : Datum *values;
610 : : bool *isnull;
611 : : BufferHeapTupleTableSlot *hslot;
2123 fujii@postgresql.org 612 : 438 : BlockNumber prev_cblock = InvalidBlockNumber;
167 alvherre@kurilemu.de 613 : 438 : bool concurrent = snapshot != NULL;
614 : :
615 : : /* Remember if it's a system catalog */
2733 andres@anarazel.de 616 : 438 : is_system_catalog = IsSystemRelation(OldHeap);
617 : :
618 : : /*
619 : : * Valid smgr_targblock implies something already wrote to the relation.
620 : : * This may be harmless, but this function hasn't planned for it.
621 : : */
622 [ - + - - ]: 438 : Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber);
623 : :
624 : : /* Preallocate values/isnull arrays */
625 : 438 : natts = newTupDesc->natts;
284 michael@paquier.xyz 626 : 438 : values = palloc_array(Datum, natts);
627 : 438 : isnull = palloc_array(bool, natts);
628 : :
629 : : /*
630 : : * In non-concurrent mode, initialize the rewrite operation. This is not
631 : : * needed in concurrent mode.
632 : : */
167 alvherre@kurilemu.de 633 [ + + ]: 438 : if (!concurrent)
634 : 429 : rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
635 : : *xid_cutoff, *multi_cutoff);
636 : : else
637 : 9 : rwstate = NULL;
638 : :
639 : : /* In concurrent mode, prepare for bulk-insert operation. */
640 [ + + ]: 438 : if (concurrent)
641 : 9 : bistate = GetBulkInsertState();
642 : : else
643 : 429 : bistate = NULL;
644 : :
645 : : /* Set up sorting if wanted */
2733 andres@anarazel.de 646 [ + + ]: 438 : if (use_sort)
1198 pg@bowt.ie 647 : 85 : tuplesort = tuplesort_begin_cluster(oldTupDesc, OldIndex,
648 : : maintenance_work_mem,
649 : : NULL, TUPLESORT_NONE);
650 : : else
2733 andres@anarazel.de 651 : 353 : tuplesort = NULL;
652 : :
653 : : /*
654 : : * Prepare to scan the OldHeap. To ensure we see recently-dead tuples
655 : : * that still need to be copied, we scan with SnapshotAny and use
656 : : * HeapTupleSatisfiesVacuum for the visibility test.
657 : : *
658 : : * In the CONCURRENTLY case, we do regular MVCC visibility tests, using
659 : : * the snapshot passed by the caller.
660 : : */
661 [ + + + + ]: 438 : if (OldIndex != NULL && !use_sort)
662 : : {
10 fujii@postgresql.org 663 : 62 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
664 : : PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP);
665 : :
2733 andres@anarazel.de 666 : 62 : tableScan = NULL;
667 : 62 : heapScan = NULL;
5 pg@bowt.ie 668 [ + + ]:GNC 62 : indexScan = index_beginscan(OldHeap, OldIndex, false,
669 : : snapshot ? snapshot : SnapshotAny,
670 : : NULL, 0, 0,
671 : : SO_NONE);
2733 andres@anarazel.de 672 :CBC 62 : index_rescan(indexScan, NULL, 0, NULL, 0);
673 : : }
674 : : else
675 : : {
676 : : /* In scan-and-sort mode and also VACUUM FULL, set phase */
194 alvherre@kurilemu.de 677 : 376 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
678 : : PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
679 : :
167 680 [ + + ]: 376 : tableScan = table_beginscan(OldHeap,
681 : : snapshot ? snapshot : SnapshotAny,
682 : : 0, (ScanKey) NULL,
683 : : SO_NONE);
2733 andres@anarazel.de 684 : 376 : heapScan = (HeapScanDesc) tableScan;
685 : 376 : indexScan = NULL;
686 : :
687 : : /* Set total heap blocks */
194 alvherre@kurilemu.de 688 : 376 : pgstat_progress_update_param(PROGRESS_REPACK_TOTAL_HEAP_BLKS,
2733 andres@anarazel.de 689 : 376 : heapScan->rs_nblocks);
690 : : }
691 : :
692 : 438 : slot = table_slot_create(OldHeap, NULL);
693 : 438 : hslot = (BufferHeapTupleTableSlot *) slot;
694 : :
695 : : /*
696 : : * Scan through the OldHeap, either in OldIndex order or sequentially;
697 : : * copy each tuple into the NewHeap, or transiently to the tuplesort
698 : : * module. Note that we don't bother sorting dead tuples (they won't get
699 : : * to the new table anyway).
700 : : */
701 : : for (;;)
702 : 485333 : {
703 : : HeapTuple tuple;
704 : : Buffer buf;
705 : : bool isdead;
706 : :
707 [ - + ]: 485771 : CHECK_FOR_INTERRUPTS();
708 : :
709 [ + + ]: 485771 : if (indexScan != NULL)
710 : : {
5 pg@bowt.ie 711 [ + + ]:GNC 1204 : if (!table_index_getnext_slot(indexScan, ForwardScanDirection,
712 : : slot))
2733 andres@anarazel.de 713 :CBC 62 : break;
714 : :
715 : : /* Since we used no scan keys, should never need to recheck */
716 [ - + ]: 1142 : if (indexScan->xs_recheck)
2733 andres@anarazel.de 717 [ # # ]:UBC 0 : elog(ERROR, "CLUSTER does not support lossy index conditions");
718 : : }
719 : : else
720 : : {
2733 andres@anarazel.de 721 [ + + ]:CBC 484567 : if (!table_scan_getnextslot(tableScan, ForwardScanDirection, slot))
722 : : {
723 : : /*
724 : : * If the last pages of the scan were empty, we would go to
725 : : * the next phase while heap_blks_scanned != heap_blks_total.
726 : : * Instead, to ensure that heap_blks_scanned is equivalent to
727 : : * heap_blks_total after the table scan phase, this parameter
728 : : * is manually updated to the correct value when the table
729 : : * scan finishes.
730 : : */
194 alvherre@kurilemu.de 731 : 376 : pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
2123 fujii@postgresql.org 732 : 376 : heapScan->rs_nblocks);
2733 andres@anarazel.de 733 : 376 : break;
734 : : }
735 : :
736 : : /*
737 : : * In scan-and-sort mode and also VACUUM FULL, set heap blocks
738 : : * scanned
739 : : *
740 : : * Note that heapScan may start at an offset and wrap around, i.e.
741 : : * rs_startblock may be >0, and rs_cblock may end with a number
742 : : * below rs_startblock. To prevent showing this wraparound to the
743 : : * user, we offset rs_cblock by rs_startblock (modulo rs_nblocks).
744 : : */
2123 fujii@postgresql.org 745 [ + + ]: 484191 : if (prev_cblock != heapScan->rs_cblock)
746 : : {
194 alvherre@kurilemu.de 747 : 7137 : pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
2123 fujii@postgresql.org 748 : 7137 : (heapScan->rs_cblock +
749 : 7137 : heapScan->rs_nblocks -
750 : 7137 : heapScan->rs_startblock
751 : 7137 : ) % heapScan->rs_nblocks + 1);
752 : 7137 : prev_cblock = heapScan->rs_cblock;
753 : : }
754 : : }
755 : :
2733 andres@anarazel.de 756 : 485333 : tuple = ExecFetchSlotHeapTuple(slot, false, NULL);
757 : 485333 : buf = hslot->buffer;
758 : :
759 : : /*
760 : : * In concurrent mode, our table or index scan has used regular MVCC
761 : : * visibility test against a snapshot passed by caller; therefore we
762 : : * don't need another visibility test. In non-concurrent mode
763 : : * however, we must test the visibility of each tuple we read.
764 : : */
167 alvherre@kurilemu.de 765 [ + + ]: 485333 : if (!concurrent)
766 : : {
767 : : /*
768 : : * To be able to guarantee that we can set the hint bit, acquire
769 : : * an exclusive lock on the old buffer. We need the hint bits, set
770 : : * in heapam_relation_copy_for_cluster() ->
771 : : * HeapTupleSatisfiesVacuum(), to be set, as otherwise
772 : : * reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get
773 : : * confused. Specifically, rewrite_heap_tuple() checks for
774 : : * HEAP_XMAX_INVALID in the old tuple to determine whether to
775 : : * check the old-to-new mapping hash table.
776 : : *
777 : : * It'd be better if we somehow could avoid setting hint bits on
778 : : * the old page. One reason to use VACUUM FULL are very bloated
779 : : * tables - rewriting most of the old table during VACUUM FULL
780 : : * doesn't exactly help...
781 : : */
782 : 485300 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
783 : :
784 [ + + + + : 485300 : switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+ - ]
785 : : {
786 : 21702 : case HEAPTUPLE_DEAD:
787 : : /* Definitely dead */
788 : 21702 : isdead = true;
789 : 21702 : break;
790 : 19085 : case HEAPTUPLE_RECENTLY_DEAD:
791 : 19085 : *tups_recently_dead += 1;
792 : : pg_fallthrough;
793 : 463454 : case HEAPTUPLE_LIVE:
794 : : /* Live or recently dead, must copy it */
795 : 463454 : isdead = false;
796 : 463454 : break;
797 : 104 : case HEAPTUPLE_INSERT_IN_PROGRESS:
798 : :
799 : : /*
800 : : * As long as we hold exclusive lock on the relation,
801 : : * normally the only way to see this is if it was inserted
802 : : * earlier in our own transaction. However, it can happen
803 : : * in system catalogs, since we tend to release write lock
804 : : * before commit there. Give a warning if neither case
805 : : * applies; but in any case we had better copy it.
806 : : */
807 [ + + ]: 104 : if (!is_system_catalog &&
808 [ - + ]: 14 : !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
167 alvherre@kurilemu.de 809 [ # # ]:UBC 0 : elog(WARNING, "concurrent insert in progress within table \"%s\"",
810 : : RelationGetRelationName(OldHeap));
811 : : /* treat as live */
167 alvherre@kurilemu.de 812 :CBC 104 : isdead = false;
813 : 104 : break;
814 : 40 : case HEAPTUPLE_DELETE_IN_PROGRESS:
815 : :
816 : : /*
817 : : * Similar situation to INSERT_IN_PROGRESS case.
818 : : */
819 [ + + ]: 40 : if (!is_system_catalog &&
820 [ - + ]: 20 : !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
167 alvherre@kurilemu.de 821 [ # # ]:UBC 0 : elog(WARNING, "concurrent delete in progress within table \"%s\"",
822 : : RelationGetRelationName(OldHeap));
823 : : /* treat as recently dead */
167 alvherre@kurilemu.de 824 :CBC 40 : *tups_recently_dead += 1;
825 : 40 : isdead = false;
826 : 40 : break;
167 alvherre@kurilemu.de 827 :UBC 0 : default:
828 [ # # ]: 0 : elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
829 : : isdead = false; /* keep compiler quiet */
830 : : break;
831 : : }
832 : :
167 alvherre@kurilemu.de 833 :CBC 485300 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
834 : :
835 [ + + ]: 485300 : if (isdead)
836 : : {
2733 andres@anarazel.de 837 : 21702 : *tups_vacuumed += 1;
838 : : /* heap rewrite module still needs to see it... */
167 alvherre@kurilemu.de 839 [ - + ]: 21702 : if (rewrite_heap_dead_tuple(rwstate, tuple))
840 : : {
841 : : /* A previous recently-dead tuple is now known dead */
167 alvherre@kurilemu.de 842 :UBC 0 : *tups_vacuumed += 1;
843 : 0 : *tups_recently_dead -= 1;
844 : : }
845 : :
167 alvherre@kurilemu.de 846 :CBC 21702 : continue;
847 : : }
848 : : }
849 : :
2733 andres@anarazel.de 850 : 463631 : *num_tuples += 1;
851 [ + + ]: 463631 : if (tuplesort != NULL)
852 : : {
853 : 363154 : tuplesort_putheaptuple(tuplesort, tuple);
854 : :
855 : : /*
856 : : * In scan-and-sort mode, report increase in number of tuples
857 : : * scanned
858 : : */
194 alvherre@kurilemu.de 859 : 363154 : pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
2733 andres@anarazel.de 860 : 363154 : *num_tuples);
861 : : }
862 : : else
863 : : {
2731 864 : 100477 : const int ct_index[] = {
865 : : PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
866 : : PROGRESS_REPACK_HEAP_TUPLES_INSERTED
867 : : };
868 : : int64 ct_val[2];
869 : :
167 alvherre@kurilemu.de 870 [ + + ]: 100477 : if (!concurrent)
871 : 100444 : reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
872 : : values, isnull, rwstate);
873 : : else
874 : 33 : heap_insert_for_repack(tuple, OldHeap, NewHeap,
875 : : values, isnull, bistate);
876 : :
877 : : /*
878 : : * In indexscan mode and also VACUUM FULL, report increase in
879 : : * number of tuples scanned and written
880 : : */
2733 andres@anarazel.de 881 : 100477 : ct_val[0] = *num_tuples;
882 : 100477 : ct_val[1] = *num_tuples;
883 : 100477 : pgstat_progress_update_multi_param(2, ct_index, ct_val);
884 : : }
885 : : }
886 : :
887 [ + + ]: 438 : if (indexScan != NULL)
888 : 62 : index_endscan(indexScan);
889 [ + + ]: 438 : if (tableScan != NULL)
890 : 376 : table_endscan(tableScan);
891 [ + - ]: 438 : if (slot)
892 : 438 : ExecDropSingleTupleTableSlot(slot);
893 : :
894 : : /*
895 : : * In scan-and-sort mode, complete the sort, then read out all live tuples
896 : : * from the tuplestore and write them to the new relation.
897 : : */
898 [ + + ]: 438 : if (tuplesort != NULL)
899 : : {
2731 900 : 85 : double n_tuples = 0;
901 : :
902 : : /* Report that we are now sorting tuples */
194 alvherre@kurilemu.de 903 : 85 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
904 : : PROGRESS_REPACK_PHASE_SORT_TUPLES);
905 : :
2733 andres@anarazel.de 906 : 85 : tuplesort_performsort(tuplesort);
907 : :
908 : : /* Report that we are now writing new heap */
194 alvherre@kurilemu.de 909 : 85 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
910 : : PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP);
911 : :
912 : : for (;;)
2733 andres@anarazel.de 913 : 363154 : {
914 : : HeapTuple tuple;
915 : :
916 [ - + ]: 363239 : CHECK_FOR_INTERRUPTS();
917 : :
918 : 363239 : tuple = tuplesort_getheaptuple(tuplesort, true);
919 [ + + ]: 363239 : if (tuple == NULL)
920 : 85 : break;
921 : :
922 : 363154 : n_tuples += 1;
167 alvherre@kurilemu.de 923 [ + - ]: 363154 : if (!concurrent)
924 : 363154 : reform_and_rewrite_tuple(tuple,
925 : : OldHeap, NewHeap,
926 : : values, isnull,
927 : : rwstate);
928 : : else
167 alvherre@kurilemu.de 929 :UBC 0 : heap_insert_for_repack(tuple, OldHeap, NewHeap,
930 : : values, isnull, bistate);
931 : :
932 : : /* Report n_tuples */
167 alvherre@kurilemu.de 933 :CBC 363154 : pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
934 : : n_tuples);
935 : : }
936 : :
2733 andres@anarazel.de 937 : 85 : tuplesort_end(tuplesort);
938 : : }
939 : :
940 : : /* Write out any remaining tuples, and fsync if needed */
167 alvherre@kurilemu.de 941 [ + + ]: 438 : if (rwstate)
942 : 429 : end_heap_rewrite(rwstate);
943 [ + + ]: 438 : if (bistate)
944 : 9 : FreeBulkInsertState(bistate);
945 : :
946 : : /* Clean up */
2733 andres@anarazel.de 947 : 438 : pfree(values);
948 : 438 : pfree(isnull);
949 : 438 : }
950 : :
951 : : /*
952 : : * Prepare to analyze the next block in the read stream. Returns false if
953 : : * the stream is exhausted and true otherwise. The scan must have been started
954 : : * with SO_TYPE_ANALYZE option.
955 : : *
956 : : * This routine holds a buffer pin and lock on the heap page. They are held
957 : : * until heapam_scan_analyze_next_tuple() returns false. That is until all the
958 : : * items of the heap page are analyzed.
959 : : */
960 : : static bool
895 tmunro@postgresql.or 961 : 115245 : heapam_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream)
962 : : {
2731 andres@anarazel.de 963 : 115245 : HeapScanDesc hscan = (HeapScanDesc) scan;
964 : :
965 : : /*
966 : : * We must maintain a pin on the target page's buffer to ensure that
967 : : * concurrent activity - e.g. HOT pruning - doesn't delete tuples out from
968 : : * under us. It comes from the stream already pinned. We also choose to
969 : : * hold sharelock on the buffer throughout --- we could release and
970 : : * re-acquire sharelock for each tuple, but since we aren't doing much
971 : : * work per tuple, the extra lock traffic is probably better avoided.
972 : : */
895 tmunro@postgresql.or 973 : 115245 : hscan->rs_cbuf = read_stream_next_buffer(stream, NULL);
974 [ + + ]: 115245 : if (!BufferIsValid(hscan->rs_cbuf))
975 : 11272 : return false;
976 : :
2731 andres@anarazel.de 977 : 103973 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
978 : :
895 tmunro@postgresql.or 979 : 103973 : hscan->rs_cblock = BufferGetBlockNumber(hscan->rs_cbuf);
980 : 103973 : hscan->rs_cindex = FirstOffsetNumber;
981 : 103973 : return true;
982 : : }
983 : :
984 : : static bool
206 melanieplageman@gmai 985 : 8025544 : heapam_scan_analyze_next_tuple(TableScanDesc scan,
986 : : double *liverows, double *deadrows,
987 : : TupleTableSlot *slot)
988 : : {
2731 andres@anarazel.de 989 : 8025544 : HeapScanDesc hscan = (HeapScanDesc) scan;
990 : : Page targpage;
991 : : OffsetNumber maxoffset;
992 : : BufferHeapTupleTableSlot *hslot;
993 : :
994 [ - + ]: 8025544 : Assert(TTS_IS_BUFFERTUPLE(slot));
995 : :
996 : 8025544 : hslot = (BufferHeapTupleTableSlot *) slot;
997 : 8025544 : targpage = BufferGetPage(hscan->rs_cbuf);
998 : 8025544 : maxoffset = PageGetMaxOffsetNumber(targpage);
999 : :
1000 : : /* Inner loop over all tuples on the selected page */
1001 [ + + ]: 8643956 : for (; hscan->rs_cindex <= maxoffset; hscan->rs_cindex++)
1002 : : {
1003 : : ItemId itemid;
1004 : 8539983 : HeapTuple targtuple = &hslot->base.tupdata;
1005 : 8539983 : bool sample_it = false;
1006 : : TransactionId dead_after;
1007 : :
1008 : 8539983 : itemid = PageGetItemId(targpage, hscan->rs_cindex);
1009 : :
1010 : : /*
1011 : : * We ignore unused and redirect line pointers. DEAD line pointers
1012 : : * should be counted as dead, because we need vacuum to run to get rid
1013 : : * of them. Note that this rule agrees with the way that
1014 : : * heap_page_prune_and_freeze() counts things.
1015 : : */
1016 [ + + ]: 8539983 : if (!ItemIdIsNormal(itemid))
1017 : : {
1018 [ + + ]: 434050 : if (ItemIdIsDead(itemid))
1019 : 17398 : *deadrows += 1;
1020 : 434050 : continue;
1021 : : }
1022 : :
1023 : 8105933 : ItemPointerSet(&targtuple->t_self, hscan->rs_cblock, hscan->rs_cindex);
1024 : :
1025 : 8105933 : targtuple->t_tableOid = RelationGetRelid(scan->rs_rd);
1026 : 8105933 : targtuple->t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
1027 : 8105933 : targtuple->t_len = ItemIdGetLength(itemid);
1028 : :
206 melanieplageman@gmai 1029 [ + + + + : 8105933 : switch (HeapTupleSatisfiesVacuumHorizon(targtuple,
- ]
1030 : : hscan->rs_cbuf,
1031 : : &dead_after))
1032 : : {
2731 andres@anarazel.de 1033 : 7690347 : case HEAPTUPLE_LIVE:
1034 : 7690347 : sample_it = true;
1035 : 7690347 : *liverows += 1;
1036 : 7690347 : break;
1037 : :
1038 : 165387 : case HEAPTUPLE_DEAD:
1039 : : case HEAPTUPLE_RECENTLY_DEAD:
1040 : : /* Count dead and recently-dead rows */
1041 : 165387 : *deadrows += 1;
1042 : 165387 : break;
1043 : :
1044 : 248817 : case HEAPTUPLE_INSERT_IN_PROGRESS:
1045 : :
1046 : : /*
1047 : : * Insert-in-progress rows are not counted. We assume that
1048 : : * when the inserting transaction commits or aborts, it will
1049 : : * send a stats message to increment the proper count. This
1050 : : * works right only if that transaction ends after we finish
1051 : : * analyzing the table; if things happen in the other order,
1052 : : * its stats update will be overwritten by ours. However, the
1053 : : * error will be large only if the other transaction runs long
1054 : : * enough to insert many tuples, so assuming it will finish
1055 : : * after us is the safer option.
1056 : : *
1057 : : * A special case is that the inserting transaction might be
1058 : : * our own. In this case we should count and sample the row,
1059 : : * to accommodate users who load a table and analyze it in one
1060 : : * transaction. (pgstat_report_analyze has to adjust the
1061 : : * numbers we report to the cumulative stats system to make
1062 : : * this come out right.)
1063 : : */
1064 [ + + ]: 248817 : if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple->t_data)))
1065 : : {
1066 : 230986 : sample_it = true;
1067 : 230986 : *liverows += 1;
1068 : : }
1069 : 248817 : break;
1070 : :
1071 : 1382 : case HEAPTUPLE_DELETE_IN_PROGRESS:
1072 : :
1073 : : /*
1074 : : * We count and sample delete-in-progress rows the same as
1075 : : * live ones, so that the stats counters come out right if the
1076 : : * deleting transaction commits after us, per the same
1077 : : * reasoning given above.
1078 : : *
1079 : : * If the delete was done by our own transaction, however, we
1080 : : * must count the row as dead to make pgstat_report_analyze's
1081 : : * stats adjustments come out right. (Note: this works out
1082 : : * properly when the row was both inserted and deleted in our
1083 : : * xact.)
1084 : : *
1085 : : * The net effect of these choices is that we act as though an
1086 : : * IN_PROGRESS transaction hasn't happened yet, except if it
1087 : : * is our own transaction, which we assume has happened.
1088 : : *
1089 : : * This approach ensures that we behave sanely if we see both
1090 : : * the pre-image and post-image rows for a row being updated
1091 : : * by a concurrent transaction: we will sample the pre-image
1092 : : * but not the post-image. We also get sane results if the
1093 : : * concurrent transaction never commits.
1094 : : */
1095 [ + + ]: 1382 : if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple->t_data)))
2651 1096 : 1144 : *deadrows += 1;
1097 : : else
1098 : : {
2731 1099 : 238 : sample_it = true;
2651 1100 : 238 : *liverows += 1;
1101 : : }
2731 1102 : 1382 : break;
1103 : :
2731 andres@anarazel.de 1104 :UBC 0 : default:
1105 [ # # ]: 0 : elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1106 : : break;
1107 : : }
1108 : :
2731 andres@anarazel.de 1109 [ + + ]:CBC 8105933 : if (sample_it)
1110 : : {
1111 : 7921571 : ExecStoreBufferHeapTuple(targtuple, slot, hscan->rs_cbuf);
1112 : 7921571 : hscan->rs_cindex++;
1113 : :
1114 : : /* note that we leave the buffer locked here! */
1115 : 7921571 : return true;
1116 : : }
1117 : : }
1118 : :
1119 : : /* Now release the lock and pin on the page */
1120 : 103973 : UnlockReleaseBuffer(hscan->rs_cbuf);
1121 : 103973 : hscan->rs_cbuf = InvalidBuffer;
1122 : :
1123 : : /* also prevent old slot contents from having pin on page */
1124 : 103973 : ExecClearTuple(slot);
1125 : :
1126 : 103973 : return false;
1127 : : }
1128 : :
1129 : : static double
2734 1130 : 34967 : heapam_index_build_range_scan(Relation heapRelation,
1131 : : Relation indexRelation,
1132 : : IndexInfo *indexInfo,
1133 : : bool allow_sync,
1134 : : bool anyvisible,
1135 : : bool progress,
1136 : : BlockNumber start_blockno,
1137 : : BlockNumber numblocks,
1138 : : IndexBuildCallback callback,
1139 : : void *callback_state,
1140 : : TableScanDesc scan)
1141 : : {
1142 : : HeapScanDesc hscan;
1143 : : bool is_system_catalog;
1144 : : bool checking_uniqueness;
1145 : : HeapTuple heapTuple;
1146 : : Datum values[INDEX_MAX_KEYS];
1147 : : bool isnull[INDEX_MAX_KEYS];
1148 : : double reltuples;
1149 : : ExprState *predicate;
1150 : : TupleTableSlot *slot;
1151 : : EState *estate;
1152 : : ExprContext *econtext;
1153 : : Snapshot snapshot;
1154 : 34967 : bool need_unregister_snapshot = false;
1155 : : TransactionId OldestXmin;
2678 tgl@sss.pgh.pa.us 1156 : 34967 : BlockNumber previous_blkno = InvalidBlockNumber;
2734 andres@anarazel.de 1157 : 34967 : BlockNumber root_blkno = InvalidBlockNumber;
1158 : : OffsetNumber root_offsets[MaxHeapTuplesPerPage];
1159 : :
1160 : : /*
1161 : : * sanity checks
1162 : : */
1163 [ - + ]: 34967 : Assert(OidIsValid(indexRelation->rd_rel->relam));
1164 : :
1165 : : /* Remember if it's a system catalog */
1166 : 34967 : is_system_catalog = IsSystemRelation(heapRelation);
1167 : :
1168 : : /* See whether we're verifying uniqueness/exclusion properties */
1169 [ + + ]: 44282 : checking_uniqueness = (indexInfo->ii_Unique ||
1170 [ + + ]: 9315 : indexInfo->ii_ExclusionOps != NULL);
1171 : :
1172 : : /*
1173 : : * "Any visible" mode is not compatible with uniqueness checks; make sure
1174 : : * only one of those is requested.
1175 : : */
1176 [ + + - + ]: 34967 : Assert(!(anyvisible && checking_uniqueness));
1177 : :
1178 : : /*
1179 : : * Need an EState for evaluation of index expressions and partial-index
1180 : : * predicates. Also a slot to hold the current tuple.
1181 : : */
1182 : 34967 : estate = CreateExecutorState();
1183 [ - + ]: 34967 : econtext = GetPerTupleExprContext(estate);
1184 : 34967 : slot = table_slot_create(heapRelation, NULL);
1185 : :
1186 : : /* Arrange for econtext's scan tuple to be the tuple under test */
1187 : 34967 : econtext->ecxt_scantuple = slot;
1188 : :
1189 : : /* Set up execution state for predicate, if any. */
1190 : 34967 : predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
1191 : :
1192 : : /*
1193 : : * Prepare for scan of the base relation. In a normal index build, we use
1194 : : * SnapshotAny because we must retrieve all tuples and do our own time
1195 : : * qual checks (because we have to index RECENTLY_DEAD tuples). In a
1196 : : * concurrent build, or during bootstrap, we take a regular MVCC snapshot
1197 : : * and index whatever's live according to that.
1198 : : */
1199 : 34967 : OldestXmin = InvalidTransactionId;
1200 : :
1201 : : /* okay to ignore lazy VACUUMs here */
1202 [ + + + + ]: 34967 : if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent)
2230 1203 : 25178 : OldestXmin = GetOldestNonRemovableTransactionId(heapRelation);
1204 : :
2734 1205 [ + + ]: 34967 : if (!scan)
1206 : : {
1207 : : /*
1208 : : * Serial index build.
1209 : : *
1210 : : * Must begin our own heap scan in this case. We may also need to
1211 : : * register a snapshot whose lifetime is under our direct control.
1212 : : */
1213 [ + + ]: 34603 : if (!TransactionIdIsValid(OldestXmin))
1214 : : {
1215 : 9696 : snapshot = RegisterSnapshot(GetTransactionSnapshot());
1216 : 9696 : need_unregister_snapshot = true;
1217 : : }
1218 : : else
1219 : 24907 : snapshot = SnapshotAny;
1220 : :
1221 : 34603 : scan = table_beginscan_strat(heapRelation, /* relation */
1222 : : snapshot, /* snapshot */
1223 : : 0, /* number of keys */
1224 : : NULL, /* scan key */
1225 : : true, /* buffer access strategy OK */
1226 : : allow_sync); /* syncscan OK? */
1227 : : }
1228 : : else
1229 : : {
1230 : : /*
1231 : : * Parallel index build.
1232 : : *
1233 : : * Parallel case never registers/unregisters own snapshot. Snapshot
1234 : : * is taken from parallel heap scan, and is SnapshotAny or an MVCC
1235 : : * snapshot, based on same criteria as serial case.
1236 : : */
1237 [ - + ]: 364 : Assert(!IsBootstrapProcessingMode());
1238 [ - + ]: 364 : Assert(allow_sync);
1239 : 364 : snapshot = scan->rs_snapshot;
1240 : : }
1241 : :
1242 : 34967 : hscan = (HeapScanDesc) scan;
1243 : :
1244 : : /*
1245 : : * Must have called GetOldestNonRemovableTransactionId() if using
1246 : : * SnapshotAny. Shouldn't have for an MVCC snapshot. (It's especially
1247 : : * worth checking this for parallel builds, since ambuild routines that
1248 : : * support parallel builds must work these details out for themselves.)
1249 : : */
2230 1250 [ + + - + ]: 34967 : Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
1251 [ + + - + ]: 34967 : Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
1252 : : !TransactionIdIsValid(OldestXmin));
1253 [ + + - + ]: 34967 : Assert(snapshot == SnapshotAny || !anyvisible);
1254 : :
1255 : : /* Publish number of blocks to scan */
2728 alvherre@alvh.no-ip. 1256 [ + + ]: 34967 : if (progress)
1257 : : {
1258 : : BlockNumber nblocks;
1259 : :
1260 [ + + ]: 33242 : if (hscan->rs_base.rs_parallel != NULL)
1261 : : {
1262 : : ParallelBlockTableScanDesc pbscan;
1263 : :
1264 : 137 : pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
1265 : 137 : nblocks = pbscan->phs_nblocks;
1266 : : }
1267 : : else
1268 : 33105 : nblocks = hscan->rs_nblocks;
1269 : :
1270 : 33242 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
1271 : : nblocks);
1272 : : }
1273 : :
1274 : : /* set our scan endpoints */
2734 andres@anarazel.de 1275 [ + + ]: 34967 : if (!allow_sync)
1276 : 1929 : heap_setscanlimits(scan, start_blockno, numblocks);
1277 : : else
1278 : : {
1279 : : /* syncscan can only be requested on whole relation */
1280 [ - + ]: 33038 : Assert(start_blockno == 0);
1281 [ - + ]: 33038 : Assert(numblocks == InvalidBlockNumber);
1282 : : }
1283 : :
1284 : 34967 : reltuples = 0;
1285 : :
1286 : : /*
1287 : : * Scan all tuples in the base relation.
1288 : : */
1289 [ + + ]: 11483914 : while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1290 : : {
1291 : : bool tupleIsAlive;
1292 : :
1293 [ + + ]: 11448969 : CHECK_FOR_INTERRUPTS();
1294 : :
1295 : : /* Report scan progress, if asked to. */
2728 alvherre@alvh.no-ip. 1296 [ + + ]: 11448969 : if (progress)
1297 : : {
2678 tgl@sss.pgh.pa.us 1298 : 9368374 : BlockNumber blocks_done = heapam_scan_get_blocks_done(hscan);
1299 : :
2728 alvherre@alvh.no-ip. 1300 [ + + ]: 9368374 : if (blocks_done != previous_blkno)
1301 : : {
1302 : 118785 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
1303 : : blocks_done);
1304 : 118785 : previous_blkno = blocks_done;
1305 : : }
1306 : : }
1307 : :
1308 : : /*
1309 : : * When dealing with a HOT-chain of updated tuples, we want to index
1310 : : * the values of the live tuple (if any), but index it under the TID
1311 : : * of the chain's root tuple. This approach is necessary to preserve
1312 : : * the HOT-chain structure in the heap. So we need to be able to find
1313 : : * the root item offset for every tuple that's in a HOT-chain. When
1314 : : * first reaching a new page of the relation, call
1315 : : * heap_get_root_tuples() to build a map of root item offsets on the
1316 : : * page.
1317 : : *
1318 : : * It might look unsafe to use this information across buffer
1319 : : * lock/unlock. However, we hold ShareLock on the table so no
1320 : : * ordinary insert/update/delete should occur; and we hold pin on the
1321 : : * buffer continuously while visiting the page, so no pruning
1322 : : * operation can occur either.
1323 : : *
1324 : : * In cases with only ShareUpdateExclusiveLock on the table, it's
1325 : : * possible for some HOT tuples to appear that we didn't know about
1326 : : * when we first read the page. To handle that case, we re-obtain the
1327 : : * list of root offsets when a HOT tuple points to a root item that we
1328 : : * don't know about.
1329 : : *
1330 : : * Also, although our opinions about tuple liveness could change while
1331 : : * we scan the page (due to concurrent transaction commits/aborts),
1332 : : * the chain root locations won't, so this info doesn't need to be
1333 : : * rebuilt after waiting for another transaction.
1334 : : *
1335 : : * Note the implied assumption that there is no more than one live
1336 : : * tuple per HOT-chain --- else we could create more than one index
1337 : : * entry pointing to the same root tuple.
1338 : : */
2734 andres@anarazel.de 1339 [ + + ]: 11448969 : if (hscan->rs_cblock != root_blkno)
1340 : : {
1341 : 137362 : Page page = BufferGetPage(hscan->rs_cbuf);
1342 : :
1343 : 137362 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
1344 : 137362 : heap_get_root_tuples(page, root_offsets);
1345 : 137362 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1346 : :
1347 : 137362 : root_blkno = hscan->rs_cblock;
1348 : : }
1349 : :
1350 [ + + ]: 11448969 : if (snapshot == SnapshotAny)
1351 : : {
1352 : : /* do our own time qual check */
1353 : : bool indexIt;
1354 : : TransactionId xwait;
1355 : :
1356 : 8880474 : recheck:
1357 : :
1358 : : /*
1359 : : * We could possibly get away with not locking the buffer here,
1360 : : * since caller should hold ShareLock on the relation, but let's
1361 : : * be conservative about it. (This remark is still correct even
1362 : : * with HOT-pruning: our pin on the buffer prevents pruning.)
1363 : : */
1364 : 8880474 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
1365 : :
1366 : : /*
1367 : : * The criteria for counting a tuple as live in this block need to
1368 : : * match what analyze.c's heapam_scan_analyze_next_tuple() does,
1369 : : * otherwise CREATE INDEX and ANALYZE may produce wildly different
1370 : : * reltuples values, e.g. when there are many recently-dead
1371 : : * tuples.
1372 : : */
1373 [ + + + + : 8880474 : switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
+ - ]
1374 : : hscan->rs_cbuf))
1375 : : {
1376 : 1145 : case HEAPTUPLE_DEAD:
1377 : : /* Definitely dead, we can ignore it */
1378 : 1145 : indexIt = false;
1379 : 1145 : tupleIsAlive = false;
1380 : 1145 : break;
1381 : 6838769 : case HEAPTUPLE_LIVE:
1382 : : /* Normal case, index and unique-check it */
1383 : 6838769 : indexIt = true;
1384 : 6838769 : tupleIsAlive = true;
1385 : : /* Count it as live, too */
1386 : 6838769 : reltuples += 1;
1387 : 6838769 : break;
1388 : 154509 : case HEAPTUPLE_RECENTLY_DEAD:
1389 : :
1390 : : /*
1391 : : * If tuple is recently deleted then we must index it
1392 : : * anyway to preserve MVCC semantics. (Pre-existing
1393 : : * transactions could try to use the index after we finish
1394 : : * building it, and may need to see such tuples.)
1395 : : *
1396 : : * However, if it was HOT-updated then we must only index
1397 : : * the live tuple at the end of the HOT-chain. Since this
1398 : : * breaks semantics for pre-existing snapshots, mark the
1399 : : * index as unusable for them.
1400 : : *
1401 : : * We don't count recently-dead tuples in reltuples, even
1402 : : * if we index them; see heapam_scan_analyze_next_tuple().
1403 : : */
1404 [ + + ]: 154509 : if (HeapTupleIsHotUpdated(heapTuple))
1405 : : {
1406 : 141 : indexIt = false;
1407 : : /* mark the index as unsafe for old snapshots */
1408 : 141 : indexInfo->ii_BrokenHotChain = true;
1409 : : }
1410 : : else
1411 : 154368 : indexIt = true;
1412 : : /* In any case, exclude the tuple from unique-checking */
1413 : 154509 : tupleIsAlive = false;
1414 : 154509 : break;
1415 : 1885971 : case HEAPTUPLE_INSERT_IN_PROGRESS:
1416 : :
1417 : : /*
1418 : : * In "anyvisible" mode, this tuple is visible and we
1419 : : * don't need any further checks.
1420 : : */
1421 [ + + ]: 1885971 : if (anyvisible)
1422 : : {
1423 : 30736 : indexIt = true;
1424 : 30736 : tupleIsAlive = true;
1425 : 30736 : reltuples += 1;
1426 : 30736 : break;
1427 : : }
1428 : :
1429 : : /*
1430 : : * Since caller should hold ShareLock or better, normally
1431 : : * the only way to see this is if it was inserted earlier
1432 : : * in our own transaction. However, it can happen in
1433 : : * system catalogs, since we tend to release write lock
1434 : : * before commit there. Give a warning if neither case
1435 : : * applies.
1436 : : */
1437 : 1855235 : xwait = HeapTupleHeaderGetXmin(heapTuple->t_data);
1438 [ + + ]: 1855235 : if (!TransactionIdIsCurrentTransactionId(xwait))
1439 : : {
1440 [ - + ]: 75 : if (!is_system_catalog)
2734 andres@anarazel.de 1441 [ # # ]:UBC 0 : elog(WARNING, "concurrent insert in progress within table \"%s\"",
1442 : : RelationGetRelationName(heapRelation));
1443 : :
1444 : : /*
1445 : : * If we are performing uniqueness checks, indexing
1446 : : * such a tuple could lead to a bogus uniqueness
1447 : : * failure. In that case we wait for the inserting
1448 : : * transaction to finish and check again.
1449 : : */
2734 andres@anarazel.de 1450 [ - + ]:CBC 75 : if (checking_uniqueness)
1451 : : {
1452 : : /*
1453 : : * Must drop the lock on the buffer before we wait
1454 : : */
2734 andres@anarazel.de 1455 :UBC 0 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1456 : 0 : XactLockTableWait(xwait, heapRelation,
1457 : 0 : &heapTuple->t_self,
1458 : : XLTW_InsertIndexUnique);
1459 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1460 : 0 : goto recheck;
1461 : : }
1462 : : }
1463 : : else
1464 : : {
1465 : : /*
1466 : : * For consistency with
1467 : : * heapam_scan_analyze_next_tuple(), count
1468 : : * HEAPTUPLE_INSERT_IN_PROGRESS tuples as live only
1469 : : * when inserted by our own transaction.
1470 : : */
2734 andres@anarazel.de 1471 :CBC 1855160 : reltuples += 1;
1472 : : }
1473 : :
1474 : : /*
1475 : : * We must index such tuples, since if the index build
1476 : : * commits then they're good.
1477 : : */
1478 : 1855235 : indexIt = true;
1479 : 1855235 : tupleIsAlive = true;
1480 : 1855235 : break;
1481 : 80 : case HEAPTUPLE_DELETE_IN_PROGRESS:
1482 : :
1483 : : /*
1484 : : * As with INSERT_IN_PROGRESS case, this is unexpected
1485 : : * unless it's our own deletion or a system catalog; but
1486 : : * in anyvisible mode, this tuple is visible.
1487 : : */
1488 [ - + ]: 80 : if (anyvisible)
1489 : : {
2734 andres@anarazel.de 1490 :UBC 0 : indexIt = true;
1491 : 0 : tupleIsAlive = false;
1492 : 0 : reltuples += 1;
1493 : 0 : break;
1494 : : }
1495 : :
2734 andres@anarazel.de 1496 :CBC 80 : xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
1497 [ + + ]: 80 : if (!TransactionIdIsCurrentTransactionId(xwait))
1498 : : {
1499 [ - + ]: 36 : if (!is_system_catalog)
2734 andres@anarazel.de 1500 [ # # ]:UBC 0 : elog(WARNING, "concurrent delete in progress within table \"%s\"",
1501 : : RelationGetRelationName(heapRelation));
1502 : :
1503 : : /*
1504 : : * If we are performing uniqueness checks, assuming
1505 : : * the tuple is dead could lead to missing a
1506 : : * uniqueness violation. In that case we wait for the
1507 : : * deleting transaction to finish and check again.
1508 : : *
1509 : : * Also, if it's a HOT-updated tuple, we should not
1510 : : * index it but rather the live tuple at the end of
1511 : : * the HOT-chain. However, the deleting transaction
1512 : : * could abort, possibly leaving this tuple as live
1513 : : * after all, in which case it has to be indexed. The
1514 : : * only way to know what to do is to wait for the
1515 : : * deleting transaction to finish and check again.
1516 : : */
2734 andres@anarazel.de 1517 [ + - - + ]:CBC 72 : if (checking_uniqueness ||
1518 : 36 : HeapTupleIsHotUpdated(heapTuple))
1519 : : {
1520 : : /*
1521 : : * Must drop the lock on the buffer before we wait
1522 : : */
2734 andres@anarazel.de 1523 :UBC 0 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1524 : 0 : XactLockTableWait(xwait, heapRelation,
1525 : 0 : &heapTuple->t_self,
1526 : : XLTW_InsertIndexUnique);
1527 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1528 : 0 : goto recheck;
1529 : : }
1530 : :
1531 : : /*
1532 : : * Otherwise index it but don't check for uniqueness,
1533 : : * the same as a RECENTLY_DEAD tuple.
1534 : : */
2734 andres@anarazel.de 1535 :CBC 36 : indexIt = true;
1536 : :
1537 : : /*
1538 : : * Count HEAPTUPLE_DELETE_IN_PROGRESS tuples as live,
1539 : : * if they were not deleted by the current
1540 : : * transaction. That's what
1541 : : * heapam_scan_analyze_next_tuple() does, and we want
1542 : : * the behavior to be consistent.
1543 : : */
1544 : 36 : reltuples += 1;
1545 : : }
1546 [ - + ]: 44 : else if (HeapTupleIsHotUpdated(heapTuple))
1547 : : {
1548 : : /*
1549 : : * It's a HOT-updated tuple deleted by our own xact.
1550 : : * We can assume the deletion will commit (else the
1551 : : * index contents don't matter), so treat the same as
1552 : : * RECENTLY_DEAD HOT-updated tuples.
1553 : : */
2734 andres@anarazel.de 1554 :UBC 0 : indexIt = false;
1555 : : /* mark the index as unsafe for old snapshots */
1556 : 0 : indexInfo->ii_BrokenHotChain = true;
1557 : : }
1558 : : else
1559 : : {
1560 : : /*
1561 : : * It's a regular tuple deleted by our own xact. Index
1562 : : * it, but don't check for uniqueness nor count in
1563 : : * reltuples, the same as a RECENTLY_DEAD tuple.
1564 : : */
2734 andres@anarazel.de 1565 :CBC 44 : indexIt = true;
1566 : : }
1567 : : /* In any case, exclude the tuple from unique-checking */
1568 : 80 : tupleIsAlive = false;
1569 : 80 : break;
2734 andres@anarazel.de 1570 :UBC 0 : default:
1571 [ # # ]: 0 : elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1572 : : indexIt = tupleIsAlive = false; /* keep compiler quiet */
1573 : : break;
1574 : : }
1575 : :
2734 andres@anarazel.de 1576 :CBC 8880474 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1577 : :
1578 [ + + ]: 8880474 : if (!indexIt)
1579 : 1286 : continue;
1580 : : }
1581 : : else
1582 : : {
1583 : : /* heap_getnext did the time qual check */
1584 : 2568495 : tupleIsAlive = true;
1585 : 2568495 : reltuples += 1;
1586 : : }
1587 : :
1588 : 11447683 : MemoryContextReset(econtext->ecxt_per_tuple_memory);
1589 : :
1590 : : /* Set up for predicate or expression evaluation */
1591 : 11447683 : ExecStoreBufferHeapTuple(heapTuple, slot, hscan->rs_cbuf);
1592 : :
1593 : : /*
1594 : : * In a partial index, discard tuples that don't satisfy the
1595 : : * predicate.
1596 : : */
1597 [ + + ]: 11447683 : if (predicate != NULL)
1598 : : {
1599 [ + + ]: 131224 : if (!ExecQual(predicate, econtext))
1600 : 72197 : continue;
1601 : : }
1602 : :
1603 : : /*
1604 : : * For the current heap tuple, extract all the attributes we use in
1605 : : * this index, and note which are null. This also performs evaluation
1606 : : * of any expressions needed.
1607 : : */
1608 : 11375486 : FormIndexDatum(indexInfo,
1609 : : slot,
1610 : : estate,
1611 : : values,
1612 : : isnull);
1613 : :
1614 : : /*
1615 : : * You'd think we should go ahead and build the index tuple here, but
1616 : : * some index AMs want to do further processing on the data first. So
1617 : : * pass the values[] and isnull[] arrays, instead.
1618 : : */
1619 : :
1620 [ + + ]: 11375464 : if (HeapTupleIsHeapOnly(heapTuple))
1621 : : {
1622 : : /*
1623 : : * For a heap-only tuple, pretend its TID is that of the root. See
1624 : : * src/backend/access/heap/README.HOT for discussion.
1625 : : */
1626 : : ItemPointerData tid;
1627 : : OffsetNumber offnum;
1628 : :
1629 : 3433 : offnum = ItemPointerGetOffsetNumber(&heapTuple->t_self);
1630 : :
1631 : : /*
1632 : : * If a HOT tuple points to a root that we don't know about,
1633 : : * obtain root items afresh. If that still fails, report it as
1634 : : * corruption.
1635 : : */
2229 alvherre@alvh.no-ip. 1636 [ - + ]: 3433 : if (root_offsets[offnum - 1] == InvalidOffsetNumber)
1637 : : {
1957 tgl@sss.pgh.pa.us 1638 :UBC 0 : Page page = BufferGetPage(hscan->rs_cbuf);
1639 : :
2229 alvherre@alvh.no-ip. 1640 : 0 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
1641 : 0 : heap_get_root_tuples(page, root_offsets);
1642 : 0 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1643 : : }
1644 : :
2734 andres@anarazel.de 1645 [ + - + - :CBC 3433 : if (!OffsetNumberIsValid(root_offsets[offnum - 1]))
- + ]
2734 andres@anarazel.de 1646 [ # # ]:UBC 0 : ereport(ERROR,
1647 : : (errcode(ERRCODE_DATA_CORRUPTED),
1648 : : errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
1649 : : ItemPointerGetBlockNumber(&heapTuple->t_self),
1650 : : offnum,
1651 : : RelationGetRelationName(heapRelation))));
1652 : :
2508 andres@anarazel.de 1653 :CBC 3433 : ItemPointerSet(&tid, ItemPointerGetBlockNumber(&heapTuple->t_self),
1654 : 3433 : root_offsets[offnum - 1]);
1655 : :
1656 : : /* Call the AM's callback routine to process the tuple */
1657 : 3433 : callback(indexRelation, &tid, values, isnull, tupleIsAlive,
1658 : : callback_state);
1659 : : }
1660 : : else
1661 : : {
1662 : : /* Call the AM's callback routine to process the tuple */
1663 : 11372031 : callback(indexRelation, &heapTuple->t_self, values, isnull,
1664 : : tupleIsAlive, callback_state);
1665 : : }
1666 : : }
1667 : :
1668 : : /* Report scan progress one last time. */
2728 alvherre@alvh.no-ip. 1669 [ + + ]: 34945 : if (progress)
1670 : : {
1671 : : BlockNumber blks_done;
1672 : :
1673 [ + + ]: 33220 : if (hscan->rs_base.rs_parallel != NULL)
1674 : : {
1675 : : ParallelBlockTableScanDesc pbscan;
1676 : :
1677 : 137 : pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
1678 : 137 : blks_done = pbscan->phs_nblocks;
1679 : : }
1680 : : else
1681 : 33083 : blks_done = hscan->rs_nblocks;
1682 : :
1683 : 33220 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
1684 : : blks_done);
1685 : : }
1686 : :
2734 andres@anarazel.de 1687 : 34945 : table_endscan(scan);
1688 : :
1689 : : /* we can now forget our snapshot, if set and registered by us */
1690 [ + + ]: 34945 : if (need_unregister_snapshot)
1691 : 9678 : UnregisterSnapshot(snapshot);
1692 : :
1693 : 34945 : ExecDropSingleTupleTableSlot(slot);
1694 : :
1695 : 34945 : FreeExecutorState(estate);
1696 : :
1697 : : /* These may have been pointing to the now-gone estate */
1698 : 34945 : indexInfo->ii_ExpressionsState = NIL;
1699 : 34945 : indexInfo->ii_PredicateState = NULL;
1700 : :
1701 : 34945 : return reltuples;
1702 : : }
1703 : :
1704 : : static void
1705 : 447 : heapam_index_validate_scan(Relation heapRelation,
1706 : : Relation indexRelation,
1707 : : IndexInfo *indexInfo,
1708 : : Snapshot snapshot,
1709 : : ValidateIndexState *state)
1710 : : {
1711 : : TableScanDesc scan;
1712 : : HeapScanDesc hscan;
1713 : : HeapTuple heapTuple;
1714 : : Datum values[INDEX_MAX_KEYS];
1715 : : bool isnull[INDEX_MAX_KEYS];
1716 : : ExprState *predicate;
1717 : : TupleTableSlot *slot;
1718 : : EState *estate;
1719 : : ExprContext *econtext;
1720 : 447 : BlockNumber root_blkno = InvalidBlockNumber;
1721 : : OffsetNumber root_offsets[MaxHeapTuplesPerPage];
1722 : : bool in_index[MaxHeapTuplesPerPage];
2678 tgl@sss.pgh.pa.us 1723 : 447 : BlockNumber previous_blkno = InvalidBlockNumber;
1724 : :
1725 : : /* state variables for the merge */
2734 andres@anarazel.de 1726 : 447 : ItemPointer indexcursor = NULL;
1727 : : ItemPointerData decoded;
1728 : 447 : bool tuplesort_empty = false;
1729 : :
1730 : : /*
1731 : : * sanity checks
1732 : : */
1733 [ - + ]: 447 : Assert(OidIsValid(indexRelation->rd_rel->relam));
1734 : :
1735 : : /*
1736 : : * Need an EState for evaluation of index expressions and partial-index
1737 : : * predicates. Also a slot to hold the current tuple.
1738 : : */
1739 : 447 : estate = CreateExecutorState();
1740 [ - + ]: 447 : econtext = GetPerTupleExprContext(estate);
1741 : 447 : slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
1742 : : &TTSOpsHeapTuple);
1743 : :
1744 : : /* Arrange for econtext's scan tuple to be the tuple under test */
1745 : 447 : econtext->ecxt_scantuple = slot;
1746 : :
1747 : : /* Set up execution state for predicate, if any. */
1748 : 447 : predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
1749 : :
1750 : : /*
1751 : : * Prepare for scan of the base relation. We need just those tuples
1752 : : * satisfying the passed-in reference snapshot. We must disable syncscan
1753 : : * here, because it's critical that we read from block zero forward to
1754 : : * match the sorted TIDs.
1755 : : */
1756 : 447 : scan = table_beginscan_strat(heapRelation, /* relation */
1757 : : snapshot, /* snapshot */
1758 : : 0, /* number of keys */
1759 : : NULL, /* scan key */
1760 : : true, /* buffer access strategy OK */
1761 : : false); /* syncscan not OK */
1762 : 447 : hscan = (HeapScanDesc) scan;
1763 : :
2728 alvherre@alvh.no-ip. 1764 : 447 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
1765 : 447 : hscan->rs_nblocks);
1766 : :
1767 : : /*
1768 : : * Scan all tuples matching the snapshot.
1769 : : */
2734 andres@anarazel.de 1770 [ + + ]: 131620 : while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1771 : : {
1772 : 131173 : ItemPointer heapcursor = &heapTuple->t_self;
1773 : : ItemPointerData rootTuple;
1774 : : OffsetNumber root_offnum;
1775 : :
1776 [ - + ]: 131173 : CHECK_FOR_INTERRUPTS();
1777 : :
1778 : 131173 : state->htups += 1;
1779 : :
2728 alvherre@alvh.no-ip. 1780 [ + + ]: 131173 : if ((previous_blkno == InvalidBlockNumber) ||
1781 [ + + ]: 130915 : (hscan->rs_cblock != previous_blkno))
1782 : : {
1783 : 2431 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
1784 : 2431 : hscan->rs_cblock);
1785 : 2431 : previous_blkno = hscan->rs_cblock;
1786 : : }
1787 : :
1788 : : /*
1789 : : * As commented in table_index_build_scan, we should index heap-only
1790 : : * tuples under the TIDs of their root tuples; so when we advance onto
1791 : : * a new heap page, build a map of root item offsets on the page.
1792 : : *
1793 : : * This complicates merging against the tuplesort output: we will
1794 : : * visit the live tuples in order by their offsets, but the root
1795 : : * offsets that we need to compare against the index contents might be
1796 : : * ordered differently. So we might have to "look back" within the
1797 : : * tuplesort output, but only within the current page. We handle that
1798 : : * by keeping a bool array in_index[] showing all the
1799 : : * already-passed-over tuplesort output TIDs of the current page. We
1800 : : * clear that array here, when advancing onto a new heap page.
1801 : : */
2734 andres@anarazel.de 1802 [ + + ]: 131173 : if (hscan->rs_cblock != root_blkno)
1803 : : {
1804 : 2431 : Page page = BufferGetPage(hscan->rs_cbuf);
1805 : :
1806 : 2431 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
1807 : 2431 : heap_get_root_tuples(page, root_offsets);
1808 : 2431 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
1809 : :
1810 : 2431 : memset(in_index, 0, sizeof(in_index));
1811 : :
1812 : 2431 : root_blkno = hscan->rs_cblock;
1813 : : }
1814 : :
1815 : : /* Convert actual tuple TID to root TID */
1816 : 131173 : rootTuple = *heapcursor;
1817 : 131173 : root_offnum = ItemPointerGetOffsetNumber(heapcursor);
1818 : :
1819 [ + + ]: 131173 : if (HeapTupleIsHeapOnly(heapTuple))
1820 : : {
1821 : 15 : root_offnum = root_offsets[root_offnum - 1];
1822 [ + - + - : 15 : if (!OffsetNumberIsValid(root_offnum))
- + ]
2734 andres@anarazel.de 1823 [ # # ]:UBC 0 : ereport(ERROR,
1824 : : (errcode(ERRCODE_DATA_CORRUPTED),
1825 : : errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
1826 : : ItemPointerGetBlockNumber(heapcursor),
1827 : : ItemPointerGetOffsetNumber(heapcursor),
1828 : : RelationGetRelationName(heapRelation))));
2734 andres@anarazel.de 1829 :CBC 15 : ItemPointerSetOffsetNumber(&rootTuple, root_offnum);
1830 : : }
1831 : :
1832 : : /*
1833 : : * "merge" by skipping through the index tuples until we find or pass
1834 : : * the current root tuple.
1835 : : */
1836 [ + + + + ]: 306015 : while (!tuplesort_empty &&
1837 [ + + ]: 305699 : (!indexcursor ||
1838 : 305699 : ItemPointerCompare(indexcursor, &rootTuple) < 0))
1839 : : {
1840 : : Datum ts_val;
1841 : : bool ts_isnull;
1842 : :
1843 [ + + ]: 174842 : if (indexcursor)
1844 : : {
1845 : : /*
1846 : : * Remember index items seen earlier on the current heap page
1847 : : */
1848 [ + + ]: 174584 : if (ItemPointerGetBlockNumber(indexcursor) == root_blkno)
1849 : 171552 : in_index[ItemPointerGetOffsetNumber(indexcursor) - 1] = true;
1850 : : }
1851 : :
1852 : 174842 : tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true,
1853 : : false, &ts_val, &ts_isnull,
1423 drowley@postgresql.o 1854 : 174842 : NULL);
2734 andres@anarazel.de 1855 [ + + - + ]: 174842 : Assert(tuplesort_empty || !ts_isnull);
1856 [ + + ]: 174842 : if (!tuplesort_empty)
1857 : : {
1858 : 174820 : itemptr_decode(&decoded, DatumGetInt64(ts_val));
1859 : 174820 : indexcursor = &decoded;
1860 : : }
1861 : : else
1862 : : {
1863 : : /* Be tidy */
1864 : 22 : indexcursor = NULL;
1865 : : }
1866 : : }
1867 : :
1868 : : /*
1869 : : * If the tuplesort has overshot *and* we didn't see a match earlier,
1870 : : * then this tuple is missing from the index, so insert it.
1871 : : */
1872 [ + + + + ]: 262288 : if ((tuplesort_empty ||
1873 : 131115 : ItemPointerCompare(indexcursor, &rootTuple) > 0) &&
1874 [ + + ]: 113 : !in_index[root_offnum - 1])
1875 : : {
1876 : 102 : MemoryContextReset(econtext->ecxt_per_tuple_memory);
1877 : :
1878 : : /* Set up for predicate or expression evaluation */
1879 : 102 : ExecStoreHeapTuple(heapTuple, slot, false);
1880 : :
1881 : : /*
1882 : : * In a partial index, discard tuples that don't satisfy the
1883 : : * predicate.
1884 : : */
1885 [ + + ]: 102 : if (predicate != NULL)
1886 : : {
1887 [ + - ]: 32 : if (!ExecQual(predicate, econtext))
1888 : 32 : continue;
1889 : : }
1890 : :
1891 : : /*
1892 : : * For the current heap tuple, extract all the attributes we use
1893 : : * in this index, and note which are null. This also performs
1894 : : * evaluation of any expressions needed.
1895 : : */
1896 : 70 : FormIndexDatum(indexInfo,
1897 : : slot,
1898 : : estate,
1899 : : values,
1900 : : isnull);
1901 : :
1902 : : /*
1903 : : * You'd think we should go ahead and build the index tuple here,
1904 : : * but some index AMs want to do further processing on the data
1905 : : * first. So pass the values[] and isnull[] arrays, instead.
1906 : : */
1907 : :
1908 : : /*
1909 : : * If the tuple is already committed dead, you might think we
1910 : : * could suppress uniqueness checking, but this is no longer true
1911 : : * in the presence of HOT, because the insert is actually a proxy
1912 : : * for a uniqueness check on the whole HOT-chain. That is, the
1913 : : * tuple we have here could be dead because it was already
1914 : : * HOT-updated, and if so the updating transaction will not have
1915 : : * thought it should insert index entries. The index AM will
1916 : : * check the whole HOT-chain and correctly detect a conflict if
1917 : : * there is one.
1918 : : */
1919 : :
1920 : 70 : index_insert(indexRelation,
1921 : : values,
1922 : : isnull,
1923 : : &rootTuple,
1924 : : heapRelation,
1925 : 70 : indexInfo->ii_Unique ?
1926 : : UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
1927 : : false,
1928 : : indexInfo);
1929 : :
1930 : 70 : state->tups_inserted += 1;
1931 : : }
1932 : : }
1933 : :
1934 : 447 : table_endscan(scan);
1935 : :
1936 : 447 : ExecDropSingleTupleTableSlot(slot);
1937 : :
1938 : 447 : FreeExecutorState(estate);
1939 : :
1940 : : /* These may have been pointing to the now-gone estate */
1941 : 447 : indexInfo->ii_ExpressionsState = NIL;
1942 : 447 : indexInfo->ii_PredicateState = NULL;
1943 : 447 : }
1944 : :
1945 : : /*
1946 : : * Return the number of blocks that have been read by this scan since
1947 : : * starting. This is meant for progress reporting rather than be fully
1948 : : * accurate: in a parallel scan, workers can be concurrently reading blocks
1949 : : * further ahead than what we report.
1950 : : */
1951 : : static BlockNumber
2728 alvherre@alvh.no-ip. 1952 : 9368374 : heapam_scan_get_blocks_done(HeapScanDesc hscan)
1953 : : {
1954 : 9368374 : ParallelBlockTableScanDesc bpscan = NULL;
1955 : : BlockNumber startblock;
1956 : : BlockNumber blocks_done;
1957 : :
1958 [ + + ]: 9368374 : if (hscan->rs_base.rs_parallel != NULL)
1959 : : {
1960 : 1169638 : bpscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
16 nathan@postgresql.or 1961 :GNC 1169638 : startblock = pg_atomic_read_u32(&bpscan->phs_startblock);
1962 : : }
1963 : : else
2728 alvherre@alvh.no-ip. 1964 :CBC 8198736 : startblock = hscan->rs_startblock;
1965 : :
1966 : : /*
1967 : : * Might have wrapped around the end of the relation, if startblock was
1968 : : * not zero.
1969 : : */
1970 [ + + ]: 9368374 : if (hscan->rs_cblock > startblock)
1971 : 9020280 : blocks_done = hscan->rs_cblock - startblock;
1972 : : else
1973 : : {
1974 : : BlockNumber nblocks;
1975 : :
1976 [ + + ]: 348094 : nblocks = bpscan != NULL ? bpscan->phs_nblocks : hscan->rs_nblocks;
1977 : 348094 : blocks_done = nblocks - startblock +
1978 : 348094 : hscan->rs_cblock;
1979 : : }
1980 : :
1981 : 9368374 : return blocks_done;
1982 : : }
1983 : :
1984 : :
1985 : : /* ------------------------------------------------------------------------
1986 : : * Miscellaneous callbacks for the heap AM
1987 : : * ------------------------------------------------------------------------
1988 : : */
1989 : :
1990 : : /*
1991 : : * Check to see whether the table needs a TOAST table. It does only if
1992 : : * (1) there are any toastable attributes, and (2) the maximum length
1993 : : * of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to
1994 : : * create a toast table for something like "f1 varchar(20)".)
1995 : : */
1996 : : static bool
2679 rhaas@postgresql.org 1997 : 30106 : heapam_relation_needs_toast_table(Relation rel)
1998 : : {
1999 : 30106 : int32 data_length = 0;
2000 : 30106 : bool maxlength_unknown = false;
2001 : 30106 : bool has_toastable_attrs = false;
2002 : 30106 : TupleDesc tupdesc = rel->rd_att;
2003 : : int32 tuple_length;
2004 : : int i;
2005 : :
2006 [ + + ]: 116933 : for (i = 0; i < tupdesc->natts; i++)
2007 : : {
2008 : 86827 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
2009 : :
2010 [ + + ]: 86827 : if (att->attisdropped)
2011 : 729 : continue;
590 peter@eisentraut.org 2012 [ + + ]: 86098 : if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
2013 : 665 : continue;
2679 rhaas@postgresql.org 2014 : 85433 : data_length = att_align_nominal(data_length, att->attalign);
2015 [ + + ]: 85433 : if (att->attlen > 0)
2016 : : {
2017 : : /* Fixed-length types are never toastable */
2018 : 64667 : data_length += att->attlen;
2019 : : }
2020 : : else
2021 : : {
2022 : 20766 : int32 maxlen = type_maximum_size(att->atttypid,
2023 : : att->atttypmod);
2024 : :
2025 [ + + ]: 20766 : if (maxlen < 0)
2026 : 19314 : maxlength_unknown = true;
2027 : : else
2028 : 1452 : data_length += maxlen;
2391 tgl@sss.pgh.pa.us 2029 [ + + ]: 20766 : if (att->attstorage != TYPSTORAGE_PLAIN)
2679 rhaas@postgresql.org 2030 : 20059 : has_toastable_attrs = true;
2031 : : }
2032 : : }
2033 [ + + ]: 30106 : if (!has_toastable_attrs)
2034 : 18285 : return false; /* nothing to toast? */
2035 [ + + ]: 11821 : if (maxlength_unknown)
2036 : 10750 : return true; /* any unlimited-length attrs? */
2037 : 1071 : tuple_length = MAXALIGN(SizeofHeapTupleHeader +
2038 : 1071 : BITMAPLEN(tupdesc->natts)) +
2039 : 1071 : MAXALIGN(data_length);
2040 : 1071 : return (tuple_length > TOAST_TUPLE_THRESHOLD);
2041 : : }
2042 : :
2043 : : /*
2044 : : * TOAST tables for heap relations are just heap relations.
2045 : : */
2046 : : static Oid
2448 2047 : 11040 : heapam_relation_toast_am(Relation rel)
2048 : : {
2049 : 11040 : return rel->rd_rel->relam;
2050 : : }
2051 : :
2052 : :
2053 : : /* ------------------------------------------------------------------------
2054 : : * Planner related callbacks for the heap AM
2055 : : * ------------------------------------------------------------------------
2056 : : */
2057 : :
2058 : : #define HEAP_OVERHEAD_BYTES_PER_TUPLE \
2059 : : (MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData))
2060 : : #define HEAP_USABLE_BYTES_PER_PAGE \
2061 : : (BLCKSZ - SizeOfPageHeaderData)
2062 : :
2063 : : static void
2731 andres@anarazel.de 2064 : 352957 : heapam_estimate_rel_size(Relation rel, int32 *attr_widths,
2065 : : BlockNumber *pages, double *tuples,
2066 : : double *allvisfrac)
2067 : : {
2631 rhaas@postgresql.org 2068 : 352957 : table_block_relation_estimate_size(rel, attr_widths, pages,
2069 : : tuples, allvisfrac,
2070 : : HEAP_OVERHEAD_BYTES_PER_TUPLE,
2071 : : HEAP_USABLE_BYTES_PER_PAGE);
2731 andres@anarazel.de 2072 : 352957 : }
2073 : :
2074 : :
2075 : : /* ------------------------------------------------------------------------
2076 : : * Executor related callbacks for the heap AM
2077 : : * ------------------------------------------------------------------------
2078 : : */
2079 : :
2080 : : static bool
554 melanieplageman@gmai 2081 : 4102253 : heapam_scan_bitmap_next_tuple(TableScanDesc scan,
2082 : : TupleTableSlot *slot,
2083 : : bool *recheck,
2084 : : uint64 *lossy_pages,
2085 : : uint64 *exact_pages)
2086 : : {
612 2087 : 4102253 : BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
2088 : 4102253 : HeapScanDesc hscan = (HeapScanDesc) bscan;
2089 : : OffsetNumber targoffset;
2090 : : Page page;
2091 : : ItemId lp;
2092 : :
2093 : : /*
2094 : : * Out of range? If so, nothing more to look at on this page
2095 : : */
554 2096 [ + + ]: 4345973 : while (hscan->rs_cindex >= hscan->rs_ntuples)
2097 : : {
2098 : : /*
2099 : : * Returns false if the bitmap is exhausted and there are no further
2100 : : * blocks we need to scan.
2101 : : */
2102 [ + + ]: 257935 : if (!BitmapHeapScanNextBlock(scan, recheck, lossy_pages, exact_pages))
2103 : 14212 : return false;
2104 : : }
2105 : :
2730 andres@anarazel.de 2106 : 4088038 : targoffset = hscan->rs_vistuples[hscan->rs_cindex];
1404 peter@eisentraut.org 2107 : 4088038 : page = BufferGetPage(hscan->rs_cbuf);
2108 : 4088038 : lp = PageGetItemId(page, targoffset);
2730 andres@anarazel.de 2109 [ - + ]: 4088038 : Assert(ItemIdIsNormal(lp));
2110 : :
1404 peter@eisentraut.org 2111 : 4088038 : hscan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
2730 andres@anarazel.de 2112 : 4088038 : hscan->rs_ctup.t_len = ItemIdGetLength(lp);
2113 : 4088038 : hscan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
2114 : 4088038 : ItemPointerSet(&hscan->rs_ctup.t_self, hscan->rs_cblock, targoffset);
2115 : :
2116 [ - + - - : 4088038 : pgstat_count_heap_fetch(scan->rs_rd);
+ - - + ]
2117 : :
2118 : : /*
2119 : : * Set up the result slot to point to this tuple. Note that the slot
2120 : : * acquires a pin on the buffer.
2121 : : */
2122 : 4088038 : ExecStoreBufferHeapTuple(&hscan->rs_ctup,
2123 : : slot,
2124 : : hscan->rs_cbuf);
2125 : :
2126 : 4088038 : hscan->rs_cindex++;
2127 : :
2128 : 4088038 : return true;
2129 : : }
2130 : :
2131 : : static bool
2731 2132 : 8588 : heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
2133 : : {
2134 : 8588 : HeapScanDesc hscan = (HeapScanDesc) scan;
2135 : 8588 : TsmRoutine *tsm = scanstate->tsmroutine;
2136 : : BlockNumber blockno;
2137 : :
2138 : : /* return false immediately if relation is empty */
2139 [ - + ]: 8588 : if (hscan->rs_nblocks == 0)
2731 andres@anarazel.de 2140 :UBC 0 : return false;
2141 : :
2142 : : /* release previous scan buffer, if any */
899 drowley@postgresql.o 2143 [ + + ]:CBC 8588 : if (BufferIsValid(hscan->rs_cbuf))
2144 : : {
2145 : 8474 : ReleaseBuffer(hscan->rs_cbuf);
2146 : 8474 : hscan->rs_cbuf = InvalidBuffer;
2147 : : }
2148 : :
2149 [ + + ]: 8588 : if (tsm->NextSampleBlock)
2150 : 2944 : blockno = tsm->NextSampleBlock(scanstate, hscan->rs_nblocks);
2151 : : else
2152 : : {
2153 : : /* scanning table sequentially */
2154 : :
2731 andres@anarazel.de 2155 [ + + ]: 5644 : if (hscan->rs_cblock == InvalidBlockNumber)
2156 : : {
2157 [ - + ]: 52 : Assert(!hscan->rs_inited);
2158 : 52 : blockno = hscan->rs_startblock;
2159 : : }
2160 : : else
2161 : : {
2162 [ - + ]: 5592 : Assert(hscan->rs_inited);
2163 : :
2164 : 5592 : blockno = hscan->rs_cblock + 1;
2165 : :
2166 [ + + ]: 5592 : if (blockno >= hscan->rs_nblocks)
2167 : : {
2168 : : /* wrap to beginning of rel, might not have started at 0 */
2169 : 52 : blockno = 0;
2170 : : }
2171 : :
2172 : : /*
2173 : : * Report our new scan position for synchronization purposes.
2174 : : *
2175 : : * Note: we do this before checking for end of scan so that the
2176 : : * final state of the position hint is back at the start of the
2177 : : * rel. That's not strictly necessary, but otherwise when you run
2178 : : * the same query multiple times the starting position would shift
2179 : : * a little bit backwards on every invocation, which is confusing.
2180 : : * We don't guarantee any specific ordering in general, though.
2181 : : */
2681 2182 [ - + ]: 5592 : if (scan->rs_flags & SO_ALLOW_SYNC)
2731 andres@anarazel.de 2183 :UBC 0 : ss_report_location(scan->rs_rd, blockno);
2184 : :
2731 andres@anarazel.de 2185 [ + + ]:CBC 5592 : if (blockno == hscan->rs_startblock)
2186 : : {
2187 : 52 : blockno = InvalidBlockNumber;
2188 : : }
2189 : : }
2190 : : }
2191 : :
899 drowley@postgresql.o 2192 : 8588 : hscan->rs_cblock = blockno;
2193 : :
2731 andres@anarazel.de 2194 [ + + ]: 8588 : if (!BlockNumberIsValid(blockno))
2195 : : {
2196 : 110 : hscan->rs_inited = false;
2197 : 110 : return false;
2198 : : }
2199 : :
899 drowley@postgresql.o 2200 [ - + ]: 8478 : Assert(hscan->rs_cblock < hscan->rs_nblocks);
2201 : :
2202 : : /*
2203 : : * Be sure to check for interrupts at least once per page. Checks at
2204 : : * higher code levels won't be able to stop a sample scan that encounters
2205 : : * many pages' worth of consecutive dead tuples.
2206 : : */
2207 [ - + ]: 8478 : CHECK_FOR_INTERRUPTS();
2208 : :
2209 : : /* Read page using selected strategy */
2210 : 8478 : hscan->rs_cbuf = ReadBufferExtended(hscan->rs_base.rs_rd, MAIN_FORKNUM,
2211 : : blockno, RBM_NORMAL, hscan->rs_strategy);
2212 : :
2213 : : /* in pagemode, prune the page and determine visible tuple offsets */
2214 [ + + ]: 8478 : if (hscan->rs_base.rs_flags & SO_ALLOW_PAGEMODE)
2215 : 5686 : heap_prepare_pagescan(scan);
2216 : :
2217 : 8478 : hscan->rs_inited = true;
2731 andres@anarazel.de 2218 : 8478 : return true;
2219 : : }
2220 : :
2221 : : static bool
2222 : 169182 : heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
2223 : : TupleTableSlot *slot)
2224 : : {
2225 : 169182 : HeapScanDesc hscan = (HeapScanDesc) scan;
2226 : 169182 : TsmRoutine *tsm = scanstate->tsmroutine;
2227 : 169182 : BlockNumber blockno = hscan->rs_cblock;
2681 2228 : 169182 : bool pagemode = (scan->rs_flags & SO_ALLOW_PAGEMODE) != 0;
2229 : :
2230 : : Page page;
2231 : : bool all_visible;
2232 : : OffsetNumber maxoffset;
2233 : :
2234 : : /*
2235 : : * When not using pagemode, we must lock the buffer during tuple
2236 : : * visibility checks.
2237 : : */
2731 2238 [ + + ]: 169182 : if (!pagemode)
2239 : 2796 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
2240 : :
387 peter@eisentraut.org 2241 : 169182 : page = BufferGetPage(hscan->rs_cbuf);
2731 andres@anarazel.de 2242 [ + + ]: 337841 : all_visible = PageIsAllVisible(page) &&
2243 [ + - ]: 168659 : !scan->rs_snapshot->takenDuringRecovery;
2244 : 169182 : maxoffset = PageGetMaxOffsetNumber(page);
2245 : :
2246 : : for (;;)
2731 andres@anarazel.de 2247 :UBC 0 : {
2248 : : OffsetNumber tupoffset;
2249 : :
2731 andres@anarazel.de 2250 [ - + ]:CBC 169182 : CHECK_FOR_INTERRUPTS();
2251 : :
2252 : : /* Ask the tablesample method which tuples to check on this page. */
2253 : 169182 : tupoffset = tsm->NextSampleTuple(scanstate,
2254 : : blockno,
2255 : : maxoffset);
2256 : :
2257 [ + + + - : 169182 : if (OffsetNumberIsValid(tupoffset))
+ + ]
2258 : : {
2259 : : ItemId itemid;
2260 : : bool visible;
2261 : 160708 : HeapTuple tuple = &(hscan->rs_ctup);
2262 : :
2263 : : /* Skip invalid tuple pointers. */
2264 : 160708 : itemid = PageGetItemId(page, tupoffset);
2265 [ - + ]: 160708 : if (!ItemIdIsNormal(itemid))
2731 andres@anarazel.de 2266 :UBC 0 : continue;
2267 : :
2731 andres@anarazel.de 2268 :CBC 160708 : tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
2269 : 160708 : tuple->t_len = ItemIdGetLength(itemid);
2270 : 160708 : ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
2271 : :
2272 : :
2273 [ + + ]: 160708 : if (all_visible)
2274 : 160336 : visible = true;
2275 : : else
2276 : 372 : visible = SampleHeapTupleVisible(scan, hscan->rs_cbuf,
2277 : : tuple, tupoffset);
2278 : :
2279 : : /* in pagemode, heap_prepare_pagescan did this for us */
2280 [ + + ]: 160708 : if (!pagemode)
2427 tmunro@postgresql.or 2281 : 4 : HeapCheckForSerializableConflictOut(visible, scan->rs_rd, tuple,
2282 : : hscan->rs_cbuf, scan->rs_snapshot);
2283 : :
2284 : : /* Try next tuple from same page. */
2731 andres@anarazel.de 2285 [ - + ]: 160708 : if (!visible)
2731 andres@anarazel.de 2286 :UBC 0 : continue;
2287 : :
2288 : : /* Found visible tuple, return it. */
2731 andres@anarazel.de 2289 [ + + ]:CBC 160708 : if (!pagemode)
2290 : 4 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
2291 : :
2292 : 160708 : ExecStoreBufferHeapTuple(tuple, slot, hscan->rs_cbuf);
2293 : :
2294 : : /* Count successfully-fetched tuples as heap fetches */
2295 [ - + - - : 160708 : pgstat_count_heap_getnext(scan->rs_rd);
+ - - + ]
2296 : :
2297 : 160708 : return true;
2298 : : }
2299 : : else
2300 : : {
2301 : : /*
2302 : : * If we get here, it means we've exhausted the items on this page
2303 : : * and it's time to move to the next.
2304 : : */
2305 [ + + ]: 8474 : if (!pagemode)
2306 : 2792 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
2307 : :
2308 : 8474 : ExecClearTuple(slot);
2309 : 8474 : return false;
2310 : : }
2311 : : }
2312 : :
2313 : : Assert(0);
2314 : : }
2315 : :
2316 : :
2317 : : /* ----------------------------------------------------------------------------
2318 : : * Helper functions for the above.
2319 : : * ----------------------------------------------------------------------------
2320 : : */
2321 : :
2322 : : /*
2323 : : * Reconstruct and rewrite the given tuple
2324 : : *
2325 : : * We cannot simply copy the tuple as-is, for several reasons:
2326 : : *
2327 : : * 1. We'd like to squeeze out the values of any dropped columns, both
2328 : : * to save space and to ensure we have no corner-case failures. (It's
2329 : : * possible for example that the new table hasn't got a TOAST table
2330 : : * and so is unable to store any large values of dropped cols.)
2331 : : *
2332 : : * 2. The tuple might not even be legal for the new table; this is
2333 : : * currently only known to happen as an after-effect of ALTER TABLE
2334 : : * SET WITHOUT OIDS.
2335 : : *
2336 : : * So, we must reconstruct the tuple from component Datums.
2337 : : */
2338 : : static void
2339 : 463598 : reform_and_rewrite_tuple(HeapTuple tuple,
2340 : : Relation OldHeap, Relation NewHeap,
2341 : : Datum *values, bool *isnull, RewriteState rwstate)
2342 : : {
2343 : : HeapTuple newtuple;
2344 : :
167 alvherre@kurilemu.de 2345 : 463598 : newtuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
2346 : :
2347 : : /* The heap rewrite module does the rest */
2348 : 463598 : rewrite_heap_tuple(rwstate, tuple, newtuple);
2349 : :
2350 : 463598 : heap_freetuple(newtuple);
2351 : 463598 : }
2352 : :
2353 : : /*
2354 : : * Insert tuple when processing REPACK CONCURRENTLY.
2355 : : *
2356 : : * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
2357 : : * difficult to do the same in the catch-up phase (as the logical
2358 : : * decoding does not provide us with sufficient visibility
2359 : : * information). Thus we must use heap_insert() both during the
2360 : : * catch-up and here.
2361 : : *
2362 : : * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
2363 : : * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
2364 : : * this relation, so no logical replication subscription should need the data.
2365 : : *
2366 : : * BulkInsertState is used because many tuples are inserted in the typical
2367 : : * case.
2368 : : */
2369 : : static void
2370 : 33 : heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
2371 : : Datum *values, bool *isnull, BulkInsertState bistate)
2372 : : {
2373 : : HeapTuple newtuple;
2374 : :
2375 : 33 : newtuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
2376 : :
2377 : 33 : heap_insert(NewHeap, newtuple, GetCurrentCommandId(true),
2378 : : HEAP_INSERT_NO_LOGICAL, bistate);
2379 : :
2380 : 33 : heap_freetuple(newtuple);
2381 : 33 : }
2382 : :
2383 : : /*
2384 : : * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
2385 : : *
2386 : : * Deform the given tuple, set values of dropped columns to NULL, and fill in
2387 : : * any values from attmissingval; then form a new tuple and return it. If no
2388 : : * attributes need to be changed, a copy of the original tuple is returned.
2389 : : * Caller is responsible for freeing the returned tuple.
2390 : : *
2391 : : * XXX this coding assumes that both relations have the same tupledesc.
2392 : : */
2393 : : static HeapTuple
2394 : 463631 : reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
2395 : : Datum *values, bool *isnull)
2396 : : {
2731 andres@anarazel.de 2397 : 463631 : TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
2398 : 463631 : TupleDesc newTupDesc = RelationGetDescr(NewHeap);
167 alvherre@kurilemu.de 2399 : 463631 : bool needs_reform = false;
2400 : :
2401 : : /*
2402 : : * A short tuple might require values from attmissing val, so activate the
2403 : : * coding unconditionally in that case. The value might legitimally be
2404 : : * NULL otherwise, so this is slightly wasteful, but it probably beats
2405 : : * having to test each attribute for presence of attmissingval each time.
2406 : : */
138 2407 [ + + ]: 463631 : if (HeapTupleHeaderGetNatts(tuple->t_data) < newTupDesc->natts)
2408 : 41 : needs_reform = true;
2409 : :
2410 : : /*
2411 : : * If the column has been dropped but a value is still present, we can
2412 : : * optimize storage now by getting rid of it.
2413 : : */
2414 [ + + ]: 463631 : if (!needs_reform)
2415 : : {
2416 [ + + ]: 3903170 : for (int i = 0; i < newTupDesc->natts; i++)
2417 : : {
2418 [ + + ]: 3439590 : if (TupleDescCompactAttr(newTupDesc, i)->attisdropped &&
2419 [ + + ]: 30 : !heap_attisnull(tuple, i + 1, newTupDesc))
2420 : : {
2421 : 10 : needs_reform = true;
2422 : 10 : break;
2423 : : }
2424 : : }
2425 : : }
2426 : :
2427 : : /* Skip work if no changes are needed */
167 2428 [ + + ]: 463631 : if (!needs_reform)
2429 : 463580 : return heap_copytuple(tuple);
2430 : :
2731 andres@anarazel.de 2431 : 51 : heap_deform_tuple(tuple, oldTupDesc, values, isnull);
2432 : :
167 alvherre@kurilemu.de 2433 [ + + ]: 255 : for (int i = 0; i < newTupDesc->natts; i++)
2434 : : {
639 drowley@postgresql.o 2435 [ + + ]: 204 : if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
2731 andres@anarazel.de 2436 : 20 : isnull[i] = true;
2437 : : }
2438 : :
167 alvherre@kurilemu.de 2439 : 51 : return heap_form_tuple(newTupDesc, values, isnull);
2440 : : }
2441 : :
2442 : : /*
2443 : : * Check visibility of the tuple.
2444 : : */
2445 : : static bool
2731 andres@anarazel.de 2446 : 372 : SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
2447 : : HeapTuple tuple,
2448 : : OffsetNumber tupoffset)
2449 : : {
2450 : 372 : HeapScanDesc hscan = (HeapScanDesc) scan;
2451 : :
2681 2452 [ + + ]: 372 : if (scan->rs_flags & SO_ALLOW_PAGEMODE)
2453 : : {
639 melanieplageman@gmai 2454 : 368 : uint32 start = 0,
2455 : 368 : end = hscan->rs_ntuples;
2456 : :
2457 : : /*
2458 : : * In pageatatime mode, heap_prepare_pagescan() already did visibility
2459 : : * checks, so just look at the info it left in rs_vistuples[].
2460 : : *
2461 : : * We use a binary search over the known-sorted array. Note: we could
2462 : : * save some effort if we insisted that NextSampleTuple select tuples
2463 : : * in increasing order, but it's not clear that there would be enough
2464 : : * gain to justify the restriction.
2465 : : */
2466 [ + - ]: 689 : while (start < end)
2467 : : {
2468 : 689 : uint32 mid = start + (end - start) / 2;
2731 andres@anarazel.de 2469 : 689 : OffsetNumber curoffset = hscan->rs_vistuples[mid];
2470 : :
2471 [ + + ]: 689 : if (tupoffset == curoffset)
2472 : 368 : return true;
2473 [ + + ]: 321 : else if (tupoffset < curoffset)
639 melanieplageman@gmai 2474 : 165 : end = mid;
2475 : : else
2731 andres@anarazel.de 2476 : 156 : start = mid + 1;
2477 : : }
2478 : :
2731 andres@anarazel.de 2479 :UBC 0 : return false;
2480 : : }
2481 : : else
2482 : : {
2483 : : /* Otherwise, we have to check the tuple individually. */
2731 andres@anarazel.de 2484 :CBC 4 : return HeapTupleSatisfiesVisibility(tuple, scan->rs_snapshot,
2485 : : buffer);
2486 : : }
2487 : : }
2488 : :
2489 : : /*
2490 : : * Helper function get the next block of a bitmap heap scan. Returns true when
2491 : : * it got the next block and saved it in the scan descriptor and false when
2492 : : * the bitmap and or relation are exhausted.
2493 : : */
2494 : : static bool
554 melanieplageman@gmai 2495 : 257935 : BitmapHeapScanNextBlock(TableScanDesc scan,
2496 : : bool *recheck,
2497 : : uint64 *lossy_pages, uint64 *exact_pages)
2498 : : {
2499 : 257935 : BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
2500 : 257935 : HeapScanDesc hscan = (HeapScanDesc) bscan;
2501 : : BlockNumber block;
2502 : : void *per_buffer_data;
2503 : : Buffer buffer;
2504 : : Snapshot snapshot;
2505 : : int ntup;
2506 : : TBMIterateResult *tbmres;
2507 : : OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE];
2508 : 257935 : int noffsets = -1;
2509 : :
2510 [ - + ]: 257935 : Assert(scan->rs_flags & SO_TYPE_BITMAPSCAN);
2511 [ - + ]: 257935 : Assert(hscan->rs_read_stream);
2512 : :
2513 : 257935 : hscan->rs_cindex = 0;
2514 : 257935 : hscan->rs_ntuples = 0;
2515 : :
2516 : : /* Release buffer containing previous block. */
2517 [ + + ]: 257935 : if (BufferIsValid(hscan->rs_cbuf))
2518 : : {
2519 : 243461 : ReleaseBuffer(hscan->rs_cbuf);
2520 : 243461 : hscan->rs_cbuf = InvalidBuffer;
2521 : : }
2522 : :
2523 : 257935 : hscan->rs_cbuf = read_stream_next_buffer(hscan->rs_read_stream,
2524 : : &per_buffer_data);
2525 : :
2526 [ + + ]: 257935 : if (BufferIsInvalid(hscan->rs_cbuf))
2527 : : {
2528 : : /* the bitmap is exhausted */
536 andres@anarazel.de 2529 : 14212 : return false;
2530 : : }
2531 : :
554 melanieplageman@gmai 2532 [ - + ]: 243723 : Assert(per_buffer_data);
2533 : :
2534 : 243723 : tbmres = per_buffer_data;
2535 : :
2536 [ - + ]: 243723 : Assert(BlockNumberIsValid(tbmres->blockno));
2537 [ - + ]: 243723 : Assert(BufferGetBlockNumber(hscan->rs_cbuf) == tbmres->blockno);
2538 : :
2539 : : /* Exact pages need their tuple offsets extracted. */
2540 [ + + ]: 243723 : if (!tbmres->lossy)
2541 : 136793 : noffsets = tbm_extract_page_tuple(tbmres, offsets,
2542 : : TBM_MAX_TUPLES_PER_PAGE);
2543 : :
2544 : 243723 : *recheck = tbmres->recheck;
2545 : :
2546 : 243723 : block = hscan->rs_cblock = tbmres->blockno;
2547 : 243723 : buffer = hscan->rs_cbuf;
2548 : 243723 : snapshot = scan->rs_snapshot;
2549 : :
2550 : 243723 : ntup = 0;
2551 : :
2552 : : /*
2553 : : * Prune and repair fragmentation for the whole page, if possible.
2554 : : */
174 2555 : 243723 : heap_page_prune_opt(scan->rs_rd, buffer, &hscan->rs_vmbuffer,
2556 : 243723 : scan->rs_flags & SO_HINT_REL_READ_ONLY);
2557 : :
2558 : : /*
2559 : : * We must hold share lock on the buffer content while examining tuple
2560 : : * visibility. Afterwards, however, the tuples we have found to be
2561 : : * visible are guaranteed good as long as we hold the buffer pin.
2562 : : */
554 2563 : 243723 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
2564 : :
2565 : : /*
2566 : : * We need two separate strategies for lossy and non-lossy cases.
2567 : : */
2568 [ + + ]: 243723 : if (!tbmres->lossy)
2569 : : {
2570 : : /*
2571 : : * Bitmap is non-lossy, so we just look through the offsets listed in
2572 : : * tbmres; but we have to follow any HOT chain starting at each such
2573 : : * offset.
2574 : : */
2575 : : int curslot;
2576 : :
2577 : : /* We must have extracted the tuple offsets by now */
2578 [ - + ]: 136793 : Assert(noffsets > -1);
2579 : :
2580 [ + + ]: 3560232 : for (curslot = 0; curslot < noffsets; curslot++)
2581 : : {
2582 : 3423442 : OffsetNumber offnum = offsets[curslot];
2583 : : ItemPointerData tid;
2584 : : HeapTupleData heapTuple;
2585 : :
2586 : 3423442 : ItemPointerSet(&tid, block, offnum);
2587 [ + + ]: 3423442 : if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
2588 : : &heapTuple, NULL, true))
2589 : 3273642 : hscan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
2590 : : }
2591 : : }
2592 : : else
2593 : : {
2594 : : /*
2595 : : * Bitmap is lossy, so we must examine each line pointer on the page.
2596 : : * But we can ignore HOT chains, since we'll check each tuple anyway.
2597 : : */
2598 : 106930 : Page page = BufferGetPage(buffer);
2599 : 106930 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
2600 : : OffsetNumber offnum;
2601 : :
2602 [ + + ]: 923170 : for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
2603 : : {
2604 : : ItemId lp;
2605 : : HeapTupleData loctup;
2606 : : bool valid;
2607 : :
2608 : 816240 : lp = PageGetItemId(page, offnum);
2609 [ - + ]: 816240 : if (!ItemIdIsNormal(lp))
554 melanieplageman@gmai 2610 :UBC 0 : continue;
554 melanieplageman@gmai 2611 :CBC 816240 : loctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
2612 : 816240 : loctup.t_len = ItemIdGetLength(lp);
2613 : 816240 : loctup.t_tableOid = scan->rs_rd->rd_id;
2614 : 816240 : ItemPointerSet(&loctup.t_self, block, offnum);
2615 : 816240 : valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
2616 [ + + ]: 816240 : if (valid)
2617 : : {
2618 : 816156 : hscan->rs_vistuples[ntup++] = offnum;
2619 : 816156 : PredicateLockTID(scan->rs_rd, &loctup.t_self, snapshot,
2620 : 816156 : HeapTupleHeaderGetXmin(loctup.t_data));
2621 : : }
2622 : 816240 : HeapCheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
2623 : : buffer, snapshot);
2624 : : }
2625 : : }
2626 : :
2627 : 243720 : LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
2628 : :
2629 [ - + ]: 243720 : Assert(ntup <= MaxHeapTuplesPerPage);
2630 : 243720 : hscan->rs_ntuples = ntup;
2631 : :
2632 [ + + ]: 243720 : if (tbmres->lossy)
2633 : 106930 : (*lossy_pages)++;
2634 : : else
2635 : 136790 : (*exact_pages)++;
2636 : :
2637 : : /*
2638 : : * Return true to indicate that a valid block was found and the bitmap is
2639 : : * not exhausted. If there are no visible tuples on this page,
2640 : : * hscan->rs_ntuples will be 0 and heapam_scan_bitmap_next_tuple() will
2641 : : * return false returning control to this function to advance to the next
2642 : : * block in the bitmap.
2643 : : */
2644 : 243720 : return true;
2645 : : }
2646 : :
2647 : : /* ------------------------------------------------------------------------
2648 : : * Definition of the heap table access method.
2649 : : * ------------------------------------------------------------------------
2650 : : */
2651 : :
2652 : : static const TableAmRoutine heapam_methods = {
2653 : : .type = T_TableAmRoutine,
2654 : :
2655 : : .slot_callbacks = heapam_slot_callbacks,
2656 : :
2657 : : .scan_begin = heap_beginscan,
2658 : : .scan_end = heap_endscan,
2659 : : .scan_rescan = heap_rescan,
2660 : : .scan_getnextslot = heap_getnextslot,
2661 : :
2662 : : .scan_set_tidrange = heap_set_tidrange,
2663 : : .scan_getnextslot_tidrange = heap_getnextslot_tidrange,
2664 : :
2665 : : .parallelscan_estimate = table_block_parallelscan_estimate,
2666 : : .parallelscan_initialize = table_block_parallelscan_initialize,
2667 : : .parallelscan_reinitialize = table_block_parallelscan_reinitialize,
2668 : :
2669 : : .index_scan_begin = heapam_index_scan_begin,
2670 : : .index_scan_reset = heapam_index_scan_reset,
2671 : : .index_scan_end = heapam_index_scan_end,
2672 : :
2673 : : .tuple_insert = heapam_tuple_insert,
2674 : : .tuple_insert_speculative = heapam_tuple_insert_speculative,
2675 : : .tuple_complete_speculative = heapam_tuple_complete_speculative,
2676 : : .multi_insert = heap_multi_insert,
2677 : : .tuple_delete = heapam_tuple_delete,
2678 : : .tuple_update = heapam_tuple_update,
2679 : : .tuple_lock = heapam_tuple_lock,
2680 : :
2681 : : .fetch_tid = heapam_fetch_tid,
2682 : : .tuple_fetch_row_version = heapam_fetch_row_version,
2683 : : .tuple_get_latest_tid = heap_get_latest_tid,
2684 : : .tuple_tid_valid = heapam_tuple_tid_valid,
2685 : : .tuple_satisfies_snapshot = heapam_tuple_satisfies_snapshot,
2686 : : .index_delete_tuples = heap_index_delete_tuples,
2687 : :
2688 : : .relation_set_new_filelocator = heapam_relation_set_new_filelocator,
2689 : : .relation_nontransactional_truncate = heapam_relation_nontransactional_truncate,
2690 : : .relation_copy_data = heapam_relation_copy_data,
2691 : : .relation_copy_for_cluster = heapam_relation_copy_for_cluster,
2692 : : .relation_vacuum = heap_vacuum_rel,
2693 : : .scan_analyze_next_block = heapam_scan_analyze_next_block,
2694 : : .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple,
2695 : : .index_build_range_scan = heapam_index_build_range_scan,
2696 : : .index_validate_scan = heapam_index_validate_scan,
2697 : :
2698 : : .relation_size = table_block_relation_size,
2699 : : .relation_needs_toast_table = heapam_relation_needs_toast_table,
2700 : : .relation_toast_am = heapam_relation_toast_am,
2701 : : .relation_fetch_toast_slice = heap_fetch_toast_slice,
2702 : :
2703 : : .relation_estimate_size = heapam_estimate_rel_size,
2704 : :
2705 : : .scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
2706 : : .scan_sample_next_block = heapam_scan_sample_next_block,
2707 : : .scan_sample_next_tuple = heapam_scan_sample_next_tuple
2708 : : };
2709 : :
2710 : :
2711 : : const TableAmRoutine *
2755 andres@anarazel.de 2712 : 11956023 : GetHeapamTableAmRoutine(void)
2713 : : {
2714 : 11956023 : return &heapam_methods;
2715 : : }
2716 : :
2717 : : Datum
2718 : 1299621 : heap_tableam_handler(PG_FUNCTION_ARGS)
2719 : : {
2720 : 1299621 : PG_RETURN_POINTER(&heapam_methods);
2721 : : }
|