v14-0001-Refactor-nbtree-insertion-scankeys.patch
application/octet-stream
Filename: v14-0001-Refactor-nbtree-insertion-scankeys.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v14-0001
Subject: Refactor nbtree insertion scankeys.
| File | + | − |
|---|---|---|
| contrib/amcheck/verify_nbtree.c | 24 | 28 |
| src/backend/access/nbtree/nbtinsert.c | 209 | 125 |
| src/backend/access/nbtree/nbtpage.c | 5 | 7 |
| src/backend/access/nbtree/nbtsearch.c | 103 | 65 |
| src/backend/access/nbtree/nbtsort.c | 3 | 5 |
| src/backend/access/nbtree/nbtutils.c | 29 | 69 |
| src/backend/utils/sort/tuplesort.c | 8 | 8 |
| src/include/access/nbtree.h | 48 | 13 |
From eed4ac506df1ee54faf82d820fde8b1943140f34 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Sat, 29 Dec 2018 15:34:48 -0800
Subject: [PATCH v14 1/7] Refactor nbtree insertion scankeys.
Use dedicated struct to represent nbtree insertion scan keys. Having a
dedicated struct makes the difference between search type scankeys and
insertion scankeys a lot clearer, and simplifies the signature of
several related functions.
Use the new struct to store mutable state about an in-progress binary
search, rather than having _bt_check_unique() callers cache
_bt_binsrch() effort in an ad-hoc manner. This makes it easy to add a
new optimization: _bt_check_unique() now falls out of its loop
immediately in the common case where it's already clear that there
couldn't possibly be a duplicate. More importantly, the new
_bt_check_unique() scheme makes it a lot easier to manage cached binary
search effort afterwards, from within _bt_findinsertloc(). This is
needed for the upcoming patch to make nbtree tuples unique by treating
heap TID as a final tie-breaker column.
Based on a suggestion by Andrey Lepikhov.
---
contrib/amcheck/verify_nbtree.c | 52 ++--
src/backend/access/nbtree/nbtinsert.c | 334 ++++++++++++++++----------
src/backend/access/nbtree/nbtpage.c | 12 +-
src/backend/access/nbtree/nbtsearch.c | 168 ++++++++-----
src/backend/access/nbtree/nbtsort.c | 8 +-
src/backend/access/nbtree/nbtutils.c | 98 +++-----
src/backend/utils/sort/tuplesort.c | 16 +-
src/include/access/nbtree.h | 61 ++++-
8 files changed, 429 insertions(+), 320 deletions(-)
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 964200a767..053ac9d192 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -126,9 +126,9 @@ static void bt_check_every_level(Relation rel, Relation heaprel,
static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state,
BtreeLevel level);
static void bt_target_page_check(BtreeCheckState *state);
-static ScanKey bt_right_page_check_scankey(BtreeCheckState *state);
-static void bt_downlink_check(BtreeCheckState *state, BlockNumber childblock,
- ScanKey targetkey);
+static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state);
+static void bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
+ BlockNumber childblock);
static void bt_downlink_missing_check(BtreeCheckState *state);
static void bt_tuple_present_callback(Relation index, HeapTuple htup,
Datum *values, bool *isnull,
@@ -138,14 +138,14 @@ static IndexTuple bt_normalize_tuple(BtreeCheckState *state,
static inline bool offset_is_negative_infinity(BTPageOpaque opaque,
OffsetNumber offset);
static inline bool invariant_leq_offset(BtreeCheckState *state,
- ScanKey key,
+ BTScanInsert key,
OffsetNumber upperbound);
static inline bool invariant_geq_offset(BtreeCheckState *state,
- ScanKey key,
+ BTScanInsert key,
OffsetNumber lowerbound);
static inline bool invariant_leq_nontarget_offset(BtreeCheckState *state,
- Page other,
- ScanKey key,
+ BTScanInsert key,
+ Page nontarget,
OffsetNumber upperbound);
static Page palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum);
@@ -837,8 +837,8 @@ bt_target_page_check(BtreeCheckState *state)
{
ItemId itemid;
IndexTuple itup;
- ScanKey skey;
size_t tupsize;
+ BTScanInsert skey;
CHECK_FOR_INTERRUPTS();
@@ -1029,7 +1029,7 @@ bt_target_page_check(BtreeCheckState *state)
*/
else if (offset == max)
{
- ScanKey rightkey;
+ BTScanInsert rightkey;
/* Get item in next/right page */
rightkey = bt_right_page_check_scankey(state);
@@ -1081,7 +1081,7 @@ bt_target_page_check(BtreeCheckState *state)
{
BlockNumber childblock = BTreeInnerTupleGetDownLink(itup);
- bt_downlink_check(state, childblock, skey);
+ bt_downlink_check(state, skey, childblock);
}
}
@@ -1110,11 +1110,12 @@ bt_target_page_check(BtreeCheckState *state)
* Note that !readonly callers must reverify that target page has not
* been concurrently deleted.
*/
-static ScanKey
+static BTScanInsert
bt_right_page_check_scankey(BtreeCheckState *state)
{
BTPageOpaque opaque;
ItemId rightitem;
+ IndexTuple firstitup;
BlockNumber targetnext;
Page rightpage;
OffsetNumber nline;
@@ -1302,8 +1303,8 @@ bt_right_page_check_scankey(BtreeCheckState *state)
* Return first real item scankey. Note that this relies on right page
* memory remaining allocated.
*/
- return _bt_mkscankey(state->rel,
- (IndexTuple) PageGetItem(rightpage, rightitem));
+ firstitup = (IndexTuple) PageGetItem(rightpage, rightitem);
+ return _bt_mkscankey(state->rel, firstitup);
}
/*
@@ -1316,8 +1317,8 @@ bt_right_page_check_scankey(BtreeCheckState *state)
* verification this way around is much more practical.
*/
static void
-bt_downlink_check(BtreeCheckState *state, BlockNumber childblock,
- ScanKey targetkey)
+bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
+ BlockNumber childblock)
{
OffsetNumber offset;
OffsetNumber maxoffset;
@@ -1422,8 +1423,7 @@ bt_downlink_check(BtreeCheckState *state, BlockNumber childblock,
if (offset_is_negative_infinity(copaque, offset))
continue;
- if (!invariant_leq_nontarget_offset(state, child,
- targetkey, offset))
+ if (!invariant_leq_nontarget_offset(state, targetkey, child, offset))
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("down-link lower bound invariant violated for index \"%s\"",
@@ -1863,13 +1863,12 @@ offset_is_negative_infinity(BTPageOpaque opaque, OffsetNumber offset)
* to corruption.
*/
static inline bool
-invariant_leq_offset(BtreeCheckState *state, ScanKey key,
+invariant_leq_offset(BtreeCheckState *state, BTScanInsert key,
OffsetNumber upperbound)
{
- int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
int32 cmp;
- cmp = _bt_compare(state->rel, nkeyatts, key, state->target, upperbound);
+ cmp = _bt_compare(state->rel, key, state->target, upperbound);
return cmp <= 0;
}
@@ -1882,13 +1881,12 @@ invariant_leq_offset(BtreeCheckState *state, ScanKey key,
* to corruption.
*/
static inline bool
-invariant_geq_offset(BtreeCheckState *state, ScanKey key,
+invariant_geq_offset(BtreeCheckState *state, BTScanInsert key,
OffsetNumber lowerbound)
{
- int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
int32 cmp;
- cmp = _bt_compare(state->rel, nkeyatts, key, state->target, lowerbound);
+ cmp = _bt_compare(state->rel, key, state->target, lowerbound);
return cmp >= 0;
}
@@ -1904,14 +1902,12 @@ invariant_geq_offset(BtreeCheckState *state, ScanKey key,
* to corruption.
*/
static inline bool
-invariant_leq_nontarget_offset(BtreeCheckState *state,
- Page nontarget, ScanKey key,
- OffsetNumber upperbound)
+invariant_leq_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
+ Page nontarget, OffsetNumber upperbound)
{
- int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
int32 cmp;
- cmp = _bt_compare(state->rel, nkeyatts, key, nontarget, upperbound);
+ cmp = _bt_compare(state->rel, key, nontarget, upperbound);
return cmp <= 0;
}
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 2b18028823..f143ea8be2 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -51,19 +51,19 @@ typedef struct
static Buffer _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf);
-static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
- Relation heapRel, Buffer buf, OffsetNumber offset,
- ScanKey itup_scankey,
+static TransactionId _bt_check_unique(Relation rel, BTScanInsert itup_key,
+ IndexTuple itup, Relation heapRel, Buffer buf,
IndexUniqueCheck checkUnique, bool *is_unique,
uint32 *speculativeToken);
-static void _bt_findinsertloc(Relation rel,
+static OffsetNumber _bt_findinsertloc(Relation rel,
+ BTScanInsert itup_key,
Buffer *bufptr,
- OffsetNumber *offsetptr,
- int keysz,
- ScanKey scankey,
+ bool checkingunique,
IndexTuple newtup,
BTStack stack,
Relation heapRel);
+static bool _bt_useduplicatepage(Relation rel, Relation heapRel, Buffer buf,
+ bool *restorebinsrch, Size itemsz);
static void _bt_insertonpg(Relation rel, Buffer buf, Buffer cbuf,
BTStack stack,
IndexTuple itup,
@@ -83,8 +83,8 @@ static void _bt_checksplitloc(FindSplitData *state,
int dataitemstoleft, Size firstoldonrightsz);
static bool _bt_pgaddtup(Page page, Size itemsize, IndexTuple itup,
OffsetNumber itup_off);
-static bool _bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
- int keysz, ScanKey scankey);
+static bool _bt_isequal(TupleDesc itupdesc, BTScanInsert itup_key,
+ Page page, OffsetNumber offnum);
static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
/*
@@ -97,7 +97,9 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
* will allow duplicates. Otherwise (UNIQUE_CHECK_YES or
* UNIQUE_CHECK_EXISTING) it will throw error for a duplicate.
* For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and
- * don't actually insert.
+ * don't actually insert. If rel is a unique index, then every call
+ * here is a checkingunique call (i.e. every call does a duplicate
+ * check, though perhaps only a tentative check).
*
* The result value is only significant for UNIQUE_CHECK_PARTIAL:
* it must be true if the entry is known unique, else false.
@@ -110,18 +112,14 @@ _bt_doinsert(Relation rel, IndexTuple itup,
IndexUniqueCheck checkUnique, Relation heapRel)
{
bool is_unique = false;
- int indnkeyatts;
- ScanKey itup_scankey;
+ BTScanInsert itup_key;
BTStack stack = NULL;
Buffer buf;
- OffsetNumber offset;
bool fastpath;
-
- indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
- Assert(indnkeyatts != 0);
+ bool checkingunique = (checkUnique != UNIQUE_CHECK_NO);
/* we need an insertion scan key to do our search, so build one */
- itup_scankey = _bt_mkscankey(rel, itup);
+ itup_key = _bt_mkscankey(rel, itup);
/*
* It's very common to have an index on an auto-incremented or
@@ -144,7 +142,6 @@ _bt_doinsert(Relation rel, IndexTuple itup,
*/
top:
fastpath = false;
- offset = InvalidOffsetNumber;
if (RelationGetTargetBlock(rel) != InvalidBlockNumber)
{
Size itemsz;
@@ -179,8 +176,7 @@ top:
!P_IGNORE(lpageop) &&
(PageGetFreeSpace(page) > itemsz) &&
PageGetMaxOffsetNumber(page) >= P_FIRSTDATAKEY(lpageop) &&
- _bt_compare(rel, indnkeyatts, itup_scankey, page,
- P_FIRSTDATAKEY(lpageop)) > 0)
+ _bt_compare(rel, itup_key, page, P_FIRSTDATAKEY(lpageop)) > 0)
{
/*
* The right-most block should never have an incomplete split.
@@ -219,8 +215,7 @@ top:
* Find the first page containing this key. Buffer returned by
* _bt_search() is locked in exclusive mode.
*/
- stack = _bt_search(rel, indnkeyatts, itup_scankey, false, &buf, BT_WRITE,
- NULL);
+ stack = _bt_search(rel, itup_key, &buf, BT_WRITE, NULL);
}
/*
@@ -244,13 +239,12 @@ top:
* let the tuple in and return false for possibly non-unique, or true for
* definitely unique.
*/
- if (checkUnique != UNIQUE_CHECK_NO)
+ if (checkingunique)
{
TransactionId xwait;
uint32 speculativeToken;
- offset = _bt_binsrch(rel, buf, indnkeyatts, itup_scankey, false);
- xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
+ xwait = _bt_check_unique(rel, itup_key, itup, heapRel, buf,
checkUnique, &is_unique, &speculativeToken);
if (TransactionIdIsValid(xwait))
@@ -277,6 +271,8 @@ top:
if (checkUnique != UNIQUE_CHECK_EXISTING)
{
+ OffsetNumber newitemoff;
+
/*
* The only conflict predicate locking cares about for indexes is when
* an index tuple insert conflicts with an existing lock. Since the
@@ -287,10 +283,16 @@ top:
* attributes are not considered part of the key space.
*/
CheckForSerializableConflictIn(rel, NULL, buf);
- /* do the insertion */
- _bt_findinsertloc(rel, &buf, &offset, indnkeyatts, itup_scankey, itup,
- stack, heapRel);
- _bt_insertonpg(rel, buf, InvalidBuffer, stack, itup, offset, false);
+
+ /*
+ * Do the insertion. Note that itup_key contains mutable state used
+ * by _bt_check_unique to help _bt_findinsertloc avoid repeating its
+ * binary search. !checkingunique case must start own binary search.
+ */
+ newitemoff = _bt_findinsertloc(rel, itup_key, &buf, checkingunique,
+ itup, stack, heapRel);
+ _bt_insertonpg(rel, buf, InvalidBuffer, stack, itup, newitemoff,
+ false);
}
else
{
@@ -301,7 +303,7 @@ top:
/* be tidy */
if (stack)
_bt_freestack(stack);
- _bt_freeskey(itup_scankey);
+ pfree(itup_key);
return is_unique;
}
@@ -309,9 +311,9 @@ top:
/*
* _bt_check_unique() -- Check for violation of unique index constraint
*
- * offset points to the first possible item that could conflict. It can
- * also point to end-of-page, which means that the first tuple to check
- * is the first tuple on the next page.
+ * Sets state in itup_key sufficient for later _bt_findinsertloc() call to
+ * reuse most of the work of our initial binary search to find conflicting
+ * tuples.
*
* Returns InvalidTransactionId if there is no conflict, else an xact ID
* we must wait for to see if it commits a conflicting tuple. If an actual
@@ -326,14 +328,14 @@ top:
* core code must redo the uniqueness check later.
*/
static TransactionId
-_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
- Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
+_bt_check_unique(Relation rel, BTScanInsert itup_key,
+ IndexTuple itup, Relation heapRel, Buffer buf,
IndexUniqueCheck checkUnique, bool *is_unique,
uint32 *speculativeToken)
{
TupleDesc itupdesc = RelationGetDescr(rel);
- int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
SnapshotData SnapshotDirty;
+ OffsetNumber offset;
OffsetNumber maxoff;
Page page;
BTPageOpaque opaque;
@@ -349,9 +351,17 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
maxoff = PageGetMaxOffsetNumber(page);
+ /*
+ * Save binary search bounds. Note that this is also used within
+ * _bt_findinsertloc() later.
+ */
+ itup_key->savebinsrch = true;
+ offset = _bt_binsrch(rel, itup_key, buf);
+
/*
* Scan over all equal tuples, looking for live conflicts.
*/
+ Assert(itup_key->low == offset);
for (;;)
{
ItemId curitemid;
@@ -364,6 +374,26 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
*/
if (offset <= maxoff)
{
+ /*
+ * Fastpath: _bt_binsrch() search bounds can be used to limit our
+ * consideration to items that are definitely duplicates in most
+ * cases (though not when original page is empty, or when initial
+ * offset is past the end of the original page, which may indicate
+ * that we'll have to examine a second or subsequent page).
+ *
+ * Note that this optimization avoids calling _bt_isequal()
+ * entirely when there are no duplicates, provided initial offset
+ * isn't past end of the initial page (and provided page has at
+ * least one item).
+ */
+ if (nbuf == InvalidBuffer && offset == itup_key->stricthigh)
+ {
+ Assert(itup_key->low >= P_FIRSTDATAKEY(opaque));
+ Assert(itup_key->low <= itup_key->stricthigh);
+ Assert(!_bt_isequal(itupdesc, itup_key, page, offset));
+ break;
+ }
+
curitemid = PageGetItemId(page, offset);
/*
@@ -378,7 +408,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
* first, so that we didn't actually get to exit any sooner
* anyway. So now we just advance over killed items as quickly as
* we can. We only apply _bt_isequal() when we get to a non-killed
- * item or the end of the page.
+ * item.
*/
if (!ItemIdIsDead(curitemid))
{
@@ -391,7 +421,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
* in real comparison, but only for ordering/finding items on
* pages. - vadim 03/24/97
*/
- if (!_bt_isequal(itupdesc, page, offset, indnkeyatts, itup_scankey))
+ if (!_bt_isequal(itupdesc, itup_key, page, offset))
break; /* we're past all the equal tuples */
/* okay, we gotta fetch the heap tuple ... */
@@ -552,11 +582,14 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
offset = OffsetNumberNext(offset);
else
{
+ int highkeycmp;
+
/* If scankey == hikey we gotta check the next page too */
if (P_RIGHTMOST(opaque))
break;
- if (!_bt_isequal(itupdesc, page, P_HIKEY,
- indnkeyatts, itup_scankey))
+ highkeycmp = _bt_compare(rel, itup_key, page, P_HIKEY);
+ Assert(highkeycmp <= 0);
+ if (highkeycmp != 0)
break;
/* Advance to next non-dead page --- there must be one */
for (;;)
@@ -611,39 +644,40 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
* Once we have chosen the page to put the key on, we'll insert it before
* any existing equal keys because of the way _bt_binsrch() works.
*
- * If there's not enough room in the space, we try to make room by
- * removing any LP_DEAD tuples.
+ * _bt_check_unique() callers arrange for their insertion scan key to
+ * save the progress of the last binary search performed. No additional
+ * binary search comparisons occur in the common case where there was no
+ * existing duplicate tuple, though we may occasionally still not be able
+ * to reuse their work for our own reasons. Even when there are garbage
+ * duplicates, very few binary search comparisons will be performed
+ * without being strictly necessary.
*
- * On entry, *bufptr and *offsetptr point to the first legal position
- * where the new tuple could be inserted. The caller should hold an
- * exclusive lock on *bufptr. *offsetptr can also be set to
- * InvalidOffsetNumber, in which case the function will search for the
- * right location within the page if needed. On exit, they point to the
- * chosen insert location. If _bt_findinsertloc decides to move right,
- * the lock and pin on the original page will be released and the new
- * page returned to the caller is exclusively locked instead.
+ * The caller should hold an exclusive lock on *bufptr in all cases. On
+ * exit, bufptr points to the chosen insert location in all cases. If
+ * we have to move right, the lock and pin on the original page will be
+ * released, and the new page returned to the caller is exclusively
+ * locked instead. In any case, we return the offset that caller should
+ * use to insert into the buffer pointed to by bufptr on return.
*
- * newtup is the new tuple we're inserting, and scankey is an insertion
- * type scan key for it.
+ * This is also where opportunistic microvacuuming of LP_DEAD tuples
+ * occurs. It has to happen here, since it may invalidate a
+ * _bt_check_unique() caller's cached binary search work.
*/
-static void
+static OffsetNumber
_bt_findinsertloc(Relation rel,
+ BTScanInsert itup_key,
Buffer *bufptr,
- OffsetNumber *offsetptr,
- int keysz,
- ScanKey scankey,
+ bool checkingunique,
IndexTuple newtup,
BTStack stack,
Relation heapRel)
{
Buffer buf = *bufptr;
Page page = BufferGetPage(buf);
+ bool restorebinsrch = checkingunique;
Size itemsz;
BTPageOpaque lpageop;
- bool movedright,
- vacuumed;
OffsetNumber newitemoff;
- OffsetNumber firstlegaloff = *offsetptr;
lpageop = (BTPageOpaque) PageGetSpecialPointer(page);
@@ -672,55 +706,36 @@ _bt_findinsertloc(Relation rel,
errtableconstraint(heapRel,
RelationGetRelationName(rel))));
- /*----------
- * If we will need to split the page to put the item on this page,
- * check whether we can put the tuple somewhere to the right,
- * instead. Keep scanning right until we
- * (a) find a page with enough free space,
- * (b) reach the last page where the tuple can legally go, or
- * (c) get tired of searching.
- * (c) is not flippant; it is important because if there are many
- * pages' worth of equal keys, it's better to split one of the early
- * pages than to scan all the way to the end of the run of equal keys
- * on every insert. We implement "get tired" as a random choice,
- * since stopping after scanning a fixed number of pages wouldn't work
- * well (we'd never reach the right-hand side of previously split
- * pages). Currently the probability of moving right is set at 0.99,
- * which may seem too high to change the behavior much, but it does an
- * excellent job of preventing O(N^2) behavior with many equal keys.
- *----------
- */
- movedright = false;
- vacuumed = false;
- while (PageGetFreeSpace(page) < itemsz)
+ Assert(P_ISLEAF(lpageop) && !P_INCOMPLETE_SPLIT(lpageop));
+ for (;;)
{
+ int cmpval;
Buffer rbuf;
BlockNumber rblkno;
/*
- * before considering moving right, see if we can obtain enough space
- * by erasing LP_DEAD items
+ * The checkingunique (restorebinsrch) case may well have established
+ * bounds within _bt_check_unique()'s binary search that preclude the
+ * need for a further high key check. This fastpath isn't used when
+ * there are no items on the existing page (other than high key), or
+ * when it looks like the new item belongs last on the page, but it
+ * might go on a later page instead.
*/
- if (P_ISLEAF(lpageop) && P_HAS_GARBAGE(lpageop))
- {
- _bt_vacuum_one_page(rel, buf, heapRel);
+ if (restorebinsrch && itup_key->low <= itup_key->stricthigh &&
+ itup_key->stricthigh <= PageGetMaxOffsetNumber(page))
+ break;
- /*
- * remember that we vacuumed this page, because that makes the
- * hint supplied by the caller invalid
- */
- vacuumed = true;
-
- if (PageGetFreeSpace(page) >= itemsz)
- break; /* OK, now we have enough space */
- }
+ if (P_RIGHTMOST(lpageop))
+ break;
+ cmpval = _bt_compare(rel, itup_key, page, P_HIKEY);
/*
- * nope, so check conditions (b) and (c) enumerated above
+ * May have to handle case where there is a choice of which page to
+ * place new tuple on, and we must balance space utilization as best
+ * we can.
*/
- if (P_RIGHTMOST(lpageop) ||
- _bt_compare(rel, keysz, scankey, page, P_HIKEY) != 0 ||
- random() <= (MAX_RANDOM_VALUE / 100))
+ if (cmpval != 0 || _bt_useduplicatepage(rel, heapRel, buf,
+ &restorebinsrch, itemsz))
break;
/*
@@ -763,27 +778,98 @@ _bt_findinsertloc(Relation rel,
}
_bt_relbuf(rel, buf);
buf = rbuf;
- movedright = true;
- vacuumed = false;
+ restorebinsrch = false;
+ }
+
+ /* Loop should not break until correct page located */
+ Assert(P_RIGHTMOST(lpageop) ||
+ _bt_compare(rel, itup_key, page, P_HIKEY) <= 0);
+
+ /*
+ * Perform microvacuuming of the page we're about to insert tuple on if it
+ * looks like it has LP_DEAD items. Only microvacuum when it's likely to
+ * forestall a page split, though.
+ */
+ if (P_HAS_GARBAGE(lpageop) && PageGetFreeSpace(page) < itemsz)
+ {
+ _bt_vacuum_one_page(rel, buf, heapRel);
+
+ restorebinsrch = false;
}
/*
- * Now we are on the right page, so find the insert position. If we moved
- * right at all, we know we should insert at the start of the page. If we
- * didn't move right, we can use the firstlegaloff hint if the caller
- * supplied one, unless we vacuumed the page which might have moved tuples
- * around making the hint invalid. If we didn't move right or can't use
- * the hint, find the position by searching.
+ * Reuse binary search bounds established within _bt_check_unique if
+ * caller is checkingunique caller, and first page locked is also where
+ * new tuple should be inserted
*/
- if (movedright)
- newitemoff = P_FIRSTDATAKEY(lpageop);
- else if (firstlegaloff != InvalidOffsetNumber && !vacuumed)
- newitemoff = firstlegaloff;
- else
- newitemoff = _bt_binsrch(rel, buf, keysz, scankey, false);
+ itup_key->restorebinsrch = restorebinsrch;
+ newitemoff = _bt_binsrch(rel, itup_key, buf);
+ Assert(!itup_key->restorebinsrch);
+ Assert(!restorebinsrch || newitemoff == _bt_binsrch(rel, itup_key, buf));
*bufptr = buf;
- *offsetptr = newitemoff;
+ return newitemoff;
+}
+
+/*
+ * _bt_useduplicatepage() -- Settle for this page of duplicates?
+ *
+ * This function handles the question of whether or not an insertion
+ * of a duplicate into a pg_upgrade'd !heapkeyspace index should
+ * insert on the page contained in buf when a choice must be made.
+ * Preemptive microvacuuming is performed here when that could allow
+ * caller to insert on to the page in buf.
+ *
+ * Returns true if caller should proceed with insert on buf's page.
+ * Otherwise, caller should move on to the page to the right (caller
+ * must always be able to still move right following call here).
+ */
+static bool
+_bt_useduplicatepage(Relation rel, Relation heapRel, Buffer buf,
+ bool *restorebinsrch, Size itemsz)
+{
+ Page page = BufferGetPage(buf);
+ BTPageOpaque lpageop;
+
+ lpageop = (BTPageOpaque) PageGetSpecialPointer(page);
+ Assert(P_ISLEAF(lpageop) && !P_RIGHTMOST(lpageop));
+
+ /* Easy case -- there is space free on this page already */
+ if (PageGetFreeSpace(page) >= itemsz)
+ return true;
+
+ if (P_HAS_GARBAGE(lpageop))
+ {
+ _bt_vacuum_one_page(rel, buf, heapRel);
+
+ *restorebinsrch = false;
+ if (PageGetFreeSpace(page) >= itemsz)
+ return true; /* OK, now we have enough space */
+ }
+
+ /*----------
+ * It's now clear that _bt_findinsertloc() caller will need to split
+ * the page if it is to insert new item on to it. The choice to move
+ * right to the next page remains open to it, but we should not search
+ * for free space exhaustively when there are many pages to look through.
+ *
+ * _bt_findinsertloc() keeps scanning right until it:
+ * (a) reaches the last page where the tuple can legally go
+ * Or until we:
+ * (b) find a page with enough free space, or
+ * (c) get tired of searching.
+ * (c) is not flippant; it is important because if there are many
+ * pages' worth of equal keys, it's better to split one of the early
+ * pages than to scan all the way to the end of the run of equal keys
+ * on every insert. We implement "get tired" as a random choice,
+ * since stopping after scanning a fixed number of pages wouldn't work
+ * well (we'd never reach the right-hand side of previously split
+ * pages). The probability of moving right is set at 0.99, which may
+ * seem too high to change the behavior much, but it does an excellent
+ * job of preventing O(N^2) behavior with many equal keys.
+ *----------
+ */
+ return random() <= (MAX_RANDOM_VALUE / 100);
}
/*----------
@@ -1189,8 +1275,9 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
* If the page we're splitting is not the rightmost page at its level in
* the tree, then the first entry on the page is the high key for the
* page. We need to copy that to the right half. Otherwise (meaning the
- * rightmost page case), all the items on the right half will be user
- * data.
+ * rightmost page case), all the items on the right half will be user data
+ * (there is no existing high key that needs to be relocated to the new
+ * right page).
*/
rightoff = P_HIKEY;
@@ -2310,24 +2397,21 @@ _bt_pgaddtup(Page page,
* Rule is simple: NOT_NULL not equal NULL, NULL not equal NULL too.
*/
static bool
-_bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
- int keysz, ScanKey scankey)
+_bt_isequal(TupleDesc itupdesc, BTScanInsert itup_key, Page page,
+ OffsetNumber offnum)
{
IndexTuple itup;
+ ScanKey scankey;
int i;
- /* Better be comparing to a leaf item */
+ /* Better be comparing to a non-pivot item */
Assert(P_ISLEAF((BTPageOpaque) PageGetSpecialPointer(page)));
+ Assert(offnum >= P_FIRSTDATAKEY((BTPageOpaque) PageGetSpecialPointer(page)));
+ scankey = itup_key->scankeys;
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
- /*
- * It's okay that we might perform a comparison against a truncated page
- * high key when caller needs to determine if _bt_check_unique scan must
- * continue on to the next page. Caller never asks us to compare non-key
- * attributes within an INCLUDE index.
- */
- for (i = 1; i <= keysz; i++)
+ for (i = 1; i <= itup_key->keysz; i++)
{
AttrNumber attno;
Datum datum;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 9c785bca95..56041c3d38 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -1371,7 +1371,7 @@ _bt_pagedel(Relation rel, Buffer buf)
*/
if (!stack)
{
- ScanKey itup_scankey;
+ BTScanInsert itup_key;
ItemId itemid;
IndexTuple targetkey;
Buffer lbuf;
@@ -1421,12 +1421,10 @@ _bt_pagedel(Relation rel, Buffer buf)
}
/* we need an insertion scan key for the search, so build one */
- itup_scankey = _bt_mkscankey(rel, targetkey);
- /* find the leftmost leaf page containing this key */
- stack = _bt_search(rel,
- IndexRelationGetNumberOfKeyAttributes(rel),
- itup_scankey, false, &lbuf, BT_READ, NULL);
- /* don't need a pin on the page */
+ itup_key = _bt_mkscankey(rel, targetkey);
+ /* get stack to leaf page by searching index */
+ stack = _bt_search(rel, itup_key, &lbuf, BT_READ, NULL);
+ /* don't need a lock or second pin on the page */
_bt_relbuf(rel, lbuf);
/*
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 92832237a8..7940297305 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -71,13 +71,9 @@ _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp)
* _bt_search() -- Search the tree for a particular scankey,
* or more precisely for the first leaf page it could be on.
*
- * The passed scankey must be an insertion-type scankey (see nbtree/README),
+ * The passed scankey is an insertion-type scankey (see nbtree/README),
* but it can omit the rightmost column(s) of the index.
*
- * When nextkey is false (the usual case), we are looking for the first
- * item >= scankey. When nextkey is true, we are looking for the first
- * item strictly greater than scankey.
- *
* Return value is a stack of parent-page pointers. *bufP is set to the
* address of the leaf-page buffer, which is read-locked and pinned.
* No locks are held on the parent pages, however!
@@ -93,8 +89,8 @@ _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp)
* during the search will be finished.
*/
BTStack
-_bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
- Buffer *bufP, int access, Snapshot snapshot)
+_bt_search(Relation rel, BTScanInsert key, Buffer *bufP, int access,
+ Snapshot snapshot)
{
BTStack stack_in = NULL;
int page_access = BT_READ;
@@ -130,8 +126,7 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
* if the leaf page is split and we insert to the parent page). But
* this is a good opportunity to finish splits of internal pages too.
*/
- *bufP = _bt_moveright(rel, *bufP, keysz, scankey, nextkey,
- (access == BT_WRITE), stack_in,
+ *bufP = _bt_moveright(rel, key, *bufP, (access == BT_WRITE), stack_in,
page_access, snapshot);
/* if this is a leaf page, we're done */
@@ -144,7 +139,7 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
* Find the appropriate item on the internal page, and get the child
* page that it points to.
*/
- offnum = _bt_binsrch(rel, *bufP, keysz, scankey, nextkey);
+ offnum = _bt_binsrch(rel, key, *bufP);
itemid = PageGetItemId(page, offnum);
itup = (IndexTuple) PageGetItem(page, itemid);
blkno = BTreeInnerTupleGetDownLink(itup);
@@ -198,8 +193,8 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
* need to move right in the tree. See Lehman and Yao for an
* excruciatingly precise description.
*/
- *bufP = _bt_moveright(rel, *bufP, keysz, scankey, nextkey,
- true, stack_in, BT_WRITE, snapshot);
+ *bufP = _bt_moveright(rel, key, *bufP, true, stack_in, BT_WRITE,
+ snapshot);
}
return stack_in;
@@ -215,16 +210,17 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
* or strictly to the right of it.
*
* This routine decides whether or not we need to move right in the
- * tree by examining the high key entry on the page. If that entry
- * is strictly less than the scankey, or <= the scankey in the nextkey=true
- * case, then we followed the wrong link and we need to move right.
+ * tree by examining the high key entry on the page. If that entry is
+ * strictly less than the scankey, or <= the scankey in the
+ * key.nextkey=true case, then we followed the wrong link and we need
+ * to move right.
*
- * The passed scankey must be an insertion-type scankey (see nbtree/README),
- * but it can omit the rightmost column(s) of the index.
+ * The passed insertion-type scankey can omit the rightmost column(s) of the
+ * index. (see nbtree/README)
*
- * When nextkey is false (the usual case), we are looking for the first
- * item >= scankey. When nextkey is true, we are looking for the first
- * item strictly greater than scankey.
+ * When key.nextkey is false (the usual case), we are looking for the first
+ * item >= key. When key.nextkey is true, we are looking for the first item
+ * strictly greater than key.
*
* If forupdate is true, we will attempt to finish any incomplete splits
* that we encounter. This is required when locking a target page for an
@@ -241,10 +237,8 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
*/
Buffer
_bt_moveright(Relation rel,
+ BTScanInsert key,
Buffer buf,
- int keysz,
- ScanKey scankey,
- bool nextkey,
bool forupdate,
BTStack stack,
int access,
@@ -269,7 +263,7 @@ _bt_moveright(Relation rel,
* We also have to move right if we followed a link that brought us to a
* dead page.
*/
- cmpval = nextkey ? 0 : 1;
+ cmpval = key->nextkey ? 0 : 1;
for (;;)
{
@@ -304,7 +298,7 @@ _bt_moveright(Relation rel,
continue;
}
- if (P_IGNORE(opaque) || _bt_compare(rel, keysz, scankey, page, P_HIKEY) >= cmpval)
+ if (P_IGNORE(opaque) || _bt_compare(rel, key, page, P_HIKEY) >= cmpval)
{
/* step right one page */
buf = _bt_relandgetbuf(rel, buf, opaque->btpo_next, access);
@@ -324,13 +318,6 @@ _bt_moveright(Relation rel,
/*
* _bt_binsrch() -- Do a binary search for a key on a particular page.
*
- * The passed scankey must be an insertion-type scankey (see nbtree/README),
- * but it can omit the rightmost column(s) of the index.
- *
- * When nextkey is false (the usual case), we are looking for the first
- * item >= scankey. When nextkey is true, we are looking for the first
- * item strictly greater than scankey.
- *
* On a leaf page, _bt_binsrch() returns the OffsetNumber of the first
* key >= given scankey, or > scankey if nextkey is true. (NOTE: in
* particular, this means it is possible to return a value 1 greater than the
@@ -346,27 +333,45 @@ _bt_moveright(Relation rel,
*
* This procedure is not responsible for walking right, it just examines
* the given page. _bt_binsrch() has no lock or refcount side effects
- * on the buffer.
+ * on the buffer. When key.savebinsrch is set, modifies mutable fields
+ * of insertion scan key, so that a subsequent call where caller sets
+ * key.savebinsrch can reuse the low and strict high bound of original
+ * binary search. Callers that use these fields directly must be
+ * prepared for the case where stricthigh isn't on the same page (it
+ * exceeds maxoff for the page), and the case where there are no items
+ * on the page (high < low).
*/
OffsetNumber
_bt_binsrch(Relation rel,
- Buffer buf,
- int keysz,
- ScanKey scankey,
- bool nextkey)
+ BTScanInsert key,
+ Buffer buf)
{
Page page;
BTPageOpaque opaque;
OffsetNumber low,
- high;
+ high,
+ stricthigh;
int32 result,
cmpval;
+ bool isleaf;
page = BufferGetPage(buf);
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
- low = P_FIRSTDATAKEY(opaque);
- high = PageGetMaxOffsetNumber(page);
+ if (!key->restorebinsrch)
+ {
+ low = P_FIRSTDATAKEY(opaque);
+ high = PageGetMaxOffsetNumber(page);
+ isleaf = P_ISLEAF(opaque);
+ }
+ else
+ {
+ /* Restore result of previous binary search against same page */
+ Assert(P_ISLEAF(opaque));
+ low = key->low;
+ high = key->stricthigh;
+ isleaf = true;
+ }
/*
* If there are no keys on the page, return the first available slot. Note
@@ -375,8 +380,19 @@ _bt_binsrch(Relation rel,
* This can never happen on an internal page, however, since they are
* never empty (an internal page must have children).
*/
- if (high < low)
+ if (unlikely(high < low))
+ {
+ if (key->savebinsrch)
+ {
+ Assert(isleaf);
+ /* Caller can't use stricthigh */
+ key->low = low;
+ key->stricthigh = high;
+ }
+ key->savebinsrch = false;
+ key->restorebinsrch = false;
return low;
+ }
/*
* Binary search to find the first key on the page >= scan key, or first
@@ -390,9 +406,12 @@ _bt_binsrch(Relation rel,
*
* We can fall out when high == low.
*/
- high++; /* establish the loop invariant for high */
+ if (!key->restorebinsrch)
+ high++; /* establish the loop invariant for high */
+ key->restorebinsrch = false;
+ stricthigh = high; /* high initially strictly higher */
- cmpval = nextkey ? 0 : 1; /* select comparison value */
+ cmpval = key->nextkey ? 0 : 1; /* select comparison value */
while (high > low)
{
@@ -400,12 +419,21 @@ _bt_binsrch(Relation rel,
/* We have low <= mid < high, so mid points at a real slot */
- result = _bt_compare(rel, keysz, scankey, page, mid);
+ result = _bt_compare(rel, key, page, mid);
if (result >= cmpval)
low = mid + 1;
else
+ {
high = mid;
+
+ /*
+ * high can only be reused by more restrictive binary search when
+ * it's known to be strictly greater than the original scankey
+ */
+ if (result != 0)
+ stricthigh = high;
+ }
}
/*
@@ -415,7 +443,14 @@ _bt_binsrch(Relation rel,
* On a leaf page, we always return the first key >= scan key (resp. >
* scan key), which could be the last slot + 1.
*/
- if (P_ISLEAF(opaque))
+ if (key->savebinsrch)
+ {
+ Assert(isleaf);
+ key->low = low;
+ key->stricthigh = stricthigh;
+ key->savebinsrch = false;
+ }
+ if (isleaf)
return low;
/*
@@ -428,13 +463,8 @@ _bt_binsrch(Relation rel,
}
/*----------
- * _bt_compare() -- Compare scankey to a particular tuple on the page.
+ * _bt_compare() -- Compare insertion-type scankey to tuple on a page.
*
- * The passed scankey must be an insertion-type scankey (see nbtree/README),
- * but it can omit the rightmost column(s) of the index.
- *
- * keysz: number of key conditions to be checked (might be less than the
- * number of index columns!)
* page/offnum: location of btree item to be compared to.
*
* This routine returns:
@@ -455,17 +485,17 @@ _bt_binsrch(Relation rel,
*/
int32
_bt_compare(Relation rel,
- int keysz,
- ScanKey scankey,
+ BTScanInsert key,
Page page,
OffsetNumber offnum)
{
TupleDesc itupdesc = RelationGetDescr(rel);
BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page);
IndexTuple itup;
- int i;
+ ScanKey scankey;
Assert(_bt_check_natts(rel, page, offnum));
+ Assert(key->keysz <= IndexRelationGetNumberOfKeyAttributes(rel));
/*
* Force result ">" if target item is first data item on an internal page
@@ -488,7 +518,8 @@ _bt_compare(Relation rel,
* _bt_first).
*/
- for (i = 1; i <= keysz; i++)
+ scankey = key->scankeys;
+ for (int i = 1; i <= key->keysz; i++)
{
Datum datum;
bool isNull;
@@ -574,8 +605,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
StrategyNumber strat;
bool nextkey;
bool goback;
+ BTScanInsertData inskey;
ScanKey startKeys[INDEX_MAX_KEYS];
- ScanKeyData scankeys[INDEX_MAX_KEYS];
ScanKeyData notnullkeys[INDEX_MAX_KEYS];
int keysCount = 0;
int i;
@@ -821,8 +852,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
/*
* We want to start the scan somewhere within the index. Set up an
* insertion scankey we can use to search for the boundary point we
- * identified above. The insertion scankey is built in the local
- * scankeys[] array, using the keys identified by startKeys[].
+ * identified above. The insertion scankey is built using the keys
+ * identified by startKeys[]. (Remaining insertion scankey fields are
+ * initialized after initial-positioning strategy is finalized.)
*/
Assert(keysCount <= INDEX_MAX_KEYS);
for (i = 0; i < keysCount; i++)
@@ -850,7 +882,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
_bt_parallel_done(scan);
return false;
}
- memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
+ memcpy(inskey.scankeys + i, subkey, sizeof(ScanKeyData));
/*
* If the row comparison is the last positioning key we accepted,
@@ -882,7 +914,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
if (subkey->sk_flags & SK_ISNULL)
break; /* can't use null keys */
Assert(keysCount < INDEX_MAX_KEYS);
- memcpy(scankeys + keysCount, subkey, sizeof(ScanKeyData));
+ memcpy(inskey.scankeys + keysCount, subkey,
+ sizeof(ScanKeyData));
keysCount++;
if (subkey->sk_flags & SK_ROW_END)
{
@@ -928,7 +961,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
FmgrInfo *procinfo;
procinfo = index_getprocinfo(rel, cur->sk_attno, BTORDER_PROC);
- ScanKeyEntryInitializeWithInfo(scankeys + i,
+ ScanKeyEntryInitializeWithInfo(inskey.scankeys + i,
cur->sk_flags,
cur->sk_attno,
InvalidStrategy,
@@ -949,7 +982,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
elog(ERROR, "missing support function %d(%u,%u) for attribute %d of index \"%s\"",
BTORDER_PROC, rel->rd_opcintype[i], cur->sk_subtype,
cur->sk_attno, RelationGetRelationName(rel));
- ScanKeyEntryInitialize(scankeys + i,
+ ScanKeyEntryInitialize(inskey.scankeys + i,
cur->sk_flags,
cur->sk_attno,
InvalidStrategy,
@@ -1052,12 +1085,17 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
return false;
}
+ /* Initialize remaining insertion scan key fields */
+ inskey.savebinsrch = inskey.restorebinsrch = false;
+ inskey.low = inskey.stricthigh = InvalidOffsetNumber;
+ inskey.nextkey = nextkey;
+ inskey.keysz = keysCount;
+
/*
* Use the manufactured insertion scan key to descend the tree and
* position ourselves on the target leaf page.
*/
- stack = _bt_search(rel, keysCount, scankeys, nextkey, &buf, BT_READ,
- scan->xs_snapshot);
+ stack = _bt_search(rel, &inskey, &buf, BT_READ, scan->xs_snapshot);
/* don't need to keep the stack around... */
_bt_freestack(stack);
@@ -1086,7 +1124,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
_bt_initialize_more_data(so, dir);
/* position to the precise item on the page */
- offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
+ offnum = _bt_binsrch(rel, &inskey, buf);
/*
* If nextkey = false, we are positioned at the first item >= scan key, or
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index dc398e1186..759859c302 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -254,6 +254,7 @@ typedef struct BTWriteState
{
Relation heap;
Relation index;
+ BTScanInsert inskey; /* generic insertion scankey */
bool btws_use_wal; /* dump pages to WAL? */
BlockNumber btws_pages_alloced; /* # pages allocated */
BlockNumber btws_pages_written; /* # pages written out */
@@ -531,6 +532,7 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2)
wstate.heap = btspool->heap;
wstate.index = btspool->index;
+ wstate.inskey = _bt_mkscankey(wstate.index, NULL);
/*
* We need to log index creation in WAL iff WAL archiving/streaming is
@@ -1076,7 +1078,6 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
TupleDesc tupdes = RelationGetDescr(wstate->index);
int i,
keysz = IndexRelationGetNumberOfKeyAttributes(wstate->index);
- ScanKey indexScanKey = NULL;
SortSupport sortKeys;
if (merge)
@@ -1089,7 +1090,6 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
/* the preparation of merge */
itup = tuplesort_getindextuple(btspool->sortstate, true);
itup2 = tuplesort_getindextuple(btspool2->sortstate, true);
- indexScanKey = _bt_mkscankey_nodata(wstate->index);
/* Prepare SortSupport data for each column */
sortKeys = (SortSupport) palloc0(keysz * sizeof(SortSupportData));
@@ -1097,7 +1097,7 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
for (i = 0; i < keysz; i++)
{
SortSupport sortKey = sortKeys + i;
- ScanKey scanKey = indexScanKey + i;
+ ScanKey scanKey = wstate->inskey->scankeys + i;
int16 strategy;
sortKey->ssup_cxt = CurrentMemoryContext;
@@ -1116,8 +1116,6 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
PrepareSortSupportFromIndexRel(wstate->index, strategy, sortKey);
}
- _bt_freeskey(indexScanKey);
-
for (;;)
{
load1 = true; /* load BTSpool next ? */
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5e45..e010bcdcfa 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -56,34 +56,39 @@ static bool _bt_check_rowcompare(ScanKey skey,
* Build an insertion scan key that contains comparison data from itup
* as well as comparator routines appropriate to the key datatypes.
*
- * The result is intended for use with _bt_compare().
+ * Result is intended for use with _bt_compare(). Callers that don't
+ * need to fill out the insertion scankey arguments (e.g. they use an own
+ * ad-hoc comparison routine) can pass a NULL index tuple.
*/
-ScanKey
+BTScanInsert
_bt_mkscankey(Relation rel, IndexTuple itup)
{
+ BTScanInsert key;
ScanKey skey;
TupleDesc itupdesc;
- int indnatts PG_USED_FOR_ASSERTS_ONLY;
int indnkeyatts;
int16 *indoption;
+ int tupnatts;
int i;
itupdesc = RelationGetDescr(rel);
- indnatts = IndexRelationGetNumberOfAttributes(rel);
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
indoption = rel->rd_indoption;
+ tupnatts = itup ? BTreeTupleGetNAtts(itup, rel) : 0;
- Assert(indnkeyatts > 0);
- Assert(indnkeyatts <= indnatts);
- Assert(BTreeTupleGetNAtts(itup, rel) == indnatts ||
- BTreeTupleGetNAtts(itup, rel) == indnkeyatts);
+ Assert(tupnatts <= IndexRelationGetNumberOfAttributes(rel));
/*
* We'll execute search using scan key constructed on key columns. Non-key
* (INCLUDE index) columns are always omitted from scan keys.
*/
- skey = (ScanKey) palloc(indnkeyatts * sizeof(ScanKeyData));
-
+ key = palloc(offsetof(BTScanInsertData, scankeys) +
+ sizeof(ScanKeyData) * indnkeyatts);
+ key->savebinsrch = key->restorebinsrch = false;
+ key->low = key->stricthigh = InvalidOffsetNumber;
+ key->nextkey = false;
+ key->keysz = Min(indnkeyatts, tupnatts);
+ skey = key->scankeys;
for (i = 0; i < indnkeyatts; i++)
{
FmgrInfo *procinfo;
@@ -96,7 +101,19 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
* comparison can be needed.
*/
procinfo = index_getprocinfo(rel, i + 1, BTORDER_PROC);
- arg = index_getattr(itup, i + 1, itupdesc, &null);
+
+ /*
+ * Key arguments built when caller provides no tuple are defensively
+ * represented as NULL values, though they should still not
+ * participate in comparisons.
+ */
+ if (i < tupnatts)
+ arg = index_getattr(itup, i + 1, itupdesc, &null);
+ else
+ {
+ arg = (Datum) 0;
+ null = true;
+ }
flags = (null ? SK_ISNULL : 0) | (indoption[i] << SK_BT_INDOPTION_SHIFT);
ScanKeyEntryInitializeWithInfo(&skey[i],
flags,
@@ -108,64 +125,7 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
arg);
}
- return skey;
-}
-
-/*
- * _bt_mkscankey_nodata
- * Build an insertion scan key that contains 3-way comparator routines
- * appropriate to the key datatypes, but no comparison data. The
- * comparison data ultimately used must match the key datatypes.
- *
- * The result cannot be used with _bt_compare(), unless comparison
- * data is first stored into the key entries. Currently this
- * routine is only called by nbtsort.c and tuplesort.c, which have
- * their own comparison routines.
- */
-ScanKey
-_bt_mkscankey_nodata(Relation rel)
-{
- ScanKey skey;
- int indnkeyatts;
- int16 *indoption;
- int i;
-
- indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
- indoption = rel->rd_indoption;
-
- skey = (ScanKey) palloc(indnkeyatts * sizeof(ScanKeyData));
-
- for (i = 0; i < indnkeyatts; i++)
- {
- FmgrInfo *procinfo;
- int flags;
-
- /*
- * We can use the cached (default) support procs since no cross-type
- * comparison can be needed.
- */
- procinfo = index_getprocinfo(rel, i + 1, BTORDER_PROC);
- flags = SK_ISNULL | (indoption[i] << SK_BT_INDOPTION_SHIFT);
- ScanKeyEntryInitializeWithInfo(&skey[i],
- flags,
- (AttrNumber) (i + 1),
- InvalidStrategy,
- InvalidOid,
- rel->rd_indcollation[i],
- procinfo,
- (Datum) 0);
- }
-
- return skey;
-}
-
-/*
- * free a scan key made by either _bt_mkscankey or _bt_mkscankey_nodata.
- */
-void
-_bt_freeskey(ScanKey skey)
-{
- pfree(skey);
+ return key;
}
/*
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7b10fd2974..f97a82ae7b 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -884,7 +884,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
{
Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate,
randomAccess);
- ScanKey indexScanKey;
+ BTScanInsert indexScanKey;
MemoryContext oldcontext;
int i;
@@ -919,7 +919,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
state->tupDesc = tupDesc; /* assume we need not copy tupDesc */
- indexScanKey = _bt_mkscankey_nodata(indexRel);
+ indexScanKey = _bt_mkscankey(indexRel, NULL);
if (state->indexInfo->ii_Expressions != NULL)
{
@@ -945,7 +945,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
for (i = 0; i < state->nKeys; i++)
{
SortSupport sortKey = state->sortKeys + i;
- ScanKey scanKey = indexScanKey + i;
+ ScanKey scanKey = indexScanKey->scankeys + i;
int16 strategy;
sortKey->ssup_cxt = CurrentMemoryContext;
@@ -964,7 +964,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
PrepareSortSupportFromIndexRel(indexRel, strategy, sortKey);
}
- _bt_freeskey(indexScanKey);
+ pfree(indexScanKey);
MemoryContextSwitchTo(oldcontext);
@@ -981,7 +981,7 @@ tuplesort_begin_index_btree(Relation heapRel,
{
Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate,
randomAccess);
- ScanKey indexScanKey;
+ BTScanInsert indexScanKey;
MemoryContext oldcontext;
int i;
@@ -1014,7 +1014,7 @@ tuplesort_begin_index_btree(Relation heapRel,
state->indexRel = indexRel;
state->enforceUnique = enforceUnique;
- indexScanKey = _bt_mkscankey_nodata(indexRel);
+ indexScanKey = _bt_mkscankey(indexRel, NULL);
/* Prepare SortSupport data for each column */
state->sortKeys = (SortSupport) palloc0(state->nKeys *
@@ -1023,7 +1023,7 @@ tuplesort_begin_index_btree(Relation heapRel,
for (i = 0; i < state->nKeys; i++)
{
SortSupport sortKey = state->sortKeys + i;
- ScanKey scanKey = indexScanKey + i;
+ ScanKey scanKey = indexScanKey->scankeys + i;
int16 strategy;
sortKey->ssup_cxt = CurrentMemoryContext;
@@ -1042,7 +1042,7 @@ tuplesort_begin_index_btree(Relation heapRel,
PrepareSortSupportFromIndexRel(indexRel, strategy, sortKey);
}
- _bt_freeskey(indexScanKey);
+ pfree(indexScanKey);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 60622ea790..950a61958d 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -319,6 +319,47 @@ typedef struct BTStackData
typedef BTStackData *BTStack;
+/*
+ * BTScanInsert is the btree-private state needed to find an initial position
+ * for an indexscan, or to insert new tuples -- an "insertion scankey" (not to
+ * be confused with a search scankey). It's used to descend a B-Tree using
+ * _bt_search. For details on its mutable state, see _bt_binsrch and
+ * _bt_findinsertloc.
+ *
+ * When nextkey is false (the usual case), _bt_search and _bt_binsrch will
+ * locate the first item >= scankey. When nextkey is true, they will locate
+ * the first item > scan key.
+ *
+ * keysz is the number of insertion scankeys present.
+ *
+ * scankeys is an array of scan key entries for attributes that are compared.
+ * During insertion, there must be a scan key for every attribute, but when
+ * starting a regular index scan some can be omitted. The array is used as a
+ * flexible array member, though it's sized in a way that makes it possible to
+ * use stack allocations. See nbtree/README for full details.
+ */
+
+typedef struct BTScanInsertData
+{
+ /*
+ * Mutable state used by _bt_binsrch to inexpensively repeat a binary
+ * search on the leaf level. Only used for insertions where
+ * _bt_check_unique is called.
+ */
+ bool savebinsrch;
+ bool restorebinsrch;
+ OffsetNumber low;
+ OffsetNumber stricthigh;
+
+ /* State used to locate a position at the leaf level */
+ bool nextkey;
+ int keysz; /* Size of scankeys */
+ ScanKeyData scankeys[INDEX_MAX_KEYS]; /* Must appear last */
+} BTScanInsertData;
+
+typedef BTScanInsertData *BTScanInsert;
+
+
/*
* BTScanOpaqueData is the btree-private state needed for an indexscan.
* This consists of preprocessed scan keys (see _bt_preprocess_keys() for
@@ -558,16 +599,12 @@ extern int _bt_pagedel(Relation rel, Buffer buf);
/*
* prototypes for functions in nbtsearch.c
*/
-extern BTStack _bt_search(Relation rel,
- int keysz, ScanKey scankey, bool nextkey,
- Buffer *bufP, int access, Snapshot snapshot);
-extern Buffer _bt_moveright(Relation rel, Buffer buf, int keysz,
- ScanKey scankey, bool nextkey, bool forupdate, BTStack stack,
- int access, Snapshot snapshot);
-extern OffsetNumber _bt_binsrch(Relation rel, Buffer buf, int keysz,
- ScanKey scankey, bool nextkey);
-extern int32 _bt_compare(Relation rel, int keysz, ScanKey scankey,
- Page page, OffsetNumber offnum);
+extern BTStack _bt_search(Relation rel, BTScanInsert key, Buffer *bufP,
+ int access, Snapshot snapshot);
+extern Buffer _bt_moveright(Relation rel, BTScanInsert key, Buffer buf,
+ bool forupdate, BTStack stack, int access, Snapshot snapshot);
+extern OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf);
+extern int32 _bt_compare(Relation rel, BTScanInsert key, Page page, OffsetNumber offnum);
extern bool _bt_first(IndexScanDesc scan, ScanDirection dir);
extern bool _bt_next(IndexScanDesc scan, ScanDirection dir);
extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
@@ -576,9 +613,7 @@ extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
/*
* prototypes for functions in nbtutils.c
*/
-extern ScanKey _bt_mkscankey(Relation rel, IndexTuple itup);
-extern ScanKey _bt_mkscankey_nodata(Relation rel);
-extern void _bt_freeskey(ScanKey skey);
+extern BTScanInsert _bt_mkscankey(Relation rel, IndexTuple itup);
extern void _bt_freestack(BTStack stack);
extern void _bt_preprocess_array_keys(IndexScanDesc scan);
extern void _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir);
--
2.17.1