/src/postgres/src/backend/access/nbtree/nbtutils.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nbtutils.c |
4 | | * Utility code for Postgres btree 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/nbtree/nbtutils.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | |
16 | | #include "postgres.h" |
17 | | |
18 | | #include <time.h> |
19 | | |
20 | | #include "access/nbtree.h" |
21 | | #include "access/reloptions.h" |
22 | | #include "access/relscan.h" |
23 | | #include "commands/progress.h" |
24 | | #include "common/int.h" |
25 | | #include "lib/qunique.h" |
26 | | #include "miscadmin.h" |
27 | | #include "storage/lwlock.h" |
28 | | #include "storage/subsystems.h" |
29 | | #include "utils/datum.h" |
30 | | #include "utils/lsyscache.h" |
31 | | #include "utils/rel.h" |
32 | | |
33 | | |
34 | | static int _bt_compare_int(const void *va, const void *vb); |
35 | | static int _bt_keep_natts(Relation rel, IndexTuple lastleft, |
36 | | IndexTuple firstright, BTScanInsert itup_key); |
37 | | |
38 | | |
39 | | /* |
40 | | * _bt_mkscankey |
41 | | * Build an insertion scan key that contains comparison data from itup |
42 | | * as well as comparator routines appropriate to the key datatypes. |
43 | | * |
44 | | * The result is intended for use with _bt_compare() and _bt_truncate(). |
45 | | * Callers that don't need to fill out the insertion scankey arguments |
46 | | * (e.g. they use an ad-hoc comparison routine, or only need a scankey |
47 | | * for _bt_truncate()) can pass a NULL index tuple. The scankey will |
48 | | * be initialized as if an "all truncated" pivot tuple was passed |
49 | | * instead. |
50 | | * |
51 | | * Note that we may occasionally have to share lock the metapage to |
52 | | * determine whether or not the keys in the index are expected to be |
53 | | * unique (i.e. if this is a "heapkeyspace" index). We assume a |
54 | | * heapkeyspace index when caller passes a NULL tuple, allowing index |
55 | | * build callers to avoid accessing the non-existent metapage. We |
56 | | * also assume that the index is _not_ allequalimage when a NULL tuple |
57 | | * is passed; CREATE INDEX callers call _bt_allequalimage() to set the |
58 | | * field themselves. |
59 | | */ |
60 | | BTScanInsert |
61 | | _bt_mkscankey(Relation rel, IndexTuple itup) |
62 | 0 | { |
63 | 0 | BTScanInsert key; |
64 | 0 | ScanKey skey; |
65 | 0 | TupleDesc itupdesc; |
66 | 0 | int indnkeyatts; |
67 | 0 | int16 *indoption; |
68 | 0 | int tupnatts; |
69 | 0 | int i; |
70 | |
|
71 | 0 | itupdesc = RelationGetDescr(rel); |
72 | 0 | indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); |
73 | 0 | indoption = rel->rd_indoption; |
74 | 0 | tupnatts = itup ? BTreeTupleGetNAtts(itup, rel) : 0; |
75 | |
|
76 | 0 | Assert(tupnatts <= IndexRelationGetNumberOfAttributes(rel)); |
77 | | |
78 | | /* |
79 | | * We'll execute search using scan key constructed on key columns. |
80 | | * Truncated attributes and non-key attributes are omitted from the final |
81 | | * scan key. |
82 | | */ |
83 | 0 | key = palloc(offsetof(BTScanInsertData, scankeys) + |
84 | 0 | sizeof(ScanKeyData) * indnkeyatts); |
85 | 0 | if (itup) |
86 | 0 | _bt_metaversion(rel, &key->heapkeyspace, &key->allequalimage); |
87 | 0 | else |
88 | 0 | { |
89 | | /* Utility statement callers can set these fields themselves */ |
90 | 0 | key->heapkeyspace = true; |
91 | 0 | key->allequalimage = false; |
92 | 0 | } |
93 | 0 | key->anynullkeys = false; /* initial assumption */ |
94 | 0 | key->nextkey = false; /* usual case, required by btinsert */ |
95 | 0 | key->backward = false; /* usual case, required by btinsert */ |
96 | 0 | key->keysz = Min(indnkeyatts, tupnatts); |
97 | 0 | key->scantid = key->heapkeyspace && itup ? |
98 | 0 | BTreeTupleGetHeapTID(itup) : NULL; |
99 | 0 | skey = key->scankeys; |
100 | 0 | for (i = 0; i < indnkeyatts; i++) |
101 | 0 | { |
102 | 0 | FmgrInfo *procinfo; |
103 | 0 | Datum arg; |
104 | 0 | bool null; |
105 | 0 | int flags; |
106 | | |
107 | | /* |
108 | | * We can use the cached (default) support procs since no cross-type |
109 | | * comparison can be needed. |
110 | | */ |
111 | 0 | procinfo = index_getprocinfo(rel, i + 1, BTORDER_PROC); |
112 | | |
113 | | /* |
114 | | * Key arguments built from truncated attributes (or when caller |
115 | | * provides no tuple) are defensively represented as NULL values. They |
116 | | * should never be used. |
117 | | */ |
118 | 0 | if (i < tupnatts) |
119 | 0 | arg = index_getattr(itup, i + 1, itupdesc, &null); |
120 | 0 | else |
121 | 0 | { |
122 | 0 | arg = (Datum) 0; |
123 | 0 | null = true; |
124 | 0 | } |
125 | 0 | flags = (null ? SK_ISNULL : 0) | (indoption[i] << SK_BT_INDOPTION_SHIFT); |
126 | 0 | ScanKeyEntryInitializeWithInfo(&skey[i], |
127 | 0 | flags, |
128 | 0 | (AttrNumber) (i + 1), |
129 | 0 | InvalidStrategy, |
130 | 0 | InvalidOid, |
131 | 0 | rel->rd_indcollation[i], |
132 | 0 | procinfo, |
133 | 0 | arg); |
134 | | /* Record if any key attribute is NULL (or truncated) */ |
135 | 0 | if (null) |
136 | 0 | key->anynullkeys = true; |
137 | 0 | } |
138 | | |
139 | | /* |
140 | | * In NULLS NOT DISTINCT mode, we pretend that there are no null keys, so |
141 | | * that full uniqueness check is done. |
142 | | */ |
143 | 0 | if (rel->rd_index->indnullsnotdistinct) |
144 | 0 | key->anynullkeys = false; |
145 | |
|
146 | 0 | return key; |
147 | 0 | } |
148 | | |
149 | | /* |
150 | | * qsort comparison function for int arrays |
151 | | */ |
152 | | static int |
153 | | _bt_compare_int(const void *va, const void *vb) |
154 | 0 | { |
155 | 0 | int a = *((const int *) va); |
156 | 0 | int b = *((const int *) vb); |
157 | |
|
158 | 0 | return pg_cmp_s32(a, b); |
159 | 0 | } |
160 | | |
161 | | /* |
162 | | * _bt_killitems - set LP_DEAD state for items an indexscan caller has |
163 | | * told us were killed |
164 | | * |
165 | | * scan->opaque, referenced locally through so, contains information about the |
166 | | * current page and killed tuples thereon (generally, this should only be |
167 | | * called if so->numKilled > 0). |
168 | | * |
169 | | * Caller should not have a lock on the so->currPos page, but must hold a |
170 | | * buffer pin when !so->dropPin. When we return, it still won't be locked. |
171 | | * It'll continue to hold whatever pins were held before calling here. |
172 | | * |
173 | | * We match items by heap TID before assuming they are the right ones to set |
174 | | * LP_DEAD. If the scan is one that holds a buffer pin on the target page |
175 | | * continuously from initially reading the items until applying this function |
176 | | * (if it is a !so->dropPin scan), VACUUM cannot have deleted any items on the |
177 | | * page, so the page's TIDs can't have been recycled by now. There's no risk |
178 | | * that we'll confuse a new index tuple that happens to use a recycled TID |
179 | | * with a now-removed tuple with the same TID (that used to be on this same |
180 | | * page). We can't rely on that during scans that drop buffer pins eagerly |
181 | | * (so->dropPin scans), though, so we must condition setting LP_DEAD bits on |
182 | | * the page LSN having not changed since back when _bt_readpage saw the page. |
183 | | * We totally give up on setting LP_DEAD bits when the page LSN changed. |
184 | | * |
185 | | * We give up much less often during !so->dropPin scans, but it still happens. |
186 | | * We cope with cases where items have moved right due to insertions. If an |
187 | | * item has moved off the current page due to a split, we'll fail to find it |
188 | | * and just give up on it. |
189 | | */ |
190 | | void |
191 | | _bt_killitems(IndexScanDesc scan) |
192 | 0 | { |
193 | 0 | Relation rel = scan->indexRelation; |
194 | 0 | BTScanOpaque so = (BTScanOpaque) scan->opaque; |
195 | 0 | Page page; |
196 | 0 | BTPageOpaque opaque; |
197 | 0 | OffsetNumber minoff; |
198 | 0 | OffsetNumber maxoff; |
199 | 0 | int numKilled = so->numKilled; |
200 | 0 | bool killedsomething = false; |
201 | 0 | Buffer buf; |
202 | |
|
203 | 0 | Assert(numKilled > 0); |
204 | 0 | Assert(BTScanPosIsValid(so->currPos)); |
205 | 0 | Assert(scan->heapRelation != NULL); /* can't be a bitmap index scan */ |
206 | | |
207 | | /* Always invalidate so->killedItems[] before leaving so->currPos */ |
208 | 0 | so->numKilled = 0; |
209 | | |
210 | | /* |
211 | | * We need to iterate through so->killedItems[] in leaf page order; the |
212 | | * loop below expects this (when marking posting list tuples, at least). |
213 | | * so->killedItems[] is now in whatever order the scan returned items in. |
214 | | * Scrollable cursor scans might have even saved the same item/TID twice. |
215 | | * |
216 | | * Sort and unique-ify so->killedItems[] to deal with all this. |
217 | | */ |
218 | 0 | if (numKilled > 1) |
219 | 0 | { |
220 | 0 | qsort(so->killedItems, numKilled, sizeof(int), _bt_compare_int); |
221 | 0 | numKilled = qunique(so->killedItems, numKilled, sizeof(int), |
222 | 0 | _bt_compare_int); |
223 | 0 | } |
224 | |
|
225 | 0 | if (!so->dropPin) |
226 | 0 | { |
227 | | /* |
228 | | * We have held the pin on this page since we read the index tuples, |
229 | | * so all we need to do is lock it. The pin will have prevented |
230 | | * concurrent VACUUMs from recycling any of the TIDs on the page. |
231 | | */ |
232 | 0 | Assert(BTScanPosIsPinned(so->currPos)); |
233 | 0 | buf = so->currPos.buf; |
234 | 0 | _bt_lockbuf(rel, buf, BT_READ); |
235 | 0 | } |
236 | 0 | else |
237 | 0 | { |
238 | 0 | XLogRecPtr latestlsn; |
239 | |
|
240 | 0 | Assert(!BTScanPosIsPinned(so->currPos)); |
241 | 0 | buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ); |
242 | |
|
243 | 0 | latestlsn = BufferGetLSNAtomic(buf); |
244 | 0 | Assert(so->currPos.lsn <= latestlsn); |
245 | 0 | if (so->currPos.lsn != latestlsn) |
246 | 0 | { |
247 | | /* Modified, give up on hinting */ |
248 | 0 | _bt_relbuf(rel, buf); |
249 | 0 | return; |
250 | 0 | } |
251 | | |
252 | | /* Unmodified, hinting is safe */ |
253 | 0 | } |
254 | | |
255 | 0 | page = BufferGetPage(buf); |
256 | 0 | opaque = BTPageGetOpaque(page); |
257 | 0 | minoff = P_FIRSTDATAKEY(opaque); |
258 | 0 | maxoff = PageGetMaxOffsetNumber(page); |
259 | | |
260 | | /* Iterate through so->killedItems[] in leaf page order */ |
261 | 0 | for (int i = 0; i < numKilled; i++) |
262 | 0 | { |
263 | 0 | int itemIndex = so->killedItems[i]; |
264 | 0 | BTScanPosItem *kitem = &so->currPos.items[itemIndex]; |
265 | 0 | OffsetNumber offnum = kitem->indexOffset; |
266 | |
|
267 | 0 | Assert(itemIndex >= so->currPos.firstItem && |
268 | 0 | itemIndex <= so->currPos.lastItem); |
269 | 0 | Assert(i == 0 || |
270 | 0 | offnum >= so->currPos.items[so->killedItems[i - 1]].indexOffset); |
271 | |
|
272 | 0 | if (offnum < minoff) |
273 | 0 | continue; /* pure paranoia */ |
274 | 0 | while (offnum <= maxoff) |
275 | 0 | { |
276 | 0 | ItemId iid = PageGetItemId(page, offnum); |
277 | 0 | IndexTuple ituple = (IndexTuple) PageGetItem(page, iid); |
278 | 0 | bool killtuple = false; |
279 | |
|
280 | 0 | if (BTreeTupleIsPosting(ituple)) |
281 | 0 | { |
282 | 0 | int pi = i + 1; |
283 | 0 | int nposting = BTreeTupleGetNPosting(ituple); |
284 | 0 | int j; |
285 | | |
286 | | /* |
287 | | * Note that the page may have been modified in almost any way |
288 | | * since we first read it (in the !so->dropPin case), so it's |
289 | | * possible that this posting list tuple wasn't a posting list |
290 | | * tuple when we first encountered its heap TIDs. |
291 | | */ |
292 | 0 | for (j = 0; j < nposting; j++) |
293 | 0 | { |
294 | 0 | ItemPointer item = BTreeTupleGetPostingN(ituple, j); |
295 | |
|
296 | 0 | if (!ItemPointerEquals(item, &kitem->heapTid)) |
297 | 0 | break; /* out of posting list loop */ |
298 | | |
299 | | /* |
300 | | * kitem must have matching offnum when heap TIDs match, |
301 | | * though only in the common case where the page can't |
302 | | * have been concurrently modified |
303 | | */ |
304 | 0 | Assert(kitem->indexOffset == offnum || !so->dropPin); |
305 | | |
306 | | /* |
307 | | * Read-ahead to later kitems here. |
308 | | * |
309 | | * We rely on the assumption that not advancing kitem here |
310 | | * will prevent us from considering the posting list tuple |
311 | | * fully dead by not matching its next heap TID in next |
312 | | * loop iteration. |
313 | | * |
314 | | * If, on the other hand, this is the final heap TID in |
315 | | * the posting list tuple, then tuple gets killed |
316 | | * regardless (i.e. we handle the case where the last |
317 | | * kitem is also the last heap TID in the last index tuple |
318 | | * correctly -- posting tuple still gets killed). |
319 | | */ |
320 | 0 | if (pi < numKilled) |
321 | 0 | kitem = &so->currPos.items[so->killedItems[pi++]]; |
322 | 0 | } |
323 | | |
324 | | /* |
325 | | * Don't bother advancing the outermost loop's int iterator to |
326 | | * avoid processing killed items that relate to the same |
327 | | * offnum/posting list tuple. This micro-optimization hardly |
328 | | * seems worth it. (Further iterations of the outermost loop |
329 | | * will fail to match on this same posting list's first heap |
330 | | * TID instead, so we'll advance to the next offnum/index |
331 | | * tuple pretty quickly.) |
332 | | */ |
333 | 0 | if (j == nposting) |
334 | 0 | killtuple = true; |
335 | 0 | } |
336 | 0 | else if (ItemPointerEquals(&ituple->t_tid, &kitem->heapTid)) |
337 | 0 | killtuple = true; |
338 | | |
339 | | /* |
340 | | * Mark index item as dead, if it isn't already. Since this |
341 | | * happens while holding a buffer lock possibly in shared mode, |
342 | | * it's possible that multiple processes attempt to do this |
343 | | * simultaneously, leading to multiple full-page images being sent |
344 | | * to WAL (if wal_log_hints or data checksums are enabled), which |
345 | | * is undesirable. |
346 | | */ |
347 | 0 | if (killtuple && !ItemIdIsDead(iid)) |
348 | 0 | { |
349 | 0 | if (!killedsomething) |
350 | 0 | { |
351 | | /* |
352 | | * Use the hint bit infrastructure to check if we can |
353 | | * update the page while just holding a share lock. If we |
354 | | * are not allowed, there's no point continuing. |
355 | | */ |
356 | 0 | if (!BufferBeginSetHintBits(buf)) |
357 | 0 | goto unlock_page; |
358 | 0 | } |
359 | | |
360 | | /* found the item/all posting list items */ |
361 | 0 | ItemIdMarkDead(iid); |
362 | 0 | killedsomething = true; |
363 | 0 | break; /* out of inner search loop */ |
364 | 0 | } |
365 | 0 | offnum = OffsetNumberNext(offnum); |
366 | 0 | } |
367 | 0 | } |
368 | | |
369 | | /* |
370 | | * Since this can be redone later if needed, mark as dirty hint. |
371 | | * |
372 | | * Whenever we mark anything LP_DEAD, we also set the page's |
373 | | * BTP_HAS_GARBAGE flag, which is likewise just a hint. (Note that we |
374 | | * only rely on the page-level flag in !heapkeyspace indexes.) |
375 | | */ |
376 | 0 | if (killedsomething) |
377 | 0 | { |
378 | 0 | opaque->btpo_flags |= BTP_HAS_GARBAGE; |
379 | 0 | BufferFinishSetHintBits(buf, true, true); |
380 | 0 | } |
381 | |
|
382 | 0 | unlock_page: |
383 | 0 | if (!so->dropPin) |
384 | 0 | _bt_unlockbuf(rel, buf); |
385 | 0 | else |
386 | 0 | _bt_relbuf(rel, buf); |
387 | 0 | } |
388 | | |
389 | | |
390 | | /* |
391 | | * The following routines manage a shared-memory area in which we track |
392 | | * assignment of "vacuum cycle IDs" to currently-active btree vacuuming |
393 | | * operations. There is a single counter which increments each time we |
394 | | * start a vacuum to assign it a cycle ID. Since multiple vacuums could |
395 | | * be active concurrently, we have to track the cycle ID for each active |
396 | | * vacuum; this requires at most MaxBackends entries (usually far fewer). |
397 | | * We assume at most one vacuum can be active for a given index. |
398 | | * |
399 | | * Access to the shared memory area is controlled by BtreeVacuumLock. |
400 | | * In principle we could use a separate lmgr locktag for each index, |
401 | | * but a single LWLock is much cheaper, and given the short time that |
402 | | * the lock is ever held, the concurrency hit should be minimal. |
403 | | */ |
404 | | |
405 | | typedef struct BTOneVacInfo |
406 | | { |
407 | | LockRelId relid; /* global identifier of an index */ |
408 | | BTCycleId cycleid; /* cycle ID for its active VACUUM */ |
409 | | } BTOneVacInfo; |
410 | | |
411 | | typedef struct BTVacInfo |
412 | | { |
413 | | BTCycleId cycle_ctr; /* cycle ID most recently assigned */ |
414 | | int num_vacuums; /* number of currently active VACUUMs */ |
415 | | int max_vacuums; /* allocated length of vacuums[] array */ |
416 | | BTOneVacInfo vacuums[FLEXIBLE_ARRAY_MEMBER]; |
417 | | } BTVacInfo; |
418 | | |
419 | | static BTVacInfo *btvacinfo; |
420 | | |
421 | | static void BTreeShmemRequest(void *arg); |
422 | | static void BTreeShmemInit(void *arg); |
423 | | |
424 | | const ShmemCallbacks BTreeShmemCallbacks = { |
425 | | .request_fn = BTreeShmemRequest, |
426 | | .init_fn = BTreeShmemInit, |
427 | | }; |
428 | | |
429 | | /* |
430 | | * _bt_vacuum_cycleid --- get the active vacuum cycle ID for an index, |
431 | | * or zero if there is no active VACUUM |
432 | | * |
433 | | * Note: for correct interlocking, the caller must already hold pin and |
434 | | * exclusive lock on each buffer it will store the cycle ID into. This |
435 | | * ensures that even if a VACUUM starts immediately afterwards, it cannot |
436 | | * process those pages until the page split is complete. |
437 | | */ |
438 | | BTCycleId |
439 | | _bt_vacuum_cycleid(Relation rel) |
440 | 0 | { |
441 | 0 | BTCycleId result = 0; |
442 | 0 | int i; |
443 | | |
444 | | /* Share lock is enough since this is a read-only operation */ |
445 | 0 | LWLockAcquire(BtreeVacuumLock, LW_SHARED); |
446 | |
|
447 | 0 | for (i = 0; i < btvacinfo->num_vacuums; i++) |
448 | 0 | { |
449 | 0 | BTOneVacInfo *vac = &btvacinfo->vacuums[i]; |
450 | |
|
451 | 0 | if (vac->relid.relId == rel->rd_lockInfo.lockRelId.relId && |
452 | 0 | vac->relid.dbId == rel->rd_lockInfo.lockRelId.dbId) |
453 | 0 | { |
454 | 0 | result = vac->cycleid; |
455 | 0 | break; |
456 | 0 | } |
457 | 0 | } |
458 | |
|
459 | 0 | LWLockRelease(BtreeVacuumLock); |
460 | 0 | return result; |
461 | 0 | } |
462 | | |
463 | | /* |
464 | | * _bt_start_vacuum --- assign a cycle ID to a just-starting VACUUM operation |
465 | | * |
466 | | * Note: the caller must guarantee that it will eventually call |
467 | | * _bt_end_vacuum, else we'll permanently leak an array slot. To ensure |
468 | | * that this happens even in elog(FATAL) scenarios, the appropriate coding |
469 | | * is not just a PG_TRY, but |
470 | | * PG_ENSURE_ERROR_CLEANUP(_bt_end_vacuum_callback, PointerGetDatum(rel)) |
471 | | */ |
472 | | BTCycleId |
473 | | _bt_start_vacuum(Relation rel) |
474 | 0 | { |
475 | 0 | BTCycleId result; |
476 | 0 | int i; |
477 | 0 | BTOneVacInfo *vac; |
478 | |
|
479 | 0 | LWLockAcquire(BtreeVacuumLock, LW_EXCLUSIVE); |
480 | | |
481 | | /* |
482 | | * Assign the next cycle ID, being careful to avoid zero as well as the |
483 | | * reserved high values. |
484 | | */ |
485 | 0 | result = ++(btvacinfo->cycle_ctr); |
486 | 0 | if (result == 0 || result > MAX_BT_CYCLE_ID) |
487 | 0 | result = btvacinfo->cycle_ctr = 1; |
488 | | |
489 | | /* Let's just make sure there's no entry already for this index */ |
490 | 0 | for (i = 0; i < btvacinfo->num_vacuums; i++) |
491 | 0 | { |
492 | 0 | vac = &btvacinfo->vacuums[i]; |
493 | 0 | if (vac->relid.relId == rel->rd_lockInfo.lockRelId.relId && |
494 | 0 | vac->relid.dbId == rel->rd_lockInfo.lockRelId.dbId) |
495 | 0 | { |
496 | | /* |
497 | | * Unlike most places in the backend, we have to explicitly |
498 | | * release our LWLock before throwing an error. This is because |
499 | | * we expect _bt_end_vacuum() to be called before transaction |
500 | | * abort cleanup can run to release LWLocks. |
501 | | */ |
502 | 0 | LWLockRelease(BtreeVacuumLock); |
503 | 0 | elog(ERROR, "multiple active vacuums for index \"%s\"", |
504 | 0 | RelationGetRelationName(rel)); |
505 | 0 | } |
506 | 0 | } |
507 | | |
508 | | /* OK, add an entry */ |
509 | 0 | if (btvacinfo->num_vacuums >= btvacinfo->max_vacuums) |
510 | 0 | { |
511 | 0 | LWLockRelease(BtreeVacuumLock); |
512 | 0 | elog(ERROR, "out of btvacinfo slots"); |
513 | 0 | } |
514 | 0 | vac = &btvacinfo->vacuums[btvacinfo->num_vacuums]; |
515 | 0 | vac->relid = rel->rd_lockInfo.lockRelId; |
516 | 0 | vac->cycleid = result; |
517 | 0 | btvacinfo->num_vacuums++; |
518 | |
|
519 | 0 | LWLockRelease(BtreeVacuumLock); |
520 | 0 | return result; |
521 | 0 | } |
522 | | |
523 | | /* |
524 | | * _bt_end_vacuum --- mark a btree VACUUM operation as done |
525 | | * |
526 | | * Note: this is deliberately coded not to complain if no entry is found; |
527 | | * this allows the caller to put PG_TRY around the start_vacuum operation. |
528 | | */ |
529 | | void |
530 | | _bt_end_vacuum(Relation rel) |
531 | 0 | { |
532 | 0 | int i; |
533 | |
|
534 | 0 | LWLockAcquire(BtreeVacuumLock, LW_EXCLUSIVE); |
535 | | |
536 | | /* Find the array entry */ |
537 | 0 | for (i = 0; i < btvacinfo->num_vacuums; i++) |
538 | 0 | { |
539 | 0 | BTOneVacInfo *vac = &btvacinfo->vacuums[i]; |
540 | |
|
541 | 0 | if (vac->relid.relId == rel->rd_lockInfo.lockRelId.relId && |
542 | 0 | vac->relid.dbId == rel->rd_lockInfo.lockRelId.dbId) |
543 | 0 | { |
544 | | /* Remove it by shifting down the last entry */ |
545 | 0 | *vac = btvacinfo->vacuums[btvacinfo->num_vacuums - 1]; |
546 | 0 | btvacinfo->num_vacuums--; |
547 | 0 | break; |
548 | 0 | } |
549 | 0 | } |
550 | |
|
551 | 0 | LWLockRelease(BtreeVacuumLock); |
552 | 0 | } |
553 | | |
554 | | /* |
555 | | * _bt_end_vacuum wrapped as an on_shmem_exit callback function |
556 | | */ |
557 | | void |
558 | | _bt_end_vacuum_callback(int code, Datum arg) |
559 | 0 | { |
560 | 0 | _bt_end_vacuum((Relation) DatumGetPointer(arg)); |
561 | 0 | } |
562 | | |
563 | | /* |
564 | | * BTreeShmemRequest --- register this module's shared memory |
565 | | */ |
566 | | static void |
567 | | BTreeShmemRequest(void *arg) |
568 | 0 | { |
569 | 0 | Size size; |
570 | |
|
571 | 0 | size = offsetof(BTVacInfo, vacuums); |
572 | 0 | size = add_size(size, mul_size(MaxBackends, sizeof(BTOneVacInfo))); |
573 | |
|
574 | 0 | ShmemRequestStruct(.name = "BTree Vacuum State", |
575 | 0 | .size = size, |
576 | 0 | .ptr = (void **) &btvacinfo, |
577 | 0 | ); |
578 | 0 | } |
579 | | |
580 | | /* |
581 | | * BTreeShmemInit --- initialize this module's shared memory |
582 | | */ |
583 | | static void |
584 | | BTreeShmemInit(void *arg) |
585 | 0 | { |
586 | | /* |
587 | | * It doesn't really matter what the cycle counter starts at, but having |
588 | | * it always start the same doesn't seem good. Seed with low-order bits |
589 | | * of time() instead. |
590 | | */ |
591 | 0 | btvacinfo->cycle_ctr = (BTCycleId) time(NULL); |
592 | |
|
593 | 0 | btvacinfo->num_vacuums = 0; |
594 | 0 | btvacinfo->max_vacuums = MaxBackends; |
595 | 0 | } |
596 | | |
597 | | bytea * |
598 | | btoptions(Datum reloptions, bool validate) |
599 | 0 | { |
600 | 0 | static const relopt_parse_elt tab[] = { |
601 | 0 | {"fillfactor", RELOPT_TYPE_INT, offsetof(BTOptions, fillfactor)}, |
602 | 0 | {"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL, |
603 | 0 | offsetof(BTOptions, vacuum_cleanup_index_scale_factor)}, |
604 | 0 | {"deduplicate_items", RELOPT_TYPE_BOOL, |
605 | 0 | offsetof(BTOptions, deduplicate_items)} |
606 | 0 | }; |
607 | |
|
608 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
609 | 0 | RELOPT_KIND_BTREE, |
610 | 0 | sizeof(BTOptions), |
611 | 0 | tab, lengthof(tab)); |
612 | 0 | } |
613 | | |
614 | | /* |
615 | | * btproperty() -- Check boolean properties of indexes. |
616 | | * |
617 | | * This is optional, but handling AMPROP_RETURNABLE here saves opening the rel |
618 | | * to call btcanreturn. |
619 | | */ |
620 | | bool |
621 | | btproperty(Oid index_oid, int attno, |
622 | | IndexAMProperty prop, const char *propname, |
623 | | bool *res, bool *isnull) |
624 | 0 | { |
625 | 0 | switch (prop) |
626 | 0 | { |
627 | 0 | case AMPROP_RETURNABLE: |
628 | | /* answer only for columns, not AM or whole index */ |
629 | 0 | if (attno == 0) |
630 | 0 | return false; |
631 | | /* otherwise, btree can always return data */ |
632 | 0 | *res = true; |
633 | 0 | return true; |
634 | | |
635 | 0 | default: |
636 | 0 | return false; /* punt to generic code */ |
637 | 0 | } |
638 | 0 | } |
639 | | |
640 | | /* |
641 | | * btbuildphasename() -- Return name of index build phase. |
642 | | */ |
643 | | char * |
644 | | btbuildphasename(int64 phasenum) |
645 | 0 | { |
646 | 0 | switch (phasenum) |
647 | 0 | { |
648 | 0 | case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: |
649 | 0 | return "initializing"; |
650 | 0 | case PROGRESS_BTREE_PHASE_INDEXBUILD_TABLESCAN: |
651 | 0 | return "scanning table"; |
652 | 0 | case PROGRESS_BTREE_PHASE_PERFORMSORT_1: |
653 | 0 | return "sorting live tuples"; |
654 | 0 | case PROGRESS_BTREE_PHASE_PERFORMSORT_2: |
655 | 0 | return "sorting dead tuples"; |
656 | 0 | case PROGRESS_BTREE_PHASE_LEAF_LOAD: |
657 | 0 | return "loading tuples in tree"; |
658 | 0 | default: |
659 | 0 | return NULL; |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | /* |
664 | | * _bt_truncate() -- create tuple without unneeded suffix attributes. |
665 | | * |
666 | | * Returns truncated pivot index tuple allocated in caller's memory context, |
667 | | * with key attributes copied from caller's firstright argument. If rel is |
668 | | * an INCLUDE index, non-key attributes will definitely be truncated away, |
669 | | * since they're not part of the key space. More aggressive suffix |
670 | | * truncation can take place when it's clear that the returned tuple does not |
671 | | * need one or more suffix key attributes. We only need to keep firstright |
672 | | * attributes up to and including the first non-lastleft-equal attribute. |
673 | | * Caller's insertion scankey is used to compare the tuples; the scankey's |
674 | | * argument values are not considered here. |
675 | | * |
676 | | * Note that returned tuple's t_tid offset will hold the number of attributes |
677 | | * present, so the original item pointer offset is not represented. Caller |
678 | | * should only change truncated tuple's downlink. Note also that truncated |
679 | | * key attributes are treated as containing "minus infinity" values by |
680 | | * _bt_compare(). |
681 | | * |
682 | | * In the worst case (when a heap TID must be appended to distinguish lastleft |
683 | | * from firstright), the size of the returned tuple is the size of firstright |
684 | | * plus the size of an additional MAXALIGN()'d item pointer. This guarantee |
685 | | * is important, since callers need to stay under the 1/3 of a page |
686 | | * restriction on tuple size. If this routine is ever taught to truncate |
687 | | * within an attribute/datum, it will need to avoid returning an enlarged |
688 | | * tuple to caller when truncation + TOAST compression ends up enlarging the |
689 | | * final datum. |
690 | | */ |
691 | | IndexTuple |
692 | | _bt_truncate(Relation rel, IndexTuple lastleft, IndexTuple firstright, |
693 | | BTScanInsert itup_key) |
694 | 0 | { |
695 | 0 | TupleDesc itupdesc = RelationGetDescr(rel); |
696 | 0 | int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); |
697 | 0 | int keepnatts; |
698 | 0 | IndexTuple pivot; |
699 | 0 | IndexTuple tidpivot; |
700 | 0 | ItemPointer pivotheaptid; |
701 | 0 | Size newsize; |
702 | | |
703 | | /* |
704 | | * We should only ever truncate non-pivot tuples from leaf pages. It's |
705 | | * never okay to truncate when splitting an internal page. |
706 | | */ |
707 | 0 | Assert(!BTreeTupleIsPivot(lastleft) && !BTreeTupleIsPivot(firstright)); |
708 | | |
709 | | /* Determine how many attributes must be kept in truncated tuple */ |
710 | 0 | keepnatts = _bt_keep_natts(rel, lastleft, firstright, itup_key); |
711 | |
|
712 | | #ifdef DEBUG_NO_TRUNCATE |
713 | | /* Force truncation to be ineffective for testing purposes */ |
714 | | keepnatts = nkeyatts + 1; |
715 | | #endif |
716 | |
|
717 | 0 | pivot = index_truncate_tuple(itupdesc, firstright, |
718 | 0 | Min(keepnatts, nkeyatts)); |
719 | |
|
720 | 0 | if (BTreeTupleIsPosting(pivot)) |
721 | 0 | { |
722 | | /* |
723 | | * index_truncate_tuple() just returns a straight copy of firstright |
724 | | * when it has no attributes to truncate. When that happens, we may |
725 | | * need to truncate away a posting list here instead. |
726 | | */ |
727 | 0 | Assert(keepnatts == nkeyatts || keepnatts == nkeyatts + 1); |
728 | 0 | Assert(IndexRelationGetNumberOfAttributes(rel) == nkeyatts); |
729 | 0 | pivot->t_info &= ~INDEX_SIZE_MASK; |
730 | 0 | pivot->t_info |= MAXALIGN(BTreeTupleGetPostingOffset(firstright)); |
731 | 0 | } |
732 | | |
733 | | /* |
734 | | * If there is a distinguishing key attribute within pivot tuple, we're |
735 | | * done |
736 | | */ |
737 | 0 | if (keepnatts <= nkeyatts) |
738 | 0 | { |
739 | 0 | BTreeTupleSetNAtts(pivot, keepnatts, false); |
740 | 0 | return pivot; |
741 | 0 | } |
742 | | |
743 | | /* |
744 | | * We have to store a heap TID in the new pivot tuple, since no non-TID |
745 | | * key attribute value in firstright distinguishes the right side of the |
746 | | * split from the left side. nbtree conceptualizes this case as an |
747 | | * inability to truncate away any key attributes, since heap TID is |
748 | | * treated as just another key attribute (despite lacking a pg_attribute |
749 | | * entry). |
750 | | * |
751 | | * Use enlarged space that holds a copy of pivot. We need the extra space |
752 | | * to store a heap TID at the end (using the special pivot tuple |
753 | | * representation). Note that the original pivot already has firstright's |
754 | | * possible posting list/non-key attribute values removed at this point. |
755 | | */ |
756 | 0 | newsize = MAXALIGN(IndexTupleSize(pivot)) + MAXALIGN(sizeof(ItemPointerData)); |
757 | 0 | tidpivot = palloc0(newsize); |
758 | 0 | memcpy(tidpivot, pivot, MAXALIGN(IndexTupleSize(pivot))); |
759 | | /* Cannot leak memory here */ |
760 | 0 | pfree(pivot); |
761 | | |
762 | | /* |
763 | | * Store all of firstright's key attribute values plus a tiebreaker heap |
764 | | * TID value in enlarged pivot tuple |
765 | | */ |
766 | 0 | tidpivot->t_info &= ~INDEX_SIZE_MASK; |
767 | 0 | tidpivot->t_info |= newsize; |
768 | 0 | BTreeTupleSetNAtts(tidpivot, nkeyatts, true); |
769 | 0 | pivotheaptid = BTreeTupleGetHeapTID(tidpivot); |
770 | | |
771 | | /* |
772 | | * Lehman & Yao use lastleft as the leaf high key in all cases, but don't |
773 | | * consider suffix truncation. It seems like a good idea to follow that |
774 | | * example in cases where no truncation takes place -- use lastleft's heap |
775 | | * TID. (This is also the closest value to negative infinity that's |
776 | | * legally usable.) |
777 | | */ |
778 | 0 | ItemPointerCopy(BTreeTupleGetMaxHeapTID(lastleft), pivotheaptid); |
779 | | |
780 | | /* |
781 | | * We're done. Assert() that heap TID invariants hold before returning. |
782 | | * |
783 | | * Lehman and Yao require that the downlink to the right page, which is to |
784 | | * be inserted into the parent page in the second phase of a page split be |
785 | | * a strict lower bound on items on the right page, and a non-strict upper |
786 | | * bound for items on the left page. Assert that heap TIDs follow these |
787 | | * invariants, since a heap TID value is apparently needed as a |
788 | | * tiebreaker. |
789 | | */ |
790 | 0 | #ifndef DEBUG_NO_TRUNCATE |
791 | 0 | Assert(ItemPointerCompare(BTreeTupleGetMaxHeapTID(lastleft), |
792 | 0 | BTreeTupleGetHeapTID(firstright)) < 0); |
793 | 0 | Assert(ItemPointerCompare(pivotheaptid, |
794 | 0 | BTreeTupleGetHeapTID(lastleft)) >= 0); |
795 | 0 | Assert(ItemPointerCompare(pivotheaptid, |
796 | 0 | BTreeTupleGetHeapTID(firstright)) < 0); |
797 | | #else |
798 | | |
799 | | /* |
800 | | * Those invariants aren't guaranteed to hold for lastleft + firstright |
801 | | * heap TID attribute values when they're considered here only because |
802 | | * DEBUG_NO_TRUNCATE is defined (a heap TID is probably not actually |
803 | | * needed as a tiebreaker). DEBUG_NO_TRUNCATE must therefore use a heap |
804 | | * TID value that always works as a strict lower bound for items to the |
805 | | * right. In particular, it must avoid using firstright's leading key |
806 | | * attribute values along with lastleft's heap TID value when lastleft's |
807 | | * TID happens to be greater than firstright's TID. |
808 | | */ |
809 | | ItemPointerCopy(BTreeTupleGetHeapTID(firstright), pivotheaptid); |
810 | | |
811 | | /* |
812 | | * Pivot heap TID should never be fully equal to firstright. Note that |
813 | | * the pivot heap TID will still end up equal to lastleft's heap TID when |
814 | | * that's the only usable value. |
815 | | */ |
816 | | ItemPointerSetOffsetNumber(pivotheaptid, |
817 | | OffsetNumberPrev(ItemPointerGetOffsetNumber(pivotheaptid))); |
818 | | Assert(ItemPointerCompare(pivotheaptid, |
819 | | BTreeTupleGetHeapTID(firstright)) < 0); |
820 | | #endif |
821 | |
|
822 | 0 | return tidpivot; |
823 | 0 | } |
824 | | |
825 | | /* |
826 | | * _bt_keep_natts - how many key attributes to keep when truncating. |
827 | | * |
828 | | * Caller provides two tuples that enclose a split point. Caller's insertion |
829 | | * scankey is used to compare the tuples; the scankey's argument values are |
830 | | * not considered here. |
831 | | * |
832 | | * This can return a number of attributes that is one greater than the |
833 | | * number of key attributes for the index relation. This indicates that the |
834 | | * caller must use a heap TID as a unique-ifier in new pivot tuple. |
835 | | */ |
836 | | static int |
837 | | _bt_keep_natts(Relation rel, IndexTuple lastleft, IndexTuple firstright, |
838 | | BTScanInsert itup_key) |
839 | 0 | { |
840 | 0 | int nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); |
841 | 0 | TupleDesc itupdesc = RelationGetDescr(rel); |
842 | 0 | int keepnatts; |
843 | 0 | ScanKey scankey; |
844 | | |
845 | | /* |
846 | | * _bt_compare() treats truncated key attributes as having the value minus |
847 | | * infinity, which would break searches within !heapkeyspace indexes. We |
848 | | * must still truncate away non-key attribute values, though. |
849 | | */ |
850 | 0 | if (!itup_key->heapkeyspace) |
851 | 0 | return nkeyatts; |
852 | | |
853 | 0 | scankey = itup_key->scankeys; |
854 | 0 | keepnatts = 1; |
855 | 0 | for (int attnum = 1; attnum <= nkeyatts; attnum++, scankey++) |
856 | 0 | { |
857 | 0 | Datum datum1, |
858 | 0 | datum2; |
859 | 0 | bool isNull1, |
860 | 0 | isNull2; |
861 | |
|
862 | 0 | datum1 = index_getattr(lastleft, attnum, itupdesc, &isNull1); |
863 | 0 | datum2 = index_getattr(firstright, attnum, itupdesc, &isNull2); |
864 | |
|
865 | 0 | if (isNull1 != isNull2) |
866 | 0 | break; |
867 | | |
868 | 0 | if (!isNull1 && |
869 | 0 | DatumGetInt32(FunctionCall2Coll(&scankey->sk_func, |
870 | 0 | scankey->sk_collation, |
871 | 0 | datum1, |
872 | 0 | datum2)) != 0) |
873 | 0 | break; |
874 | | |
875 | 0 | keepnatts++; |
876 | 0 | } |
877 | | |
878 | | /* |
879 | | * Assert that _bt_keep_natts_fast() agrees with us in passing. This is |
880 | | * expected in an allequalimage index. |
881 | | */ |
882 | 0 | Assert(!itup_key->allequalimage || |
883 | 0 | keepnatts == _bt_keep_natts_fast(rel, lastleft, firstright)); |
884 | |
|
885 | 0 | return keepnatts; |
886 | 0 | } |
887 | | |
888 | | /* |
889 | | * _bt_keep_natts_fast - fast bitwise variant of _bt_keep_natts. |
890 | | * |
891 | | * This is exported so that a candidate split point can have its effect on |
892 | | * suffix truncation inexpensively evaluated ahead of time when finding a |
893 | | * split location. A naive bitwise approach to datum comparisons is used to |
894 | | * save cycles. |
895 | | * |
896 | | * The approach taken here usually provides the same answer as _bt_keep_natts |
897 | | * will (for the same pair of tuples from a heapkeyspace index), since the |
898 | | * majority of btree opclasses can never indicate that two datums are equal |
899 | | * unless they're bitwise equal after detoasting. When an index only has |
900 | | * "equal image" columns, routine is guaranteed to give the same result as |
901 | | * _bt_keep_natts would. |
902 | | * |
903 | | * Callers can rely on the fact that attributes considered equal here are |
904 | | * definitely also equal according to _bt_keep_natts, even when the index uses |
905 | | * an opclass or collation that is not "allequalimage"/deduplication-safe. |
906 | | * This weaker guarantee is good enough for nbtsplitloc.c caller, since false |
907 | | * negatives generally only have the effect of making leaf page splits use a |
908 | | * more balanced split point. |
909 | | */ |
910 | | int |
911 | | _bt_keep_natts_fast(Relation rel, IndexTuple lastleft, IndexTuple firstright) |
912 | 0 | { |
913 | 0 | TupleDesc itupdesc = RelationGetDescr(rel); |
914 | 0 | int keysz = IndexRelationGetNumberOfKeyAttributes(rel); |
915 | 0 | int keepnatts; |
916 | |
|
917 | 0 | keepnatts = 1; |
918 | 0 | for (int attnum = 1; attnum <= keysz; attnum++) |
919 | 0 | { |
920 | 0 | Datum datum1, |
921 | 0 | datum2; |
922 | 0 | bool isNull1, |
923 | 0 | isNull2; |
924 | 0 | CompactAttribute *att; |
925 | |
|
926 | 0 | datum1 = index_getattr(lastleft, attnum, itupdesc, &isNull1); |
927 | 0 | datum2 = index_getattr(firstright, attnum, itupdesc, &isNull2); |
928 | 0 | att = TupleDescCompactAttr(itupdesc, attnum - 1); |
929 | |
|
930 | 0 | if (isNull1 != isNull2) |
931 | 0 | break; |
932 | | |
933 | 0 | if (!isNull1 && |
934 | 0 | !datum_image_eq(datum1, datum2, att->attbyval, att->attlen)) |
935 | 0 | break; |
936 | | |
937 | 0 | keepnatts++; |
938 | 0 | } |
939 | |
|
940 | 0 | return keepnatts; |
941 | 0 | } |
942 | | |
943 | | /* |
944 | | * _bt_check_natts() -- Verify tuple has expected number of attributes. |
945 | | * |
946 | | * Returns value indicating if the expected number of attributes were found |
947 | | * for a particular offset on page. This can be used as a general purpose |
948 | | * sanity check. |
949 | | * |
950 | | * Testing a tuple directly with BTreeTupleGetNAtts() should generally be |
951 | | * preferred to calling here. That's usually more convenient, and is always |
952 | | * more explicit. Call here instead when offnum's tuple may be a negative |
953 | | * infinity tuple that uses the pre-v11 on-disk representation, or when a low |
954 | | * context check is appropriate. This routine is as strict as possible about |
955 | | * what is expected on each version of btree. |
956 | | */ |
957 | | bool |
958 | | _bt_check_natts(Relation rel, bool heapkeyspace, Page page, OffsetNumber offnum) |
959 | 0 | { |
960 | 0 | int16 natts = IndexRelationGetNumberOfAttributes(rel); |
961 | 0 | int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); |
962 | 0 | BTPageOpaque opaque = BTPageGetOpaque(page); |
963 | 0 | IndexTuple itup; |
964 | 0 | int tupnatts; |
965 | | |
966 | | /* |
967 | | * We cannot reliably test a deleted or half-dead page, since they have |
968 | | * dummy high keys |
969 | | */ |
970 | 0 | if (P_IGNORE(opaque)) |
971 | 0 | return true; |
972 | | |
973 | 0 | Assert(offnum >= FirstOffsetNumber && |
974 | 0 | offnum <= PageGetMaxOffsetNumber(page)); |
975 | |
|
976 | 0 | itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); |
977 | 0 | tupnatts = BTreeTupleGetNAtts(itup, rel); |
978 | | |
979 | | /* !heapkeyspace indexes do not support deduplication */ |
980 | 0 | if (!heapkeyspace && BTreeTupleIsPosting(itup)) |
981 | 0 | return false; |
982 | | |
983 | | /* Posting list tuples should never have "pivot heap TID" bit set */ |
984 | 0 | if (BTreeTupleIsPosting(itup) && |
985 | 0 | (ItemPointerGetOffsetNumberNoCheck(&itup->t_tid) & |
986 | 0 | BT_PIVOT_HEAP_TID_ATTR) != 0) |
987 | 0 | return false; |
988 | | |
989 | | /* INCLUDE indexes do not support deduplication */ |
990 | 0 | if (natts != nkeyatts && BTreeTupleIsPosting(itup)) |
991 | 0 | return false; |
992 | | |
993 | 0 | if (P_ISLEAF(opaque)) |
994 | 0 | { |
995 | 0 | if (offnum >= P_FIRSTDATAKEY(opaque)) |
996 | 0 | { |
997 | | /* |
998 | | * Non-pivot tuple should never be explicitly marked as a pivot |
999 | | * tuple |
1000 | | */ |
1001 | 0 | if (BTreeTupleIsPivot(itup)) |
1002 | 0 | return false; |
1003 | | |
1004 | | /* |
1005 | | * Leaf tuples that are not the page high key (non-pivot tuples) |
1006 | | * should never be truncated. (Note that tupnatts must have been |
1007 | | * inferred, even with a posting list tuple, because only pivot |
1008 | | * tuples store tupnatts directly.) |
1009 | | */ |
1010 | 0 | return tupnatts == natts; |
1011 | 0 | } |
1012 | 0 | else |
1013 | 0 | { |
1014 | | /* |
1015 | | * Rightmost page doesn't contain a page high key, so tuple was |
1016 | | * checked above as ordinary leaf tuple |
1017 | | */ |
1018 | 0 | Assert(!P_RIGHTMOST(opaque)); |
1019 | | |
1020 | | /* |
1021 | | * !heapkeyspace high key tuple contains only key attributes. Note |
1022 | | * that tupnatts will only have been explicitly represented in |
1023 | | * !heapkeyspace indexes that happen to have non-key attributes. |
1024 | | */ |
1025 | 0 | if (!heapkeyspace) |
1026 | 0 | return tupnatts == nkeyatts; |
1027 | | |
1028 | | /* Use generic heapkeyspace pivot tuple handling */ |
1029 | 0 | } |
1030 | 0 | } |
1031 | 0 | else /* !P_ISLEAF(opaque) */ |
1032 | 0 | { |
1033 | 0 | if (offnum == P_FIRSTDATAKEY(opaque)) |
1034 | 0 | { |
1035 | | /* |
1036 | | * The first tuple on any internal page (possibly the first after |
1037 | | * its high key) is its negative infinity tuple. Negative |
1038 | | * infinity tuples are always truncated to zero attributes. They |
1039 | | * are a particular kind of pivot tuple. |
1040 | | */ |
1041 | 0 | if (heapkeyspace) |
1042 | 0 | return tupnatts == 0; |
1043 | | |
1044 | | /* |
1045 | | * The number of attributes won't be explicitly represented if the |
1046 | | * negative infinity tuple was generated during a page split that |
1047 | | * occurred with a version of Postgres before v11. There must be |
1048 | | * a problem when there is an explicit representation that is |
1049 | | * non-zero, or when there is no explicit representation and the |
1050 | | * tuple is evidently not a pre-pg_upgrade tuple. |
1051 | | * |
1052 | | * Prior to v11, downlinks always had P_HIKEY as their offset. |
1053 | | * Accept that as an alternative indication of a valid |
1054 | | * !heapkeyspace negative infinity tuple. |
1055 | | */ |
1056 | 0 | return tupnatts == 0 || |
1057 | 0 | ItemPointerGetOffsetNumber(&(itup->t_tid)) == P_HIKEY; |
1058 | 0 | } |
1059 | 0 | else |
1060 | 0 | { |
1061 | | /* |
1062 | | * !heapkeyspace downlink tuple with separator key contains only |
1063 | | * key attributes. Note that tupnatts will only have been |
1064 | | * explicitly represented in !heapkeyspace indexes that happen to |
1065 | | * have non-key attributes. |
1066 | | */ |
1067 | 0 | if (!heapkeyspace) |
1068 | 0 | return tupnatts == nkeyatts; |
1069 | | |
1070 | | /* Use generic heapkeyspace pivot tuple handling */ |
1071 | 0 | } |
1072 | 0 | } |
1073 | | |
1074 | | /* Handle heapkeyspace pivot tuples (excluding minus infinity items) */ |
1075 | 0 | Assert(heapkeyspace); |
1076 | | |
1077 | | /* |
1078 | | * Explicit representation of the number of attributes is mandatory with |
1079 | | * heapkeyspace index pivot tuples, regardless of whether or not there are |
1080 | | * non-key attributes. |
1081 | | */ |
1082 | 0 | if (!BTreeTupleIsPivot(itup)) |
1083 | 0 | return false; |
1084 | | |
1085 | | /* Pivot tuple should not use posting list representation (redundant) */ |
1086 | 0 | if (BTreeTupleIsPosting(itup)) |
1087 | 0 | return false; |
1088 | | |
1089 | | /* |
1090 | | * Heap TID is a tiebreaker key attribute, so it cannot be untruncated |
1091 | | * when any other key attribute is truncated |
1092 | | */ |
1093 | 0 | if (BTreeTupleGetHeapTID(itup) != NULL && tupnatts != nkeyatts) |
1094 | 0 | return false; |
1095 | | |
1096 | | /* |
1097 | | * Pivot tuple must have at least one untruncated key attribute (minus |
1098 | | * infinity pivot tuples are the only exception). Pivot tuples can never |
1099 | | * represent that there is a value present for a key attribute that |
1100 | | * exceeds pg_index.indnkeyatts for the index. |
1101 | | */ |
1102 | 0 | return tupnatts > 0 && tupnatts <= nkeyatts; |
1103 | 0 | } |
1104 | | |
1105 | | /* |
1106 | | * |
1107 | | * _bt_check_third_page() -- check whether tuple fits on a btree page at all. |
1108 | | * |
1109 | | * We actually need to be able to fit three items on every page, so restrict |
1110 | | * any one item to 1/3 the per-page available space. Note that itemsz should |
1111 | | * not include the ItemId overhead. |
1112 | | * |
1113 | | * It might be useful to apply TOAST methods rather than throw an error here. |
1114 | | * Using out of line storage would break assumptions made by suffix truncation |
1115 | | * and by contrib/amcheck, though. |
1116 | | */ |
1117 | | void |
1118 | | _bt_check_third_page(Relation rel, Relation heap, bool needheaptidspace, |
1119 | | Page page, IndexTuple newtup) |
1120 | 0 | { |
1121 | 0 | Size itemsz; |
1122 | 0 | BTPageOpaque opaque; |
1123 | |
|
1124 | 0 | itemsz = MAXALIGN(IndexTupleSize(newtup)); |
1125 | | |
1126 | | /* Double check item size against limit */ |
1127 | 0 | if (itemsz <= BTMaxItemSize) |
1128 | 0 | return; |
1129 | | |
1130 | | /* |
1131 | | * Tuple is probably too large to fit on page, but it's possible that the |
1132 | | * index uses version 2 or version 3, or that page is an internal page, in |
1133 | | * which case a slightly higher limit applies. |
1134 | | */ |
1135 | 0 | if (!needheaptidspace && itemsz <= BTMaxItemSizeNoHeapTid) |
1136 | 0 | return; |
1137 | | |
1138 | | /* |
1139 | | * Internal page insertions cannot fail here, because that would mean that |
1140 | | * an earlier leaf level insertion that should have failed didn't |
1141 | | */ |
1142 | 0 | opaque = BTPageGetOpaque(page); |
1143 | 0 | if (!P_ISLEAF(opaque)) |
1144 | 0 | elog(ERROR, "cannot insert oversized tuple of size %zu on internal page of index \"%s\"", |
1145 | 0 | itemsz, RelationGetRelationName(rel)); |
1146 | | |
1147 | 0 | ereport(ERROR, |
1148 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
1149 | 0 | errmsg("index row size %zu exceeds btree version %u maximum %zu for index \"%s\"", |
1150 | 0 | itemsz, |
1151 | 0 | needheaptidspace ? BTREE_VERSION : BTREE_NOVAC_VERSION, |
1152 | 0 | needheaptidspace ? BTMaxItemSize : BTMaxItemSizeNoHeapTid, |
1153 | 0 | RelationGetRelationName(rel)), |
1154 | 0 | errdetail("Index row references tuple (%u,%u) in relation \"%s\".", |
1155 | 0 | ItemPointerGetBlockNumber(BTreeTupleGetHeapTID(newtup)), |
1156 | 0 | ItemPointerGetOffsetNumber(BTreeTupleGetHeapTID(newtup)), |
1157 | 0 | RelationGetRelationName(heap)), |
1158 | 0 | errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n" |
1159 | 0 | "Consider a function index of an MD5 hash of the value, " |
1160 | 0 | "or use full text indexing."), |
1161 | 0 | errtableconstraint(heap, RelationGetRelationName(rel)))); |
1162 | 0 | } |
1163 | | |
1164 | | /* |
1165 | | * Are all attributes in rel "equality is image equality" attributes? |
1166 | | * |
1167 | | * We use each attribute's BTEQUALIMAGE_PROC opclass procedure. If any |
1168 | | * opclass either lacks a BTEQUALIMAGE_PROC procedure or returns false, we |
1169 | | * return false; otherwise we return true. |
1170 | | * |
1171 | | * Returned boolean value is stored in index metapage during index builds. |
1172 | | * Deduplication can only be used when we return true. |
1173 | | */ |
1174 | | bool |
1175 | | _bt_allequalimage(Relation rel, bool debugmessage) |
1176 | | { |
1177 | | bool allequalimage = true; |
1178 | | |
1179 | | /* INCLUDE indexes can never support deduplication */ |
1180 | | if (IndexRelationGetNumberOfAttributes(rel) != |
1181 | | IndexRelationGetNumberOfKeyAttributes(rel)) |
1182 | | return false; |
1183 | | |
1184 | | for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(rel); i++) |
1185 | | { |
1186 | | Oid opfamily = rel->rd_opfamily[i]; |
1187 | | Oid opcintype = rel->rd_opcintype[i]; |
1188 | | Oid collation = rel->rd_indcollation[i]; |
1189 | | Oid equalimageproc; |
1190 | | |
1191 | | equalimageproc = get_opfamily_proc(opfamily, opcintype, opcintype, |
1192 | | BTEQUALIMAGE_PROC); |
1193 | | |
1194 | | /* |
1195 | | * If there is no BTEQUALIMAGE_PROC then deduplication is assumed to |
1196 | | * be unsafe. Otherwise, actually call proc and see what it says. |
1197 | | */ |
1198 | | if (!OidIsValid(equalimageproc) || |
1199 | | !DatumGetBool(OidFunctionCall1Coll(equalimageproc, collation, |
1200 | | ObjectIdGetDatum(opcintype)))) |
1201 | | { |
1202 | | allequalimage = false; |
1203 | | break; |
1204 | | } |
1205 | | } |
1206 | | |
1207 | | if (debugmessage) |
1208 | | { |
1209 | | if (allequalimage) |
1210 | | elog(DEBUG1, "index \"%s\" can safely use deduplication", |
1211 | | RelationGetRelationName(rel)); |
1212 | | else |
1213 | | elog(DEBUG1, "index \"%s\" cannot use deduplication", |
1214 | | RelationGetRelationName(rel)); |
1215 | | } |
1216 | | |
1217 | | return allequalimage; |
1218 | | } |