agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v5 06/12] consistent language: not implemented
8+ messages / 4 participants
[nested] [flat]

* [PATCH v5 06/12] consistent language: not implemented
@ 2019-05-20 17:57  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Justin Pryzby @ 2019-05-20 17:57 UTC (permalink / raw)

Should we change the rest of these, too ?

git grep 'errmsg.*not yet implemented'
---
 src/backend/catalog/index.c      | 2 +-
 src/backend/commands/indexcmds.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bb60b23..7f607e6 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3511,7 +3511,7 @@ reindex_relation(Oid relid, int flags, int options)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+				 errmsg("REINDEX of partitioned tables is not implemented, skipping \"%s\"",
 						RelationGetRelationName(rel))));
 		table_close(rel, ShareLock);
 		return false;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6fa5738..5b0ae20 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2898,7 +2898,7 @@ ReindexRelationConcurrently(Oid relationOid, int options)
 			/* see reindex_relation() */
 			ereport(WARNING,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+					 errmsg("REINDEX of partitioned tables is not implemented, skipping \"%s\"",
 							get_rel_name(relationOid))));
 			return false;
 		default:
@@ -3344,7 +3344,7 @@ ReindexPartitionedIndex(Relation parentIdx)
 {
 	ereport(ERROR,
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("REINDEX is not yet implemented for partitioned indexes")));
+			 errmsg("REINDEX is not implemented for partitioned indexes")));
 }
 
 /*
-- 
2.7.4


--FkmkrVfFsRoUs1wW
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v5-0007-consistent-language-to-which.patch"



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH v3 06/12] consistent language: not implemented
@ 2019-05-20 17:57  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Justin Pryzby @ 2019-05-20 17:57 UTC (permalink / raw)

Should we change the rest of these, too ?

git grep 'errmsg.*not yet implemented'
---
 src/backend/catalog/index.c      | 2 +-
 src/backend/commands/indexcmds.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c8d22e1..2a2dca3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3506,7 +3506,7 @@ reindex_relation(Oid relid, int flags, int options)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+				 errmsg("REINDEX of partitioned tables is not implemented, skipping \"%s\"",
 						RelationGetRelationName(rel))));
 		table_close(rel, ShareLock);
 		return false;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 7e7c03e..b2b15ec 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2862,7 +2862,7 @@ ReindexRelationConcurrently(Oid relationOid, int options)
 			/* see reindex_relation() */
 			ereport(WARNING,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+					 errmsg("REINDEX of partitioned tables is not implemented, skipping \"%s\"",
 							get_rel_name(relationOid))));
 			return false;
 		default:
@@ -3306,7 +3306,7 @@ ReindexPartitionedIndex(Relation parentIdx)
 {
 	ereport(ERROR,
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("REINDEX is not yet implemented for partitioned indexes")));
+			 errmsg("REINDEX is not implemented for partitioned indexes")));
 }
 
 /*
-- 
2.7.4


--cWoXeonUoKmBZSoM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v3-0007-overridden-vs-overwritten.patch"



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH] Document RelationGetIndexAttrBitmap better
@ 2023-07-12 12:19  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Alvaro Herrera @ 2023-07-12 12:19 UTC (permalink / raw)

Commit 19d8e2308bc5 changed the list of set-of-columns that can be
returned by RelationGetIndexAttrBitmap, but didn't update its
"documentation".  That was pretty hard to read already, so rewrite to
make it more comprehensible, adding the missing values while at it.

Backpatch to 16, like that commit.
---
 src/backend/utils/cache/relcache.c | 12 +++++++++---
 src/include/utils/relcache.h       |  3 +++
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8e28335915..8e08ca1c68 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5150,9 +5150,15 @@ RelationGetIndexPredicate(Relation relation)
  * simple index keys, but attributes used in expressions and partial-index
  * predicates.)
  *
- * Depending on attrKind, a bitmap covering the attnums for all index columns,
- * for all potential foreign key columns, or for all columns in the configured
- * replica identity index is returned.
+ * Depending on attrKind, a bitmap covering attnums for certain columns is
+ * returned:
+ *	INDEX_ATTR_BITMAP_KEY			Columns in non-partial unique indexes not
+ *									in expressions (i.e., usable for FKs)
+ *	INDEX_ATTR_BITMAP_PRIMARY_KEY	Columns in the table's primary key
+ *	INDEX_ATTR_BITMAP_IDENTITY_KEY	Columns in the table's replica identity
+ *									index (empty if FULL)
+ *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
+ *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index beeb28b83c..38524641f4 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -54,6 +54,9 @@ extern List *RelationGetIndexPredicate(Relation relation);
 extern Datum *RelationGetIndexRawAttOptions(Relation indexrel);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
 
+/*
+ * Which set of columns to return by RelationGetIndexAttrBitmap.
+ */
 typedef enum IndexAttrBitmapKind
 {
 	INDEX_ATTR_BITMAP_KEY,
-- 
2.39.2


--sr6mye444zs4xrf5--





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH] Document RelationGetIndexAttrBitmap better
@ 2023-07-12 12:19  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Alvaro Herrera @ 2023-07-12 12:19 UTC (permalink / raw)

Commit 19d8e2308bc5 changed the list of set-of-columns that can be
returned by RelationGetIndexAttrBitmap, but didn't update its
"documentation".  That was pretty hard to read already, so rewrite to
make it more comprehensible, adding the missing values while at it.

Backpatch to 16, like that commit.
---
 src/backend/utils/cache/relcache.c | 12 +++++++++---
 src/include/utils/relcache.h       |  3 +++
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8e28335915..8e08ca1c68 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5150,9 +5150,15 @@ RelationGetIndexPredicate(Relation relation)
  * simple index keys, but attributes used in expressions and partial-index
  * predicates.)
  *
- * Depending on attrKind, a bitmap covering the attnums for all index columns,
- * for all potential foreign key columns, or for all columns in the configured
- * replica identity index is returned.
+ * Depending on attrKind, a bitmap covering attnums for certain columns is
+ * returned:
+ *	INDEX_ATTR_BITMAP_KEY			Columns in non-partial unique indexes not
+ *									in expressions (i.e., usable for FKs)
+ *	INDEX_ATTR_BITMAP_PRIMARY_KEY	Columns in the table's primary key
+ *	INDEX_ATTR_BITMAP_IDENTITY_KEY	Columns in the table's replica identity
+ *									index (empty if FULL)
+ *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
+ *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index beeb28b83c..38524641f4 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -54,6 +54,9 @@ extern List *RelationGetIndexPredicate(Relation relation);
 extern Datum *RelationGetIndexRawAttOptions(Relation indexrel);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
 
+/*
+ * Which set of columns to return by RelationGetIndexAttrBitmap.
+ */
 typedef enum IndexAttrBitmapKind
 {
 	INDEX_ATTR_BITMAP_KEY,
-- 
2.39.2


--sr6mye444zs4xrf5--





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH] Document RelationGetIndexAttrBitmap better
@ 2023-07-12 12:19  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Alvaro Herrera @ 2023-07-12 12:19 UTC (permalink / raw)

Commit 19d8e2308bc5 changed the list of set-of-columns that can be
returned by RelationGetIndexAttrBitmap, but didn't update its
"documentation".  That was pretty hard to read already, so rewrite to
make it more comprehensible, adding the missing values while at it.

Backpatch to 16, like that commit.
---
 src/backend/utils/cache/relcache.c | 12 +++++++++---
 src/include/utils/relcache.h       |  3 +++
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8e28335915..8e08ca1c68 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5150,9 +5150,15 @@ RelationGetIndexPredicate(Relation relation)
  * simple index keys, but attributes used in expressions and partial-index
  * predicates.)
  *
- * Depending on attrKind, a bitmap covering the attnums for all index columns,
- * for all potential foreign key columns, or for all columns in the configured
- * replica identity index is returned.
+ * Depending on attrKind, a bitmap covering attnums for certain columns is
+ * returned:
+ *	INDEX_ATTR_BITMAP_KEY			Columns in non-partial unique indexes not
+ *									in expressions (i.e., usable for FKs)
+ *	INDEX_ATTR_BITMAP_PRIMARY_KEY	Columns in the table's primary key
+ *	INDEX_ATTR_BITMAP_IDENTITY_KEY	Columns in the table's replica identity
+ *									index (empty if FULL)
+ *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
+ *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index beeb28b83c..38524641f4 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -54,6 +54,9 @@ extern List *RelationGetIndexPredicate(Relation relation);
 extern Datum *RelationGetIndexRawAttOptions(Relation indexrel);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
 
+/*
+ * Which set of columns to return by RelationGetIndexAttrBitmap.
+ */
 typedef enum IndexAttrBitmapKind
 {
 	INDEX_ATTR_BITMAP_KEY,
-- 
2.39.2


--sr6mye444zs4xrf5--





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH v16 2/3] GIN pageinspect support for entry tree and posting tree internal pages
@ 2025-10-13 20:14  reshke <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: reshke @ 2025-10-13 20:14 UTC (permalink / raw)

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 <[email protected]>
Reviewed-by: Andrey Borodin <[email protected]>
Reviewed-by: Roman Khapov <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Japin Li <[email protected]>

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 33c0620a5c1..f0466bc10c3 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,343 @@ 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);
+
+			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 leaf page"),
+				 errdetail("Expected special size %d, got %d.",
+						   (int) MAXALIGN(sizeof(GinPageOpaqueData)),
+						   (int) PageGetSpecialSize(page))));
+
+	opaq = GinPageGetOpaque(page);
+
+	/*
+	 * Reject non-entry-tree GIN pages, which are metapage, fastlist pages,
+	 * and posting tree pages.
+	 */
+
+	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"),
+				errhint("This appears to be a GIN posting leaf tree page. Please use gin_leafpage_items."));
+
+	maxoff = GinPageGetOpaque(page)->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.43.0


--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
 filename=v16-0003-Move-IS_INDEX-macro-to-pageinspect.h.patch



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH v10 1/8] lwlock: Invert meaning of LW_FLAG_RELEASE_OK
@ 2026-01-06 01:40  Andres Freund <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Andres Freund @ 2026-01-06 01:40 UTC (permalink / raw)

Previously, a flag was set to indicate that a lock release should wake up
waiters. Since waking waiters is the default behavior in the majority of
cases, this logic has been inverted. The new LW_FLAG_WAKE_IN_PROGRESS flag is
now set iff wakeups are explicitly inhibited.

The motivation for this change is that in an upcoming commit, content locks
will be implemented independently of lwlocks, with the lock state stored as
part of BufferDesc.state. As all of a buffer's flags are cleared when the
buffer is invalidated, without this change we would have to re-add the
RELEASE_OK flag after clearing the flags; otherwise, the next lock release
would not wake waiters.

It seems good to keep the implementation of lwlocks and buffer content locks
as similar as reasonably possible.

Discussion: https://postgr.es/m/4csodkvvfbfloxxjlkgsnl2lgfv2mtzdl7phqzd4jxjadxm4o5@usw7feyb5bzf
---
 src/backend/storage/lmgr/lwlock.c | 42 +++++++++++++++----------------
 1 file changed, 20 insertions(+), 22 deletions(-)

diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 6a9f86d5025..148309cc186 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -92,7 +92,7 @@
 
 
 #define LW_FLAG_HAS_WAITERS			((uint32) 1 << 31)
-#define LW_FLAG_RELEASE_OK			((uint32) 1 << 30)
+#define LW_FLAG_WAKE_IN_PROGRESS	((uint32) 1 << 30)
 #define LW_FLAG_LOCKED				((uint32) 1 << 29)
 #define LW_FLAG_BITS				3
 #define LW_FLAG_MASK				(((1<<LW_FLAG_BITS)-1)<<(32-LW_FLAG_BITS))
@@ -246,14 +246,14 @@ PRINT_LWDEBUG(const char *where, LWLock *lock, LWLockMode mode)
 		ereport(LOG,
 				(errhidestmt(true),
 				 errhidecontext(true),
-				 errmsg_internal("%d: %s(%s %p): excl %u shared %u haswaiters %u waiters %u rOK %d",
+				 errmsg_internal("%d: %s(%s %p): excl %u shared %u haswaiters %u waiters %u waking %d",
 								 MyProcPid,
 								 where, T_NAME(lock), lock,
 								 (state & LW_VAL_EXCLUSIVE) != 0,
 								 state & LW_SHARED_MASK,
 								 (state & LW_FLAG_HAS_WAITERS) != 0,
 								 pg_atomic_read_u32(&lock->nwaiters),
-								 (state & LW_FLAG_RELEASE_OK) != 0)));
+								 (state & LW_FLAG_WAKE_IN_PROGRESS) != 0)));
 	}
 }
 
@@ -700,7 +700,7 @@ LWLockInitialize(LWLock *lock, int tranche_id)
 	/* verify the tranche_id is valid */
 	(void) GetLWTrancheName(tranche_id);
 
-	pg_atomic_init_u32(&lock->state, LW_FLAG_RELEASE_OK);
+	pg_atomic_init_u32(&lock->state, 0);
 #ifdef LOCK_DEBUG
 	pg_atomic_init_u32(&lock->nwaiters, 0);
 #endif
@@ -929,15 +929,13 @@ LWLockWaitListUnlock(LWLock *lock)
 static void
 LWLockWakeup(LWLock *lock)
 {
-	bool		new_release_ok;
+	bool		new_release_in_progress = false;
 	bool		wokeup_somebody = false;
 	proclist_head wakeup;
 	proclist_mutable_iter iter;
 
 	proclist_init(&wakeup);
 
-	new_release_ok = true;
-
 	/* lock wait list while collecting backends to wake up */
 	LWLockWaitListLock(lock);
 
@@ -958,7 +956,7 @@ LWLockWakeup(LWLock *lock)
 			 * that are just waiting for the lock to become free don't retry
 			 * automatically.
 			 */
-			new_release_ok = false;
+			new_release_in_progress = true;
 
 			/*
 			 * Don't wakeup (further) exclusive locks.
@@ -997,10 +995,10 @@ LWLockWakeup(LWLock *lock)
 
 			/* compute desired flags */
 
-			if (new_release_ok)
-				desired_state |= LW_FLAG_RELEASE_OK;
+			if (new_release_in_progress)
+				desired_state |= LW_FLAG_WAKE_IN_PROGRESS;
 			else
-				desired_state &= ~LW_FLAG_RELEASE_OK;
+				desired_state &= ~LW_FLAG_WAKE_IN_PROGRESS;
 
 			if (proclist_is_empty(&lock->waiters))
 				desired_state &= ~LW_FLAG_HAS_WAITERS;
@@ -1131,10 +1129,10 @@ LWLockDequeueSelf(LWLock *lock)
 		 */
 
 		/*
-		 * Reset RELEASE_OK flag if somebody woke us before we removed
-		 * ourselves - they'll have set it to false.
+		 * Clear LW_FLAG_WAKE_IN_PROGRESS if somebody woke us before we
+		 * removed ourselves - they'll have set it.
 		 */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 		/*
 		 * Now wait for the scheduled wakeup, otherwise our ->lwWaiting would
@@ -1301,7 +1299,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode)
 		}
 
 		/* Retrying, allow LWLockRelease to release waiters again. */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 #ifdef LOCK_DEBUG
 		{
@@ -1636,10 +1634,10 @@ LWLockWaitForVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 oldval,
 		LWLockQueueSelf(lock, LW_WAIT_UNTIL_FREE);
 
 		/*
-		 * Set RELEASE_OK flag, to make sure we get woken up as soon as the
-		 * lock is released.
+		 * Clear LW_FLAG_WAKE_IN_PROGRESS flag, to make sure we get woken up
+		 * as soon as the lock is released.
 		 */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 		/*
 		 * We're now guaranteed to be woken up if necessary. Recheck the lock
@@ -1852,11 +1850,11 @@ LWLockReleaseInternal(LWLock *lock, LWLockMode mode)
 		TRACE_POSTGRESQL_LWLOCK_RELEASE(T_NAME(lock));
 
 	/*
-	 * We're still waiting for backends to get scheduled, don't wake them up
-	 * again.
+	 * Check if we're still waiting for backends to get scheduled, if so,
+	 * don't wake them up again.
 	 */
-	if ((oldstate & (LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK)) ==
-		(LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK) &&
+	if ((oldstate & LW_FLAG_HAS_WAITERS) &&
+		!(oldstate & LW_FLAG_WAKE_IN_PROGRESS) &&
 		(oldstate & LW_LOCK_MASK) == 0)
 		check_waiters = true;
 	else
-- 
2.48.1.76.g4e746b1a31.dirty


--jmawlk4t5yqwiemy
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v10-0002-bufmgr-Make-definitions-related-to-buffer-descri.patch"



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* [PATCH v9 04/10] lwlock: Invert meaning of LW_FLAG_RELEASE_OK
@ 2026-01-06 01:40  Andres Freund <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Andres Freund @ 2026-01-06 01:40 UTC (permalink / raw)

Instead of setting a flag whenever a lock release is supposed to wake up
waiters - the majority of the time - set a flag whenever wakeups are
inhibited. The motivation for this that in an upcoming commit, buffer content
locks are implemented separately from lwlocks, and for buffer content locks it
is useful to be able to reset all buffer flags when a buffer invalidated,
alternatively we would have to set the release-ok flag when making a buffer
valid.  It seems good to keep the implementation of lwlocks and buffer content
locks as similar as reasonably possible.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/storage/lmgr/lwlock.c | 42 +++++++++++++++----------------
 1 file changed, 20 insertions(+), 22 deletions(-)

diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 6a9f86d5025..148309cc186 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -92,7 +92,7 @@
 
 
 #define LW_FLAG_HAS_WAITERS			((uint32) 1 << 31)
-#define LW_FLAG_RELEASE_OK			((uint32) 1 << 30)
+#define LW_FLAG_WAKE_IN_PROGRESS	((uint32) 1 << 30)
 #define LW_FLAG_LOCKED				((uint32) 1 << 29)
 #define LW_FLAG_BITS				3
 #define LW_FLAG_MASK				(((1<<LW_FLAG_BITS)-1)<<(32-LW_FLAG_BITS))
@@ -246,14 +246,14 @@ PRINT_LWDEBUG(const char *where, LWLock *lock, LWLockMode mode)
 		ereport(LOG,
 				(errhidestmt(true),
 				 errhidecontext(true),
-				 errmsg_internal("%d: %s(%s %p): excl %u shared %u haswaiters %u waiters %u rOK %d",
+				 errmsg_internal("%d: %s(%s %p): excl %u shared %u haswaiters %u waiters %u waking %d",
 								 MyProcPid,
 								 where, T_NAME(lock), lock,
 								 (state & LW_VAL_EXCLUSIVE) != 0,
 								 state & LW_SHARED_MASK,
 								 (state & LW_FLAG_HAS_WAITERS) != 0,
 								 pg_atomic_read_u32(&lock->nwaiters),
-								 (state & LW_FLAG_RELEASE_OK) != 0)));
+								 (state & LW_FLAG_WAKE_IN_PROGRESS) != 0)));
 	}
 }
 
@@ -700,7 +700,7 @@ LWLockInitialize(LWLock *lock, int tranche_id)
 	/* verify the tranche_id is valid */
 	(void) GetLWTrancheName(tranche_id);
 
-	pg_atomic_init_u32(&lock->state, LW_FLAG_RELEASE_OK);
+	pg_atomic_init_u32(&lock->state, 0);
 #ifdef LOCK_DEBUG
 	pg_atomic_init_u32(&lock->nwaiters, 0);
 #endif
@@ -929,15 +929,13 @@ LWLockWaitListUnlock(LWLock *lock)
 static void
 LWLockWakeup(LWLock *lock)
 {
-	bool		new_release_ok;
+	bool		new_release_in_progress = false;
 	bool		wokeup_somebody = false;
 	proclist_head wakeup;
 	proclist_mutable_iter iter;
 
 	proclist_init(&wakeup);
 
-	new_release_ok = true;
-
 	/* lock wait list while collecting backends to wake up */
 	LWLockWaitListLock(lock);
 
@@ -958,7 +956,7 @@ LWLockWakeup(LWLock *lock)
 			 * that are just waiting for the lock to become free don't retry
 			 * automatically.
 			 */
-			new_release_ok = false;
+			new_release_in_progress = true;
 
 			/*
 			 * Don't wakeup (further) exclusive locks.
@@ -997,10 +995,10 @@ LWLockWakeup(LWLock *lock)
 
 			/* compute desired flags */
 
-			if (new_release_ok)
-				desired_state |= LW_FLAG_RELEASE_OK;
+			if (new_release_in_progress)
+				desired_state |= LW_FLAG_WAKE_IN_PROGRESS;
 			else
-				desired_state &= ~LW_FLAG_RELEASE_OK;
+				desired_state &= ~LW_FLAG_WAKE_IN_PROGRESS;
 
 			if (proclist_is_empty(&lock->waiters))
 				desired_state &= ~LW_FLAG_HAS_WAITERS;
@@ -1131,10 +1129,10 @@ LWLockDequeueSelf(LWLock *lock)
 		 */
 
 		/*
-		 * Reset RELEASE_OK flag if somebody woke us before we removed
-		 * ourselves - they'll have set it to false.
+		 * Clear LW_FLAG_WAKE_IN_PROGRESS if somebody woke us before we
+		 * removed ourselves - they'll have set it.
 		 */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 		/*
 		 * Now wait for the scheduled wakeup, otherwise our ->lwWaiting would
@@ -1301,7 +1299,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode)
 		}
 
 		/* Retrying, allow LWLockRelease to release waiters again. */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 #ifdef LOCK_DEBUG
 		{
@@ -1636,10 +1634,10 @@ LWLockWaitForVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 oldval,
 		LWLockQueueSelf(lock, LW_WAIT_UNTIL_FREE);
 
 		/*
-		 * Set RELEASE_OK flag, to make sure we get woken up as soon as the
-		 * lock is released.
+		 * Clear LW_FLAG_WAKE_IN_PROGRESS flag, to make sure we get woken up
+		 * as soon as the lock is released.
 		 */
-		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);
+		pg_atomic_fetch_and_u32(&lock->state, ~LW_FLAG_WAKE_IN_PROGRESS);
 
 		/*
 		 * We're now guaranteed to be woken up if necessary. Recheck the lock
@@ -1852,11 +1850,11 @@ LWLockReleaseInternal(LWLock *lock, LWLockMode mode)
 		TRACE_POSTGRESQL_LWLOCK_RELEASE(T_NAME(lock));
 
 	/*
-	 * We're still waiting for backends to get scheduled, don't wake them up
-	 * again.
+	 * Check if we're still waiting for backends to get scheduled, if so,
+	 * don't wake them up again.
 	 */
-	if ((oldstate & (LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK)) ==
-		(LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK) &&
+	if ((oldstate & LW_FLAG_HAS_WAITERS) &&
+		!(oldstate & LW_FLAG_WAKE_IN_PROGRESS) &&
 		(oldstate & LW_LOCK_MASK) == 0)
 		check_waiters = true;
 	else
-- 
2.48.1.76.g4e746b1a31.dirty


--rkiyqpij3ajqn7ww
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v9-0005-bufmgr-Make-definitions-related-to-buffer-descrip.patch"



^ permalink  raw  reply  [nested|flat] 8+ messages in thread


end of thread, other threads:[~2026-01-06 01:40 UTC | newest]

Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-05-20 17:57 [PATCH v5 06/12] consistent language: not implemented Justin Pryzby <[email protected]>
2019-05-20 17:57 [PATCH v3 06/12] consistent language: not implemented Justin Pryzby <[email protected]>
2023-07-12 12:19 [PATCH] Document RelationGetIndexAttrBitmap better Alvaro Herrera <[email protected]>
2023-07-12 12:19 [PATCH] Document RelationGetIndexAttrBitmap better Alvaro Herrera <[email protected]>
2023-07-12 12:19 [PATCH] Document RelationGetIndexAttrBitmap better Alvaro Herrera <[email protected]>
2025-10-13 20:14 [PATCH v16 2/3] GIN pageinspect support for entry tree and posting tree internal pages reshke <[email protected]>
2026-01-06 01:40 [PATCH v10 1/8] lwlock: Invert meaning of LW_FLAG_RELEASE_OK Andres Freund <[email protected]>
2026-01-06 01:40 [PATCH v9 04/10] lwlock: Invert meaning of LW_FLAG_RELEASE_OK Andres Freund <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox