From: Kirill Reshke <reshkekirill@gmail.com>
To: Andrey Borodin <x4mmm@yandex-team.ru>
Cc: pgsql-hackers <pgsql-hackers@postgresql.org>
Cc: Roman Khapov <rkhapov@yandex-team.ru>
Subject: Re: GIN pageinspect support for entry tree and posting tree
Date: Sun, 4 Jan 2026 00:10:13 +0500
Message-ID: <CALdSSPh7_GVFrGD4aQ76bOF7jxRkAkzY-eJCbzUh-X9F3QSb9A@mail.gmail.com> (raw)
In-Reply-To: <CALdSSPgkFk+c=Y=wRbg7YyCW1yQtRLeZ7yqKRSOWqpCeVXsVKg@mail.gmail.com>
References: <CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com>
<CALdSSPgpD5RfPn5qMbozU4_SQpZAbG3V_=KdxV9YaEG9gX=qEA@mail.gmail.com>
<798928DA-2937-46F5-8AD0-C3A5BAB5C7E1@yandex-team.ru>
<CALdSSPh2xBThU2vpaiQrsUSxqdH6psxuSa3kXVJHSNTkj3_DgA@mail.gmail.com>
<CALdSSPgkFk+c=Y=wRbg7YyCW1yQtRLeZ7yqKRSOWqpCeVXsVKg@mail.gmail.com>
On Sat, 3 Jan 2026 at 00:16, Kirill Reshke <reshkekirill@gmail.com> wrote:
>
> > PFA v3.
>
> PFA v4 with typo fixes.
PFA v5 with pageinspect documentation updates.
--
Best regards,
Kirill Reshke
Attachments:
[application/octet-stream] v5-0001-GIN-pageinspect-support-for-entry-tree-and-postin.patch (22.8K, ../CALdSSPh7_GVFrGD4aQ76bOF7jxRkAkzY-eJCbzUh-X9F3QSb9A@mail.gmail.com/2-v5-0001-GIN-pageinspect-support-for-entry-tree-and-postin.patch)
download | inline diff:
From 947687725296941bbf3773e399bda93bf0da8148 Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Mon, 13 Oct 2025 20:14:26 +0000
Subject: [PATCH v5] GIN pageinspect support for entry tree and posting tree
internal pages
This patch provides new version for pageinspect contrib module including
two new functions:
* gin_entrypage_items.
* gin_datapage_items.
These two functions can be used to examine GIN entry tree and posting
tree pages. Namely, gin_entrypage_items can be used of both leaf and
non-leaf entry tree pages. gin_datapage_items is provided in pairs with
already-existing gin_leafpage_items to examine non-leaf posting tree
pages.
We keep the different functions here mainly because of different GIN
pages layoff.
Note that fast-list pages are out of scope of this patch.
Reviewed-by: Andrey Borodin x4mmm@yandex-team.ru
Reviewed-by: Roman Khapov rkhapov@yandex-team.ru
---
contrib/pageinspect/Makefile | 2 +-
contrib/pageinspect/expected/gin.out | 84 ++++-
contrib/pageinspect/ginfuncs.c | 335 ++++++++++++++++++
.../pageinspect/pageinspect--1.13--1.14.sql | 28 ++
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/sql/gin.sql | 34 +-
doc/src/sgml/pageinspect.sgml | 54 +++
7 files changed, 533 insertions(+), 6 deletions(-)
create mode 100644 contrib/pageinspect/pageinspect--1.13--1.14.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefileindex eae989569d0..09774fd340c 100644--- a/contrib/pageinspect/Makefile+++ b/contrib/pageinspect/Makefile@@ -13,7 +13,7 @@ OBJS = \
rawpage.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 \
diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.outindex ff1da6a5a17..97057e37529 100644--- a/contrib/pageinspect/expected/gin.out+++ b/contrib/pageinspect/expected/gin.out@@ -1,6 +1,8 @@-CREATE TABLE test1 (x int, y int[]);-INSERT INTO test1 VALUES (1, ARRAY[11, 111]);+CREATE TABLE test1 (x int, y int[], z text[]);+INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']);
CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off);
+CREATE INDEX test2_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = off);+CREATE INDEX test3_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = on);
\x
SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0));
-[ RECORD 1 ]----+-----------
@@ -27,6 +29,45 @@ flags | {leaf}
SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1));
ERROR: input page is not a compressed GIN data leaf page
DETAIL: Flags 0002, expected 0083
+SELECT * FROM gin_entrypage_items(get_raw_page('test1_y_idx', 1), 'test1_y_idx'::regclass);+-[ RECORD 1 ]--------------+itemoffset | 1+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | y=11+-[ RECORD 2 ]--------------+itemoffset | 2+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | y=111++SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 1), 'test2_y_z_idx'::regclass);+-[ RECORD 1 ]--------------+itemoffset | 1+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | y=11+-[ RECORD 2 ]--------------+itemoffset | 2+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | y=111+-[ RECORD 3 ]--------------+itemoffset | 3+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | z=a+-[ RECORD 4 ]--------------+itemoffset | 4+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | z=b+-[ RECORD 5 ]--------------+itemoffset | 5+downlink | (2147483664,1)+tids | {"(0,1)"}+keys | z=c+
INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x;
SELECT COUNT(*) > 0
FROM gin_leafpage_items(get_raw_page('test1_y_idx',
@@ -35,6 +76,23 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx',
-[ RECORD 1 ]
?column? | t
+-- Now test posting tree non-leaf page.+-- This requires inserting many tuples on a single leaf page to trigger page split.+CREATE TABLE test_data_page(i INT[]);+CREATE INDEX test_data_page_i_idx ON test_data_page USING gin(i) WITH (fastupdate = off);+INSERT INTO test_data_page SELECT ARRAY[1] FROM generate_series(1, 10000);+-- For this index, block 0 is metapage, block 1 is entry tree, block 2 is +-- posting tree non-leaf page and block 3 & 4 are compressed data leaf pages.+SELECT * FROM gin_datapage_items(get_raw_page('test_data_page_i_idx', 2));+-[ RECORD 1 ]-------+itemoffset | 1+downlink | 4+item_tid | (44,83)+-[ RECORD 2 ]-------+itemoffset | 2+downlink | 3+item_tid | (0,0)+
-- Failure with various modes.
-- Suppress the DETAIL message, to allow the tests to work across various
-- page sizes and architectures.
@@ -54,12 +112,34 @@ ERROR: input page is not a valid GIN data leaf page
SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
ERROR: input page is not a valid GIN data leaf page
\set VERBOSITY default
+-- Reject unsupported page types in gin_entrypage_items.+SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 0), 'test2_y_z_idx'::regclass);+ERROR: gin_entrypage_items is unsupported for metapage+-- Check the error message for the internal posting tree page.+SELECT * FROM gin_entrypage_items(get_raw_page('test_data_page_i_idx', 2), 'test_data_page_i_idx'::regclass);+ERROR: input page is not a GIN entry tree page+HINT: This appears to be a GIN posting tree page. Please use gin_datapage_items+-- insert new row to trigger new (fast-list) page allocation.+INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']);+-- double check that the new page is fast-list.+SELECT * FROM gin_page_opaque_info(get_raw_page('test3_y_z_idx', 2));+-[ RECORD 1 ]------------------+rightlink | 3+maxoff | 120+flags | {list,list_fullrow}++-- reject fast-list pages.+SELECT * FROM gin_entrypage_items(get_raw_page('test3_y_z_idx', 3), 'test3_y_z_idx'::regclass);+ERROR: gin_entrypage_items is unsupported for fast list pages
-- Tests with all-zero pages.
SHOW block_size \gset
SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
-[ RECORD 1 ]------+-
gin_leafpage_items |
+SELECT gin_datapage_items(decode(repeat('00', :block_size), 'hex'));+(0 rows)+
SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
-[ RECORD 1 ]-----+-
gin_metapage_info |
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.cindex ebcc2b3db5c..059db497e93 100644--- a/contrib/pageinspect/ginfuncs.c+++ b/contrib/pageinspect/ginfuncs.c@@ -11,18 +11,27 @@
#include "access/gin_private.h"
#include "access/htup_details.h"
+#include "access/relation.h"+#include "access/tupdesc.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pageinspect.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"+#include "utils/rel.h"+#include "utils/ruleutils.h"
PG_FUNCTION_INFO_V1(gin_metapage_info);
PG_FUNCTION_INFO_V1(gin_page_opaque_info);
+PG_FUNCTION_INFO_V1(gin_entrypage_items);
PG_FUNCTION_INFO_V1(gin_leafpage_items);
+PG_FUNCTION_INFO_V1(gin_datapage_items);+#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)+#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
Datum
gin_metapage_info(PG_FUNCTION_ARGS)
@@ -175,6 +184,332 @@ typedef struct gin_leafpage_items_state
GinPostingList *lastseg;
} gin_leafpage_items_state;
+Datum+gin_entrypage_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;+ OffsetNumber maxoff, offset;+ TupleDesc tupdesc;+ bool oneCol;+ Page page;+ GinPageOpaque opaq;+ StringInfoData buf;++ if (!superuser())+ ereport(ERROR,+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),+ errmsg("must be superuser to use raw page functions")));++ InitMaterializedSRF(fcinfo, 0);++ /* Open the relation */+ indexRel = index_open(indexRelid, AccessShareLock);++ if (!IS_INDEX(indexRel) || !IS_GIN(indexRel))+ ereport(ERROR,+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),+ errmsg("\"%s\" is not a %s index",+ RelationGetRelationName(indexRel), "GIN")));++ page = get_page_from_raw(raw_page);++ if (PageIsNew(page))+ {+ index_close(indexRel, AccessShareLock);+ PG_RETURN_NULL();+ }++ if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("input page is not a valid GIN entry tree page"),+ errdetail("Expected special size %d, got %d.",+ (int) MAXALIGN(sizeof(GinPageOpaqueData)),+ (int) PageGetSpecialSize(page))));++ opaq = GinPageGetOpaque(page);+++ /* we only support entry tree in this function, check that */+ if (opaq->flags & GIN_META)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("gin_entrypage_items is unsupported for metapage")));+ ++ if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("gin_entrypage_items is unsupported for fast list pages")));+ + + if (opaq->flags & GIN_DATA)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("input page is not a GIN entry tree page"),+ errhint("This appears to be a GIN posting tree page. Please use gin_datapage_items")));+ + maxoff = PageGetMaxOffsetNumber(page);++ tupdesc = RelationGetDescr(indexRel);+ oneCol = tupdesc->natts == 1;++ for (offset = FirstOffsetNumber;+ offset <= maxoff;+ offset = OffsetNumberNext(offset))+ {+ OffsetNumber indAtt;+ Datum values[4];+ bool nulls[4];+ int ndecoded, i;+ Datum *tids_datum;+ ItemPointer items_orig;+ bool free_items_orig;+ Datum attrVal;+ Oid foutoid;+ bool typisvarlena;+ Oid typoid;+ char* value;+ bool nq;+ char* tmp;+ bool isnull;+ IndexTuple idxtuple;+ ItemId iid = PageGetItemId(page, offset);++ if (!ItemIdIsValid(iid))+ elog(ERROR, "invalid ItemId");++ idxtuple = (IndexTuple) PageGetItem(page, iid);++ memset(nulls, 0, sizeof(nulls));+ + values[0] = UInt16GetDatum(offset);++ if (oneCol)+ {+ indAtt = FirstOffsetNumber;+ /* here we can safely reuse pg_class's tuple descriptor. */+ attrVal = index_getattr(idxtuple, FirstOffsetNumber, tupdesc,+ &isnull);+ if (isnull)+ ereport(ERROR,+ (errcode(ERRCODE_INDEX_CORRUPTED),+ errmsg("invalid gin entry page tuple at offset %d", offset)));+ }+ else+ {+ TupleDesc tmpTupdesc;+ Datum res;+ Form_pg_attribute attr;++ /* orig tuple reuse is safe */++ res = index_getattr(idxtuple, FirstOffsetNumber, tupdesc,+ &isnull);++ /* we do not expect null for first attr in multi-column GIN */+ if (isnull)+ ereport(ERROR,+ (errcode(ERRCODE_INDEX_CORRUPTED),+ errmsg("invalid gin entry page tuple at offset %d", offset)));++ indAtt = DatumGetUInt16(res);++ attr = TupleDescAttr(tupdesc, indAtt - 1);++ tmpTupdesc = CreateTemplateTupleDesc(2);++ TupleDescInitEntry(tmpTupdesc, (AttrNumber) 1, NULL,+ INT2OID, -1, 0);+ TupleDescInitEntry(tmpTupdesc, (AttrNumber) 2, NULL,+ attr->atttypid,+ attr->atttypmod,+ attr->attndims);+ TupleDescInitEntryCollation(tmpTupdesc, (AttrNumber) 2,+ attr->attcollation);++ attrVal = index_getattr(idxtuple, OffsetNumberNext(FirstOffsetNumber),+ tmpTupdesc,+ &isnull);++ FreeTupleDesc(tmpTupdesc);+ }++ initStringInfo(&buf);+ appendStringInfo(&buf, "%s=", quote_identifier(TupleDescAttr(tupdesc, indAtt - 1)->attname.data));++ if (!isnull) {+ /* Most of this is copied from record_out(). */+ typoid = TupleDescAttr(tupdesc, indAtt - 1)->atttypid;+ getTypeOutputInfo(typoid, &foutoid, &typisvarlena);+ value = OidOutputFunctionCall(foutoid, attrVal);+ ++ /* 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, '"');+ }+ else+ {+ appendStringInfo(&buf, "NULL");+ }+ ++ values[3] = CStringGetTextDatum(buf.data);+ resetStringInfo(&buf);++ if (GinIsPostingTree(idxtuple))+ {+ values[1] = ItemPointerGetDatum(&idxtuple->t_tid);+ nulls[2] = true;+ }+ else+ {+ values[1] = ItemPointerGetDatum(&idxtuple->t_tid);+ /* Get list of item pointers from the tuple. */+ if (GinItupIsCompressed(idxtuple))+ {+ items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(idxtuple), &ndecoded);+ free_items_orig = true;+ }+ else+ {+ items_orig = (ItemPointer) GinGetPosting(idxtuple);+ ndecoded = GinGetNPosting(idxtuple);+ free_items_orig = false;+ }++ tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum));+ for (i = 0; i < ndecoded; i++)+ tids_datum[i] = ItemPointerGetDatum(&items_orig[i]);+ values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID));++ pfree(tids_datum);++ if (free_items_orig)+ pfree(items_orig);+ }++ /* Build and return the result tuple. */+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);+ }++ relation_close(indexRel, AccessShareLock);++ return (Datum) 0;+}+++Datum+gin_datapage_items(PG_FUNCTION_ARGS)+{+ bytea *raw_page = PG_GETARG_BYTEA_P(0);+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;+ OffsetNumber maxoff, offset;+ Page page;+ GinPageOpaque opaq;+++ if (!superuser())+ ereport(ERROR,+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),+ errmsg("must be superuser to use raw page functions")));+++ InitMaterializedSRF(fcinfo, 0);+ page = get_page_from_raw(raw_page);++ if (PageIsNew(page))+ {+ PG_RETURN_NULL();+ }++ if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("input page is not a valid GIN data leaf page"),+ errdetail("Expected special size %d, got %d.",+ (int) MAXALIGN(sizeof(GinPageOpaqueData)),+ (int) PageGetSpecialSize(page))));++ opaq = GinPageGetOpaque(page);+++ /* we only support posting tree non-leaf in this function, check that */++ if (opaq->flags & (GIN_META))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("gin_datapage_items is unsupported for metapage")));++ if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("gin_datapage_items is unsupported for GIN fast update list")));+ + if (!(opaq->flags & GIN_DATA))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("input page is not a GIN data tree page")));++ if (opaq->flags & GIN_LEAF)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),+ errmsg("input page is a GIN data leaf tree page")));++ maxoff = GinPageGetOpaque(page)->maxoff;++ for (offset = FirstOffsetNumber;+ offset <= maxoff;+ offset = OffsetNumberNext(offset))+ {+ Datum values[3];+ bool nulls[3];+ PostingItem* item = GinDataPageGetPostingItem(page, offset);++ memset(nulls, 0, sizeof(nulls));+ + + values[0] = UInt16GetDatum(offset);++ values[1] = UInt32GetDatum(BlockIdGetBlockNumber(&item->child_blkno));+ values[2] = ItemPointerGetDatum(&item->key);++ /* Build and return the result tuple. */+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);+ }++ return (Datum) 0;+}+
Datum
gin_leafpage_items(PG_FUNCTION_ARGS)
{
diff --git a/contrib/pageinspect/pageinspect--1.13--1.14.sql b/contrib/pageinspect/pageinspect--1.13--1.14.sqlnew file mode 100644index 00000000000..72f5e9bbea7--- /dev/null+++ b/contrib/pageinspect/pageinspect--1.13--1.14.sql@@ -0,0 +1,28 @@+/* 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++--+-- gin_entrypage_items()+--+CREATE FUNCTION gin_entrypage_items(IN page bytea, IN reloid OID,+ OUT itemoffset smallint,+ OUT downlink tid,+ OUT tids tid[],+ OUT keys text)+RETURNS SETOF record+AS 'MODULE_PATHNAME', 'gin_entrypage_items'+LANGUAGE C STRICT PARALLEL SAFE;++--+-- gin_datapage_items()+--+CREATE FUNCTION gin_datapage_items(IN page bytea,+ OUT itemoffset smallint,+ OUT downlink int,+ OUT item_tid tid)+RETURNS SETOF record+AS 'MODULE_PATHNAME', 'gin_datapage_items'+LANGUAGE C STRICT PARALLEL SAFE;+diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.controlindex 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/sql/gin.sql b/contrib/pageinspect/sql/gin.sqlindex b57466d7ebf..3e2738c71b0 100644--- a/contrib/pageinspect/sql/gin.sql+++ b/contrib/pageinspect/sql/gin.sql@@ -1,6 +1,8 @@-CREATE TABLE test1 (x int, y int[]);-INSERT INTO test1 VALUES (1, ARRAY[11, 111]);+CREATE TABLE test1 (x int, y int[], z text[]);+INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']);
CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off);
+CREATE INDEX test2_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = off);+CREATE INDEX test3_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = on);
\x
@@ -11,6 +13,10 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1));
SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1));
+SELECT * FROM gin_entrypage_items(get_raw_page('test1_y_idx', 1), 'test1_y_idx'::regclass);++SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 1), 'test2_y_z_idx'::regclass);+
INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x;
SELECT COUNT(*) > 0
@@ -18,6 +24,18 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx',
(pg_relation_size('test1_y_idx') /
current_setting('block_size')::bigint)::int - 1));
+-- Now test posting tree non-leaf page.+-- This requires inserting many tuples on a single leaf page to trigger page split.++CREATE TABLE test_data_page(i INT[]);+CREATE INDEX test_data_page_i_idx ON test_data_page USING gin(i) WITH (fastupdate = off);++INSERT INTO test_data_page SELECT ARRAY[1] FROM generate_series(1, 10000);++-- For this index, block 0 is metapage, block 1 is entry tree, block 2 is +-- posting tree non-leaf page and block 3 & 4 are compressed data leaf pages.+SELECT * FROM gin_datapage_items(get_raw_page('test_data_page_i_idx', 2));+
-- Failure with various modes.
-- Suppress the DETAIL message, to allow the tests to work across various
-- page sizes and architectures.
@@ -32,9 +50,21 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0));
SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
\set VERBOSITY default
+-- Reject unsupported page types in gin_entrypage_items.+SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 0), 'test2_y_z_idx'::regclass);+-- Check the error message for the internal posting tree page.+SELECT * FROM gin_entrypage_items(get_raw_page('test_data_page_i_idx', 2), 'test_data_page_i_idx'::regclass);+-- insert new row to trigger new (fast-list) page allocation.+INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']);+-- double check that the new page is fast-list.+SELECT * FROM gin_page_opaque_info(get_raw_page('test3_y_z_idx', 2));+-- reject fast-list pages.+SELECT * FROM gin_entrypage_items(get_raw_page('test3_y_z_idx', 3), 'test3_y_z_idx'::regclass);+
-- Tests with all-zero pages.
SHOW block_size \gset
SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
+SELECT gin_datapage_items(decode(repeat('00', :block_size), 'hex'));
SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgmlindex 3a113439e1d..4c1c7162b54 100644--- a/doc/src/sgml/pageinspect.sgml+++ b/doc/src/sgml/pageinspect.sgml@@ -718,6 +718,60 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</para>
</listitem>
</varlistentry>
++ <varlistentry>+ <term>+ <function>gin_entrypage_items(page bytea, reloid oid) returns setof record</function>+ <indexterm>+ <primary>gin_entrypage_items</primary>+ </indexterm>+ </term>++ <listitem>+ <para>+ <function>gin_entrypage_items</function> returns information about+ the data stored in a entry tree <acronym>GIN</acronym> page. For example:+<screen>+test=# select * from gin_entrypage_items(get_raw_page('gin_test_idx',+1), 'gin_test_idx'::regclass);+ itemoffset | downlink | tids | keys+------------+----------+------+------------------------------------+ 1 | (3,0) | {} | i=113+ 2 | (5,0) | {} | j=34173cb38f07f89ddbebc2ac9128303f+ 3 | (2,0) | {} | j=a0a080f42e6f13b3a2df133f073095dd+ 4 | (4,0) | {} | j=fc490ca45c00b1249bbe3554a4fdf6fb+(4 rows)+</screen>+ </para>+ </listitem>++ <varlistentry>+ <term>+ <function>gin_datapage_items(page bytea) returns setof record</function>+ <indexterm>+ <primary>gin_datapage_items</primary>+ </indexterm>+ </term>++ <listitem>+ <para>+ <function>gin_datapage_items</function> returns information about+ the data stored in a posting tree <acronym>GIN</acronym> internal page. For example:+<screen>+test=# select * from gin_datapage_items(get_raw_page('gin_test_idx',+43));+ itemoffset | downlink | item_tid+------------+----------+----------+ 1 | 124 | (162,12)+ 2 | 123 | (314,37)+ 3 | 251 | (467,23)+ 4 | 373 | (0,0)+(4 rows)+</screen>+ </para>+ </listitem>++ </varlistentry>
</variablelist>
</sect2>
--
2.43.0
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: reshkekirill@gmail.com, x4mmm@yandex-team.ru, rkhapov@yandex-team.ru
Subject: Re: GIN pageinspect support for entry tree and posting tree
In-Reply-To: <CALdSSPh7_GVFrGD4aQ76bOF7jxRkAkzY-eJCbzUh-X9F3QSb9A@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox