From 3b301a01ac22ae86069fefe4f566ae3881caf334 Mon Sep 17 00:00:00 2001 From: Shihao Date: Fri, 25 Sep 2026 11:36:40 -0400 Subject: [PATCH v2026-09-25 3/3] amcheck: Review fixes for gist_index_check() Do per-tuple work in a temporary context, so memory use no longer grows with the table size. A downlink or right link of InvalidBlockNumber is P_NEW, so the check extended the index. A root page marked as split crashed on a NULL pointer. Report both as corruption. Add a TAP test that corrupts the index file. Also some small review fixes. --- contrib/amcheck/meson.build | 1 + contrib/amcheck/t/007_verify_gist.pl | 239 +++++++++++++++++++++++++++ contrib/amcheck/verify_gist.c | 168 +++++++++++-------- doc/src/sgml/amcheck.sgml | 17 +- src/tools/pgindent/typedefs.list | 2 + 5 files changed, 350 insertions(+), 77 deletions(-) create mode 100644 contrib/amcheck/t/007_verify_gist.pl diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build index 18a87fa8f8c..d78953bc874 100644 --- a/contrib/amcheck/meson.build +++ b/contrib/amcheck/meson.build @@ -53,6 +53,7 @@ tests += { 't/004_verify_nbtree_unique.pl', 't/005_pitr.pl', 't/006_verify_gin.pl', + 't/007_verify_gist.pl', ], }, } diff --git a/contrib/amcheck/t/007_verify_gist.pl b/contrib/amcheck/t/007_verify_gist.pl new file mode 100644 index 00000000000..3bad88a1b66 --- /dev/null +++ b/contrib/amcheck/t/007_verify_gist.pl @@ -0,0 +1,239 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Check that gist_index_check() reports corruption it is supposed to find. +# The regression tests only run it on healthy indexes, which cannot tell a +# working checker from one that never reports anything. + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +my $node; +my $blksize; + +$node = PostgreSQL::Test::Cluster->new('test'); +$node->init(no_data_checksums => 1); +$node->append_conf('postgresql.conf', 'autovacuum=off'); +$node->start; +$blksize = int($node->safe_psql('postgres', 'SHOW block_size;')); +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); + +inconsistent_parent_key_test(); +missing_heap_tuple_test(); +invalid_link_test('rightlink'); +invalid_link_test('downlink'); +invalid_link_test('root'); + +$node->stop; +done_testing(); + +# Build a point index with an internal root page. All points are in +# [0, 1000] except the ones given, so their coordinates are easy to find +# in the index file. +sub create_point_index +{ + my ($relname, $indexname, @extra_points) = @_; + + my $extra = join('', + map { "INSERT INTO $relname VALUES (point($_, $_));\n" } + @extra_points); + + $node->safe_psql( + 'postgres', qq( + DROP TABLE IF EXISTS $relname; + CREATE TABLE $relname (p point); + INSERT INTO $relname + SELECT point(random() * 1000, random() * 1000) + FROM generate_series(1, 5000); + $extra + CREATE INDEX $indexname ON $relname USING gist (p); + )); + + # The corruption below relies on the root page being an internal page. + my $pages = $node->safe_psql('postgres', + qq(SELECT pg_relation_size('$indexname') / $blksize)); + cmp_ok($pages, '>', 2, "$indexname has more than one level"); + + # A healthy index must pass both kinds of check. + my ($ret, $stdout, $stderr) = $node->psql('postgres', + qq(SELECT gist_index_check('$indexname', true))); + is($ret, 0, "healthy $indexname passes gist_index_check"); + is($stderr, '', "healthy $indexname reports nothing"); +} + +# Shrink a downlink key in the root page, so that it no longer covers the +# keys on the child page. +sub inconsistent_parent_key_test +{ + my $relname = 'gist_parent'; + my $indexname = 'gist_parent_idx'; + + create_point_index($relname, $indexname, 99999); + my $relpath = relation_filepath($indexname); + + $node->stop; + my $n = float8_replace_blocks($relpath, 99999, 1, 0, 0); + cmp_ok($n, '>', 0, 'corrupted downlink key in root page'); + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + qq(SELECT gist_index_check('$indexname', false))); + like( + $stderr, + qr/index "$indexname" has inconsistent records on page \d+ offset \d+/, + 'inconsistent downlink key is reported'); +} + +# Change one leaf key to a value still covered by its parent. The tree +# stays consistent, so only heapallindexed can notice the heap tuple that +# has no index entry. +sub missing_heap_tuple_test +{ + my $relname = 'gist_leaf'; + my $indexname = 'gist_leaf_idx'; + + create_point_index($relname, $indexname, 77777, 99999); + my $relpath = relation_filepath($indexname); + my $nblocks = (-s $relpath) / $blksize; + + $node->stop; + # Skip the root page, only the leaf copy of the key is changed. + my $n = float8_replace_blocks($relpath, 77777, 77776.5, 1, $nblocks - 1); + is($n, 4, 'changed both corners of one leaf key'); + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + qq(SELECT gist_index_check('$indexname', false))); + is($stderr, '', 'structure check alone does not notice a changed leaf key'); + + ($ret, $stdout, $stderr) = $node->psql('postgres', + qq(SELECT gist_index_check('$indexname', true))); + like( + $stderr, + qr/heap tuple \(\d+,\d+\) from table "$relname" lacks matching index tuple within index "$indexname"/, + 'heapallindexed reports the heap tuple without index tuple'); +} + +# Corrupt a link that the check follows. InvalidBlockNumber is P_NEW, and +# reading it would extend the index, so check that the size does not change. +sub invalid_link_test +{ + my ($kind) = @_; + my $relname = "gist_$kind"; + my $indexname = "gist_${kind}_idx"; + + create_point_index($relname, $indexname, 99999); + my $relpath = relation_filepath($indexname); + my $size = -s $relpath; + + $node->stop; + if ($kind eq 'rightlink') + { + # Mark a non-root page as split, with no right sibling. + modify_block($relpath, 1, + sub { set_opaque($_[0], 0xFFFFFFFF, 1 << 3) }); + } + elsif ($kind eq 'root') + { + # Mark the root page as split, with a right sibling that exists. + modify_block($relpath, 0, sub { set_opaque($_[0], 1, 1 << 3) }); + } + else + { + # The root downlink whose key covers the extra point. Its t_tid + # is just before the key. + modify_block( + $relpath, 0, + sub { + my $pos = index($_[0], pack('dd', 99999, 99999)); + die "downlink key not found" if $pos < 8; + substr($_[0], $pos - 8, 4) = pack('SS', 0xFFFF, 0xFFFF); + }); + } + $node->start; + + my %expected = ( + rightlink => qr/index "$indexname" has page 1 marked as split without right sibling/, + downlink => qr/index "$indexname" has invalid downlink on page 0 offset \d+/, + root => qr/index "$indexname" has root page marked as split/); + my ($ret, $stdout, $stderr) = $node->psql('postgres', + qq(SELECT gist_index_check('$indexname', false))); + like($stderr, $expected{$kind}, "invalid $kind is reported"); + is(-s $relpath, $size, "check with invalid $kind does not extend the index"); +} + +# Set the right link and add flags in the GiST page opaque data. +sub set_opaque +{ + my ($rightlink, $flags) = @_[1, 2]; + my $special = unpack('S', substr($_[0], 16, 2)); + my $oldflags = unpack('S', substr($_[0], $special + 12, 2)); + + substr($_[0], $special + 8, 6) = pack('LS', $rightlink, $oldflags | $flags); +} + +# Read one block, let the callback change it in place, and write it back. +sub modify_block +{ + my ($filename, $blkno, $callback) = @_; + my $buffer; + + open(my $fh, '+<', $filename) or BAIL_OUT("open failed: $!"); + binmode $fh; + sysseek($fh, $blkno * $blksize, 0) or BAIL_OUT("seek failed: $!"); + sysread($fh, $buffer, $blksize) == $blksize + or BAIL_OUT("read failed: $!"); + $callback->($buffer); + sysseek($fh, $blkno * $blksize, 0) or BAIL_OUT("seek failed: $!"); + syswrite($fh, $buffer) == $blksize or BAIL_OUT("write failed: $!"); + close($fh) or BAIL_OUT("close failed: $!"); +} + +sub relation_filepath +{ + my ($relname) = @_; + + my $pgdata = $node->data_dir; + my $rel = $node->safe_psql('postgres', + qq(SELECT pg_relation_filepath('$relname'))); + die "path not found for relation $relname" unless defined $rel; + return "$pgdata/$rel"; +} + +# Replace every float8 value 'find' with 'replace' in blocks first..last of +# the file. Values are packed in native byte order, as stored on disk. +# Returns the number of replacements. +sub float8_replace_blocks +{ + my ($filename, $find, $replace, $first, $last) = @_; + my $pattern = quotemeta(pack('d', $find)); + my $new = pack('d', $replace); + my $count = 0; + + open(my $fh, '+<', $filename) or BAIL_OUT("open failed: $!"); + binmode $fh; + + for my $blkno ($first .. $last) + { + my $offset = $blkno * $blksize; + my $buffer; + + sysseek($fh, $offset, 0) or BAIL_OUT("seek failed: $!"); + sysread($fh, $buffer, $blksize) == $blksize + or BAIL_OUT("read failed: $!"); + + my $n = ($buffer =~ s/$pattern/$new/g); + next unless $n; + $count += $n; + + sysseek($fh, $offset, 0) or BAIL_OUT("seek failed: $!"); + syswrite($fh, $buffer) == $blksize or BAIL_OUT("write failed: $!"); + } + + close($fh) or BAIL_OUT("close failed: $!"); + return $count; +} diff --git a/contrib/amcheck/verify_gist.c b/contrib/amcheck/verify_gist.c index 0d9a50c9dd4..ee6641d9cc9 100644 --- a/contrib/amcheck/verify_gist.c +++ b/contrib/amcheck/verify_gist.c @@ -5,12 +5,12 @@ * * Verification checks that all paths in GiST graph contain * consistent keys: tuples on parent pages consistently include tuples - * from children pages. Also, verification checks graph invariants: - * internal page must have at least one downlink, internal page can - * reference either only leaf pages or only internal pages. + * from children pages. Also, verification checks that all leaf pages are + * at the same depth, so an internal page references either only leaf pages + * or only internal pages. * * - * Copyright (c) 2017-2025, PostgreSQL Global Development Group + * Copyright (c) 2017-2026, PostgreSQL Global Development Group * * IDENTIFICATION * contrib/amcheck/verify_gist.c @@ -48,7 +48,8 @@ typedef struct GistScanItem /* * LSN to handle concurrent scans of the page. It's necessary to avoid - * missing some subtrees from the page that was split just before we read it. + * missing some subtrees from the page that was split just before we read + * it. */ XLogRecPtr parentlsn; @@ -60,7 +61,7 @@ typedef struct GistScanItem /* Pointer to the next stack item. */ struct GistScanItem *next; -} GistScanItem; +} GistScanItem; typedef struct GistCheckState { @@ -83,14 +84,14 @@ typedef struct GistCheckState BlockNumber deltablocks; int leafdepth; -} GistCheckState; +} GistCheckState; PG_FUNCTION_INFO_V1(gist_index_check); -static void giststate_init_heapallindexed(Relation rel, GistCheckState * result); +static void giststate_init_heapallindexed(Relation rel, GistCheckState *result); static void gist_check_parent_keys_consistency(Relation rel, Relation heaprel, void *callback_state, bool readonly); -static void gist_check_page(GistCheckState * check_state, GistScanItem * stack, +static void gist_check_page(GistCheckState *check_state, GistScanItem *stack, Page page, bool heapallindexed, BufferAccessStrategy strategy); static void check_index_page(Relation rel, Buffer buffer, BlockNumber blockNo); @@ -102,8 +103,6 @@ static ItemId PageGetItemIdCareful(Relation rel, BlockNumber block, static void gist_tuple_present_callback(Relation index, ItemPointer tid, Datum *values, bool *isnull, bool tupleIsAlive, void *checkstate); -static IndexTuple gistFormNormalizedTuple(GistCheckState *giststate, - IndexTuple itup); /* * gist_index_check(index regclass) @@ -128,11 +127,11 @@ gist_index_check(PG_FUNCTION_ARGS) } /* - * Initialize GIST state files needed to perform. - * This initialized bloom filter and snapshot. + * Set up the Bloom filter and the snapshot needed for the heapallindexed + * check. */ static void -giststate_init_heapallindexed(Relation rel, GistCheckState * result) +giststate_init_heapallindexed(Relation rel, GistCheckState *result) { int64 total_pages; int64 total_elems; @@ -177,7 +176,7 @@ giststate_init_heapallindexed(Relation rel, GistCheckState * result) * * This function verifies that tuples of internal pages cover all * the key space of each tuple on the leaf page. To do this we invoke - * gist_check_internal_page() for every internal page. + * gist_check_page() for every page. * * This check allocates memory context and scans through * GiST graph. This scan is performed in a depth-first search using a stack of @@ -185,9 +184,9 @@ giststate_init_heapallindexed(Relation rel, GistCheckState * result) * each iteration the top block number is replaced by referenced block numbers. * * - * gist_check_internal_page() in its turn takes every tuple and tries to - * adjust it by tuples on the referenced child page. Parent gist tuple should - * never require any adjustments. + * gist_check_page() in its turn takes every tuple and tries to adjust the + * downlink we followed by it. Parent gist tuple should never require any + * adjustments. */ static void gist_check_parent_keys_consistency(Relation rel, Relation heaprel, @@ -207,6 +206,8 @@ gist_check_parent_keys_consistency(Relation rel, Relation heaprel, oldcontext = MemoryContextSwitchTo(mctx); state = initGISTstate(rel); + /* initGISTstate() leaves tempCxt pointing at scanCxt; we need our own */ + state->tempCxt = createTempGistContext(); check_state->state = state; check_state->rel = rel; @@ -251,7 +252,7 @@ gist_check_parent_keys_consistency(Relation rel, Relation heaprel, if (check_state->scannedblocks > check_state->reportedblocks + check_state->deltablocks) { - elog(DEBUG1, "verified level %u blocks of approximately %u total", + elog(DEBUG1, "verified %u blocks of approximately %u total", check_state->scannedblocks, check_state->totalblocks); check_state->reportedblocks = check_state->scannedblocks; } @@ -275,13 +276,30 @@ gist_check_parent_keys_consistency(Relation rel, Relation heaprel, if (GistFollowRight(page) || stack->parentlsn < GistPageGetNSN(page)) { /* split page detected, install right link to the stack */ - GistScanItem *ptr = (GistScanItem *) palloc(sizeof(GistScanItem)); + GistScanItem *ptr; + BlockNumber rightlink = GistPageGetOpaque(page)->rightlink; + /* + * The root page is never split in place. InvalidBlockNumber is + * P_NEW, and reading it would extend the index. + */ + if (stack->blkno == GIST_ROOT_BLKNO) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" has root page marked as split", + RelationGetRelationName(rel)))); + if (!BlockNumberIsValid(rightlink)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" has page %u marked as split without right sibling", + RelationGetRelationName(rel), stack->blkno))); + + ptr = (GistScanItem *) palloc(sizeof(GistScanItem)); ptr->depth = stack->depth; ptr->parenttup = CopyIndexTuple(stack->parenttup); ptr->parentblk = stack->parentblk; ptr->parentlsn = stack->parentlsn; - ptr->blkno = GistPageGetOpaque(page)->rightlink; + ptr->blkno = rightlink; ptr->next = stack->next; stack->next = ptr; } @@ -298,12 +316,19 @@ gist_check_parent_keys_consistency(Relation rel, Relation heaprel, GistScanItem *ptr; ItemId iid = PageGetItemIdCareful(rel, stack->blkno, page, i); IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid); + BlockNumber childblkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid)); + + if (!BlockNumberIsValid(childblkno)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" has invalid downlink on page %u offset %u", + RelationGetRelationName(rel), stack->blkno, i))); ptr = (GistScanItem *) palloc(sizeof(GistScanItem)); ptr->depth = stack->depth + 1; ptr->parenttup = CopyIndexTuple(idxtuple); ptr->parentblk = stack->blkno; - ptr->blkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid)); + ptr->blkno = childblkno; ptr->parentlsn = lsn; ptr->next = stack->next; stack->next = ptr; @@ -366,10 +391,11 @@ gist_check_parent_keys_consistency(Relation rel, Relation heaprel, } static void -gist_check_page(GistCheckState * check_state, GistScanItem * stack, +gist_check_page(GistCheckState *check_state, GistScanItem *stack, Page page, bool heapallindexed, BufferAccessStrategy strategy) { OffsetNumber maxoff = PageGetMaxOffsetNumber(page); + MemoryContext tempcxt = check_state->state->tempCxt; /* Check that the tree has the same height in all branches */ if (GistPageIsLeaf(page)) @@ -391,7 +417,7 @@ gist_check_page(GistCheckState * check_state, GistScanItem * stack, { ItemId iid = PageGetItemIdCareful(check_state->rel, stack->blkno, page, i); IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid); - IndexTuple tmpTuple = NULL; + MemoryContext oldcxt; /* * Check that it's not a leftover invalid tuple from pre-9.1 See also @@ -413,62 +439,62 @@ gist_check_page(GistCheckState * check_state, GistScanItem * stack, RelationGetRelationName(check_state->rel), stack->blkno, i))); /* - * Check if this tuple is consistent with the downlink in the parent. + * gistgetadjusted() and the opclass support functions it calls + * allocate memory. Do that work in the per-tuple context, so memory + * use does not grow with the size of the index. */ - if (stack->parenttup) - tmpTuple = gistgetadjusted(check_state->rel, stack->parenttup, idxtuple, check_state->state); + oldcxt = MemoryContextSwitchTo(tempcxt); - if (tmpTuple) + /* + * Check if this tuple is consistent with the downlink in the parent. + */ + if (stack->parenttup && + gistgetadjusted(check_state->rel, stack->parenttup, idxtuple, + check_state->state) != NULL) { /* * There was a discrepancy between parent and child tuples. We * need to verify it is not a result of concurrent call of - * gistplacetopage(). So, lock parent and try to find a downlink for - * current page. It may be missing due to concurrent page split, - * this is OK. + * gistplacetopage(). So, lock parent and try to find a downlink + * for current page. It may be missing due to concurrent page + * split, this is OK. + * + * Note that when we acquire parent tuple now we hold lock for + * both parent and child buffers. Thus the parent tuple must + * include the keyspace of the child. * - * Note that when we acquire parent tuple now we hold lock for both - * parent and child buffers. Thus the parent tuple must include the - * keyspace of the child. + * The new parent tuple is used for the rest of the page, so it + * must not live in the per-tuple context. */ - - pfree(tmpTuple); + MemoryContextSwitchTo(oldcxt); pfree(stack->parenttup); stack->parenttup = gist_refind_parent(check_state->rel, stack->parentblk, stack->blkno, strategy); + MemoryContextSwitchTo(tempcxt); /* We found it - make a final check before failing */ if (!stack->parenttup) - elog(NOTICE, "Unable to find parent tuple for block %u on block %u due to concurrent split", + elog(DEBUG1, "unable to find parent tuple for block %u on block %u due to concurrent split", stack->blkno, stack->parentblk); - else if (gistgetadjusted(check_state->rel, stack->parenttup, idxtuple, check_state->state)) + else if (gistgetadjusted(check_state->rel, stack->parenttup, idxtuple, + check_state->state) != NULL) ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("index \"%s\" has inconsistent records on page %u offset %u", RelationGetRelationName(check_state->rel), stack->blkno, i))); - else - { - /* - * But now it is properly adjusted - nothing to do here. - */ - } } if (GistPageIsLeaf(page)) { if (heapallindexed) { - IndexTuple norm; + IndexTuple norm; - norm = gistFormNormalizedTuple(check_state, idxtuple); + norm = amcheck_normalize_tuple(check_state->rel, idxtuple); bloom_add_element(check_state->filter, (unsigned char *) norm, IndexTupleSize(norm)); - - /* Be tidy */ - if (norm != idxtuple) - pfree(norm); } } else @@ -478,25 +504,13 @@ gist_check_page(GistCheckState * check_state, GistScanItem * stack, if (off != TUPLE_IS_VALID) ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), - errmsg("index \"%s\" has on page %u offset %u has item id not pointing to 0xffff, but %hu", + errmsg("index \"%s\" has item id not pointing to 0xffff on page %u offset %u, but %hu", RelationGetRelationName(check_state->rel), stack->blkno, i, off))); } - } -} -/* - * gistFormNormalizedTuple - analogue to gistFormTuple, but performs deTOASTing - * of all included data (for covering indexes). While we do not expect - * toasted attributes in normal indexes, this can happen as a result of - * intervention into system catalog. Detoasting of key attributes is expected - * to be done by opclass decompression methods, if the indexed type might be - * toasted. - */ -static IndexTuple -gistFormNormalizedTuple(GistCheckState *giststate, - IndexTuple itup) -{ - return amcheck_normalize_tuple(giststate->rel, itup); + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(tempcxt); + } } static void @@ -504,15 +518,25 @@ gist_tuple_present_callback(Relation index, ItemPointer tid, Datum *values, bool *isnull, bool tupleIsAlive, void *checkstate) { GistCheckState *state = (GistCheckState *) checkstate; - IndexTuple itup, norm; + IndexTuple itup, + norm; Datum compatt[INDEX_MAX_KEYS]; + MemoryContext oldcxt; + + /* + * The opclass compress functions allocate memory for every heap tuple. + * Work in the per-tuple context and reset it afterwards, like + * gistBuildCallback() does, so memory use does not grow with the size of + * the table. + */ + oldcxt = MemoryContextSwitchTo(state->state->tempCxt); /* Generate a normalized index tuple for fingerprinting */ gistCompressValues(state->state, index, values, isnull, true, compatt); itup = index_form_tuple(RelationGetDescr(index), compatt, isnull); itup->t_tid = *tid; - norm = gistFormNormalizedTuple(state, itup); + norm = amcheck_normalize_tuple(state->rel, itup); /* Probe Bloom filter -- tuple should be present */ if (bloom_lacks_element(state->filter, (unsigned char *) norm, @@ -527,10 +551,8 @@ gist_tuple_present_callback(Relation index, ItemPointer tid, Datum *values, state->heaptuplespresent++; - pfree(itup); - /* Be tidy */ - if (norm != itup) - pfree(norm); + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(state->state->tempCxt); } /* @@ -584,7 +606,7 @@ gist_refind_parent(Relation rel, Buffer parentbuf; Page parentpage; OffsetNumber parent_maxoff, - off; + off; IndexTuple result = NULL; parentbuf = ReadBufferExtended(rel, MAIN_FORKNUM, parentblkno, RBM_NORMAL, diff --git a/doc/src/sgml/amcheck.sgml b/doc/src/sgml/amcheck.sgml index 9fcd6004823..c54628d18d6 100644 --- a/doc/src/sgml/amcheck.sgml +++ b/doc/src/sgml/amcheck.sgml @@ -219,10 +219,19 @@ ORDER BY c.relpages DESC LIMIT 10; gist_index_check tests that its target GiST - has consistent parent-child tuples relations (no parent tuples + index has consistent parent-child tuples relations (no parent tuples require tuple adjustment) and page graph respects balanced-tree invariants (internal pages reference only leaf page or only internal - pages). + pages). When heapallindexed is + true, it also verifies the presence of all heap + tuples as index tuples within the index. + + + gist_index_check acquires an + AccessShareLock on the target index and the heap + relation it belongs to, so it can run while the index is being + modified. A key that looks inconsistent with its parent is checked + again with both pages locked, to rule out a concurrent page split. @@ -397,8 +406,8 @@ SET client_min_messages = DEBUG1; Optional <parameter>heapallindexed</parameter> Verification - When the heapallindexed argument to B-Tree - verification functions is true, an additional + When the heapallindexed argument to B-Tree or + GiST verification functions is true, an additional phase of verification is performed against the table associated with the target index relation. This consists of a dummy CREATE INDEX CONCURRENTLY operation, which checks for the diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 656f1f60862..f6900c39b55 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1133,11 +1133,13 @@ GinTuple GinTupleCollector GinVacuumState GistBuildMode +GistCheckState GistEntryVector GistHstoreOptions GistInetKey GistNSN GistOptBufferingMode +GistScanItem GistSortedBuildLevelState GistSplitUnion GistSplitVector -- 2.37.1 (Apple Git-137.1)