From bb80ecd61a830bc24b297d486a99d419ad77fc5c Mon Sep 17 00:00:00 2001 From: reshke Date: Mon, 13 Oct 2025 20:14:26 +0000 Subject: [PATCH v3] 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 | 337 ++++++++++++++++++ .../pageinspect/pageinspect--1.13--1.14.sql | 28 ++ contrib/pageinspect/pageinspect.control | 2 +- contrib/pageinspect/sql/gin.sql | 34 +- 6 files changed, 481 insertions(+), 6 deletions(-) create mode 100644 contrib/pageinspect/pageinspect--1.13--1.14.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index 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.out index 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.c index ebcc2b3db5c..0fb98c0efd6 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,334 @@ 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"))); + + maxoff = InvalidOffsetNumber; + + 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("invalud 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("invalud 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.sql new file mode 100644 index 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.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/sql/gin.sql b/contrib/pageinspect/sql/gin.sql index 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')); -- 2.43.0