/src/postgres/src/backend/access/hash/hashutil.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * hashutil.c |
4 | | * Utility code for Postgres hash implementation. |
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/hash/hashutil.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/hash.h" |
18 | | #include "access/reloptions.h" |
19 | | #include "access/relscan.h" |
20 | | #include "port/pg_bitutils.h" |
21 | | #include "utils/lsyscache.h" |
22 | | #include "utils/rel.h" |
23 | | |
24 | | #define CALC_NEW_BUCKET(old_bucket, lowmask) \ |
25 | 0 | old_bucket | (lowmask + 1) |
26 | | |
27 | | /* |
28 | | * _hash_checkqual -- does the index tuple satisfy the scan conditions? |
29 | | */ |
30 | | bool |
31 | | _hash_checkqual(IndexScanDesc scan, IndexTuple itup) |
32 | 0 | { |
33 | | /* |
34 | | * Currently, we can't check any of the scan conditions since we do not |
35 | | * have the original index entry value to supply to the sk_func. Always |
36 | | * return true; we expect that hashgettuple already set the recheck flag |
37 | | * to make the main indexscan code do it. |
38 | | */ |
39 | | #ifdef NOT_USED |
40 | | TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); |
41 | | ScanKey key = scan->keyData; |
42 | | int scanKeySize = scan->numberOfKeys; |
43 | | |
44 | | while (scanKeySize > 0) |
45 | | { |
46 | | Datum datum; |
47 | | bool isNull; |
48 | | Datum test; |
49 | | |
50 | | datum = index_getattr(itup, |
51 | | key->sk_attno, |
52 | | tupdesc, |
53 | | &isNull); |
54 | | |
55 | | /* assume sk_func is strict */ |
56 | | if (isNull) |
57 | | return false; |
58 | | if (key->sk_flags & SK_ISNULL) |
59 | | return false; |
60 | | |
61 | | test = FunctionCall2Coll(&key->sk_func, key->sk_collation, |
62 | | datum, key->sk_argument); |
63 | | |
64 | | if (!DatumGetBool(test)) |
65 | | return false; |
66 | | |
67 | | key++; |
68 | | scanKeySize--; |
69 | | } |
70 | | #endif |
71 | |
|
72 | 0 | return true; |
73 | 0 | } |
74 | | |
75 | | /* |
76 | | * _hash_datum2hashkey -- given a Datum, call the index's hash function |
77 | | * |
78 | | * The Datum is assumed to be of the index's column type, so we can use the |
79 | | * "primary" hash function that's tracked for us by the generic index code. |
80 | | */ |
81 | | uint32 |
82 | | _hash_datum2hashkey(Relation rel, Datum key) |
83 | 0 | { |
84 | 0 | FmgrInfo *procinfo; |
85 | 0 | Oid collation; |
86 | | |
87 | | /* XXX assumes index has only one attribute */ |
88 | 0 | procinfo = index_getprocinfo(rel, 1, HASHSTANDARD_PROC); |
89 | 0 | collation = rel->rd_indcollation[0]; |
90 | |
|
91 | 0 | return DatumGetUInt32(FunctionCall1Coll(procinfo, collation, key)); |
92 | 0 | } |
93 | | |
94 | | /* |
95 | | * _hash_datum2hashkey_type -- given a Datum of a specified type, |
96 | | * hash it in a fashion compatible with this index |
97 | | * |
98 | | * This is much more expensive than _hash_datum2hashkey, so use it only in |
99 | | * cross-type situations. |
100 | | */ |
101 | | uint32 |
102 | | _hash_datum2hashkey_type(Relation rel, Datum key, Oid keytype) |
103 | 0 | { |
104 | 0 | RegProcedure hash_proc; |
105 | 0 | Oid collation; |
106 | | |
107 | | /* XXX assumes index has only one attribute */ |
108 | 0 | hash_proc = get_opfamily_proc(rel->rd_opfamily[0], |
109 | 0 | keytype, |
110 | 0 | keytype, |
111 | 0 | HASHSTANDARD_PROC); |
112 | 0 | if (!RegProcedureIsValid(hash_proc)) |
113 | 0 | elog(ERROR, "missing support function %d(%u,%u) for index \"%s\"", |
114 | 0 | HASHSTANDARD_PROC, keytype, keytype, |
115 | 0 | RelationGetRelationName(rel)); |
116 | 0 | collation = rel->rd_indcollation[0]; |
117 | |
|
118 | 0 | return DatumGetUInt32(OidFunctionCall1Coll(hash_proc, collation, key)); |
119 | 0 | } |
120 | | |
121 | | /* |
122 | | * _hash_hashkey2bucket -- determine which bucket the hashkey maps to. |
123 | | */ |
124 | | Bucket |
125 | | _hash_hashkey2bucket(uint32 hashkey, uint32 maxbucket, |
126 | | uint32 highmask, uint32 lowmask) |
127 | 0 | { |
128 | 0 | Bucket bucket; |
129 | |
|
130 | 0 | bucket = hashkey & highmask; |
131 | 0 | if (bucket > maxbucket) |
132 | 0 | bucket = bucket & lowmask; |
133 | |
|
134 | 0 | return bucket; |
135 | 0 | } |
136 | | |
137 | | /* |
138 | | * _hash_spareindex -- returns spare index / global splitpoint phase of the |
139 | | * bucket |
140 | | */ |
141 | | uint32 |
142 | | _hash_spareindex(uint32 num_bucket) |
143 | 0 | { |
144 | 0 | uint32 splitpoint_group; |
145 | 0 | uint32 splitpoint_phases; |
146 | |
|
147 | 0 | splitpoint_group = pg_ceil_log2_32(num_bucket); |
148 | |
|
149 | 0 | if (splitpoint_group < HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE) |
150 | 0 | return splitpoint_group; |
151 | | |
152 | | /* account for single-phase groups */ |
153 | 0 | splitpoint_phases = HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE; |
154 | | |
155 | | /* account for multi-phase groups before splitpoint_group */ |
156 | 0 | splitpoint_phases += |
157 | 0 | ((splitpoint_group - HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE) << |
158 | 0 | HASH_SPLITPOINT_PHASE_BITS); |
159 | | |
160 | | /* account for phases within current group */ |
161 | 0 | splitpoint_phases += |
162 | 0 | (((num_bucket - 1) >> |
163 | 0 | (splitpoint_group - (HASH_SPLITPOINT_PHASE_BITS + 1))) & |
164 | 0 | HASH_SPLITPOINT_PHASE_MASK); /* to 0-based value. */ |
165 | |
|
166 | 0 | return splitpoint_phases; |
167 | 0 | } |
168 | | |
169 | | /* |
170 | | * _hash_get_totalbuckets -- returns total number of buckets allocated till |
171 | | * the given splitpoint phase. |
172 | | */ |
173 | | uint32 |
174 | | _hash_get_totalbuckets(uint32 splitpoint_phase) |
175 | 0 | { |
176 | 0 | uint32 splitpoint_group; |
177 | 0 | uint32 total_buckets; |
178 | 0 | uint32 phases_within_splitpoint_group; |
179 | |
|
180 | 0 | if (splitpoint_phase < HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE) |
181 | 0 | return (1 << splitpoint_phase); |
182 | | |
183 | | /* get splitpoint's group */ |
184 | 0 | splitpoint_group = HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE; |
185 | 0 | splitpoint_group += |
186 | 0 | ((splitpoint_phase - HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE) >> |
187 | 0 | HASH_SPLITPOINT_PHASE_BITS); |
188 | | |
189 | | /* account for buckets before splitpoint_group */ |
190 | 0 | total_buckets = (1 << (splitpoint_group - 1)); |
191 | | |
192 | | /* account for buckets within splitpoint_group */ |
193 | 0 | phases_within_splitpoint_group = |
194 | 0 | (((splitpoint_phase - HASH_SPLITPOINT_GROUPS_WITH_ONE_PHASE) & |
195 | 0 | HASH_SPLITPOINT_PHASE_MASK) + 1); /* from 0-based to 1-based */ |
196 | 0 | total_buckets += |
197 | 0 | (((1 << (splitpoint_group - 1)) >> HASH_SPLITPOINT_PHASE_BITS) * |
198 | 0 | phases_within_splitpoint_group); |
199 | |
|
200 | 0 | return total_buckets; |
201 | 0 | } |
202 | | |
203 | | /* |
204 | | * _hash_checkpage -- sanity checks on the format of all hash pages |
205 | | * |
206 | | * If flags is not zero, it is a bitwise OR of the acceptable page types |
207 | | * (values of hasho_flag & LH_PAGE_TYPE). |
208 | | */ |
209 | | void |
210 | | _hash_checkpage(Relation rel, Buffer buf, int flags) |
211 | 0 | { |
212 | 0 | Page page = BufferGetPage(buf); |
213 | | |
214 | | /* |
215 | | * ReadBuffer verifies that every newly-read page passes |
216 | | * PageHeaderIsValid, which means it either contains a reasonably sane |
217 | | * page header or is all-zero. We have to defend against the all-zero |
218 | | * case, however. |
219 | | */ |
220 | 0 | if (PageIsNew(page)) |
221 | 0 | ereport(ERROR, |
222 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
223 | 0 | errmsg("index \"%s\" contains unexpected zero page at block %u", |
224 | 0 | RelationGetRelationName(rel), |
225 | 0 | BufferGetBlockNumber(buf)), |
226 | 0 | errhint("Please REINDEX it."))); |
227 | | |
228 | | /* |
229 | | * Additionally check that the special area looks sane. |
230 | | */ |
231 | 0 | if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData))) |
232 | 0 | ereport(ERROR, |
233 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
234 | 0 | errmsg("index \"%s\" contains corrupted page at block %u", |
235 | 0 | RelationGetRelationName(rel), |
236 | 0 | BufferGetBlockNumber(buf)), |
237 | 0 | errhint("Please REINDEX it."))); |
238 | | |
239 | 0 | if (flags) |
240 | 0 | { |
241 | 0 | HashPageOpaque opaque = HashPageGetOpaque(page); |
242 | |
|
243 | 0 | if ((opaque->hasho_flag & flags) == 0) |
244 | 0 | ereport(ERROR, |
245 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
246 | 0 | errmsg("index \"%s\" contains corrupted page at block %u", |
247 | 0 | RelationGetRelationName(rel), |
248 | 0 | BufferGetBlockNumber(buf)), |
249 | 0 | errhint("Please REINDEX it."))); |
250 | 0 | } |
251 | | |
252 | | /* |
253 | | * When checking the metapage, also verify magic number and version. |
254 | | */ |
255 | 0 | if (flags == LH_META_PAGE) |
256 | 0 | { |
257 | 0 | HashMetaPage metap = HashPageGetMeta(page); |
258 | |
|
259 | 0 | if (metap->hashm_magic != HASH_MAGIC) |
260 | 0 | ereport(ERROR, |
261 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
262 | 0 | errmsg("index \"%s\" is not a hash index", |
263 | 0 | RelationGetRelationName(rel)))); |
264 | | |
265 | 0 | if (metap->hashm_version != HASH_VERSION) |
266 | 0 | ereport(ERROR, |
267 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
268 | 0 | errmsg("index \"%s\" has wrong hash version", |
269 | 0 | RelationGetRelationName(rel)), |
270 | 0 | errhint("Please REINDEX it."))); |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | bytea * |
275 | | hashoptions(Datum reloptions, bool validate) |
276 | 0 | { |
277 | 0 | static const relopt_parse_elt tab[] = { |
278 | 0 | {"fillfactor", RELOPT_TYPE_INT, offsetof(HashOptions, fillfactor)}, |
279 | 0 | }; |
280 | |
|
281 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
282 | 0 | RELOPT_KIND_HASH, |
283 | 0 | sizeof(HashOptions), |
284 | 0 | tab, lengthof(tab)); |
285 | 0 | } |
286 | | |
287 | | /* |
288 | | * _hash_get_indextuple_hashkey - get the hash index tuple's hash key value |
289 | | */ |
290 | | uint32 |
291 | | _hash_get_indextuple_hashkey(IndexTuple itup) |
292 | 0 | { |
293 | 0 | char *attp; |
294 | | |
295 | | /* |
296 | | * We assume the hash key is the first attribute and can't be null, so |
297 | | * this can be done crudely but very very cheaply ... |
298 | | */ |
299 | 0 | attp = (char *) itup + IndexInfoFindDataOffset(itup->t_info); |
300 | 0 | return *((uint32 *) attp); |
301 | 0 | } |
302 | | |
303 | | /* |
304 | | * _hash_convert_tuple - convert raw index data to hash key |
305 | | * |
306 | | * Inputs: values and isnull arrays for the user data column(s) |
307 | | * Outputs: values and isnull arrays for the index tuple, suitable for |
308 | | * passing to index_form_tuple(). |
309 | | * |
310 | | * Returns true if successful, false if not (because there are null values). |
311 | | * On a false result, the given data need not be indexed. |
312 | | * |
313 | | * Note: callers know that the index-column arrays are always of length 1. |
314 | | * In principle, there could be more than one input column, though we do not |
315 | | * currently support that. |
316 | | */ |
317 | | bool |
318 | | _hash_convert_tuple(Relation index, |
319 | | const Datum *user_values, const bool *user_isnull, |
320 | | Datum *index_values, bool *index_isnull) |
321 | 0 | { |
322 | 0 | uint32 hashkey; |
323 | | |
324 | | /* |
325 | | * We do not insert null values into hash indexes. This is okay because |
326 | | * the only supported search operator is '=', and we assume it is strict. |
327 | | */ |
328 | 0 | if (user_isnull[0]) |
329 | 0 | return false; |
330 | | |
331 | 0 | hashkey = _hash_datum2hashkey(index, user_values[0]); |
332 | 0 | index_values[0] = UInt32GetDatum(hashkey); |
333 | 0 | index_isnull[0] = false; |
334 | 0 | return true; |
335 | 0 | } |
336 | | |
337 | | /* |
338 | | * _hash_binsearch - Return the offset number in the page where the |
339 | | * specified hash value should be sought or inserted. |
340 | | * |
341 | | * We use binary search, relying on the assumption that the existing entries |
342 | | * are ordered by hash key. |
343 | | * |
344 | | * Returns the offset of the first index entry having hashkey >= hash_value, |
345 | | * or the page's max offset plus one if hash_value is greater than all |
346 | | * existing hash keys in the page. This is the appropriate place to start |
347 | | * a search, or to insert a new item. |
348 | | */ |
349 | | OffsetNumber |
350 | | _hash_binsearch(Page page, uint32 hash_value) |
351 | 0 | { |
352 | 0 | OffsetNumber upper; |
353 | 0 | OffsetNumber lower; |
354 | | |
355 | | /* Loop invariant: lower <= desired place <= upper */ |
356 | 0 | upper = PageGetMaxOffsetNumber(page) + 1; |
357 | 0 | lower = FirstOffsetNumber; |
358 | |
|
359 | 0 | while (upper > lower) |
360 | 0 | { |
361 | 0 | OffsetNumber off; |
362 | 0 | IndexTuple itup; |
363 | 0 | uint32 hashkey; |
364 | |
|
365 | 0 | off = (upper + lower) / 2; |
366 | 0 | Assert(OffsetNumberIsValid(off)); |
367 | |
|
368 | 0 | itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); |
369 | 0 | hashkey = _hash_get_indextuple_hashkey(itup); |
370 | 0 | if (hashkey < hash_value) |
371 | 0 | lower = off + 1; |
372 | 0 | else |
373 | 0 | upper = off; |
374 | 0 | } |
375 | |
|
376 | 0 | return lower; |
377 | 0 | } |
378 | | |
379 | | /* |
380 | | * _hash_binsearch_last |
381 | | * |
382 | | * Same as above, except that if there are multiple matching items in the |
383 | | * page, we return the offset of the last one instead of the first one, |
384 | | * and the possible range of outputs is 0..maxoffset not 1..maxoffset+1. |
385 | | * This is handy for starting a new page in a backwards scan. |
386 | | */ |
387 | | OffsetNumber |
388 | | _hash_binsearch_last(Page page, uint32 hash_value) |
389 | 0 | { |
390 | 0 | OffsetNumber upper; |
391 | 0 | OffsetNumber lower; |
392 | | |
393 | | /* Loop invariant: lower <= desired place <= upper */ |
394 | 0 | upper = PageGetMaxOffsetNumber(page); |
395 | 0 | lower = FirstOffsetNumber - 1; |
396 | |
|
397 | 0 | while (upper > lower) |
398 | 0 | { |
399 | 0 | IndexTuple itup; |
400 | 0 | OffsetNumber off; |
401 | 0 | uint32 hashkey; |
402 | |
|
403 | 0 | off = (upper + lower + 1) / 2; |
404 | 0 | Assert(OffsetNumberIsValid(off)); |
405 | |
|
406 | 0 | itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); |
407 | 0 | hashkey = _hash_get_indextuple_hashkey(itup); |
408 | 0 | if (hashkey > hash_value) |
409 | 0 | upper = off - 1; |
410 | 0 | else |
411 | 0 | lower = off; |
412 | 0 | } |
413 | |
|
414 | 0 | return lower; |
415 | 0 | } |
416 | | |
417 | | /* |
418 | | * _hash_get_oldblock_from_newbucket() -- get the block number of a bucket |
419 | | * from which current (new) bucket is being split. |
420 | | */ |
421 | | BlockNumber |
422 | | _hash_get_oldblock_from_newbucket(Relation rel, Bucket new_bucket) |
423 | 0 | { |
424 | 0 | Bucket old_bucket; |
425 | 0 | uint32 mask; |
426 | 0 | Buffer metabuf; |
427 | 0 | HashMetaPage metap; |
428 | 0 | BlockNumber blkno; |
429 | | |
430 | | /* |
431 | | * To get the old bucket from the current bucket, we need a mask to modulo |
432 | | * into lower half of table. This mask is stored in meta page as |
433 | | * hashm_lowmask, but here we can't rely on the same, because we need a |
434 | | * value of lowmask that was prevalent at the time when bucket split was |
435 | | * started. Masking the most significant bit of new bucket would give us |
436 | | * old bucket. |
437 | | */ |
438 | 0 | mask = (((uint32) 1) << pg_leftmost_one_pos32(new_bucket)) - 1; |
439 | 0 | old_bucket = new_bucket & mask; |
440 | |
|
441 | 0 | metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE); |
442 | 0 | metap = HashPageGetMeta(BufferGetPage(metabuf)); |
443 | |
|
444 | 0 | blkno = BUCKET_TO_BLKNO(metap, old_bucket); |
445 | |
|
446 | 0 | _hash_relbuf(rel, metabuf); |
447 | |
|
448 | 0 | return blkno; |
449 | 0 | } |
450 | | |
451 | | /* |
452 | | * _hash_get_newblock_from_oldbucket() -- get the block number of a bucket |
453 | | * that will be generated after split from old bucket. |
454 | | * |
455 | | * This is used to find the new bucket from old bucket based on current table |
456 | | * half. It is mainly required to finish the incomplete splits where we are |
457 | | * sure that not more than one bucket could have split in progress from old |
458 | | * bucket. |
459 | | */ |
460 | | BlockNumber |
461 | | _hash_get_newblock_from_oldbucket(Relation rel, Bucket old_bucket) |
462 | 0 | { |
463 | 0 | Bucket new_bucket; |
464 | 0 | Buffer metabuf; |
465 | 0 | HashMetaPage metap; |
466 | 0 | BlockNumber blkno; |
467 | |
|
468 | 0 | metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE); |
469 | 0 | metap = HashPageGetMeta(BufferGetPage(metabuf)); |
470 | |
|
471 | 0 | new_bucket = _hash_get_newbucket_from_oldbucket(rel, old_bucket, |
472 | 0 | metap->hashm_lowmask, |
473 | 0 | metap->hashm_maxbucket); |
474 | 0 | blkno = BUCKET_TO_BLKNO(metap, new_bucket); |
475 | |
|
476 | 0 | _hash_relbuf(rel, metabuf); |
477 | |
|
478 | 0 | return blkno; |
479 | 0 | } |
480 | | |
481 | | /* |
482 | | * _hash_get_newbucket_from_oldbucket() -- get the new bucket that will be |
483 | | * generated after split from current (old) bucket. |
484 | | * |
485 | | * This is used to find the new bucket from old bucket. New bucket can be |
486 | | * obtained by OR'ing old bucket with most significant bit of current table |
487 | | * half (lowmask passed in this function can be used to identify msb of |
488 | | * current table half). There could be multiple buckets that could have |
489 | | * been split from current bucket. We need the first such bucket that exists. |
490 | | * Caller must ensure that no more than one split has happened from old |
491 | | * bucket. |
492 | | */ |
493 | | Bucket |
494 | | _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket, |
495 | | uint32 lowmask, uint32 maxbucket) |
496 | 0 | { |
497 | 0 | Bucket new_bucket; |
498 | |
|
499 | 0 | new_bucket = CALC_NEW_BUCKET(old_bucket, lowmask); |
500 | 0 | if (new_bucket > maxbucket) |
501 | 0 | { |
502 | 0 | lowmask = lowmask >> 1; |
503 | 0 | new_bucket = CALC_NEW_BUCKET(old_bucket, lowmask); |
504 | 0 | } |
505 | |
|
506 | 0 | return new_bucket; |
507 | 0 | } |
508 | | |
509 | | /* |
510 | | * _hash_kill_items - set LP_DEAD state for items an indexscan caller has |
511 | | * told us were killed. |
512 | | * |
513 | | * scan->opaque, referenced locally through so, contains information about the |
514 | | * current page and killed tuples thereon (generally, this should only be |
515 | | * called if so->numKilled > 0). |
516 | | * |
517 | | * The caller does not have a lock on the page and may or may not have the |
518 | | * page pinned in a buffer. Note that read-lock is sufficient for setting |
519 | | * LP_DEAD status (which is only a hint). |
520 | | * |
521 | | * The caller must have pin on bucket buffer, but may or may not have pin |
522 | | * on overflow buffer, as indicated by HashScanPosIsPinned(so->currPos). |
523 | | * |
524 | | * We match items by heap TID before assuming they are the right ones to |
525 | | * delete. |
526 | | * |
527 | | * There are never any scans active in a bucket at the time VACUUM begins, |
528 | | * because VACUUM takes a cleanup lock on the primary bucket page and scans |
529 | | * hold a pin. A scan can begin after VACUUM leaves the primary bucket page |
530 | | * but before it finishes the entire bucket, but it can never pass VACUUM, |
531 | | * because VACUUM always locks the next page before releasing the lock on |
532 | | * the previous one. Therefore, we don't have to worry about accidentally |
533 | | * killing a TID that has been reused for an unrelated tuple. |
534 | | */ |
535 | | void |
536 | | _hash_kill_items(IndexScanDesc scan) |
537 | 0 | { |
538 | 0 | HashScanOpaque so = (HashScanOpaque) scan->opaque; |
539 | 0 | Relation rel = scan->indexRelation; |
540 | 0 | BlockNumber blkno; |
541 | 0 | Buffer buf; |
542 | 0 | Page page; |
543 | 0 | HashPageOpaque opaque; |
544 | 0 | OffsetNumber offnum, |
545 | 0 | maxoff; |
546 | 0 | int numKilled = so->numKilled; |
547 | 0 | int i; |
548 | 0 | bool killedsomething = false; |
549 | 0 | bool havePin = false; |
550 | |
|
551 | 0 | Assert(so->numKilled > 0); |
552 | 0 | Assert(so->killedItems != NULL); |
553 | 0 | Assert(HashScanPosIsValid(so->currPos)); |
554 | | |
555 | | /* |
556 | | * Always reset the scan state, so we don't look for same items on other |
557 | | * pages. |
558 | | */ |
559 | 0 | so->numKilled = 0; |
560 | |
|
561 | 0 | blkno = so->currPos.currPage; |
562 | 0 | if (HashScanPosIsPinned(so->currPos)) |
563 | 0 | { |
564 | | /* |
565 | | * We already have pin on this buffer, so, all we need to do is |
566 | | * acquire lock on it. |
567 | | */ |
568 | 0 | havePin = true; |
569 | 0 | buf = so->currPos.buf; |
570 | 0 | LockBuffer(buf, BUFFER_LOCK_SHARE); |
571 | 0 | } |
572 | 0 | else |
573 | 0 | buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE); |
574 | |
|
575 | 0 | page = BufferGetPage(buf); |
576 | 0 | opaque = HashPageGetOpaque(page); |
577 | 0 | maxoff = PageGetMaxOffsetNumber(page); |
578 | |
|
579 | 0 | for (i = 0; i < numKilled; i++) |
580 | 0 | { |
581 | 0 | int itemIndex = so->killedItems[i]; |
582 | 0 | HashScanPosItem *currItem = &so->currPos.items[itemIndex]; |
583 | |
|
584 | 0 | offnum = currItem->indexOffset; |
585 | |
|
586 | 0 | Assert(itemIndex >= so->currPos.firstItem && |
587 | 0 | itemIndex <= so->currPos.lastItem); |
588 | |
|
589 | 0 | while (offnum <= maxoff) |
590 | 0 | { |
591 | 0 | ItemId iid = PageGetItemId(page, offnum); |
592 | 0 | IndexTuple ituple = (IndexTuple) PageGetItem(page, iid); |
593 | |
|
594 | 0 | if (ItemPointerEquals(&ituple->t_tid, &currItem->heapTid)) |
595 | 0 | { |
596 | 0 | if (!killedsomething) |
597 | 0 | { |
598 | | /* |
599 | | * Use the hint bit infrastructure to check if we can |
600 | | * update the page while just holding a share lock. If we |
601 | | * are not allowed, there's no point continuing. |
602 | | */ |
603 | 0 | if (!BufferBeginSetHintBits(buf)) |
604 | 0 | goto unlock_page; |
605 | 0 | } |
606 | | |
607 | | /* found the item */ |
608 | 0 | ItemIdMarkDead(iid); |
609 | 0 | killedsomething = true; |
610 | 0 | break; /* out of inner search loop */ |
611 | 0 | } |
612 | 0 | offnum = OffsetNumberNext(offnum); |
613 | 0 | } |
614 | 0 | } |
615 | | |
616 | | /* |
617 | | * Since this can be redone later if needed, mark as dirty hint. Whenever |
618 | | * we mark anything LP_DEAD, we also set the page's |
619 | | * LH_PAGE_HAS_DEAD_TUPLES flag, which is likewise just a hint. |
620 | | */ |
621 | 0 | if (killedsomething) |
622 | 0 | { |
623 | 0 | opaque->hasho_flag |= LH_PAGE_HAS_DEAD_TUPLES; |
624 | 0 | BufferFinishSetHintBits(buf, true, true); |
625 | 0 | } |
626 | |
|
627 | 0 | unlock_page: |
628 | 0 | if (havePin) |
629 | 0 | LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK); |
630 | 0 | else |
631 | 0 | _hash_relbuf(rel, buf); |
632 | 0 | } |