From c64d25854f09301d0546748983216af6d7fb2c93 Mon Sep 17 00:00:00 2001
From: reshke kirill <reshke@double.cloud>
Date: Mon, 25 Nov 2024 18:03:44 +0000
Subject: [PATCH v3] Use stream read interface for pgstattuple routines.

Patch implements new streaming read API for pgstattuple contrib
extension.

This is not perfomance speedup patch.
This patch enables this extension to benefit from stream API in the future
without introducing any performance improvements.

Author: Andrey Borodin <amborodin@acm.org>
Author: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-By: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-By: Nazir Bilal Yavuz <byavuz81@gmail.com>
---
 contrib/pgstattuple/expected/pgstattuple.out | 21 +++++-
 contrib/pgstattuple/pgstattuple.c            | 68 ++++++++++++--------
 contrib/pgstattuple/sql/pgstattuple.sql      |  8 ++-
 3 files changed, 69 insertions(+), 28 deletions(-)

diff --git a/contrib/pgstattuple/expected/pgstattuple.out b/contrib/pgstattuple/expected/pgstattuple.out
index 9176dc98b6a..79c792bd402 100644
--- a/contrib/pgstattuple/expected/pgstattuple.out
+++ b/contrib/pgstattuple/expected/pgstattuple.out
@@ -4,7 +4,7 @@ CREATE EXTENSION pgstattuple;
 -- the pgstattuple functions, but the results for empty tables and
 -- indexes should be that.
 --
-create table test (a int primary key, b int[]);
+create table test (a int primary key, b int[], c point);
 select * from pgstattuple('test');
  table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent 
 -----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
@@ -137,6 +137,7 @@ select * from pgstathashindex('test_hashidx');
        4 |            4 |              0 |            1 |            0 |          0 |          0 |          100
 (1 row)
 
+create index test_gistidx ON test USING gist(c);
 -- these should error with the wrong type
 select pgstatginindex('test_pkey');
 ERROR:  relation "test_pkey" is not a GIN index
@@ -150,6 +151,24 @@ select pgstatindex('test_hashidx');
 ERROR:  relation "test_hashidx" is not a btree index
 select pgstatginindex('test_hashidx');
 ERROR:  relation "test_hashidx" is not a GIN index
+select pgstattuple('test_pkey');
+      pgstattuple       
+------------------------
+ (8192,0,0,0,0,0,0,0,0)
+(1 row)
+
+select pgstattuple('test_gistidx');
+      pgstattuple       
+------------------------
+ (8192,0,0,0,0,0,0,0,0)
+(1 row)
+
+select pgstattuple('test_hashidx');
+           pgstattuple           
+---------------------------------
+ (49152,0,0,0,0,0,0,32608,66.34)
+(1 row)
+
 -- check that using any of these functions with unsupported relations will fail
 create table test_partitioned (a int) partition by range (a);
 create index test_partitioned_index on test_partitioned(a);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 48cb8f59c4f..fecc1c8f031 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -61,22 +61,18 @@ typedef struct pgstattuple_type
 	uint64		free_space;		/* free/reusable space in bytes */
 } pgstattuple_type;
 
-typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber,
-							 BufferAccessStrategy);
+typedef void (*pgstat_page) (pgstattuple_type *, Relation, Buffer);
 
 static Datum build_pgstattuple_type(pgstattuple_type *stat,
 									FunctionCallInfo fcinfo);
 static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
 static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
 static void pgstat_btree_page(pgstattuple_type *stat,
-							  Relation rel, BlockNumber blkno,
-							  BufferAccessStrategy bstrategy);
+							  Relation rel, Buffer buf);
 static void pgstat_hash_page(pgstattuple_type *stat,
-							 Relation rel, BlockNumber blkno,
-							 BufferAccessStrategy bstrategy);
+							 Relation rel, Buffer buf);
 static void pgstat_gist_page(pgstattuple_type *stat,
-							 Relation rel, BlockNumber blkno,
-							 BufferAccessStrategy bstrategy);
+							 Relation rel, Buffer buf);
 static Datum pgstat_index(Relation rel, BlockNumber start,
 						  pgstat_page pagefn, FunctionCallInfo fcinfo);
 static void pgstat_index_page(pgstattuple_type *stat, Page page,
@@ -405,13 +401,10 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
  * pgstat_btree_page -- check tuples in a btree page
  */
 static void
-pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				  BufferAccessStrategy bstrategy)
+pgstat_btree_page(pgstattuple_type *stat, Relation rel, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
 	LockBuffer(buf, BT_READ);
 	page = BufferGetPage(buf);
 
@@ -449,13 +442,15 @@ pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
  * pgstat_hash_page -- check tuples in a hash page
  */
 static void
-pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				 BufferAccessStrategy bstrategy)
+pgstat_hash_page(pgstattuple_type *stat, Relation rel, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 
-	buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy);
+	LockBuffer(buf, HASH_READ);
+
+	/* ref count and lock type are correct */
+
+	_hash_checkpage(rel, buf, 0);
 	page = BufferGetPage(buf);
 
 	if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
@@ -491,13 +486,10 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
  * pgstat_gist_page -- check tuples in a gist page
  */
 static void
-pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				 BufferAccessStrategy bstrategy)
+pgstat_gist_page(pgstattuple_type *stat, Relation rel, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
 	LockBuffer(buf, GIST_SHARE);
 	gistcheckpage(rel, buf);
 	page = BufferGetPage(buf);
@@ -523,14 +515,25 @@ pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
 			 FunctionCallInfo fcinfo)
 {
 	BlockNumber nblocks;
-	BlockNumber blkno;
 	BufferAccessStrategy bstrategy;
 	pgstattuple_type stat = {0};
+	Buffer		buf;
+	BlockRangeReadStreamPrivate p;
+	ReadStream *stream = NULL;
+	int64		block;
 
 	/* prepare access strategy for this index */
 	bstrategy = GetAccessStrategy(BAS_BULKREAD);
 
-	blkno = start;
+	p.current_blocknum = start;
+	stream = read_stream_begin_relation(READ_STREAM_FULL,
+										bstrategy,
+										rel,
+										MAIN_FORKNUM,
+										block_range_read_stream_cb,
+										&p,
+										0);
+
 	for (;;)
 	{
 		/* Get the current relation length */
@@ -539,21 +542,34 @@ pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
 		UnlockRelationForExtension(rel, ExclusiveLock);
 
 		/* Quit if we've scanned the whole relation */
-		if (blkno >= nblocks)
+		if (p.current_blocknum >= nblocks)
 		{
 			stat.table_len = (uint64) nblocks * BLCKSZ;
 
 			break;
 		}
 
-		for (; blkno < nblocks; blkno++)
+		p.last_exclusive = nblocks;
+
+		for (block = start; block < nblocks; ++block)
 		{
 			CHECK_FOR_INTERRUPTS();
-
-			pagefn(&stat, rel, blkno, bstrategy);
+			buf = read_stream_next_buffer(stream, NULL);
+			Assert(buf != InvalidBuffer);
+			pagefn(&stat, rel, buf);
 		}
+
+		Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
+
+		/*
+		 * After reaching the end we have to reset stream to use it again.
+		 * Extra restart in case of just one iteration does not cost us much.
+		 */
+		read_stream_reset(stream);
 	}
 
+	read_stream_end(stream);
+
 	relation_close(rel, AccessShareLock);
 
 	return build_pgstattuple_type(&stat, fcinfo);
diff --git a/contrib/pgstattuple/sql/pgstattuple.sql b/contrib/pgstattuple/sql/pgstattuple.sql
index 7e72c567a06..2fa16ebf05f 100644
--- a/contrib/pgstattuple/sql/pgstattuple.sql
+++ b/contrib/pgstattuple/sql/pgstattuple.sql
@@ -6,7 +6,7 @@ CREATE EXTENSION pgstattuple;
 -- indexes should be that.
 --
 
-create table test (a int primary key, b int[]);
+create table test (a int primary key, b int[], c point);
 
 select * from pgstattuple('test');
 select * from pgstattuple('test'::text);
@@ -52,6 +52,8 @@ create index test_hashidx on test using hash (b);
 
 select * from pgstathashindex('test_hashidx');
 
+create index test_gistidx ON test USING gist(c);
+
 -- these should error with the wrong type
 select pgstatginindex('test_pkey');
 select pgstathashindex('test_pkey');
@@ -62,6 +64,10 @@ select pgstathashindex('test_ginidx');
 select pgstatindex('test_hashidx');
 select pgstatginindex('test_hashidx');
 
+select pgstattuple('test_pkey');
+select pgstattuple('test_gistidx');
+select pgstattuple('test_hashidx');
+
 -- check that using any of these functions with unsupported relations will fail
 create table test_partitioned (a int) partition by range (a);
 create index test_partitioned_index on test_partitioned(a);
-- 
2.34.1

