From c251ce2bb79b3d242a3c76b9fa5477bda1c06562 Mon Sep 17 00:00:00 2001 From: reshke Date: Sat, 10 Jan 2026 17:29:57 +0000 Subject: [PATCH v2] Add pageinspect support for SpGiST indexes. --- contrib/pageinspect/Makefile | 6 +- contrib/pageinspect/expected/spgist.out | 44 ++ contrib/pageinspect/meson.build | 3 + .../pageinspect/pageinspect--1.13--1.14.sql | 60 ++ contrib/pageinspect/pageinspect.control | 2 +- contrib/pageinspect/spgistfuncs.c | 526 ++++++++++++++++++ contrib/pageinspect/sql/spgist.sql | 27 + 7 files changed, 665 insertions(+), 3 deletions(-) create mode 100644 contrib/pageinspect/expected/spgist.out create mode 100644 contrib/pageinspect/pageinspect--1.13--1.14.sql create mode 100644 contrib/pageinspect/spgistfuncs.c create mode 100644 contrib/pageinspect/sql/spgist.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index eae989569d0..c565c0d9b23 100644 --- a/contrib/pageinspect/Makefile +++ b/contrib/pageinspect/Makefile @@ -10,10 +10,11 @@ OBJS = \ gistfuncs.o \ hashfuncs.o \ heapfuncs.o \ - rawpage.o + rawpage.o \ + spgistfuncs.o EXTENSION = pageinspect -DATA = pageinspect--1.12--1.13.sql \ +DATA = pageinspect--1.13--1.14.sql pageinspect--1.12--1.13.sql \ pageinspect--1.11--1.12.sql pageinspect--1.10--1.11.sql \ pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \ pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \ @@ -32,6 +33,7 @@ REGRESS = \ gin \ gist \ hash \ + spgist \ oldextversions ifdef USE_PGXS diff --git a/contrib/pageinspect/expected/spgist.out b/contrib/pageinspect/expected/spgist.out new file mode 100644 index 00000000000..339878b5bbb --- /dev/null +++ b/contrib/pageinspect/expected/spgist.out @@ -0,0 +1,44 @@ +-- The gist_page_opaque_info() function prints the page's LSN. +-- Use an unlogged index, so that the LSN is predictable. +CREATE UNLOGGED TABLE test_gist AS SELECT point(i,i) p, i::text t FROM + generate_series(1,1000) i; +CREATE INDEX test_spgist_idx ON test_gist USING spgist (p); +-- Page 0 is the root, the rest are leaf pages +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 0)); + lsn | nplaceholder | nredirection | flags +------------+--------------+--------------+-------- + 0/00000000 | 0 | 0 | {meta} +(1 row) + +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 1)); + lsn | nplaceholder | nredirection | flags +------------+--------------+--------------+------- + 0/00000000 | 0 | 0 | {} +(1 row) + +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 2)); + lsn | nplaceholder | nredirection | flags +------------+--------------+--------------+-------------- + 0/00000000 | 0 | 0 | {leaf,nulls} +(1 row) + +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- Failure with various modes. +-- invalid page size +SELECT spgist_page_opaque_info('aaa'::bytea); +ERROR: invalid page size +-- invalid special area size +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_gist', 0)); +ERROR: input page is not a valid SpGiST page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT spgist_page_opaque_info(decode(repeat('00', :block_size), 'hex')); + spgist_page_opaque_info +------------------------- + +(1 row) + +DROP TABLE test_gist; diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build index c43ea400a4d..13408d0491a 100644 --- a/contrib/pageinspect/meson.build +++ b/contrib/pageinspect/meson.build @@ -9,6 +9,7 @@ pageinspect_sources = files( 'hashfuncs.c', 'heapfuncs.c', 'rawpage.c', + 'spgistfuncs.c', ) if host_system == 'windows' @@ -38,6 +39,7 @@ install_data( 'pageinspect--1.10--1.11.sql', 'pageinspect--1.11--1.12.sql', 'pageinspect--1.12--1.13.sql', + 'pageinspect--1.13--1.14.sql', 'pageinspect.control', kwargs: contrib_data_args, ) @@ -56,6 +58,7 @@ tests += { 'hash', 'checksum', 'oldextversions', + 'spgist', ], }, } diff --git a/contrib/pageinspect/pageinspect--1.13--1.14.sql b/contrib/pageinspect/pageinspect--1.13--1.14.sql new file mode 100644 index 00000000000..c8b42e467a1 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.13--1.14.sql @@ -0,0 +1,60 @@ +/* contrib/pageinspect/pageinspect--1.13--1.14.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.14'" to load this file. \quit + +-- +-- spgist_page_opaque_info() +-- +CREATE FUNCTION spgist_page_opaque_info(IN page bytea, + OUT lsn pg_lsn, + OUT nPlaceholder SMALLINT, + OUT nRedirection SMALLINT, + OUT flags text[]) +AS 'MODULE_PATHNAME', 'spgist_page_opaque_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- spgist_leafpage_items() +-- +CREATE FUNCTION spgist_leafpage_items(IN page bytea, + IN index_oid regclass, + OUT itemoffset smallint, + OUT ctid tid, + OUT size smallint, + OUT hasnullmask boolean, + OUT nextoffset smallint, + OUT state TEXT, + OUT xid xid, + OUT keys text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'spgist_leafpage_items' +LANGUAGE C STRICT PARALLEL SAFE; + + +-- +-- spgist_innerpage_items() +-- +CREATE FUNCTION spgist_innerpage_items(IN page bytea, + IN index_oid regclass, + OUT itemoffset smallint, + OUT allTheSame INT, + OUT nNodes INT, + OUT prefixSize INT, + OUT size smallint, + OUT state TEXT) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'spgist_innerpage_items' +LANGUAGE C STRICT PARALLEL SAFE; + + +-- +-- spgist_metapage_items() +-- +CREATE FUNCTION spgist_metapage_items(IN page bytea, + OUT itemoffset int, + OUT blkno smallint, + OUT freespace int) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'spgist_metapage_items' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control index cfc87feac03..aee3f598a9e 100644 --- a/contrib/pageinspect/pageinspect.control +++ b/contrib/pageinspect/pageinspect.control @@ -1,5 +1,5 @@ # pageinspect extension comment = 'inspect the contents of database pages at a low level' -default_version = '1.13' +default_version = '1.14' module_pathname = '$libdir/pageinspect' relocatable = true diff --git a/contrib/pageinspect/spgistfuncs.c b/contrib/pageinspect/spgistfuncs.c new file mode 100644 index 00000000000..175b0ee2ec9 --- /dev/null +++ b/contrib/pageinspect/spgistfuncs.c @@ -0,0 +1,526 @@ +/* + * gistfuncs.c + * Functions to investigate the content of GiST indexes + * + * Copyright (c) 2014-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/gistfuncs.c + */ +#include "postgres.h" + +#include "access/relation.h" +#include "access/spgist.h" +#include "access/spgist_private.h" +#include "access/htup.h" +#include "access/htup_details.h" +#include "catalog/pg_am_d.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/pg_lsn.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" + +PG_FUNCTION_INFO_V1(spgist_page_opaque_info); +PG_FUNCTION_INFO_V1(spgist_leafpage_items); +PG_FUNCTION_INFO_V1(spgist_innerpage_items); +PG_FUNCTION_INFO_V1(spgist_metapage_items); + +#define IS_SPGIST(r) ((r)->rd_rel->relam == SPGIST_AM_OID) + +static Page verify_gist_page(bytea *raw_page); + +/* + * Verify that the given bytea contains a GIST page or die in the attempt. + * A pointer to the page is returned. + */ +static Page +verify_gist_page(bytea *raw_page) +{ + Page page = get_page_from_raw(raw_page); + SpGistPageOpaque opaq; + + if (PageIsNew(page)) + return page; + + /* verify the special space has the expected size */ + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(SpGistPageOpaque))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "SpGiST"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(SpGistPageOpaque)), + (int) PageGetSpecialSize(page)))); + + opaq = SpGistPageGetOpaque(page); + if (opaq->spgist_page_id != SPGIST_PAGE_ID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "GiST"), + errdetail("Expected %08x, got %08x.", + SPGIST_PAGE_ID, + opaq->spgist_page_id))); + + return page; +} + +Datum +spgist_page_opaque_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + TupleDesc tupdesc; + Page page; + HeapTuple resultTuple; + Datum values[4]; + bool nulls[4]; + Datum flags[16]; + int nflags = 0; + uint16 flagbits; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Convert the flags bitmask to an array of human-readable names */ + flagbits = SpGistPageGetOpaque(page)->flags; + if (flagbits & SPGIST_META) + flags[nflags++] = CStringGetTextDatum("meta"); + if (flagbits & SPGIST_DELETED) + flags[nflags++] = CStringGetTextDatum("deleted"); + if (flagbits & SPGIST_LEAF) + flags[nflags++] = CStringGetTextDatum("leaf"); + if (flagbits & SPGIST_NULLS) + flags[nflags++] = CStringGetTextDatum("nulls"); + flagbits &= ~(SPGIST_META | SPGIST_DELETED | SPGIST_LEAF | SPGIST_NULLS); + if (flagbits) + { + /* any flags we don't recognize are printed in hex */ + flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits)); + } + + memset(nulls, 0, sizeof(nulls)); + + values[0] = LSNGetDatum(PageGetLSN(page)); + values[1] = Int16GetDatum(SpGistPageGetOpaque(page)->nPlaceholder); + values[2] = Int16GetDatum(SpGistPageGetOpaque(page)->nRedirection); + values[3] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID)); + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(tupdesc, values, nulls); + + return HeapTupleGetDatum(resultTuple); +} + + +Datum +spgist_metapage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Page metapage; + uint16 flagbits; + SpGistMetaPageData *metadata; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + InitMaterializedSRF(fcinfo, 0); + + + metapage = verify_gist_page(raw_page); + + flagbits = SpGistPageGetOpaque(metapage)->flags; + + if (!(flagbits & SPGIST_META)) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("input page is not a %s meta page", "SpGiST")); + + metadata = SpGistPageGetMeta(metapage); + + if (metadata->magicNumber != SPGIST_MAGIC_NUMBER) + ereport(ERROR, + errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("input page is not a valid SpGiST metapage"), + errdetail("Expected special size %d, got %d.", + (int) SPGIST_MAGIC_NUMBER, + (int) metadata->magicNumber)); + + for (int i = 0; i < SPGIST_CACHED_PAGES; i++) + { + Datum values[3]; + bool nulls[3]; + + memset(nulls, 0, sizeof(nulls)); + + values[0] = Int32GetDatum(i); + values[1] = Int16GetDatum(metadata->lastUsedPages.cachedPage[i].blkno); + values[2] = Int32GetDatum(metadata->lastUsedPages.cachedPage[i].freeSpace); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +Datum +spgist_leafpage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Oid indexRelid = PG_GETARG_OID(1); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Relation indexRel; + TupleDesc tupdesc; + Page page; + uint16 flagbits; + bits16 printflags = 0; + OffsetNumber maxoff = InvalidOffsetNumber; + char *index_columns; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + InitMaterializedSRF(fcinfo, 0); + + /* Open the index relation */ + indexRel = index_open(indexRelid, AccessShareLock); + + if (!IS_SPGIST(indexRel)) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "SpGiST")); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + { + index_close(indexRel, AccessShareLock); + PG_RETURN_NULL(); + } + + flagbits = SpGistPageGetOpaque(page)->flags; + + if (flagbits & SPGIST_META) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("input page is not a %s leaf page", "SpGiST"), + errhint("this appears to be %s metapage. Please use spgist_metapage_items.", "SpGiST")); + + /* + * Included attributes are added when dealing with leaf pages, discarded + * for non-leaf pages as these include only data for key attributes. + */ + printflags |= RULE_INDEXDEF_PRETTY; + if (flagbits & SPGIST_LEAF) + tupdesc = RelationGetDescr(indexRel); + else + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("input page is not a %s leaf page", "SpGiST"), + errhint("this appears to be %s inner page. Please use spgist_innerpage_items.", "SpGiST")); + + index_columns = pg_get_indexdef_columns_extended(indexRelid, + printflags); + + /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ + if (SpGistPageIsDeleted(page)) + elog(NOTICE, "page is deleted"); + else + maxoff = PageGetMaxOffsetNumber(page); + + for (OffsetNumber offset = FirstOffsetNumber; + offset <= maxoff; + offset++) + { + Datum values[8]; + bool nulls[8]; + ItemId id; + IndexTuple itup; + Datum itup_values[INDEX_MAX_KEYS]; + bool itup_isnull[INDEX_MAX_KEYS]; + SpGistLeafTuple leafTuple; + int i; + bool hasNullsMask; + bool has_datums; + char *tp; + bits8 *bp; + + id = PageGetItemId(page, offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(page, id); + leafTuple = (SpGistLeafTuple) itup; + hasNullsMask = SGLT_GET_HASNULLMASK(leafTuple); + + tp = (char *) leafTuple + SGLTHDRSZ(hasNullsMask); + bp = (bits8 *) ((char *) leafTuple + sizeof(SpGistLeafTupleData)); + has_datums = false; + + index_deform_tuple_internal(tupdesc, + itup_values, itup_isnull, + tp, bp, hasNullsMask); + + memset(nulls, 0, sizeof(nulls)); + + values[0] = UInt16GetDatum(offset); + values[1] = ItemPointerGetDatum(&leafTuple->heapPtr); + values[2] = UInt32GetDatum(leafTuple->size); + values[3] = BoolGetDatum(hasNullsMask); + values[4] = UInt16GetDatum(SGLT_GET_NEXTOFFSET(leafTuple)); + + values[6] = InvalidTransactionId; + + switch (leafTuple->tupstate) + { + case SPGIST_LIVE: + values[5] = CStringGetTextDatum("LIVE"); + has_datums = true; + break; + case SPGIST_REDIRECT: + values[5] = CStringGetTextDatum("REDIRECT"); + values[6] = ((SpGistDeadTuple) leafTuple)->xid; + break; + case SPGIST_DEAD: + values[5] = CStringGetTextDatum("DEAD"); + values[6] = ((SpGistDeadTuple) leafTuple)->xid; + break; + case SPGIST_PLACEHOLDER: + values[5] = CStringGetTextDatum("PLACEHOLDER"); + values[6] = ((SpGistDeadTuple) leafTuple)->xid; + break; + default: + ereport(ERROR, errcode(ERRCODE_INDEX_CORRUPTED), errmsg("malformed SpGist leaf tuple state %d", leafTuple->tupstate)); + } + + if (has_datums && index_columns) + { + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfo(&buf, "(%s)=(", index_columns); + + /* Most of this is copied from record_out(). */ + for (i = 0; i < tupdesc->natts; i++) + { + char *value; + char *tmp; + bool nq = false; + + if (itup_isnull[i]) + value = "null"; + else + { + Oid foutoid; + bool typisvarlena; + Oid typoid; + + typoid = TupleDescAttr(tupdesc, i)->atttypid; + getTypeOutputInfo(typoid, &foutoid, &typisvarlena); + value = OidOutputFunctionCall(foutoid, itup_values[i]); + } + + if (i == IndexRelationGetNumberOfKeyAttributes(indexRel)) + appendStringInfoString(&buf, ") INCLUDE ("); + else if (i > 0) + appendStringInfoString(&buf, ", "); + + /* Check whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoCharMacro(&buf, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoCharMacro(&buf, ch); + appendStringInfoCharMacro(&buf, ch); + } + if (nq) + appendStringInfoCharMacro(&buf, '"'); + } + + appendStringInfoChar(&buf, ')'); + + values[7] = CStringGetTextDatum(buf.data); + } + else + { + values[7] = (Datum) 0; + nulls[7] = true; + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + index_close(indexRel, AccessShareLock); + + return (Datum) 0; +} + + +Datum +spgist_innerpage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Oid indexRelid = PG_GETARG_OID(1); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Relation indexRel; + TupleDesc tupdesc; + Page page; + uint16 flagbits; + bits16 printflags = 0; + OffsetNumber maxoff = InvalidOffsetNumber; + char *index_columns; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + InitMaterializedSRF(fcinfo, 0); + + /* Open the index relation */ + indexRel = index_open(indexRelid, AccessShareLock); + + if (!IS_SPGIST(indexRel)) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "SpGiST")); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + { + index_close(indexRel, AccessShareLock); + PG_RETURN_NULL(); + } + + flagbits = SpGistPageGetOpaque(page)->flags; + + if (flagbits & SPGIST_META) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("input page is not a %s inner page", "SpGiST"), + errhint("this appears to be %s metapage. Please use spgist_metapage_items.", "SpGiST")); + + /* + * Included attributes are added when dealing with leaf pages, discarded + * for non-leaf pages as these include only data for key attributes. + */ + printflags |= RULE_INDEXDEF_PRETTY; + if (flagbits & SPGIST_LEAF) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("input page is not a %s inner page", "SpGiST"), + errhint("this appears to be %s leaf page. Please use spgist_leafpage_items.", "SpGiST")); + + tupdesc = CreateTupleDescTruncatedCopy(RelationGetDescr(indexRel), + IndexRelationGetNumberOfKeyAttributes(indexRel)); + printflags |= RULE_INDEXDEF_KEYS_ONLY; + + index_columns = pg_get_indexdef_columns_extended(indexRelid, + printflags); + + /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ + if (SpGistPageIsDeleted(page)) + elog(NOTICE, "page is deleted"); + else + maxoff = PageGetMaxOffsetNumber(page); + + for (OffsetNumber offset = FirstOffsetNumber; + offset <= maxoff; + offset++) + { + Datum values[8]; + bool nulls[8]; + ItemId id; + IndexTuple itup; + Datum itup_values[INDEX_MAX_KEYS]; + bool itup_isnull[INDEX_MAX_KEYS]; + SpGistInnerTuple innerTuple; + int i; + bool hasNullsMask; + bool has_datums; + char *tp; + bits8 *bp; + + id = PageGetItemId(page, offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(page, id); + innerTuple = (SpGistInnerTuple) itup; + + memset(nulls, 0, sizeof(nulls)); + + values[0] = UInt16GetDatum(offset); + values[1] = UInt32GetDatum(innerTuple->allTheSame); + values[2] = UInt32GetDatum(innerTuple->nNodes); + values[3] = UInt32GetDatum(innerTuple->prefixSize); + values[4] = UInt16GetDatum(innerTuple->size); + + switch (innerTuple->tupstate) + { + case SPGIST_LIVE: + values[5] = CStringGetTextDatum("LIVE"); + has_datums = true; + break; + case SPGIST_REDIRECT: + values[5] = CStringGetTextDatum("REDIRECT"); + break; + case SPGIST_DEAD: + values[5] = CStringGetTextDatum("DEAD"); + break; + case SPGIST_PLACEHOLDER: + values[5] = CStringGetTextDatum("PLACEHOLDER"); + break; + default: + ereport(ERROR, errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("malformed SpGist leaf tuple state %d", innerTuple->tupstate)); + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + index_close(indexRel, AccessShareLock); + + return (Datum) 0; +} diff --git a/contrib/pageinspect/sql/spgist.sql b/contrib/pageinspect/sql/spgist.sql new file mode 100644 index 00000000000..780b820989a --- /dev/null +++ b/contrib/pageinspect/sql/spgist.sql @@ -0,0 +1,27 @@ +-- The gist_page_opaque_info() function prints the page's LSN. +-- Use an unlogged index, so that the LSN is predictable. +CREATE UNLOGGED TABLE test_gist AS SELECT point(i,i) p, i::text t FROM + generate_series(1,1000) i; +CREATE INDEX test_spgist_idx ON test_gist USING spgist (p); + +-- Page 0 is the root, the rest are leaf pages +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 0)); +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 1)); +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_spgist_idx', 2)); + +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse + +-- Failure with various modes. +-- invalid page size +SELECT spgist_page_opaque_info('aaa'::bytea); +-- invalid special area size +SELECT * FROM spgist_page_opaque_info(get_raw_page('test_gist', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT spgist_page_opaque_info(decode(repeat('00', :block_size), 'hex')); + +DROP TABLE test_gist; -- 2.43.0