Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * hashovfl.c
4 : : * Overflow page management code for the Postgres hash access method
5 : : *
6 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/hash/hashovfl.c
12 : : *
13 : : * NOTES
14 : : * Overflow pages look like ordinary relation pages.
15 : : *
16 : : *-------------------------------------------------------------------------
17 : : */
18 : : #include "postgres.h"
19 : :
20 : : #include "access/hash.h"
21 : : #include "access/hash_xlog.h"
22 : : #include "access/xloginsert.h"
23 : : #include "miscadmin.h"
24 : : #include "utils/rel.h"
25 : :
26 : :
27 : : static uint32 _hash_firstfreebit(uint32 map);
28 : :
29 : :
30 : : /*
31 : : * Convert overflow page bit number (its index in the free-page bitmaps)
32 : : * to block number within the index.
33 : : */
34 : : static BlockNumber
7531 tgl@sss.pgh.pa.us 35 :CBC 199 : bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
36 : : {
37 : 199 : uint32 splitnum = metap->hashm_ovflpoint;
38 : : uint32 i;
39 : :
40 : : /* Convert zero-based bitnumber to 1-based page number */
41 : 199 : ovflbitnum += 1;
42 : :
43 : : /* Determine the split number for this page (must be >= 1) */
44 : 199 : for (i = 1;
45 [ + + + + ]: 1089 : i < splitnum && ovflbitnum > metap->hashm_spares[i];
46 : 890 : i++)
47 : : /* loop */ ;
48 : :
49 : : /*
50 : : * Convert to absolute page number by adding the number of bucket pages
51 : : * that exist before this split point.
52 : : */
2568 rhaas@postgresql.org 53 : 199 : return (BlockNumber) (_hash_get_totalbuckets(i) + ovflbitnum);
54 : : }
55 : :
56 : : /*
57 : : * _hash_ovflblkno_to_bitno
58 : : *
59 : : * Convert overflow page block number to bit number for free-page bitmap.
60 : : */
61 : : uint32
2628 62 : 97 : _hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
63 : : {
7531 tgl@sss.pgh.pa.us 64 : 97 : uint32 splitnum = metap->hashm_ovflpoint;
65 : : uint32 i;
66 : : uint32 bitnum;
67 : :
68 : : /* Determine the split number containing this page */
69 [ + - ]: 354 : for (i = 1; i <= splitnum; i++)
70 : : {
2568 rhaas@postgresql.org 71 [ + + ]: 354 : if (ovflblkno <= (BlockNumber) _hash_get_totalbuckets(i))
7531 tgl@sss.pgh.pa.us 72 : 4 : break; /* oops */
2568 rhaas@postgresql.org 73 : 350 : bitnum = ovflblkno - _hash_get_totalbuckets(i);
74 : :
75 : : /*
76 : : * bitnum has to be greater than number of overflow page added in
77 : : * previous split point. The overflow page at this splitnum (i) if any
78 : : * should start from (_hash_get_totalbuckets(i) +
79 : : * metap->hashm_spares[i - 1] + 1).
80 : : */
2621 81 [ + - ]: 350 : if (bitnum > metap->hashm_spares[i - 1] &&
82 [ + + ]: 350 : bitnum <= metap->hashm_spares[i])
7531 tgl@sss.pgh.pa.us 83 : 93 : return bitnum - 1; /* -1 to convert 1-based to 0-based */
84 : : }
85 : :
2621 rhaas@postgresql.org 86 [ + - ]: 4 : ereport(ERROR,
87 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
88 : : errmsg("invalid overflow block number %u", ovflblkno)));
89 : : return 0; /* keep compiler quiet */
90 : : }
91 : :
92 : : /*
93 : : * _hash_addovflpage
94 : : *
95 : : * Add an overflow page to the bucket whose last page is pointed to by 'buf'.
96 : : *
97 : : * On entry, the caller must hold a pin but no lock on 'buf'. The pin is
98 : : * dropped before exiting (we assume the caller is not interested in 'buf'
99 : : * anymore) if not asked to retain. The pin will be retained only for the
100 : : * primary bucket. The returned overflow page will be pinned and
101 : : * write-locked; it is guaranteed to be empty.
102 : : *
103 : : * The caller must hold a pin, but no lock, on the metapage buffer.
104 : : * That buffer is returned in the same state.
105 : : *
106 : : * NB: since this could be executed concurrently by multiple processes,
107 : : * one should not assume that the returned overflow page will be the
108 : : * immediate successor of the originally passed 'buf'. Additional overflow
109 : : * pages might have been added to the bucket chain in between.
110 : : */
111 : : Buffer
2692 112 : 199 : _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf, bool retain_pin)
113 : : {
114 : : Buffer ovflbuf;
115 : : Page page;
116 : : Page ovflpage;
117 : : HashPageOpaque pageopaque;
118 : : HashPageOpaque ovflopaque;
119 : : HashMetaPage metap;
2603 120 : 199 : Buffer mapbuf = InvalidBuffer;
121 : 199 : Buffer newmapbuf = InvalidBuffer;
122 : : BlockNumber blkno;
123 : : uint32 orig_firstfree;
124 : : uint32 splitnum;
125 : 199 : uint32 *freep = NULL;
126 : : uint32 max_ovflpg;
127 : : uint32 bit;
128 : : uint32 bitmap_page_bit;
129 : : uint32 first_page;
130 : : uint32 last_bit;
131 : : uint32 last_page;
132 : : uint32 i,
133 : : j;
134 : 199 : bool page_found = false;
135 : :
136 : : /*
137 : : * Write-lock the tail page. Here, we need to maintain locking order such
138 : : * that, first acquire the lock on tail page of bucket, then on meta page
139 : : * to find and lock the bitmap page and if it is found, then lock on meta
140 : : * page is released, then finally acquire the lock on new overflow buffer.
141 : : * We need this locking order to avoid deadlock with backends that are
142 : : * doing inserts.
143 : : *
144 : : * Note: We could have avoided locking many buffers here if we made two
145 : : * WAL records for acquiring an overflow page (one to allocate an overflow
146 : : * page and another to add it to overflow bucket chain). However, doing
147 : : * so can leak an overflow page, if the system crashes after allocation.
148 : : * Needless to say, it is better to have a single record from a
149 : : * performance point of view as well.
150 : : */
2669 151 : 199 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
152 : :
153 : : /* probably redundant... */
6191 tgl@sss.pgh.pa.us 154 : 199 : _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
155 : :
156 : : /* loop to find current tail page, in case someone else inserted too */
157 : : for (;;)
7528 tgl@sss.pgh.pa.us 158 :UBC 0 : {
159 : : BlockNumber nextblkno;
160 : :
2916 kgrittn@postgresql.o 161 :CBC 199 : page = BufferGetPage(buf);
744 michael@paquier.xyz 162 : 199 : pageopaque = HashPageGetOpaque(page);
7528 tgl@sss.pgh.pa.us 163 : 199 : nextblkno = pageopaque->hasho_nextblkno;
164 : :
165 [ + - ]: 199 : if (!BlockNumberIsValid(nextblkno))
166 : 199 : break;
167 : :
168 : : /* we assume we do not need to write the unmodified page */
2651 rhaas@postgresql.org 169 [ # # ]:UBC 0 : if (retain_pin)
170 : : {
171 : : /* pin will be retained only for the primary bucket page */
2557 tgl@sss.pgh.pa.us 172 [ # # ]: 0 : Assert((pageopaque->hasho_flag & LH_PAGE_TYPE) == LH_BUCKET_PAGE);
2669 rhaas@postgresql.org 173 : 0 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
174 : : }
175 : : else
2692 176 : 0 : _hash_relbuf(rel, buf);
177 : :
2651 178 : 0 : retain_pin = false;
179 : :
6191 tgl@sss.pgh.pa.us 180 : 0 : buf = _hash_getbuf(rel, nextblkno, HASH_WRITE, LH_OVERFLOW_PAGE);
181 : : }
182 : :
183 : : /* Get exclusive lock on the meta page */
2669 rhaas@postgresql.org 184 :CBC 199 : LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
185 : :
6734 tgl@sss.pgh.pa.us 186 : 199 : _hash_checkpage(rel, metabuf, LH_META_PAGE);
2916 kgrittn@postgresql.o 187 : 199 : metap = HashPageGetMeta(BufferGetPage(metabuf));
188 : :
189 : : /* start search at hashm_firstfree */
7528 tgl@sss.pgh.pa.us 190 : 199 : orig_firstfree = metap->hashm_firstfree;
191 : 199 : first_page = orig_firstfree >> BMPG_SHIFT(metap);
192 : 199 : bit = orig_firstfree & BMPG_MASK(metap);
193 : 199 : i = first_page;
7531 194 : 199 : j = bit / BITS_PER_MAP;
195 : 199 : bit &= ~(BITS_PER_MAP - 1);
196 : :
197 : : /* outer loop iterates once per bitmap page */
198 : : for (;;)
9716 bruce@momjian.us 199 : 165 : {
200 : : BlockNumber mapblkno;
201 : : Page mappage;
202 : : uint32 last_inpage;
203 : :
204 : : /* want to end search with the last existing overflow page */
7528 tgl@sss.pgh.pa.us 205 : 364 : splitnum = metap->hashm_ovflpoint;
206 : 364 : max_ovflpg = metap->hashm_spares[splitnum] - 1;
207 : 364 : last_page = max_ovflpg >> BMPG_SHIFT(metap);
208 : 364 : last_bit = max_ovflpg & BMPG_MASK(metap);
209 : :
210 [ + + ]: 364 : if (i > last_page)
211 : 165 : break;
212 : :
213 [ - + ]: 199 : Assert(i < metap->hashm_nmaps);
214 : 199 : mapblkno = metap->hashm_mapp[i];
215 : :
7531 216 [ + - ]: 199 : if (i == last_page)
217 : 199 : last_inpage = last_bit;
218 : : else
7531 tgl@sss.pgh.pa.us 219 :UBC 0 : last_inpage = BMPGSZ_BIT(metap) - 1;
220 : :
221 : : /* Release exclusive lock on metapage while reading bitmap page */
2669 rhaas@postgresql.org 222 :CBC 199 : LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
223 : :
6191 tgl@sss.pgh.pa.us 224 : 199 : mapbuf = _hash_getbuf(rel, mapblkno, HASH_WRITE, LH_BITMAP_PAGE);
2916 kgrittn@postgresql.o 225 : 199 : mappage = BufferGetPage(mapbuf);
7528 tgl@sss.pgh.pa.us 226 : 199 : freep = HashPageGetBitmap(mappage);
227 : :
7531 228 [ + + ]: 364 : for (; bit <= last_inpage; j++, bit += BITS_PER_MAP)
229 : : {
9716 bruce@momjian.us 230 [ + + ]: 199 : if (freep[j] != ALL_SET)
231 : : {
2603 rhaas@postgresql.org 232 : 34 : page_found = true;
233 : :
234 : : /* Reacquire exclusive lock on the meta page */
235 : 34 : LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
236 : :
237 : : /* convert bit to bit number within page */
238 : 34 : bit += _hash_firstfreebit(freep[j]);
239 : 34 : bitmap_page_bit = bit;
240 : :
241 : : /* convert bit to absolute bit number */
242 : 34 : bit += (i << BMPG_SHIFT(metap));
243 : : /* Calculate address of the recycled overflow page */
244 : 34 : blkno = bitno_to_blkno(metap, bit);
245 : :
246 : : /* Fetch and init the recycled page */
247 : 34 : ovflbuf = _hash_getinitbuf(rel, blkno);
248 : :
9716 bruce@momjian.us 249 : 34 : goto found;
250 : : }
251 : : }
252 : :
253 : : /* No free space here, try to advance to next map page */
7528 tgl@sss.pgh.pa.us 254 : 165 : _hash_relbuf(rel, mapbuf);
2603 rhaas@postgresql.org 255 : 165 : mapbuf = InvalidBuffer;
7528 tgl@sss.pgh.pa.us 256 : 165 : i++;
257 : 165 : j = 0; /* scan from start of next map page */
258 : 165 : bit = 0;
259 : :
260 : : /* Reacquire exclusive lock on the meta page */
2669 rhaas@postgresql.org 261 : 165 : LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
262 : : }
263 : :
264 : : /*
265 : : * No free pages --- have to extend the relation to add an overflow page.
266 : : * First, check to see if we have to add a new bitmap page too.
267 : : */
7531 tgl@sss.pgh.pa.us 268 [ - + ]: 165 : if (last_bit == (uint32) (BMPGSZ_BIT(metap) - 1))
269 : : {
270 : : /*
271 : : * We create the new bitmap page with all pages marked "in use".
272 : : * Actually two pages in the new bitmap's range will exist
273 : : * immediately: the bitmap page itself, and the following page which
274 : : * is the one we return to the caller. Both of these are correctly
275 : : * marked "in use". Subsequent pages do not exist yet, but it is
276 : : * convenient to pre-mark them as "in use" too.
277 : : */
7531 tgl@sss.pgh.pa.us 278 :UBC 0 : bit = metap->hashm_spares[splitnum];
279 : :
280 : : /* metapage already has a write lock */
2603 rhaas@postgresql.org 281 [ # # ]: 0 : if (metap->hashm_nmaps >= HASH_MAX_BITMAPS)
282 [ # # ]: 0 : ereport(ERROR,
283 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
284 : : errmsg("out of overflow pages in hash index \"%s\"",
285 : : RelationGetRelationName(rel))));
286 : :
287 : 0 : newmapbuf = _hash_getnewbuf(rel, bitno_to_blkno(metap, bit), MAIN_FORKNUM);
288 : : }
289 : : else
290 : : {
291 : : /*
292 : : * Nothing to do here; since the page will be past the last used page,
293 : : * we know its bitmap bit was preinitialized to "in use".
294 : : */
295 : : }
296 : :
297 : : /* Calculate address of the new overflow page */
2603 rhaas@postgresql.org 298 :CBC 165 : bit = BufferIsValid(newmapbuf) ?
299 [ - + ]: 165 : metap->hashm_spares[splitnum] + 1 : metap->hashm_spares[splitnum];
7531 tgl@sss.pgh.pa.us 300 : 165 : blkno = bitno_to_blkno(metap, bit);
301 : :
302 : : /*
303 : : * Fetch the page with _hash_getnewbuf to ensure smgr's idea of the
304 : : * relation length stays in sync with ours. XXX It's annoying to do this
305 : : * with metapage write lock held; would be better to use a lock that
306 : : * doesn't block incoming searches.
307 : : *
308 : : * It is okay to hold two buffer locks here (one on tail page of bucket
309 : : * and other on new overflow page) since there cannot be anyone else
310 : : * contending for access to ovflbuf.
311 : : */
2603 rhaas@postgresql.org 312 : 165 : ovflbuf = _hash_getnewbuf(rel, blkno, MAIN_FORKNUM);
313 : :
314 : 199 : found:
315 : :
316 : : /*
317 : : * Do the update. No ereport(ERROR) until changes are logged. We want to
318 : : * log the changes for bitmap page and overflow page together to avoid
319 : : * loss of pages in case the new page is added.
320 : : */
2588 321 : 199 : START_CRIT_SECTION();
322 : :
2603 323 [ + + ]: 199 : if (page_found)
324 : : {
325 [ - + ]: 34 : Assert(BufferIsValid(mapbuf));
326 : :
327 : : /* mark page "in use" in the bitmap */
328 : 34 : SETBIT(freep, bitmap_page_bit);
329 : 34 : MarkBufferDirty(mapbuf);
330 : : }
331 : : else
332 : : {
333 : : /* update the count to indicate new overflow page is added */
334 : 165 : metap->hashm_spares[splitnum]++;
335 : :
336 [ - + ]: 165 : if (BufferIsValid(newmapbuf))
337 : : {
2603 rhaas@postgresql.org 338 :UBC 0 : _hash_initbitmapbuffer(newmapbuf, metap->hashm_bmsize, false);
339 : 0 : MarkBufferDirty(newmapbuf);
340 : :
341 : : /* add the new bitmap page to the metapage's list of bitmaps */
342 : 0 : metap->hashm_mapp[metap->hashm_nmaps] = BufferGetBlockNumber(newmapbuf);
343 : 0 : metap->hashm_nmaps++;
344 : 0 : metap->hashm_spares[splitnum]++;
345 : : }
346 : :
2264 rhaas@postgresql.org 347 :CBC 165 : MarkBufferDirty(metabuf);
348 : :
349 : : /*
350 : : * for new overflow page, we don't need to explicitly set the bit in
351 : : * bitmap page, as by default that will be set to "in use".
352 : : */
353 : : }
354 : :
355 : : /*
356 : : * Adjust hashm_firstfree to avoid redundant searches. But don't risk
357 : : * changing it if someone moved it while we were searching bitmap pages.
358 : : */
7528 tgl@sss.pgh.pa.us 359 [ + - ]: 199 : if (metap->hashm_firstfree == orig_firstfree)
360 : : {
361 : 199 : metap->hashm_firstfree = bit + 1;
2669 rhaas@postgresql.org 362 : 199 : MarkBufferDirty(metabuf);
363 : : }
364 : :
365 : : /* initialize new overflow page */
2603 366 : 199 : ovflpage = BufferGetPage(ovflbuf);
744 michael@paquier.xyz 367 : 199 : ovflopaque = HashPageGetOpaque(ovflpage);
2603 rhaas@postgresql.org 368 : 199 : ovflopaque->hasho_prevblkno = BufferGetBlockNumber(buf);
369 : 199 : ovflopaque->hasho_nextblkno = InvalidBlockNumber;
370 : 199 : ovflopaque->hasho_bucket = pageopaque->hasho_bucket;
371 : 199 : ovflopaque->hasho_flag = LH_OVERFLOW_PAGE;
372 : 199 : ovflopaque->hasho_page_id = HASHO_PAGE_ID;
373 : :
374 : 199 : MarkBufferDirty(ovflbuf);
375 : :
376 : : /* logically chain overflow page to previous page */
377 : 199 : pageopaque->hasho_nextblkno = BufferGetBlockNumber(ovflbuf);
378 : :
379 : 199 : MarkBufferDirty(buf);
380 : :
381 : : /* XLOG stuff */
2588 382 [ + - + + : 199 : if (RelationNeedsWAL(rel))
+ + + + ]
383 : : {
384 : : XLogRecPtr recptr;
385 : : xl_hash_add_ovfl_page xlrec;
386 : :
387 : 185 : xlrec.bmpage_found = page_found;
388 : 185 : xlrec.bmsize = metap->hashm_bmsize;
389 : :
390 : 185 : XLogBeginInsert();
391 : 185 : XLogRegisterData((char *) &xlrec, SizeOfHashAddOvflPage);
392 : :
393 : 185 : XLogRegisterBuffer(0, ovflbuf, REGBUF_WILL_INIT);
394 : 185 : XLogRegisterBufData(0, (char *) &pageopaque->hasho_bucket, sizeof(Bucket));
395 : :
396 : 185 : XLogRegisterBuffer(1, buf, REGBUF_STANDARD);
397 : :
398 [ + + ]: 185 : if (BufferIsValid(mapbuf))
399 : : {
400 : 34 : XLogRegisterBuffer(2, mapbuf, REGBUF_STANDARD);
401 : 34 : XLogRegisterBufData(2, (char *) &bitmap_page_bit, sizeof(uint32));
402 : : }
403 : :
404 [ - + ]: 185 : if (BufferIsValid(newmapbuf))
2588 rhaas@postgresql.org 405 :UBC 0 : XLogRegisterBuffer(3, newmapbuf, REGBUF_WILL_INIT);
406 : :
2588 rhaas@postgresql.org 407 :CBC 185 : XLogRegisterBuffer(4, metabuf, REGBUF_STANDARD);
408 : 185 : XLogRegisterBufData(4, (char *) &metap->hashm_firstfree, sizeof(uint32));
409 : :
410 : 185 : recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_ADD_OVFL_PAGE);
411 : :
412 : 185 : PageSetLSN(BufferGetPage(ovflbuf), recptr);
413 : 185 : PageSetLSN(BufferGetPage(buf), recptr);
414 : :
415 [ + + ]: 185 : if (BufferIsValid(mapbuf))
416 : 34 : PageSetLSN(BufferGetPage(mapbuf), recptr);
417 : :
418 [ - + ]: 185 : if (BufferIsValid(newmapbuf))
2588 rhaas@postgresql.org 419 :UBC 0 : PageSetLSN(BufferGetPage(newmapbuf), recptr);
420 : :
2588 rhaas@postgresql.org 421 :CBC 185 : PageSetLSN(BufferGetPage(metabuf), recptr);
422 : : }
423 : :
424 [ - + ]: 199 : END_CRIT_SECTION();
425 : :
2603 426 [ + + ]: 199 : if (retain_pin)
427 : 55 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
428 : : else
429 : 144 : _hash_relbuf(rel, buf);
430 : :
431 [ + + ]: 199 : if (BufferIsValid(mapbuf))
432 : 34 : _hash_relbuf(rel, mapbuf);
433 : :
434 : 199 : LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
435 : :
436 [ - + ]: 199 : if (BufferIsValid(newmapbuf))
2603 rhaas@postgresql.org 437 :UBC 0 : _hash_relbuf(rel, newmapbuf);
438 : :
2603 rhaas@postgresql.org 439 :CBC 199 : return ovflbuf;
440 : : }
441 : :
442 : : /*
443 : : * _hash_firstfreebit()
444 : : *
445 : : * Return the number of the first bit that is not set in the word 'map'.
446 : : */
447 : : static uint32
10141 scrappy@hub.org 448 : 34 : _hash_firstfreebit(uint32 map)
449 : : {
450 : : uint32 i,
451 : : mask;
452 : :
9716 bruce@momjian.us 453 : 34 : mask = 0x1;
454 [ + - ]: 287 : for (i = 0; i < BITS_PER_MAP; i++)
455 : : {
456 [ + + ]: 287 : if (!(mask & map))
9357 457 : 34 : return i;
7531 tgl@sss.pgh.pa.us 458 : 253 : mask <<= 1;
459 : : }
460 : :
7528 tgl@sss.pgh.pa.us 461 [ # # ]:UBC 0 : elog(ERROR, "firstfreebit found no free bit");
462 : :
463 : : return 0; /* keep compiler quiet */
464 : : }
465 : :
466 : : /*
467 : : * _hash_freeovflpage() -
468 : : *
469 : : * Remove this overflow page from its bucket's chain, and mark the page as
470 : : * free. On entry, ovflbuf is write-locked; it is released before exiting.
471 : : *
472 : : * Add the tuples (itups) to wbuf in this function. We could do that in the
473 : : * caller as well, but the advantage of doing it here is we can easily write
474 : : * the WAL for XLOG_HASH_SQUEEZE_PAGE operation. Addition of tuples and
475 : : * removal of overflow page has to done as an atomic operation, otherwise
476 : : * during replay on standby users might find duplicate records.
477 : : *
478 : : * Since this function is invoked in VACUUM, we provide an access strategy
479 : : * parameter that controls fetches of the bucket pages.
480 : : *
481 : : * Returns the block number of the page that followed the given page
482 : : * in the bucket, or InvalidBlockNumber if no following page.
483 : : *
484 : : * NB: caller must not hold lock on metapage, nor on page, that's next to
485 : : * ovflbuf in the bucket chain. We don't acquire the lock on page that's
486 : : * prior to ovflbuf in chain if it is same as wbuf because the caller already
487 : : * has a lock on same.
488 : : */
489 : : BlockNumber
2603 rhaas@postgresql.org 490 :CBC 93 : _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
491 : : Buffer wbuf, IndexTuple *itups, OffsetNumber *itup_offsets,
492 : : Size *tups_size, uint16 nitups,
493 : : BufferAccessStrategy bstrategy)
494 : : {
495 : : HashMetaPage metap;
496 : : Buffer metabuf;
497 : : Buffer mapbuf;
498 : : BlockNumber ovflblkno;
499 : : BlockNumber prevblkno;
500 : : BlockNumber blkno;
501 : : BlockNumber nextblkno;
502 : : BlockNumber writeblkno;
503 : : HashPageOpaque ovflopaque;
504 : : Page ovflpage;
505 : : Page mappage;
506 : : uint32 *freep;
507 : : uint32 ovflbitno;
508 : : int32 bitmappage,
509 : : bitmapbit;
510 : : Bucket bucket PG_USED_FOR_ASSERTS_ONLY;
2588 511 : 93 : Buffer prevbuf = InvalidBuffer;
512 : 93 : Buffer nextbuf = InvalidBuffer;
513 : 93 : bool update_metap = false;
514 : :
515 : : /* Get information from the doomed page */
6734 tgl@sss.pgh.pa.us 516 : 93 : _hash_checkpage(rel, ovflbuf, LH_OVERFLOW_PAGE);
7168 bruce@momjian.us 517 : 93 : ovflblkno = BufferGetBlockNumber(ovflbuf);
2916 kgrittn@postgresql.o 518 : 93 : ovflpage = BufferGetPage(ovflbuf);
744 michael@paquier.xyz 519 : 93 : ovflopaque = HashPageGetOpaque(ovflpage);
9716 bruce@momjian.us 520 : 93 : nextblkno = ovflopaque->hasho_nextblkno;
521 : 93 : prevblkno = ovflopaque->hasho_prevblkno;
2692 rhaas@postgresql.org 522 : 93 : writeblkno = BufferGetBlockNumber(wbuf);
9716 bruce@momjian.us 523 : 93 : bucket = ovflopaque->hasho_bucket;
524 : :
525 : : /*
526 : : * Fix up the bucket chain. this is a doubly-linked list, so we must fix
527 : : * up the bucket chain members behind and ahead of the overflow page being
528 : : * deleted. Concurrency issues are avoided by using lock chaining as
529 : : * described atop hashbucketcleanup.
530 : : */
531 [ + - ]: 93 : if (BlockNumberIsValid(prevblkno))
532 : : {
2692 rhaas@postgresql.org 533 [ + + ]: 93 : if (prevblkno == writeblkno)
534 : 30 : prevbuf = wbuf;
535 : : else
536 : 63 : prevbuf = _hash_getbuf_with_strategy(rel,
537 : : prevblkno,
538 : : HASH_WRITE,
539 : : LH_BUCKET_PAGE | LH_OVERFLOW_PAGE,
540 : : bstrategy);
541 : : }
9716 bruce@momjian.us 542 [ - + ]: 93 : if (BlockNumberIsValid(nextblkno))
2603 rhaas@postgresql.org 543 :UBC 0 : nextbuf = _hash_getbuf_with_strategy(rel,
544 : : nextblkno,
545 : : HASH_WRITE,
546 : : LH_OVERFLOW_PAGE,
547 : : bstrategy);
548 : :
549 : : /* Note: bstrategy is intentionally not used for metapage and bitmap */
550 : :
551 : : /* Read the metapage so we can determine which bitmap page to use */
6191 tgl@sss.pgh.pa.us 552 :CBC 93 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
2916 kgrittn@postgresql.o 553 : 93 : metap = HashPageGetMeta(BufferGetPage(metabuf));
554 : :
555 : : /* Identify which bit to set */
2628 rhaas@postgresql.org 556 : 93 : ovflbitno = _hash_ovflblkno_to_bitno(metap, ovflblkno);
557 : :
7531 tgl@sss.pgh.pa.us 558 : 93 : bitmappage = ovflbitno >> BMPG_SHIFT(metap);
559 : 93 : bitmapbit = ovflbitno & BMPG_MASK(metap);
560 : :
561 [ - + ]: 93 : if (bitmappage >= metap->hashm_nmaps)
7531 tgl@sss.pgh.pa.us 562 [ # # ]:UBC 0 : elog(ERROR, "invalid overflow bit number %u", ovflbitno);
9716 bruce@momjian.us 563 :CBC 93 : blkno = metap->hashm_mapp[bitmappage];
564 : :
565 : : /* Release metapage lock while we access the bitmap page */
2669 rhaas@postgresql.org 566 : 93 : LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
567 : :
568 : : /* read the bitmap page to clear the bitmap bit */
6191 tgl@sss.pgh.pa.us 569 : 93 : mapbuf = _hash_getbuf(rel, blkno, HASH_WRITE, LH_BITMAP_PAGE);
2916 kgrittn@postgresql.o 570 : 93 : mappage = BufferGetPage(mapbuf);
9716 bruce@momjian.us 571 : 93 : freep = HashPageGetBitmap(mappage);
7528 tgl@sss.pgh.pa.us 572 [ - + ]: 93 : Assert(ISSET(freep, bitmapbit));
573 : :
574 : : /* Get write-lock on metapage to update firstfree */
2669 rhaas@postgresql.org 575 : 93 : LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
576 : :
577 : : /* This operation needs to log multiple tuples, prepare WAL for that */
2588 578 [ + - + + : 93 : if (RelationNeedsWAL(rel))
+ - + - ]
579 : 93 : XLogEnsureRecordSpace(HASH_XLOG_FREE_OVFL_BUFS, 4 + nitups);
580 : :
581 : 93 : START_CRIT_SECTION();
582 : :
583 : : /*
584 : : * we have to insert tuples on the "write" page, being careful to preserve
585 : : * hashkey ordering. (If we insert many tuples into the same "write" page
586 : : * it would be worth qsort'ing them).
587 : : */
2603 588 [ + + ]: 93 : if (nitups > 0)
589 : : {
590 : 42 : _hash_pgaddmultitup(rel, wbuf, itups, itup_offsets, nitups);
591 : 42 : MarkBufferDirty(wbuf);
592 : : }
593 : :
594 : : /*
595 : : * Reinitialize the freed overflow page. Just zeroing the page won't
596 : : * work, because WAL replay routines expect pages to be initialized. See
597 : : * explanation of RBM_NORMAL mode atop XLogReadBufferExtended. We are
598 : : * careful to make the special space valid here so that tools like
599 : : * pageinspect won't get confused.
600 : : */
601 : 93 : _hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf));
602 : :
744 michael@paquier.xyz 603 : 93 : ovflopaque = HashPageGetOpaque(ovflpage);
604 : :
2566 rhaas@postgresql.org 605 : 93 : ovflopaque->hasho_prevblkno = InvalidBlockNumber;
606 : 93 : ovflopaque->hasho_nextblkno = InvalidBlockNumber;
1017 peter@eisentraut.org 607 : 93 : ovflopaque->hasho_bucket = InvalidBucket;
2566 rhaas@postgresql.org 608 : 93 : ovflopaque->hasho_flag = LH_UNUSED_PAGE;
609 : 93 : ovflopaque->hasho_page_id = HASHO_PAGE_ID;
610 : :
2603 611 : 93 : MarkBufferDirty(ovflbuf);
612 : :
613 [ + - ]: 93 : if (BufferIsValid(prevbuf))
614 : : {
615 : 93 : Page prevpage = BufferGetPage(prevbuf);
744 michael@paquier.xyz 616 : 93 : HashPageOpaque prevopaque = HashPageGetOpaque(prevpage);
617 : :
2603 rhaas@postgresql.org 618 [ - + ]: 93 : Assert(prevopaque->hasho_bucket == bucket);
619 : 93 : prevopaque->hasho_nextblkno = nextblkno;
620 : 93 : MarkBufferDirty(prevbuf);
621 : : }
622 [ - + ]: 93 : if (BufferIsValid(nextbuf))
623 : : {
2603 rhaas@postgresql.org 624 :UBC 0 : Page nextpage = BufferGetPage(nextbuf);
744 michael@paquier.xyz 625 : 0 : HashPageOpaque nextopaque = HashPageGetOpaque(nextpage);
626 : :
2603 rhaas@postgresql.org 627 [ # # ]: 0 : Assert(nextopaque->hasho_bucket == bucket);
628 : 0 : nextopaque->hasho_prevblkno = prevblkno;
629 : 0 : MarkBufferDirty(nextbuf);
630 : : }
631 : :
632 : : /* Clear the bitmap bit to indicate that this overflow page is free */
2603 rhaas@postgresql.org 633 :CBC 93 : CLRBIT(freep, bitmapbit);
634 : 93 : MarkBufferDirty(mapbuf);
635 : :
636 : : /* if this is now the first free page, update hashm_firstfree */
7531 tgl@sss.pgh.pa.us 637 [ + + ]: 93 : if (ovflbitno < metap->hashm_firstfree)
638 : : {
639 : 90 : metap->hashm_firstfree = ovflbitno;
2588 rhaas@postgresql.org 640 : 90 : update_metap = true;
2676 641 : 90 : MarkBufferDirty(metabuf);
642 : : }
643 : :
644 : : /* XLOG stuff */
2588 645 [ + - + + : 93 : if (RelationNeedsWAL(rel))
+ - + - ]
646 : : {
647 : : xl_hash_squeeze_page xlrec;
648 : : XLogRecPtr recptr;
649 : : int i;
67 akapila@postgresql.o 650 :GNC 93 : bool mod_wbuf = false;
651 : :
2588 rhaas@postgresql.org 652 :CBC 93 : xlrec.prevblkno = prevblkno;
653 : 93 : xlrec.nextblkno = nextblkno;
654 : 93 : xlrec.ntups = nitups;
655 : 93 : xlrec.is_prim_bucket_same_wrt = (wbuf == bucketbuf);
656 : 93 : xlrec.is_prev_bucket_same_wrt = (wbuf == prevbuf);
657 : :
658 : 93 : XLogBeginInsert();
659 : 93 : XLogRegisterData((char *) &xlrec, SizeOfHashSqueezePage);
660 : :
661 : : /*
662 : : * bucket buffer was not changed, but still needs to be registered to
663 : : * ensure that we can acquire a cleanup lock on it during replay.
664 : : */
665 [ + + ]: 93 : if (!xlrec.is_prim_bucket_same_wrt)
666 : : {
174 jdavis@postgresql.or 667 :GNC 24 : uint8 flags = REGBUF_STANDARD | REGBUF_NO_IMAGE | REGBUF_NO_CHANGE;
668 : :
669 : 24 : XLogRegisterBuffer(0, bucketbuf, flags);
670 : : }
671 : :
2588 rhaas@postgresql.org 672 [ + + ]:CBC 93 : if (xlrec.ntups > 0)
673 : : {
153 akapila@postgresql.o 674 :GNC 42 : XLogRegisterBuffer(1, wbuf, REGBUF_STANDARD);
675 : :
676 : : /* Remember that wbuf is modified. */
67 677 : 42 : mod_wbuf = true;
678 : :
2588 rhaas@postgresql.org 679 :CBC 42 : XLogRegisterBufData(1, (char *) itup_offsets,
680 : : nitups * sizeof(OffsetNumber));
681 [ + + ]: 1502 : for (i = 0; i < nitups; i++)
682 : 1460 : XLogRegisterBufData(1, (char *) itups[i], tups_size[i]);
683 : : }
153 akapila@postgresql.o 684 [ + + + + ]:GNC 51 : else if (xlrec.is_prim_bucket_same_wrt || xlrec.is_prev_bucket_same_wrt)
685 : : {
686 : : uint8 wbuf_flags;
687 : :
688 : : /*
689 : : * A write buffer needs to be registered even if no tuples are
690 : : * added to it to ensure that we can acquire a cleanup lock on it
691 : : * if it is the same as primary bucket buffer or update the
692 : : * nextblkno if it is same as the previous bucket buffer.
693 : : */
694 [ - + ]: 48 : Assert(xlrec.ntups == 0);
695 : :
696 : 48 : wbuf_flags = REGBUF_STANDARD;
697 [ + + ]: 48 : if (!xlrec.is_prev_bucket_same_wrt)
698 : : {
699 : 39 : wbuf_flags |= REGBUF_NO_CHANGE;
700 : : }
701 : : else
702 : : {
703 : : /* Remember that wbuf is modified. */
67 704 : 9 : mod_wbuf = true;
705 : : }
153 706 : 48 : XLogRegisterBuffer(1, wbuf, wbuf_flags);
707 : : }
708 : :
2588 rhaas@postgresql.org 709 :CBC 93 : XLogRegisterBuffer(2, ovflbuf, REGBUF_STANDARD);
710 : :
711 : : /*
712 : : * If prevpage and the writepage (block in which we are moving tuples
713 : : * from overflow) are same, then no need to separately register
714 : : * prevpage. During replay, we can directly update the nextblock in
715 : : * writepage.
716 : : */
717 [ + - + + ]: 93 : if (BufferIsValid(prevbuf) && !xlrec.is_prev_bucket_same_wrt)
718 : 63 : XLogRegisterBuffer(3, prevbuf, REGBUF_STANDARD);
719 : :
720 [ - + ]: 93 : if (BufferIsValid(nextbuf))
2588 rhaas@postgresql.org 721 :UBC 0 : XLogRegisterBuffer(4, nextbuf, REGBUF_STANDARD);
722 : :
2588 rhaas@postgresql.org 723 :CBC 93 : XLogRegisterBuffer(5, mapbuf, REGBUF_STANDARD);
724 : 93 : XLogRegisterBufData(5, (char *) &bitmapbit, sizeof(uint32));
725 : :
726 [ + + ]: 93 : if (update_metap)
727 : : {
728 : 90 : XLogRegisterBuffer(6, metabuf, REGBUF_STANDARD);
729 : 90 : XLogRegisterBufData(6, (char *) &metap->hashm_firstfree, sizeof(uint32));
730 : : }
731 : :
732 : 93 : recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_SQUEEZE_PAGE);
733 : :
734 : : /* Set LSN iff wbuf is modified. */
67 akapila@postgresql.o 735 [ + + ]:GNC 93 : if (mod_wbuf)
736 : 51 : PageSetLSN(BufferGetPage(wbuf), recptr);
737 : :
2588 rhaas@postgresql.org 738 :CBC 93 : PageSetLSN(BufferGetPage(ovflbuf), recptr);
739 : :
740 [ + - + + ]: 93 : if (BufferIsValid(prevbuf) && !xlrec.is_prev_bucket_same_wrt)
741 : 63 : PageSetLSN(BufferGetPage(prevbuf), recptr);
742 [ - + ]: 93 : if (BufferIsValid(nextbuf))
2588 rhaas@postgresql.org 743 :UBC 0 : PageSetLSN(BufferGetPage(nextbuf), recptr);
744 : :
2588 rhaas@postgresql.org 745 :CBC 93 : PageSetLSN(BufferGetPage(mapbuf), recptr);
746 : :
747 [ + + ]: 93 : if (update_metap)
748 : 90 : PageSetLSN(BufferGetPage(metabuf), recptr);
749 : : }
750 : :
751 [ - + ]: 93 : END_CRIT_SECTION();
752 : :
753 : : /* release previous bucket if it is not same as write bucket */
2603 754 [ + - + + ]: 93 : if (BufferIsValid(prevbuf) && prevblkno != writeblkno)
755 : 63 : _hash_relbuf(rel, prevbuf);
756 : :
757 [ + - ]: 93 : if (BufferIsValid(ovflbuf))
758 : 93 : _hash_relbuf(rel, ovflbuf);
759 : :
760 [ - + ]: 93 : if (BufferIsValid(nextbuf))
2603 rhaas@postgresql.org 761 :UBC 0 : _hash_relbuf(rel, nextbuf);
762 : :
2603 rhaas@postgresql.org 763 :CBC 93 : _hash_relbuf(rel, mapbuf);
2676 764 : 93 : _hash_relbuf(rel, metabuf);
765 : :
7531 tgl@sss.pgh.pa.us 766 : 93 : return nextblkno;
767 : : }
768 : :
769 : :
770 : : /*
771 : : * _hash_initbitmapbuffer()
772 : : *
773 : : * Initialize a new bitmap page. All bits in the new bitmap page are set to
774 : : * "1", indicating "in use".
775 : : */
776 : : void
2603 rhaas@postgresql.org 777 : 186 : _hash_initbitmapbuffer(Buffer buf, uint16 bmsize, bool initpage)
778 : : {
779 : : Page pg;
780 : : HashPageOpaque op;
781 : : uint32 *freep;
782 : :
783 : 186 : pg = BufferGetPage(buf);
784 : :
785 : : /* initialize the page */
786 [ + + ]: 186 : if (initpage)
787 : 24 : _hash_pageinit(pg, BufferGetPageSize(buf));
788 : :
789 : : /* initialize the page's special space */
744 michael@paquier.xyz 790 : 186 : op = HashPageGetOpaque(pg);
2603 rhaas@postgresql.org 791 : 186 : op->hasho_prevblkno = InvalidBlockNumber;
792 : 186 : op->hasho_nextblkno = InvalidBlockNumber;
1017 peter@eisentraut.org 793 : 186 : op->hasho_bucket = InvalidBucket;
2603 rhaas@postgresql.org 794 : 186 : op->hasho_flag = LH_BITMAP_PAGE;
795 : 186 : op->hasho_page_id = HASHO_PAGE_ID;
796 : :
797 : : /* set all of the bits to 1 */
798 : 186 : freep = HashPageGetBitmap(pg);
653 peter@eisentraut.org 799 : 186 : memset(freep, 0xFF, bmsize);
800 : :
801 : : /*
802 : : * Set pd_lower just past the end of the bitmap page data. We could even
803 : : * set pd_lower equal to pd_upper, but this is more precise and makes the
804 : : * page look compressible to xlog.c.
805 : : */
2603 rhaas@postgresql.org 806 : 186 : ((PageHeader) pg)->pd_lower = ((char *) freep + bmsize) - (char *) pg;
807 : 186 : }
808 : :
809 : :
810 : : /*
811 : : * _hash_squeezebucket(rel, bucket)
812 : : *
813 : : * Try to squeeze the tuples onto pages occurring earlier in the
814 : : * bucket chain in an attempt to free overflow pages. When we start
815 : : * the "squeezing", the page from which we start taking tuples (the
816 : : * "read" page) is the last bucket in the bucket chain and the page
817 : : * onto which we start squeezing tuples (the "write" page) is the
818 : : * first page in the bucket chain. The read page works backward and
819 : : * the write page works forward; the procedure terminates when the
820 : : * read page and write page are the same page.
821 : : *
822 : : * At completion of this procedure, it is guaranteed that all pages in
823 : : * the bucket are nonempty, unless the bucket is totally empty (in
824 : : * which case all overflow pages will be freed). The original implementation
825 : : * required that to be true on entry as well, but it's a lot easier for
826 : : * callers to leave empty overflow pages and let this guy clean it up.
827 : : *
828 : : * Caller must acquire cleanup lock on the primary page of the target
829 : : * bucket to exclude any scans that are in progress, which could easily
830 : : * be confused into returning the same tuple more than once or some tuples
831 : : * not at all by the rearrangement we are performing here. To prevent
832 : : * any concurrent scan to cross the squeeze scan we use lock chaining
833 : : * similar to hashbucketcleanup. Refer comments atop hashbucketcleanup.
834 : : *
835 : : * We need to retain a pin on the primary bucket to ensure that no concurrent
836 : : * split can start.
837 : : *
838 : : * Since this function is invoked in VACUUM, we provide an access strategy
839 : : * parameter that controls fetches of the bucket pages.
840 : : */
841 : : void
10141 scrappy@hub.org 842 : 692 : _hash_squeezebucket(Relation rel,
843 : : Bucket bucket,
844 : : BlockNumber bucket_blkno,
845 : : Buffer bucket_buf,
846 : : BufferAccessStrategy bstrategy)
847 : : {
848 : : BlockNumber wblkno;
849 : : BlockNumber rblkno;
850 : : Buffer wbuf;
851 : : Buffer rbuf;
852 : : Page wpage;
853 : : Page rpage;
854 : : HashPageOpaque wopaque;
855 : : HashPageOpaque ropaque;
856 : :
857 : : /*
858 : : * start squeezing into the primary bucket page.
859 : : */
7530 tgl@sss.pgh.pa.us 860 : 692 : wblkno = bucket_blkno;
2692 rhaas@postgresql.org 861 : 692 : wbuf = bucket_buf;
2916 kgrittn@postgresql.o 862 : 692 : wpage = BufferGetPage(wbuf);
744 michael@paquier.xyz 863 : 692 : wopaque = HashPageGetOpaque(wpage);
864 : :
865 : : /*
866 : : * if there aren't any overflow pages, there's nothing to squeeze. caller
867 : : * is responsible for releasing the pin on primary bucket page.
868 : : */
9716 bruce@momjian.us 869 [ + + ]: 692 : if (!BlockNumberIsValid(wopaque->hasho_nextblkno))
870 : : {
2669 rhaas@postgresql.org 871 : 656 : LockBuffer(wbuf, BUFFER_LOCK_UNLOCK);
10141 scrappy@hub.org 872 : 656 : return;
873 : : }
874 : :
875 : : /*
876 : : * Find the last page in the bucket chain by starting at the base bucket
877 : : * page and working forward. Note: we assume that a hash bucket chain is
878 : : * usually smaller than the buffer ring being used by VACUUM, else using
879 : : * the access strategy here would be counterproductive.
880 : : */
5278 tgl@sss.pgh.pa.us 881 : 36 : rbuf = InvalidBuffer;
9716 bruce@momjian.us 882 : 36 : ropaque = wopaque;
883 : : do
884 : : {
885 : 180 : rblkno = ropaque->hasho_nextblkno;
5278 tgl@sss.pgh.pa.us 886 [ + + ]: 180 : if (rbuf != InvalidBuffer)
7528 887 : 144 : _hash_relbuf(rel, rbuf);
6164 888 : 180 : rbuf = _hash_getbuf_with_strategy(rel,
889 : : rblkno,
890 : : HASH_WRITE,
891 : : LH_OVERFLOW_PAGE,
892 : : bstrategy);
2916 kgrittn@postgresql.o 893 : 180 : rpage = BufferGetPage(rbuf);
744 michael@paquier.xyz 894 : 180 : ropaque = HashPageGetOpaque(rpage);
9716 bruce@momjian.us 895 [ - + ]: 180 : Assert(ropaque->hasho_bucket == bucket);
896 [ + + ]: 180 : } while (BlockNumberIsValid(ropaque->hasho_nextblkno));
897 : :
898 : : /*
899 : : * squeeze the tuples.
900 : : */
901 : : for (;;)
902 : 63 : {
903 : : OffsetNumber roffnum;
904 : : OffsetNumber maxroffnum;
905 : : OffsetNumber deletable[MaxOffsetNumber];
906 : : IndexTuple itups[MaxIndexTuplesPerPage];
907 : : Size tups_size[MaxIndexTuplesPerPage];
908 : : OffsetNumber itup_offsets[MaxIndexTuplesPerPage];
2603 rhaas@postgresql.org 909 : 99 : uint16 ndeletable = 0;
910 : 99 : uint16 nitups = 0;
911 : 99 : Size all_tups_size = 0;
912 : : int i;
2692 913 : 99 : bool retain_pin = false;
914 : :
2603 915 : 102 : readpage:
916 : : /* Scan each tuple in "read" page */
5278 tgl@sss.pgh.pa.us 917 : 102 : maxroffnum = PageGetMaxOffsetNumber(rpage);
918 : 102 : for (roffnum = FirstOffsetNumber;
919 [ + + ]: 2588 : roffnum <= maxroffnum;
920 : 2486 : roffnum = OffsetNumberNext(roffnum))
921 : : {
922 : : IndexTuple itup;
923 : : Size itemsz;
924 : :
925 : : /* skip dead tuples */
2714 rhaas@postgresql.org 926 [ - + ]: 2495 : if (ItemIdIsDead(PageGetItemId(rpage, roffnum)))
2714 rhaas@postgresql.org 927 :UBC 0 : continue;
928 : :
6654 tgl@sss.pgh.pa.us 929 :CBC 2495 : itup = (IndexTuple) PageGetItem(rpage,
930 : : PageGetItemId(rpage, roffnum));
2237 931 : 2495 : itemsz = IndexTupleSize(itup);
7528 932 : 2495 : itemsz = MAXALIGN(itemsz);
933 : :
934 : : /*
935 : : * Walk up the bucket chain, looking for a page big enough for
936 : : * this item and all other accumulated items. Exit if we reach
937 : : * the read page.
938 : : */
2603 rhaas@postgresql.org 939 [ + + ]: 2573 : while (PageGetFreeSpaceForMultipleTuples(wpage, nitups + 1) < (all_tups_size + itemsz))
940 : : {
2692 941 : 87 : Buffer next_wbuf = InvalidBuffer;
2603 942 : 87 : bool tups_moved = false;
943 : :
7528 tgl@sss.pgh.pa.us 944 [ - + ]: 87 : Assert(!PageIsEmpty(wpage));
945 : :
2692 rhaas@postgresql.org 946 [ + + ]: 87 : if (wblkno == bucket_blkno)
947 : 15 : retain_pin = true;
948 : :
7528 tgl@sss.pgh.pa.us 949 : 87 : wblkno = wopaque->hasho_nextblkno;
950 [ - + ]: 87 : Assert(BlockNumberIsValid(wblkno));
951 : :
952 : : /* don't need to move to next page if we reached the read page */
2692 rhaas@postgresql.org 953 [ + + ]: 87 : if (wblkno != rblkno)
954 : 81 : next_wbuf = _hash_getbuf_with_strategy(rel,
955 : : wblkno,
956 : : HASH_WRITE,
957 : : LH_OVERFLOW_PAGE,
958 : : bstrategy);
959 : :
2603 960 [ + + ]: 87 : if (nitups > 0)
961 : : {
2603 rhaas@postgresql.org 962 [ - + ]:GBC 3 : Assert(nitups == ndeletable);
963 : :
964 : : /*
965 : : * This operation needs to log multiple tuples, prepare
966 : : * WAL for that.
967 : : */
2588 968 [ + - + + : 3 : if (RelationNeedsWAL(rel))
+ - + - ]
969 : 3 : XLogEnsureRecordSpace(0, 3 + nitups);
970 : :
971 : 3 : START_CRIT_SECTION();
972 : :
973 : : /*
974 : : * we have to insert tuples on the "write" page, being
975 : : * careful to preserve hashkey ordering. (If we insert
976 : : * many tuples into the same "write" page it would be
977 : : * worth qsort'ing them).
978 : : */
2603 979 : 3 : _hash_pgaddmultitup(rel, wbuf, itups, itup_offsets, nitups);
980 : 3 : MarkBufferDirty(wbuf);
981 : :
982 : : /* Delete tuples we already moved off read page */
983 : 3 : PageIndexMultiDelete(rpage, deletable, ndeletable);
984 : 3 : MarkBufferDirty(rbuf);
985 : :
986 : : /* XLOG stuff */
2588 987 [ + - + + : 3 : if (RelationNeedsWAL(rel))
+ - + - ]
988 : : {
989 : : XLogRecPtr recptr;
990 : : xl_hash_move_page_contents xlrec;
991 : :
992 : 3 : xlrec.ntups = nitups;
949 michael@paquier.xyz 993 : 3 : xlrec.is_prim_bucket_same_wrt = (wbuf == bucket_buf);
994 : :
2588 rhaas@postgresql.org 995 : 3 : XLogBeginInsert();
996 : 3 : XLogRegisterData((char *) &xlrec, SizeOfHashMovePageContents);
997 : :
998 : : /*
999 : : * bucket buffer was not changed, but still needs to
1000 : : * be registered to ensure that we can acquire a
1001 : : * cleanup lock on it during replay.
1002 : : */
1003 [ - + ]: 3 : if (!xlrec.is_prim_bucket_same_wrt)
1004 : : {
174 jdavis@postgresql.or 1005 :UNC 0 : int flags = REGBUF_STANDARD | REGBUF_NO_IMAGE | REGBUF_NO_CHANGE;
1006 : :
1007 : 0 : XLogRegisterBuffer(0, bucket_buf, flags);
1008 : : }
1009 : :
2588 rhaas@postgresql.org 1010 :GBC 3 : XLogRegisterBuffer(1, wbuf, REGBUF_STANDARD);
1011 : 3 : XLogRegisterBufData(1, (char *) itup_offsets,
1012 : : nitups * sizeof(OffsetNumber));
1013 [ + + ]: 1029 : for (i = 0; i < nitups; i++)
1014 : 1026 : XLogRegisterBufData(1, (char *) itups[i], tups_size[i]);
1015 : :
1016 : 3 : XLogRegisterBuffer(2, rbuf, REGBUF_STANDARD);
1017 : 3 : XLogRegisterBufData(2, (char *) deletable,
1018 : : ndeletable * sizeof(OffsetNumber));
1019 : :
1020 : 3 : recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_MOVE_PAGE_CONTENTS);
1021 : :
1022 : 3 : PageSetLSN(BufferGetPage(wbuf), recptr);
1023 : 3 : PageSetLSN(BufferGetPage(rbuf), recptr);
1024 : : }
1025 : :
1026 [ - + ]: 3 : END_CRIT_SECTION();
1027 : :
2603 1028 : 3 : tups_moved = true;
1029 : : }
1030 : :
1031 : : /*
1032 : : * release the lock on previous page after acquiring the lock
1033 : : * on next page
1034 : : */
2676 rhaas@postgresql.org 1035 [ + + ]:CBC 87 : if (retain_pin)
2669 1036 : 15 : LockBuffer(wbuf, BUFFER_LOCK_UNLOCK);
1037 : : else
5278 tgl@sss.pgh.pa.us 1038 : 72 : _hash_relbuf(rel, wbuf);
1039 : :
1040 : : /* nothing more to do if we reached the read page */
7528 1041 [ + + ]: 87 : if (rblkno == wblkno)
1042 : : {
2676 rhaas@postgresql.org 1043 : 6 : _hash_relbuf(rel, rbuf);
7528 tgl@sss.pgh.pa.us 1044 : 36 : return;
1045 : : }
1046 : :
2692 rhaas@postgresql.org 1047 : 81 : wbuf = next_wbuf;
2916 kgrittn@postgresql.o 1048 : 81 : wpage = BufferGetPage(wbuf);
744 michael@paquier.xyz 1049 : 81 : wopaque = HashPageGetOpaque(wpage);
7528 tgl@sss.pgh.pa.us 1050 [ - + ]: 81 : Assert(wopaque->hasho_bucket == bucket);
2692 rhaas@postgresql.org 1051 : 81 : retain_pin = false;
1052 : :
1053 : : /* be tidy */
2603 1054 [ + + ]: 1107 : for (i = 0; i < nitups; i++)
2603 rhaas@postgresql.org 1055 :GBC 1026 : pfree(itups[i]);
2603 rhaas@postgresql.org 1056 :CBC 81 : nitups = 0;
1057 : 81 : all_tups_size = 0;
1058 : 81 : ndeletable = 0;
1059 : :
1060 : : /*
1061 : : * after moving the tuples, rpage would have been compacted,
1062 : : * so we need to rescan it.
1063 : : */
1064 [ + + ]: 81 : if (tups_moved)
2603 rhaas@postgresql.org 1065 :GBC 3 : goto readpage;
1066 : : }
1067 : :
1068 : : /* remember tuple for deletion from "read" page */
5278 tgl@sss.pgh.pa.us 1069 :CBC 2486 : deletable[ndeletable++] = roffnum;
1070 : :
1071 : : /*
1072 : : * we need a copy of index tuples as they can be freed as part of
1073 : : * overflow page, however we need them to write a WAL record in
1074 : : * _hash_freeovflpage.
1075 : : */
2603 rhaas@postgresql.org 1076 : 2486 : itups[nitups] = CopyIndexTuple(itup);
1077 : 2486 : tups_size[nitups++] = itemsz;
1078 : 2486 : all_tups_size += itemsz;
1079 : : }
1080 : :
1081 : : /*
1082 : : * If we reach here, there are no live tuples on the "read" page ---
1083 : : * it was empty when we got to it, or we moved them all. So we can
1084 : : * just free the page without bothering with deleting tuples
1085 : : * individually. Then advance to the previous "read" page.
1086 : : *
1087 : : * Tricky point here: if our read and write pages are adjacent in the
1088 : : * bucket chain, our write lock on wbuf will conflict with
1089 : : * _hash_freeovflpage's attempt to update the sibling links of the
1090 : : * removed page. In that case, we don't need to lock it again.
1091 : : */
5278 tgl@sss.pgh.pa.us 1092 : 93 : rblkno = ropaque->hasho_prevblkno;
1093 [ - + ]: 93 : Assert(BlockNumberIsValid(rblkno));
1094 : :
1095 : : /* free this overflow page (releases rbuf) */
2603 rhaas@postgresql.org 1096 : 93 : _hash_freeovflpage(rel, bucket_buf, rbuf, wbuf, itups, itup_offsets,
1097 : : tups_size, nitups, bstrategy);
1098 : :
1099 : : /* be tidy */
1100 [ + + ]: 1553 : for (i = 0; i < nitups; i++)
1101 : 1460 : pfree(itups[i]);
1102 : :
1103 : : /* are we freeing the page adjacent to wbuf? */
5278 tgl@sss.pgh.pa.us 1104 [ + + ]: 93 : if (rblkno == wblkno)
1105 : : {
1106 : : /* retain the pin on primary bucket page till end of bucket scan */
2673 rhaas@postgresql.org 1107 [ + + ]: 30 : if (wblkno == bucket_blkno)
2669 1108 : 21 : LockBuffer(wbuf, BUFFER_LOCK_UNLOCK);
1109 : : else
2673 1110 : 9 : _hash_relbuf(rel, wbuf);
5278 tgl@sss.pgh.pa.us 1111 : 30 : return;
1112 : : }
1113 : :
1114 : 63 : rbuf = _hash_getbuf_with_strategy(rel,
1115 : : rblkno,
1116 : : HASH_WRITE,
1117 : : LH_OVERFLOW_PAGE,
1118 : : bstrategy);
2916 kgrittn@postgresql.o 1119 : 63 : rpage = BufferGetPage(rbuf);
744 michael@paquier.xyz 1120 : 63 : ropaque = HashPageGetOpaque(rpage);
5278 tgl@sss.pgh.pa.us 1121 [ - + ]: 63 : Assert(ropaque->hasho_bucket == bucket);
1122 : : }
1123 : :
1124 : : /* NOTREACHED */
1125 : : }
|