summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/amcheck/expected/check_btree.out5
-rw-r--r--contrib/amcheck/sql/check_btree.sql5
-rw-r--r--contrib/amcheck/verify_nbtree.c341
-rw-r--r--contrib/pageinspect/btreefuncs.c2
-rw-r--r--contrib/pageinspect/expected/btree.out2
-rw-r--r--contrib/pgstattuple/expected/pgstattuple.out10
6 files changed, 317 insertions, 48 deletions
diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index ef5c9e1a1c3..1e6079ddd29 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -130,9 +130,12 @@ SELECT bt_index_parent_check('bttest_multi_idx', true);
--
INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
+-- Delete many entries, and vacuum. This causes page deletions.
DELETE FROM delete_test_table WHERE a > 40000;
VACUUM delete_test_table;
-DELETE FROM delete_test_table WHERE a > 10;
+-- Delete most entries, and vacuum, deleting internal pages and creating "fast
+-- root"
+DELETE FROM delete_test_table WHERE a < 79990;
VACUUM delete_test_table;
SELECT bt_index_parent_check('delete_test_table_pkey', true);
bt_index_parent_check
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index 0ad1631476d..3f1e0d17efe 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -82,9 +82,12 @@ SELECT bt_index_parent_check('bttest_multi_idx', true);
--
INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
+-- Delete many entries, and vacuum. This causes page deletions.
DELETE FROM delete_test_table WHERE a > 40000;
VACUUM delete_test_table;
-DELETE FROM delete_test_table WHERE a > 10;
+-- Delete most entries, and vacuum, deleting internal pages and creating "fast
+-- root"
+DELETE FROM delete_test_table WHERE a < 79990;
VACUUM delete_test_table;
SELECT bt_index_parent_check('delete_test_table_pkey', true);
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 5426bfd8d87..4363e6b82e7 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -46,6 +46,8 @@ PG_MODULE_MAGIC;
* block per level, which is bound by the range of BlockNumber:
*/
#define InvalidBtreeLevel ((uint32) InvalidBlockNumber)
+#define BTreeTupleGetNKeyAtts(itup, rel) \
+ Min(IndexRelationGetNumberOfKeyAttributes(rel), BTreeTupleGetNAtts(itup, rel))
/*
* State associated with verifying a B-Tree index
@@ -67,6 +69,8 @@ typedef struct BtreeCheckState
/* B-Tree Index Relation and associated heap relation */
Relation rel;
Relation heaprel;
+ /* rel is heapkeyspace index? */
+ bool heapkeyspace;
/* ShareLock held on heap/index, rather than AccessShareLock? */
bool readonly;
/* Also verifying heap has no unindexed tuples? */
@@ -123,7 +127,7 @@ static void bt_index_check_internal(Oid indrelid, bool parentcheck,
bool heapallindexed);
static inline void btree_index_checkable(Relation rel);
static void bt_check_every_level(Relation rel, Relation heaprel,
- bool readonly, bool heapallindexed);
+ bool heapkeyspace, bool readonly, bool heapallindexed);
static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state,
BtreeLevel level);
static void bt_target_page_check(BtreeCheckState *state);
@@ -138,17 +142,22 @@ static IndexTuple bt_normalize_tuple(BtreeCheckState *state,
IndexTuple itup);
static inline bool offset_is_negative_infinity(BTPageOpaque opaque,
OffsetNumber offset);
+static inline bool invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
+ OffsetNumber upperbound);
static inline bool invariant_leq_offset(BtreeCheckState *state,
BTScanInsert key,
OffsetNumber upperbound);
-static inline bool invariant_geq_offset(BtreeCheckState *state,
- BTScanInsert key,
- OffsetNumber lowerbound);
-static inline bool invariant_leq_nontarget_offset(BtreeCheckState *state,
- BTScanInsert key,
- Page nontarget,
- OffsetNumber upperbound);
+static inline bool invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
+ OffsetNumber lowerbound);
+static inline bool invariant_l_nontarget_offset(BtreeCheckState *state,
+ BTScanInsert key,
+ Page nontarget,
+ OffsetNumber upperbound);
static Page palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum);
+static inline BTScanInsert bt_mkscankey_pivotsearch(Relation rel,
+ IndexTuple itup);
+static inline ItemPointer BTreeTupleGetHeapTIDCareful(BtreeCheckState *state,
+ IndexTuple itup, bool nonpivot);
/*
* bt_index_check(index regclass, heapallindexed boolean)
@@ -205,6 +214,7 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed)
Oid heapid;
Relation indrel;
Relation heaprel;
+ bool heapkeyspace;
LOCKMODE lockmode;
if (parentcheck)
@@ -255,7 +265,9 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed)
btree_index_checkable(indrel);
/* Check index, possibly against table it is an index on */
- bt_check_every_level(indrel, heaprel, parentcheck, heapallindexed);
+ heapkeyspace = _bt_heapkeyspace(indrel);
+ bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck,
+ heapallindexed);
/*
* Release locks early. That's ok here because nothing in the called
@@ -325,8 +337,8 @@ btree_index_checkable(Relation rel)
* parent/child check cannot be affected.)
*/
static void
-bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
- bool heapallindexed)
+bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
+ bool readonly, bool heapallindexed)
{
BtreeCheckState *state;
Page metapage;
@@ -347,6 +359,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
state = palloc0(sizeof(BtreeCheckState));
state->rel = rel;
state->heaprel = heaprel;
+ state->heapkeyspace = heapkeyspace;
state->readonly = readonly;
state->heapallindexed = heapallindexed;
@@ -807,7 +820,8 @@ bt_target_page_check(BtreeCheckState *state)
* doesn't contain a high key, so nothing to check
*/
if (!P_RIGHTMOST(topaque) &&
- !_bt_check_natts(state->rel, state->target, P_HIKEY))
+ !_bt_check_natts(state->rel, state->heapkeyspace, state->target,
+ P_HIKEY))
{
ItemId itemid;
IndexTuple itup;
@@ -840,6 +854,7 @@ bt_target_page_check(BtreeCheckState *state)
IndexTuple itup;
size_t tupsize;
BTScanInsert skey;
+ bool lowersizelimit;
CHECK_FOR_INTERRUPTS();
@@ -866,7 +881,8 @@ bt_target_page_check(BtreeCheckState *state)
errhint("This could be a torn page problem.")));
/* Check the number of index tuple attributes */
- if (!_bt_check_natts(state->rel, state->target, offset))
+ if (!_bt_check_natts(state->rel, state->heapkeyspace, state->target,
+ offset))
{
char *itid,
*htid;
@@ -907,7 +923,56 @@ bt_target_page_check(BtreeCheckState *state)
continue;
/* Build insertion scankey for current page offset */
- skey = _bt_mkscankey(state->rel, itup);
+ skey = bt_mkscankey_pivotsearch(state->rel, itup);
+
+ /*
+ * Make sure tuple size does not exceed the relevant BTREE_VERSION
+ * specific limit.
+ *
+ * BTREE_VERSION 4 (which introduced heapkeyspace rules) requisitioned
+ * a small amount of space from BTMaxItemSize() in order to ensure
+ * that suffix truncation always has enough space to add an explicit
+ * heap TID back to a tuple -- we pessimistically assume that every
+ * newly inserted tuple will eventually need to have a heap TID
+ * appended during a future leaf page split, when the tuple becomes
+ * the basis of the new high key (pivot tuple) for the leaf page.
+ *
+ * Since the reclaimed space is reserved for that purpose, we must not
+ * enforce the slightly lower limit when the extra space has been used
+ * as intended. In other words, there is only a cross-version
+ * difference in the limit on tuple size within leaf pages.
+ *
+ * Still, we're particular about the details within BTREE_VERSION 4
+ * internal pages. Pivot tuples may only use the extra space for its
+ * designated purpose. Enforce the lower limit for pivot tuples when
+ * an explicit heap TID isn't actually present. (In all other cases
+ * suffix truncation is guaranteed to generate a pivot tuple that's no
+ * larger than the first right tuple provided to it by its caller.)
+ */
+ lowersizelimit = skey->heapkeyspace &&
+ (P_ISLEAF(topaque) || BTreeTupleGetHeapTID(itup) == NULL);
+ if (tupsize > (lowersizelimit ? BTMaxItemSize(state->target) :
+ BTMaxItemSizeNoHeapTid(state->target)))
+ {
+ char *itid,
+ *htid;
+
+ itid = psprintf("(%u,%u)", state->targetblock, offset);
+ htid = psprintf("(%u,%u)",
+ ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+ ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("index row size %zu exceeds maximum for index \"%s\"",
+ tupsize, RelationGetRelationName(state->rel)),
+ errdetail_internal("Index tid=%s points to %s tid=%s page lsn=%X/%X.",
+ itid,
+ P_ISLEAF(topaque) ? "heap" : "index",
+ htid,
+ (uint32) (state->targetlsn >> 32),
+ (uint32) state->targetlsn)));
+ }
/* Fingerprint leaf page tuples (those that point to the heap) */
if (state->heapallindexed && P_ISLEAF(topaque) && !ItemIdIsDead(itemid))
@@ -941,9 +1006,35 @@ bt_target_page_check(BtreeCheckState *state)
* grandparents (as well as great-grandparents, and so on). We don't
* go to those lengths because that would be prohibitively expensive,
* and probably not markedly more effective in practice.
+ *
+ * On the leaf level, we check that the key is <= the highkey.
+ * However, on non-leaf levels we check that the key is < the highkey,
+ * because the high key is "just another separator" rather than a copy
+ * of some existing key item; we expect it to be unique among all keys
+ * on the same level. (Suffix truncation will sometimes produce a
+ * leaf highkey that is an untruncated copy of the lastleft item, but
+ * never any other item, which necessitates weakening the leaf level
+ * check to <=.)
+ *
+ * Full explanation for why a highkey is never truly a copy of another
+ * item from the same level on internal levels:
+ *
+ * While the new left page's high key is copied from the first offset
+ * on the right page during an internal page split, that's not the
+ * full story. In effect, internal pages are split in the middle of
+ * the firstright tuple, not between the would-be lastleft and
+ * firstright tuples: the firstright key ends up on the left side as
+ * left's new highkey, and the firstright downlink ends up on the
+ * right side as right's new "negative infinity" item. The negative
+ * infinity tuple is truncated to zero attributes, so we're only left
+ * with the downlink. In other words, the copying is just an
+ * implementation detail of splitting in the middle of a (pivot)
+ * tuple. (See also: "Notes About Data Representation" in the nbtree
+ * README.)
*/
if (!P_RIGHTMOST(topaque) &&
- !invariant_leq_offset(state, skey, P_HIKEY))
+ !(P_ISLEAF(topaque) ? invariant_leq_offset(state, skey, P_HIKEY) :
+ invariant_l_offset(state, skey, P_HIKEY)))
{
char *itid,
*htid;
@@ -969,11 +1060,10 @@ bt_target_page_check(BtreeCheckState *state)
* * Item order check *
*
* Check that items are stored on page in logical order, by checking
- * current item is less than or equal to next item (if any).
+ * current item is strictly less than next item (if any).
*/
if (OffsetNumberNext(offset) <= max &&
- !invariant_leq_offset(state, skey,
- OffsetNumberNext(offset)))
+ !invariant_l_offset(state, skey, OffsetNumberNext(offset)))
{
char *itid,
*htid,
@@ -1036,7 +1126,7 @@ bt_target_page_check(BtreeCheckState *state)
rightkey = bt_right_page_check_scankey(state);
if (rightkey &&
- !invariant_geq_offset(state, rightkey, max))
+ !invariant_g_offset(state, rightkey, max))
{
/*
* As explained at length in bt_right_page_check_scankey(),
@@ -1214,9 +1304,9 @@ bt_right_page_check_scankey(BtreeCheckState *state)
* continued existence of target block as non-ignorable (not half-dead or
* deleted) implies that target page was not merged into from the right by
* deletion; the key space at or after target never moved left. Target's
- * parent either has the same downlink to target as before, or a <=
+ * parent either has the same downlink to target as before, or a <
* downlink due to deletion at the left of target. Target either has the
- * same highkey as before, or a highkey <= before when there is a page
+ * same highkey as before, or a highkey < before when there is a page
* split. (The rightmost concurrently-split-from-target-page page will
* still have the same highkey as target was originally found to have,
* which for our purposes is equivalent to target's highkey itself never
@@ -1305,7 +1395,7 @@ bt_right_page_check_scankey(BtreeCheckState *state)
* memory remaining allocated.
*/
firstitup = (IndexTuple) PageGetItem(rightpage, rightitem);
- return _bt_mkscankey(state->rel, firstitup);
+ return bt_mkscankey_pivotsearch(state->rel, firstitup);
}
/*
@@ -1368,7 +1458,8 @@ bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
/*
* Verify child page has the downlink key from target page (its parent) as
- * a lower bound.
+ * a lower bound; downlink must be strictly less than all keys on the
+ * page.
*
* Check all items, rather than checking just the first and trusting that
* the operator class obeys the transitive law.
@@ -1417,14 +1508,29 @@ bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
{
/*
* Skip comparison of target page key against "negative infinity"
- * item, if any. Checking it would indicate that it's not an upper
- * bound, but that's only because of the hard-coding within
- * _bt_compare().
+ * item, if any. Checking it would indicate that it's not a strict
+ * lower bound, but that's only because of the hard-coding for
+ * negative infinity items within _bt_compare().
+ *
+ * If nbtree didn't truncate negative infinity tuples during internal
+ * page splits then we'd expect child's negative infinity key to be
+ * equal to the scankey/downlink from target/parent (it would be a
+ * "low key" in this hypothetical scenario, and so it would still need
+ * to be treated as a special case here).
+ *
+ * Negative infinity items can be thought of as a strict lower bound
+ * that works transitively, with the last non-negative-infinity pivot
+ * followed during a descent from the root as its "true" strict lower
+ * bound. Only a small number of negative infinity items are truly
+ * negative infinity; those that are the first items of leftmost
+ * internal pages. In more general terms, a negative infinity item is
+ * only negative infinity with respect to the subtree that the page is
+ * at the root of.
*/
if (offset_is_negative_infinity(copaque, offset))
continue;
- if (!invariant_leq_nontarget_offset(state, targetkey, child, offset))
+ if (!invariant_l_nontarget_offset(state, targetkey, child, offset))
ereport(ERROR,
(errcode(ERRCODE_INDEX_CORRUPTED),
errmsg("down-link lower bound invariant violated for index \"%s\"",
@@ -1857,6 +1963,64 @@ offset_is_negative_infinity(BTPageOpaque opaque, OffsetNumber offset)
}
/*
+ * Does the invariant hold that the key is strictly less than a given upper
+ * bound offset item?
+ *
+ * If this function returns false, convention is that caller throws error due
+ * to corruption.
+ */
+static inline bool
+invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
+ OffsetNumber upperbound)
+{
+ int32 cmp;
+
+ Assert(key->pivotsearch);
+
+ /* pg_upgrade'd indexes may legally have equal sibling tuples */
+ if (!key->heapkeyspace)
+ return invariant_leq_offset(state, key, upperbound);
+
+ cmp = _bt_compare(state->rel, key, state->target, upperbound);
+
+ /*
+ * _bt_compare() is capable of determining that a scankey with a
+ * filled-out attribute is greater than pivot tuples where the comparison
+ * is resolved at a truncated attribute (value of attribute in pivot is
+ * minus infinity). However, it is not capable of determining that a
+ * scankey is _less than_ a tuple on the basis of a comparison resolved at
+ * _scankey_ minus infinity attribute. Complete an extra step to simulate
+ * having minus infinity values for omitted scankey attribute(s).
+ */
+ if (cmp == 0)
+ {
+ BTPageOpaque topaque;
+ ItemId itemid;
+ IndexTuple ritup;
+ int uppnkeyatts;
+ ItemPointer rheaptid;
+ bool nonpivot;
+
+ itemid = PageGetItemId(state->target, upperbound);
+ ritup = (IndexTuple) PageGetItem(state->target, itemid);
+ topaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
+ nonpivot = P_ISLEAF(topaque) && upperbound >= P_FIRSTDATAKEY(topaque);
+
+ /* Get number of keys + heap TID for item to the right */
+ uppnkeyatts = BTreeTupleGetNKeyAtts(ritup, state->rel);
+ rheaptid = BTreeTupleGetHeapTIDCareful(state, ritup, nonpivot);
+
+ /* Heap TID is tiebreaker key attribute */
+ if (key->keysz == uppnkeyatts)
+ return key->scantid == NULL && rheaptid != NULL;
+
+ return key->keysz < uppnkeyatts;
+ }
+
+ return cmp < 0;
+}
+
+/*
* Does the invariant hold that the key is less than or equal to a given upper
* bound offset item?
*
@@ -1869,48 +2033,97 @@ invariant_leq_offset(BtreeCheckState *state, BTScanInsert key,
{
int32 cmp;
+ Assert(key->pivotsearch);
+
cmp = _bt_compare(state->rel, key, state->target, upperbound);
return cmp <= 0;
}
/*
- * Does the invariant hold that the key is greater than or equal to a given
- * lower bound offset item?
+ * Does the invariant hold that the key is strictly greater than a given lower
+ * bound offset item?
*
* If this function returns false, convention is that caller throws error due
* to corruption.
*/
static inline bool
-invariant_geq_offset(BtreeCheckState *state, BTScanInsert key,
- OffsetNumber lowerbound)
+invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
+ OffsetNumber lowerbound)
{
int32 cmp;
+ Assert(key->pivotsearch);
+
cmp = _bt_compare(state->rel, key, state->target, lowerbound);
- return cmp >= 0;
+ /* pg_upgrade'd indexes may legally have equal sibling tuples */
+ if (!key->heapkeyspace)
+ return cmp >= 0;
+
+ /*
+ * No need to consider the possibility that scankey has attributes that we
+ * need to force to be interpreted as negative infinity. _bt_compare() is
+ * able to determine that scankey is greater than negative infinity. The
+ * distinction between "==" and "<" isn't interesting here, since
+ * corruption is indicated either way.
+ */
+ return cmp > 0;
}
/*
- * Does the invariant hold that the key is less than or equal to a given upper
+ * Does the invariant hold that the key is strictly less than a given upper
* bound offset item, with the offset relating to a caller-supplied page that
- * is not the current target page? Caller's non-target page is typically a
- * child page of the target, checked as part of checking a property of the
- * target page (i.e. the key comes from the target).
+ * is not the current target page?
+ *
+ * Caller's non-target page is a child page of the target, checked as part of
+ * checking a property of the target page (i.e. the key comes from the
+ * target).
*
* If this function returns false, convention is that caller throws error due
* to corruption.
*/
static inline bool
-invariant_leq_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
- Page nontarget, OffsetNumber upperbound)
+invariant_l_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
+ Page nontarget, OffsetNumber upperbound)
{
int32 cmp;
+ Assert(key->pivotsearch);
+
cmp = _bt_compare(state->rel, key, nontarget, upperbound);
- return cmp <= 0;
+ /* pg_upgrade'd indexes may legally have equal sibling tuples */
+ if (!key->heapkeyspace)
+ return cmp <= 0;
+
+ /* See invariant_l_offset() for an explanation of this extra step */
+ if (cmp == 0)
+ {
+ ItemId itemid;
+ IndexTuple child;
+ int uppnkeyatts;
+ ItemPointer childheaptid;
+ BTPageOpaque copaque;
+ bool nonpivot;
+
+ itemid = PageGetItemId(nontarget, upperbound);
+ child = (IndexTuple) PageGetItem(nontarget, itemid);
+ copaque = (BTPageOpaque) PageGetSpecialPointer(nontarget);
+ nonpivot = P_ISLEAF(copaque) && upperbound >= P_FIRSTDATAKEY(copaque);
+
+ /* Get number of keys + heap TID for child/non-target item */
+ uppnkeyatts = BTreeTupleGetNKeyAtts(child, state->rel);
+ childheaptid = BTreeTupleGetHeapTIDCareful(state, child, nonpivot);
+
+ /* Heap TID is tiebreaker key attribute */
+ if (key->keysz == uppnkeyatts)
+ return key->scantid == NULL && childheaptid != NULL;
+
+ return key->keysz < uppnkeyatts;
+ }
+
+ return cmp < 0;
}
/*
@@ -2066,3 +2279,53 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
return page;
}
+
+/*
+ * _bt_mkscankey() wrapper that automatically prevents insertion scankey from
+ * being considered greater than the pivot tuple that its values originated
+ * from (or some other identical pivot tuple) in the common case where there
+ * are truncated/minus infinity attributes. Without this extra step, there
+ * are forms of corruption that amcheck could theoretically fail to report.
+ *
+ * For example, invariant_g_offset() might miss a cross-page invariant failure
+ * on an internal level if the scankey built from the first item on the
+ * target's right sibling page happened to be equal to (not greater than) the
+ * last item on target page. The !pivotsearch tiebreaker in _bt_compare()
+ * might otherwise cause amcheck to assume (rather than actually verify) that
+ * the scankey is greater.
+ */
+static inline BTScanInsert
+bt_mkscankey_pivotsearch(Relation rel, IndexTuple itup)
+{
+ BTScanInsert skey;
+
+ skey = _bt_mkscankey(rel, itup);
+ skey->pivotsearch = true;
+
+ return skey;
+}
+
+/*
+ * BTreeTupleGetHeapTID() wrapper that lets caller enforce that a heap TID must
+ * be present in cases where that is mandatory.
+ *
+ * This doesn't add much as of BTREE_VERSION 4, since the INDEX_ALT_TID_MASK
+ * bit is effectively a proxy for whether or not the tuple is a pivot tuple.
+ * It may become more useful in the future, when non-pivot tuples support their
+ * own alternative INDEX_ALT_TID_MASK representation.
+ */
+static inline ItemPointer
+BTreeTupleGetHeapTIDCareful(BtreeCheckState *state, IndexTuple itup,
+ bool nonpivot)
+{
+ ItemPointer result = BTreeTupleGetHeapTID(itup);
+ BlockNumber targetblock = state->targetblock;
+
+ if (result == NULL && nonpivot)
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("block %u or its right sibling block or child block in index \"%s\" contains non-pivot tuple that lacks a heap TID",
+ targetblock, RelationGetRelationName(state->rel))));
+
+ return result;
+}
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index bfa0c04c2f1..8d27c9b0f6f 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -561,7 +561,7 @@ bt_metap(PG_FUNCTION_ARGS)
* Get values of extended metadata if available, use default values
* otherwise.
*/
- if (metad->btm_version == BTREE_VERSION)
+ if (metad->btm_version >= BTREE_NOVAC_VERSION)
{
values[j++] = psprintf("%u", metad->btm_oldest_btpo_xact);
values[j++] = psprintf("%f", metad->btm_last_cleanup_num_heap_tuples);
diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out
index 2aaa4df53b1..07c2dcd7714 100644
--- a/contrib/pageinspect/expected/btree.out
+++ b/contrib/pageinspect/expected/btree.out
@@ -5,7 +5,7 @@ CREATE INDEX test1_a_idx ON test1 USING btree (a);
SELECT * FROM bt_metap('test1_a_idx');
-[ RECORD 1 ]-----------+-------
magic | 340322
-version | 3
+version | 4
root | 1
level | 0
fastroot | 1
diff --git a/contrib/pgstattuple/expected/pgstattuple.out b/contrib/pgstattuple/expected/pgstattuple.out
index 9858ea69d49..9920dbfd408 100644
--- a/contrib/pgstattuple/expected/pgstattuple.out
+++ b/contrib/pgstattuple/expected/pgstattuple.out
@@ -48,7 +48,7 @@ select version, tree_level,
from pgstatindex('test_pkey');
version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
- 3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
+ 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
(1 row)
select version, tree_level,
@@ -58,7 +58,7 @@ select version, tree_level,
from pgstatindex('test_pkey'::text);
version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
- 3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
+ 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
(1 row)
select version, tree_level,
@@ -68,7 +68,7 @@ select version, tree_level,
from pgstatindex('test_pkey'::name);
version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
- 3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
+ 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
(1 row)
select version, tree_level,
@@ -78,7 +78,7 @@ select version, tree_level,
from pgstatindex('test_pkey'::regclass);
version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
- 3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
+ 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
(1 row)
select pg_relpages('test');
@@ -232,7 +232,7 @@ create index test_partition_hash_idx on test_partition using hash (a);
select pgstatindex('test_partition_idx');
pgstatindex
------------------------------
- (3,0,8192,0,0,0,0,0,NaN,NaN)
+ (4,0,8192,0,0,0,0,0,NaN,NaN)
(1 row)
select pgstathashindex('test_partition_hash_idx');