agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedFrom: Japin Li <japinli@hotmail.com>
To: Andrey Borodin <x4mmm@yandex-team.ru>
Cc: Kirill Reshke <reshkekirill@gmail.com>
Cc: Roman Khapov <rkhapov@yandex-team.ru>
Cc: Chao Li <li.evan.chao@gmail.com>
Cc: Peter Eisentraut <peter@eisentraut.org>
Cc: pgsql-hackers <pgsql-hackers@postgresql.org>
Subject: Re: GIN pageinspect support for entry tree and posting tree
Date: Wed, 02 Sep 2026 10:08:08 +0800
Message-ID: <SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com> (raw)
In-Reply-To: <SY7PR01MB1092135B5753972F5CF4A99E0B6A82@SY7PR01MB10921.ausprd01.prod.outlook.com>
References: <CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com>
<CALdSSPgT4duvh-zZSAvxo27-OFQWd3zNbEFP5DnPd0yfzYZ_Dg@mail.gmail.com>
<CALdSSPhcbAM8HdtfDHQVE=UNMoAH7-QmUGPKbAkxMTVk=3picg@mail.gmail.com>
<9B839464-8FE5-4569-B07E-24F4B54FB7BF@gmail.com>
<CALdSSPh2EDAN-OivHOyJg1SK13k-F8XsgirHzMjGJM6udj7bTw@mail.gmail.com>
<SYAPR01MB30384991904D0265F1746626B683A@SYAPR01MB3038.ausprd01.prod.outlook.com>
<CALdSSPjNL8ga9e1oFCwV5=MpoOiz0jWm0rXdJH+Wb60EwAOaMg@mail.gmail.com>
<MEAPR01MB3031BDE8BD0104D312204151B683A@MEAPR01MB3031.ausprd01.prod.outlook.com>
<CALdSSPj-ADRgBk1_gspb2Q0eY2wxQHLfiWfFOmAwSxMF_AboRQ@mail.gmail.com>
<BFCA6110-EC97-4569-917A-747A72F62CE8@yandex-team.ru>
<MEAPR01MB3031127CF9A8C02399E2D0ABB68FA@MEAPR01MB3031.ausprd01.prod.outlook.com>
<CALdSSPg6cqqWNDkAWX0RfB7CXM+oxacHivHPa90dZtX27KBGLg@mail.gmail.com>
<0A0E3993-7617-48EA-8555-6C1907C147E5@yandex-team.ru>
<MEAPR01MB3031AA324D863C181318188BB68FA@MEAPR01MB3031.ausprd01.prod.outlook.com>
<9CFF5492-B542-4BE7-9093-E32EBC7DDB1C@yandex-team.ru>
<CALdSSPivsz8FH3p68dRnEtruvsA+9FQqYB_WXpcNY6BLT19N4g@mail.gmail.com>
<SYAPR01MB3038F736792DE8876750FBDBB68FA@SYAPR01MB3038.ausprd01.prod.outlook.com>
<779DFF42-61E0-4295-94C0-53B347F45A17@yandex-team.ru>
<CALdSSPgAZk7s_kSzFq=HzLL1EqxCPr6UbNnGYPPA=sMG53_dwg@mail.gmail.com>
<E918F3EB-7706-4657-AC13-95CF05131298@yandex-team.ru>
<SY7PR01MB1092135B5753972F5CF4A99E0B6A82@SY7PR01MB10921.ausprd01.prod.outlook.com>
On Wed, 02 Sep 2026 at 00:10, Japin Li <japinli@hotmail.com> wrote:
> Hi, Andrey and Kirill
>
> On Sun, 30 Aug 2026 at 22:46, Andrey Borodin <x4mmm@yandex-team.ru> wrote:
>>> On 3 Aug 2026, at 17:40, Kirill Reshke <reshkekirill@gmail.com> wrote:
>>>
>>> Hi all,
>>>
>>> I have rebased patches and did little polishing for error
>>> messages/overall style. Posting to see how new CI reacts & also bump
>>> thread.
>>>
>> Hi Kirill,
>>
>> The CF entry is still Ready for Committer, but Japin's August 13 review
>> found that gin_entrypage_items() does not finalize its temporary tuple
>> descriptor before index_getattr(). With assertions enabled, the new
>> check added by c456e391138 can fail here.
>>
>> Could you fold the proposed TupleDescFinalize() call into a new patch
>> version? I have moved the entry to Waiting on Author for now.
>>
>
> The v20260308 patches need a rebase due to commit 51c43a5dbd86. I've rebased
> and merged the fix from [1]. Please take a look.
>
> [1] https://www.postgresql.org/message-id/SY7PR01MB109210C8E9DEB6BF5FFEA7A81B6DB2%40SY7PR01MB10921.auspr...
>
Sorry, I forgot to update the patches.
--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.
Attachments:
[text/x-patch] v20260901-0001-Modernize-coding-in-GIN-pageinspect-functi.patch (5.3K, ../SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com/2-v20260901-0001-Modernize-coding-in-GIN-pageinspect-functi.patch)
download | inline diff:
From b44852db01077d498cf7595480dd7aa8ed45eb10 Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Wed, 7 Jan 2026 19:38:52 +0000
Subject: [PATCH v20260901 1/3] Modernize coding in GIN pageinspect functions.
This patch switches palloc to our newly preferred palloc_array and modernizes
ereport calls, switching from list-style to polymorphic ereport
calls. This patch also fixes whitespace/tab issues, enforcing a single style.
across existing ginfuncs.c code.
Inspired by Peter Eisentraut's patch in the thread.
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Japin Li <japinli@hotmail.com>
Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com
---
contrib/pageinspect/ginfuncs.c | 60 +++++++++++++++++-----------------
1 file changed, 30 insertions(+), 30 deletions(-)
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
index 058ad52b671..3fe1412fe26 100644
--- a/contrib/pageinspect/ginfuncs.c
+++ b/contrib/pageinspect/ginfuncs.c
@@ -38,8 +38,8 @@ gin_metapage_info(PG_FUNCTION_ARGS)
if (!superuser())
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to use raw page functions")));
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use raw page functions"));
page = get_page_from_raw(raw_page);
@@ -48,20 +48,20 @@ gin_metapage_info(PG_FUNCTION_ARGS)
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("input page is not a valid GIN metapage"),
- errdetail("Expected special size %d, got %d.",
- (int) MAXALIGN(sizeof(GinPageOpaqueData)),
- (int) PageGetSpecialSize(page))));
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is not a valid GIN metapage"),
+ errdetail("Expected special size %d, got %d.",
+ (int) MAXALIGN(sizeof(GinPageOpaqueData)),
+ (int) PageGetSpecialSize(page)));
opaq = GinPageGetOpaque(page);
if (opaq->flags != GIN_META)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("input page is not a GIN metapage"),
- errdetail("Flags %04X, expected %04X",
- opaq->flags, GIN_META)));
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is not a GIN metapage"),
+ errdetail("Flags %04X, expected %04X",
+ opaq->flags, GIN_META));
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
@@ -108,8 +108,8 @@ gin_page_opaque_info(PG_FUNCTION_ARGS)
if (!superuser())
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to use raw page functions")));
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use raw page functions"));
page = get_page_from_raw(raw_page);
@@ -118,11 +118,11 @@ gin_page_opaque_info(PG_FUNCTION_ARGS)
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))));
+ 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);
@@ -184,8 +184,8 @@ gin_leafpage_items(PG_FUNCTION_ARGS)
if (!superuser())
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to use raw page functions")));
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use raw page functions"));
if (SRF_IS_FIRSTCALL())
{
@@ -207,20 +207,20 @@ gin_leafpage_items(PG_FUNCTION_ARGS)
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))));
+ 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);
if (opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("input page is not a compressed GIN data leaf page"),
- errdetail("Flags %04X, expected %04X",
- opaq->flags,
- (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))));
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page is not a compressed GIN data leaf page"),
+ errdetail("Flags %04X, expected %04X",
+ opaq->flags,
+ (GIN_DATA | GIN_LEAF | GIN_COMPRESSED)));
inter_call_data = palloc_object(gin_leafpage_items_state);
--
2.53.0
[text/x-patch] v20260901-0002-GIN-pageinspect-support-for-entry-tree-and.patch (31.9K, ../SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com/3-v20260901-0002-GIN-pageinspect-support-for-entry-tree-and.patch)
download | inline diff:
From e543bf25ca1ad1e1705cab2e506e32c9519fa136 Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Mon, 13 Oct 2025 20:14:26 +0000
Subject: [PATCH v20260901 2/3] GIN pageinspect support for entry tree and
posting tree internal pages
This patch provides a new version for the pageinspect contrib module, including
two new functions:
* gin_entrypage_items.
* gin_datapage_items.
These two functions can be used to examine the GIN entry tree and posting
tree pages. Namely, gin_entrypage_items can be used for both leaf and
non-leaf entry tree pages. gin_datapage_items is provided in pairs with
already-existing gin_leafpage_items to examine the 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.
Co-authored-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Reviewed-by: Roman Khapov <rkhapov@yandex-team.ru>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Japin Li <japinli@hotmail.com>
Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com
---
contrib/pageinspect/Makefile | 2 +-
contrib/pageinspect/expected/gin.out | 117 +++++-
contrib/pageinspect/expected/gin_1.out | 182 +++++++++
contrib/pageinspect/ginfuncs.c | 346 ++++++++++++++++++
contrib/pageinspect/meson.build | 1 +
.../pageinspect/pageinspect--1.13--1.14.sql | 27 ++
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/sql/gin.sql | 35 +-
doc/src/sgml/pageinspect.sgml | 54 +++
9 files changed, 759 insertions(+), 7 deletions(-)
create mode 100644 contrib/pageinspect/expected/gin_1.out
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..5a0874d1441 100644
--- a/contrib/pageinspect/expected/gin.out
+++ b/contrib/pageinspect/expected/gin.out
@@ -1,6 +1,9 @@
-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']);
+INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]);
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 ]----+-----------
@@ -12,7 +15,7 @@ n_pending_tuples | 0
n_total_pages | 2
n_entry_pages | 1
n_data_pages | 0
-n_entries | 2
+n_entries | 4
version | 2
SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1));
@@ -27,6 +30,75 @@ 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
+-[ RECORD 3 ]--------------
+itemoffset | 3
+downlink | (2147483664,1)
+tids | {"(0,2)"}
+keys | y=222
+-[ RECORD 4 ]--------------
+itemoffset | 4
+downlink | (2147483666,1)
+tids | {"(0,2)"}
+keys | y=NULL
+
+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,2)"}
+keys | y=222
+-[ RECORD 4 ]--------------
+itemoffset | 4
+downlink | (2147483672,1)
+tids | {"(0,2)"}
+keys | y=NULL
+-[ RECORD 5 ]--------------
+itemoffset | 5
+downlink | (2147483664,1)
+tids | {"(0,1)"}
+keys | z=a
+-[ RECORD 6 ]--------------
+itemoffset | 6
+downlink | (2147483664,1)
+tids | {"(0,1)"}
+keys | z=b
+-[ RECORD 7 ]--------------
+itemoffset | 7
+downlink | (2147483664,1)
+tids | {"(0,1)"}
+keys | z=c
+-[ RECORD 8 ]--------------
+itemoffset | 8
+downlink | (2147483664,1)
+tids | {"(0,2)"}
+keys | z=d
+-[ RECORD 9 ]--------------
+itemoffset | 9
+downlink | (2147483672,1)
+tids | {"(0,2)"}
+keys | z=NULL
+
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 +107,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 +143,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 does not support metapages
+-- 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: gin_entrypage_items does not support posting tree pages
+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 does not support 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/expected/gin_1.out b/contrib/pageinspect/expected/gin_1.out
new file mode 100644
index 00000000000..029324710fd
--- /dev/null
+++ b/contrib/pageinspect/expected/gin_1.out
@@ -0,0 +1,182 @@
+CREATE TABLE test1 (x int, y int[], z text[]);
+INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']);
+INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]);
+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 ]----+-----------
+pending_head | 4294967295
+pending_tail | 4294967295
+tail_free_size | 0
+n_pending_pages | 0
+n_pending_tuples | 0
+n_total_pages | 2
+n_entry_pages | 1
+n_data_pages | 0
+n_entries | 4
+version | 2
+
+SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1));
+ERROR: input page is not a GIN metapage
+DETAIL: Flags 0002, expected 0008
+SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1));
+-[ RECORD 1 ]---------
+rightlink | 4294967295
+maxoff | 0
+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 | (2147483660,1)
+tids | {"(0,1)"}
+keys | y=11
+-[ RECORD 2 ]--------------
+itemoffset | 2
+downlink | (2147483660,1)
+tids | {"(0,1)"}
+keys | y=111
+-[ RECORD 3 ]--------------
+itemoffset | 3
+downlink | (2147483660,1)
+tids | {"(0,2)"}
+keys | y=222
+-[ RECORD 4 ]--------------
+itemoffset | 4
+downlink | (2147483662,1)
+tids | {"(0,2)"}
+keys | y=NULL
+
+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,2)"}
+keys | y=222
+-[ RECORD 4 ]--------------
+itemoffset | 4
+downlink | (2147483664,1)
+tids | {"(0,2)"}
+keys | y=NULL
+-[ RECORD 5 ]--------------
+itemoffset | 5
+downlink | (2147483660,1)
+tids | {"(0,1)"}
+keys | z=a
+-[ RECORD 6 ]--------------
+itemoffset | 6
+downlink | (2147483660,1)
+tids | {"(0,1)"}
+keys | z=b
+-[ RECORD 7 ]--------------
+itemoffset | 7
+downlink | (2147483660,1)
+tids | {"(0,1)"}
+keys | z=c
+-[ RECORD 8 ]--------------
+itemoffset | 8
+downlink | (2147483660,1)
+tids | {"(0,2)"}
+keys | z=d
+-[ RECORD 9 ]--------------
+itemoffset | 9
+downlink | (2147483664,1)
+tids | {"(0,2)"}
+keys | z=NULL
+
+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',
+ (pg_relation_size('test1_y_idx') /
+ current_setting('block_size')::bigint)::int - 1));
+-[ 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 | (41,125)
+-[ 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.
+\set VERBOSITY terse
+-- invalid page size
+SELECT gin_leafpage_items('aaa'::bytea);
+ERROR: invalid page size
+SELECT gin_metapage_info('bbb'::bytea);
+ERROR: invalid page size
+SELECT gin_page_opaque_info('ccc'::bytea);
+ERROR: invalid page size
+-- invalid special area size
+SELECT * FROM gin_metapage_info(get_raw_page('test1', 0));
+ERROR: input page is not a valid GIN metapage
+SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0));
+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 does not support metapages
+-- 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: gin_entrypage_items does not support posting tree pages
+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 | 136
+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 does not support 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 |
+
+SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]--------+-
+gin_page_opaque_info |
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
index 3fe1412fe26..f51212a0280 100644
--- a/contrib/pageinspect/ginfuncs.c
+++ b/contrib/pageinspect/ginfuncs.c
@@ -11,18 +11,28 @@
#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"
+#include "utils/tuplestore.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 +185,342 @@ typedef struct gin_leafpage_items_state
GinPostingList *lastseg;
} gin_leafpage_items_state;
+/*
+ * gin_entrypage_items
+ *
+ * Allows inspection of contents of an entry tree page.
+ */
+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;
+ TupleDesc tupdesc;
+ 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 index 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 does not support metapages"));
+
+ if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gin_entrypage_items does not support fast list pages"));
+
+ if (opaq->flags & GIN_DATA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gin_entrypage_items does not support posting tree pages"),
+ errhint("This appears to be a GIN posting tree page. Please use gin_datapage_items."));
+
+ initStringInfo(&buf);
+ maxoff = PageGetMaxOffsetNumber(page);
+
+ tupdesc = RelationGetDescr(indexRel);
+
+ for (OffsetNumber offset = FirstOffsetNumber;
+ offset <= maxoff;
+ offset = OffsetNumberNext(offset))
+ {
+ OffsetNumber indAtt;
+ Datum values[4];
+ bool nulls[4] = {0};
+ Datum attrVal;
+ bool isnull;
+ IndexTuple idxtuple;
+ ItemId iid = PageGetItemId(page, offset);
+
+ if (!ItemIdIsValid(iid))
+ ereport(ERROR, errcode(ERRCODE_INDEX_CORRUPTED), errmsg("invalid ItemId at offset %u", offset));
+
+ idxtuple = (IndexTuple) PageGetItem(page, iid);
+
+ values[0] = UInt16GetDatum(offset);
+
+ if (tupdesc->natts == 1)
+ {
+ indAtt = FirstOffsetNumber;
+
+ /* Here we can safely reuse any tuple descriptor. */
+ attrVal = index_getattr(idxtuple, FirstOffsetNumber, tupdesc, &isnull);
+ }
+ else
+ {
+ TupleDesc tmpTupdesc;
+ Datum res;
+ Form_pg_attribute attr;
+
+ /*
+ * Multi-column GIN indexes store 2-attribute tuple on each page
+ * item. First attribute is which heap attribute is stored as the
+ * second value in pair. To display value with proper output
+ * function we need to recreate tuple descriptor on each offset.
+ * NB: It is safe to reuse the original index tuple. See also
+ * gintuple_get_attrnum.
+ */
+
+ 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 %u", 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);
+
+ TupleDescFinalize(tmpTupdesc);
+
+ attrVal = index_getattr(idxtuple, OffsetNumberNext(FirstOffsetNumber),
+ tmpTupdesc,
+ &isnull);
+
+ FreeTupleDesc(tmpTupdesc);
+ }
+
+ appendStringInfo(&buf, "%s=", quote_identifier(TupleDescAttr(tupdesc, indAtt - 1)->attname.data));
+
+ if (!isnull)
+ {
+ Oid foutoid;
+ bool typisvarlena;
+ Oid typoid;
+ char *value;
+ bool nq;
+
+ /*
+ * The following value output and quoting logic 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 (const char *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 (const char *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
+ {
+ int ndecoded;
+ Datum *tids_datum;
+ ItemPointer items_orig;
+ bool free_items_orig;
+
+ 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 = palloc_array(Datum, ndecoded);
+ for (int 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);
+ }
+
+ index_close(indexRel, AccessShareLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * gin_datapage_items
+ *
+ * Allows inspection of contents of an posting tree non-leaf page.
+ */
+Datum
+gin_datapage_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ OffsetNumber maxoff;
+ 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 page"),
+ errdetail("Expected special size %d, got %d.",
+ (int) MAXALIGN(sizeof(GinPageOpaqueData)),
+ (int) PageGetSpecialSize(page)));
+
+ opaq = GinPageGetOpaque(page);
+
+ /*
+ * Reject non-posting-tree-internal GIN pages, which are the metapage, fast
+ * list pages, entry tree pages and posting tree leaf pages.
+ */
+ if (opaq->flags & GIN_META)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gin_datapage_items does not support metapages"));
+
+ if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gin_datapage_items does not support fast list pages"));
+
+ 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("gin_datapage_items does not support posting tree leaf pages"),
+ errhint("This appears to be a GIN posting tree leaf page. Please use gin_leafpage_items."));
+
+ maxoff = opaq->maxoff;
+
+ for (OffsetNumber 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/meson.build b/contrib/pageinspect/meson.build
index c43ea400a4d..2f333635838 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -38,6 +38,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,
)
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..ef6fa87e0f4
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.13--1.14.sql
@@ -0,0 +1,27 @@
+/* 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..f19eef4b60f 100644
--- a/contrib/pageinspect/sql/gin.sql
+++ b/contrib/pageinspect/sql/gin.sql
@@ -1,6 +1,9 @@
-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']);
+INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]);
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 +14,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 +25,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 +51,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.sgml
index 3a113439e1d..4ed8826e92e 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -714,6 +714,60 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
(170,30) | 376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"}
(173,44) | 197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"}
(7 rows)
+</screen>
+ </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>
+
+ <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>
--
2.53.0
[text/x-patch] v20260901-0003-Move-IS_INDEX-macro-to-pageinspect.h.patch (4.0K, ../SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com/4-v20260901-0003-Move-IS_INDEX-macro-to-pageinspect.h.patch)
download | inline diff:
From 49f164407c5b2311120c912680e496ffe82f404e Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Sat, 10 Jan 2026 14:11:37 +0000
Subject: [PATCH v20260901 3/3] Move IS_INDEX macro to pageinspect.h
Suggested-by: Japin Li <japinli@hotmail.com>, Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com
---
contrib/pageinspect/btreefuncs.c | 7 +++----
contrib/pageinspect/ginfuncs.c | 5 ++---
contrib/pageinspect/hashfuncs.c | 5 ++---
contrib/pageinspect/pageinspect.h | 2 ++
4 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 9917663593b..2e9e5b96bd0 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -49,8 +49,7 @@ PG_FUNCTION_INFO_V1(bt_page_stats_1_9);
PG_FUNCTION_INFO_V1(bt_page_stats);
PG_FUNCTION_INFO_V1(bt_multi_page_stats);
-#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
-#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
+#define IS_BTREE(r) (IS_INDEX(r) && (r)->rd_rel->relam == BTREE_AM_OID)
/* ------------------------------------------------
* structure for single btree page statistics
@@ -225,7 +224,7 @@ check_relation_block_range(Relation rel, int64 blkno)
static void
bt_index_block_validate(Relation rel, int64 blkno)
{
- if (!IS_INDEX(rel) || !IS_BTREE(rel))
+ if (!IS_BTREE(rel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a %s index",
@@ -858,7 +857,7 @@ bt_metap(PG_FUNCTION_ARGS)
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
rel = relation_openrv(relrv, AccessShareLock);
- if (!IS_INDEX(rel) || !IS_BTREE(rel))
+ if (!IS_BTREE(rel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a %s index",
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
index f51212a0280..b50807a251e 100644
--- a/contrib/pageinspect/ginfuncs.c
+++ b/contrib/pageinspect/ginfuncs.c
@@ -31,8 +31,7 @@ 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)
+#define IS_GIN(r) (IS_INDEX(r) && (r)->rd_rel->relam == GIN_AM_OID)
Datum
gin_metapage_info(PG_FUNCTION_ARGS)
@@ -213,7 +212,7 @@ gin_entrypage_items(PG_FUNCTION_ARGS)
/* Open the index relation */
indexRel = index_open(indexRelid, AccessShareLock);
- if (!IS_INDEX(indexRel) || !IS_GIN(indexRel))
+ if (!IS_GIN(indexRel))
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a %s index",
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 30870329cc9..b9ec5bd34a3 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -28,8 +28,7 @@ PG_FUNCTION_INFO_V1(hash_page_items);
PG_FUNCTION_INFO_V1(hash_bitmap_info);
PG_FUNCTION_INFO_V1(hash_metapage_info);
-#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
-#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
+#define IS_HASH(r) (IS_INDEX(r) && (r)->rd_rel->relam == HASH_AM_OID)
/* ------------------------------------------------
* structure for single hash page statistics
@@ -420,7 +419,7 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
*/
indexRel = relation_open(indexRelid, AccessShareLock);
- if (!IS_INDEX(indexRel) || !IS_HASH(indexRel))
+ if (!IS_HASH(indexRel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a %s index",
diff --git a/contrib/pageinspect/pageinspect.h b/contrib/pageinspect/pageinspect.h
index b241fdc97b2..7e5d28eeb4d 100644
--- a/contrib/pageinspect/pageinspect.h
+++ b/contrib/pageinspect/pageinspect.h
@@ -24,6 +24,8 @@ enum pageinspect_version
PAGEINSPECT_V1_9,
};
+#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
+
/* in rawpage.c */
extern Page get_page_from_raw(bytea *raw_page);
--
2.53.0
view thread (49+ messages)
Message-ID: <SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com>
Permalink: ../SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com/
Also on: postgresql.org/message-id/SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com
reply
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: japinli@hotmail.com, x4mmm@yandex-team.ru, reshkekirill@gmail.com, rkhapov@yandex-team.ru, li.evan.chao@gmail.com, peter@eisentraut.org
Subject: Re: GIN pageinspect support for entry tree and posting tree
In-Reply-To: <SY7PR01MB10921891275CC615D3282326EB6B72@SY7PR01MB10921.ausprd01.prod.outlook.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox