GIN pageinspect functions

Started by Heikki Linnakangasover 11 years ago11 messages
#1Heikki Linnakangas
hlinnakangas@vmware.com
1 attachment(s)

Some time ago, when debugging a GIN bug, I wrote these pageinspect
functions to inspect GIN indexes. They were very useful; we should add them.

- Heikki

Attachments:

0001-Add-pageinspect-functions-for-inspecting-GIN-indexes.patchtext/x-diff; name=0001-Add-pageinspect-functions-for-inspecting-GIN-indexes.patchDownload
>From 91ef58aab11e9077ab6a38268a1120806e42f2dd Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 12 Sep 2014 13:36:30 +0300
Subject: [PATCH 1/1] Add pageinspect functions for inspecting GIN indexes.

---
 contrib/pageinspect/Makefile                  |   7 +-
 contrib/pageinspect/ginfuncs.c                | 264 ++++++++++++++++++++++++++
 contrib/pageinspect/pageinspect--1.2--1.3.sql |  42 ++++
 contrib/pageinspect/pageinspect--1.3.sql      | 149 +++++++++++++++
 contrib/pageinspect/pageinspect.control       |   2 +-
 5 files changed, 460 insertions(+), 4 deletions(-)
 create mode 100644 contrib/pageinspect/ginfuncs.c
 create mode 100644 contrib/pageinspect/pageinspect--1.2--1.3.sql
 create mode 100644 contrib/pageinspect/pageinspect--1.3.sql

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index f10229d..1a8b060 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -1,11 +1,12 @@
 # contrib/pageinspect/Makefile
 
 MODULE_big	= pageinspect
-OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o $(WIN32RES)
+OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o ginfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
-DATA = pageinspect--1.2.sql pageinspect--1.0--1.1.sql \
-	pageinspect--1.1--1.2.sql pageinspect--unpackaged--1.0.sql
+DATA = pageinspect--1.3.sql pageinspect--1.0--1.1.sql \
+	pageinspect--1.1--1.2.sql pageinspect--1.2--1.3.sql \
+        pageinspect--unpackaged--1.0.sql
 PGFILEDESC = "pageinspect - functions to inspect contents of database pages"
 
 ifdef USE_PGXS
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
new file mode 100644
index 0000000..949d19d
--- /dev/null
+++ b/contrib/pageinspect/ginfuncs.c
@@ -0,0 +1,264 @@
+/*
+ * contrib/pageinspect/ginfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/gin.h"
+#include "access/gin_private.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+
+#define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
+#define ItemPointerGetDatum(X)	 PointerGetDatum(X)
+
+PG_FUNCTION_INFO_V1(gin_metapage);
+PG_FUNCTION_INFO_V1(gin_pageopaq);
+PG_FUNCTION_INFO_V1(gin_dataleafpage);
+
+Datum
+gin_metapage(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	GinMetaPageData *metadata;
+	HeapTuple	resultTuple;
+	Datum		values[10];
+	bool		nulls[10];
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(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)));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	metadata = GinPageGetMeta(page);
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(metadata->head);
+	values[1] = Int64GetDatum(metadata->tail);
+	values[2] = Int32GetDatum(metadata->tailFreeSize);
+	values[3] = Int64GetDatum(metadata->nPendingPages);
+	values[4] = Int64GetDatum(metadata->nPendingHeapTuples);
+
+	/* statistics, updated by VACUUM */
+	values[5] = Int64GetDatum(metadata->nTotalPages);
+	values[6] = Int64GetDatum(metadata->nEntryPages);
+	values[7] = Int64GetDatum(metadata->nDataPages);
+	values[8] = Int64GetDatum(metadata->nEntries);
+
+	values[9] = Int32GetDatum(metadata->ginVersion);
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+
+Datum
+gin_pageopaq(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	HeapTuple	resultTuple;
+	Datum		values[3];
+	bool		nulls[10];
+	Datum		flags[16];
+	int			nflags = 0;
+	uint16		flagbits;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(page);
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Convert the flags bitmask to an array of human-readable names */
+	flagbits = opaq->flags;
+	if (flagbits & GIN_DATA)
+		flags[nflags++] = CStringGetTextDatum("data");
+	if (flagbits & GIN_LEAF)
+		flags[nflags++] = CStringGetTextDatum("leaf");
+	if (flagbits & GIN_DELETED)
+		flags[nflags++] = CStringGetTextDatum("deleted");
+	if (flagbits & GIN_META)
+		flags[nflags++] = CStringGetTextDatum("meta");
+	if (flagbits & GIN_LIST)
+		flags[nflags++] = CStringGetTextDatum("list");
+	if (flagbits & GIN_LIST_FULLROW)
+		flags[nflags++] = CStringGetTextDatum("list_fullrow");
+	if (flagbits & GIN_INCOMPLETE_SPLIT)
+		flags[nflags++] = CStringGetTextDatum("incomplete_split");
+	if (flagbits & GIN_COMPRESSED)
+		flags[nflags++] = CStringGetTextDatum("compressed");
+	flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
+				  GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
+	if (flagbits)
+	{
+		/* any flags we don't recognize are printed in hex */
+		flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits));	}
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(opaq->rightlink);
+	values[1] = Int64GetDatum(opaq->maxoff);
+	values[2] = PointerGetDatum(
+		construct_array(flags, nflags, TEXTOID, -1, false, 'i'));
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+/* -----------------------------------------------
+ * gin_dataleafpage()
+ *
+ * Usage: SELECT * FROM gin_dataleafpage(get_raw_page('indexname', 1));
+ * -----------------------------------------------
+ */
+
+typedef struct gin_dataleafpage_state
+{
+	TupleDesc	tupd;
+	GinPostingList *seg;
+	GinPostingList *lastseg;
+} gin_dataleafpage_state;
+
+Datum
+gin_dataleafpage(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	FuncCallContext *fctx;
+	gin_dataleafpage_state *inter_call_data;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+		MemoryContext mctx;
+		Page		page;
+		GinPageOpaque opaq;
+
+		if (raw_page_size < BLCKSZ)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				  errmsg("input page too small (%d bytes)", raw_page_size)));
+		page = VARDATA(raw_page);
+
+		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("Special size %d, expected %d",
+							   (int) PageGetSpecialSize(page),
+							   (int) MAXALIGN(sizeof(GinPageOpaqueData)))));
+
+		opaq = (GinPageOpaque) PageGetSpecialPointer(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))));
+
+		fctx = SRF_FIRSTCALL_INIT();
+		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+		inter_call_data = palloc(sizeof(gin_dataleafpage_state));
+
+		/* Build a tuple descriptor for our result type */
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		inter_call_data->tupd = tupdesc;
+
+		inter_call_data->seg = GinDataLeafPageGetPostingList(page);
+		inter_call_data->lastseg = (GinPostingList *)
+			(((char *) inter_call_data->seg) +
+			 GinDataLeafPageGetPostingListSize(page));
+
+		fctx->user_fctx = inter_call_data;
+
+		MemoryContextSwitchTo(mctx);
+	}
+
+	fctx = SRF_PERCALL_SETUP();
+	inter_call_data = fctx->user_fctx;
+
+	if (inter_call_data->seg != inter_call_data->lastseg)
+	{
+		HeapTuple	resultTuple;
+		Datum		result;
+		Datum		values[3];
+		bool		nulls[3];
+
+		memset(nulls, 0, sizeof(nulls));
+
+		values[0] = ItemPointerGetDatum(&inter_call_data->seg->first);
+		values[1] = UInt16GetDatum(inter_call_data->seg->nbytes);
+		/* TODO: array of decoded item pointers */
+		nulls[2] = true;
+
+		/* Build and return the result tuple. */
+		resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
+		result = HeapTupleGetDatum(resultTuple);
+
+		inter_call_data->seg = GinNextPostingListSegment(inter_call_data->seg);
+
+		SRF_RETURN_NEXT(fctx, result);
+	}
+	else
+		SRF_RETURN_DONE(fctx);
+}
diff --git a/contrib/pageinspect/pageinspect--1.2--1.3.sql b/contrib/pageinspect/pageinspect--1.2--1.3.sql
new file mode 100644
index 0000000..7ab8a8b
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.2--1.3.sql
@@ -0,0 +1,42 @@
+/* contrib/pageinspect/pageinspect--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.3'" to load this file. \quit
+
+--
+-- gin_metapage()
+--
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
+
+--
+-- gin_pageopaq()
+--
+CREATE FUNCTION gin_pageopaq(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_pageopaq'
+LANGUAGE C STRICT;
+
+--
+-- gin_dataleafpage()
+--
+CREATE FUNCTION gin_dataleafpage(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_dataleafpage'
+LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect--1.3.sql b/contrib/pageinspect/pageinspect--1.3.sql
new file mode 100644
index 0000000..98cbcb7
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.3.sql
@@ -0,0 +1,149 @@
+/* contrib/pageinspect/pageinspect--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+    OUT lsn pg_lsn,
+    OUT checksum smallint,
+    OUT flags smallint,
+    OUT lower smallint,
+    OUT upper smallint,
+    OUT special smallint,
+    OUT pagesize smallint,
+    OUT version smallint,
+    OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+    OUT magic int4,
+    OUT version int4,
+    OUT root int4,
+    OUT level int4,
+    OUT fastroot int4,
+    OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+    OUT blkno int4,
+    OUT type "char",
+    OUT live_items int4,
+    OUT dead_items int4,
+    OUT avg_item_size int4,
+    OUT page_size int4,
+    OUT free_size int4,
+    OUT btpo_prev int4,
+    OUT btpo_next int4,
+    OUT btpo int4,
+    OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+    OUT itemoffset smallint,
+    OUT ctid tid,
+    OUT itemlen smallint,
+    OUT nulls bool,
+    OUT vars bool,
+    OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage()
+--
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
+
+--
+-- gin_pageopaq()
+--
+CREATE FUNCTION gin_pageopaq(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_pageopaq'
+LANGUAGE C STRICT;
+
+--
+-- gin_dataleafpage()
+--
+CREATE FUNCTION gin_dataleafpage(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_dataleafpage'
+LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index aecd91a..a9dab33 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.2'
+default_version = '1.3'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
-- 
2.1.1

#2Oleg Bartunov
obartunov@gmail.com
In reply to: Heikki Linnakangas (#1)
Re: GIN pageinspect functions

On Tue, Oct 7, 2014 at 9:03 PM, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:

Some time ago, when debugging a GIN bug, I wrote these pageinspect
functions to inspect GIN indexes. They were very useful; we should add them.

May be we can merge it with contrib/gevel, which we use many years for
development and debug purposes ? Have you seen it ?

Show quoted text

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Oleg Bartunov (#2)
Re: GIN pageinspect functions

On 10/07/2014 08:36 PM, Oleg Bartunov wrote:

On Tue, Oct 7, 2014 at 9:03 PM, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:

Some time ago, when debugging a GIN bug, I wrote these pageinspect
functions to inspect GIN indexes. They were very useful; we should add them.

May be we can merge it with contrib/gevel, which we use many years for
development and debug purposes ? Have you seen it ?

I remember downloading it many years ago, but that's all I remember.
Where's the latest version?

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Oleg Bartunov
obartunov@gmail.com
In reply to: Heikki Linnakangas (#3)
Re: GIN pageinspect functions

On Tue, Oct 7, 2014 at 9:56 PM, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:

On 10/07/2014 08:36 PM, Oleg Bartunov wrote:

On Tue, Oct 7, 2014 at 9:03 PM, Heikki Linnakangas <
hlinnakangas@vmware.com>
wrote:

Some time ago, when debugging a GIN bug, I wrote these pageinspect

functions to inspect GIN indexes. They were very useful; we should add
them.

May be we can merge it with contrib/gevel, which we use many years for
development and debug purposes ? Have you seen it ?

I remember downloading it many years ago, but that's all I remember.
Where's the latest version?

I believe it's there
http://www.sigaev.ru/git/gitweb.cgi?p=gevel.git;a=summary

Show quoted text

- Heikki

#5Amit Kapila
amit.kapila16@gmail.com
In reply to: Heikki Linnakangas (#1)
Re: GIN pageinspect functions

On Tue, Oct 7, 2014 at 10:33 PM, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:

Some time ago, when debugging a GIN bug, I wrote these pageinspect

functions to inspect GIN indexes. They were very useful; we should add them.

I think these functions will be quite useful for debugging purpose
and we already have similar function's for other index (btree).

Few suggestions for patch:

1. Documentation seems to be missing, other API's exposed
via pageinspect are documented at:
http://www.postgresql.org/docs/devel/static/pageinspect.html

2.
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+
OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT
n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+
 OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;

a. Isn't it better to name the function as gin_metap(..) similar to
existing function bt_metap(..)?
b. Can this function have a similar signature as bt_metap() which means
it should take input as relname?

3. Can gin_dataleafpage() API have similar name and signature as
API bt_page_items() exposed for btree?

4. Can we have any better name for gin_pageopaq (other API name's
in this module are self explanatory)?

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#6Peter Geoghegan
pg@heroku.com
In reply to: Amit Kapila (#5)
1 attachment(s)
Re: GIN pageinspect functions

On Tue, Nov 4, 2014 at 7:26 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

I think these functions will be quite useful for debugging purpose
and we already have similar function's for other index (btree).

This patch has bitrotted. I attach rebased revision, for the
convenience of others - V1.3 of pageinspect will now incorporate both
GIN stuff, and BRIN stuff. Seems like this patch was affected by the
recent problems with header includes - that's fixed.

Do you intend to fix this up?

+ /* TODO: array of decoded item pointers */
+ nulls[2] = true;

--
Peter Geoghegan

Attachments:

0001-Add-pageinspect-functions-for-inspecting-GIN-indexes.patchtext/x-patch; charset=US-ASCII; name=0001-Add-pageinspect-functions-for-inspecting-GIN-indexes.patchDownload
From 5d10bfe4f6db5e37dcf087d62f42cbc6c9423c26 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@heroku.com>
Date: Tue, 18 Nov 2014 13:38:12 -0800
Subject: [PATCH] Add pageinspect functions for inspecting GIN indexes.

---
 contrib/pageinspect/Makefile                  |   2 +-
 contrib/pageinspect/ginfuncs.c                | 265 ++++++++++++++++++++++++++
 contrib/pageinspect/pageinspect--1.2--1.3.sql |  37 ++++
 contrib/pageinspect/pageinspect--1.3.sql      |  42 ++++
 4 files changed, 345 insertions(+), 1 deletion(-)
 create mode 100644 contrib/pageinspect/ginfuncs.c

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a59de8a..e651543 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -1,7 +1,7 @@
 # contrib/pageinspect/Makefile
 
 MODULE_big	= pageinspect
-OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o brinfuncs.o $(WIN32RES)
+OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o brinfuncs.o ginfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
 DATA = pageinspect--1.3.sql pageinspect--1.0--1.1.sql \
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
new file mode 100644
index 0000000..0602873
--- /dev/null
+++ b/contrib/pageinspect/ginfuncs.c
@@ -0,0 +1,265 @@
+/*
+ * contrib/pageinspect/ginfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/gin.h"
+#include "access/gin_private.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+
+#define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
+#define ItemPointerGetDatum(X)	 PointerGetDatum(X)
+
+PG_FUNCTION_INFO_V1(gin_metapage);
+PG_FUNCTION_INFO_V1(gin_pageopaq);
+PG_FUNCTION_INFO_V1(gin_dataleafpage);
+
+Datum
+gin_metapage(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	GinMetaPageData *metadata;
+	HeapTuple	resultTuple;
+	Datum		values[10];
+	bool		nulls[10];
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(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)));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	metadata = GinPageGetMeta(page);
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(metadata->head);
+	values[1] = Int64GetDatum(metadata->tail);
+	values[2] = Int32GetDatum(metadata->tailFreeSize);
+	values[3] = Int64GetDatum(metadata->nPendingPages);
+	values[4] = Int64GetDatum(metadata->nPendingHeapTuples);
+
+	/* statistics, updated by VACUUM */
+	values[5] = Int64GetDatum(metadata->nTotalPages);
+	values[6] = Int64GetDatum(metadata->nEntryPages);
+	values[7] = Int64GetDatum(metadata->nDataPages);
+	values[8] = Int64GetDatum(metadata->nEntries);
+
+	values[9] = Int32GetDatum(metadata->ginVersion);
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+
+Datum
+gin_pageopaq(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	HeapTuple	resultTuple;
+	Datum		values[3];
+	bool		nulls[10];
+	Datum		flags[16];
+	int			nflags = 0;
+	uint16		flagbits;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(page);
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Convert the flags bitmask to an array of human-readable names */
+	flagbits = opaq->flags;
+	if (flagbits & GIN_DATA)
+		flags[nflags++] = CStringGetTextDatum("data");
+	if (flagbits & GIN_LEAF)
+		flags[nflags++] = CStringGetTextDatum("leaf");
+	if (flagbits & GIN_DELETED)
+		flags[nflags++] = CStringGetTextDatum("deleted");
+	if (flagbits & GIN_META)
+		flags[nflags++] = CStringGetTextDatum("meta");
+	if (flagbits & GIN_LIST)
+		flags[nflags++] = CStringGetTextDatum("list");
+	if (flagbits & GIN_LIST_FULLROW)
+		flags[nflags++] = CStringGetTextDatum("list_fullrow");
+	if (flagbits & GIN_INCOMPLETE_SPLIT)
+		flags[nflags++] = CStringGetTextDatum("incomplete_split");
+	if (flagbits & GIN_COMPRESSED)
+		flags[nflags++] = CStringGetTextDatum("compressed");
+	flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
+				  GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
+	if (flagbits)
+	{
+		/* any flags we don't recognize are printed in hex */
+		flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits));	}
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(opaq->rightlink);
+	values[1] = Int64GetDatum(opaq->maxoff);
+	values[2] = PointerGetDatum(
+		construct_array(flags, nflags, TEXTOID, -1, false, 'i'));
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+/* -----------------------------------------------
+ * gin_dataleafpage()
+ *
+ * Usage: SELECT * FROM gin_dataleafpage(get_raw_page('indexname', 1));
+ * -----------------------------------------------
+ */
+
+typedef struct gin_dataleafpage_state
+{
+	TupleDesc	tupd;
+	GinPostingList *seg;
+	GinPostingList *lastseg;
+} gin_dataleafpage_state;
+
+Datum
+gin_dataleafpage(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	FuncCallContext *fctx;
+	gin_dataleafpage_state *inter_call_data;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+		MemoryContext mctx;
+		Page		page;
+		GinPageOpaque opaq;
+
+		if (raw_page_size < BLCKSZ)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				  errmsg("input page too small (%d bytes)", raw_page_size)));
+		page = VARDATA(raw_page);
+
+		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("Special size %d, expected %d",
+							   (int) PageGetSpecialSize(page),
+							   (int) MAXALIGN(sizeof(GinPageOpaqueData)))));
+
+		opaq = (GinPageOpaque) PageGetSpecialPointer(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))));
+
+		fctx = SRF_FIRSTCALL_INIT();
+		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+		inter_call_data = palloc(sizeof(gin_dataleafpage_state));
+
+		/* Build a tuple descriptor for our result type */
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		inter_call_data->tupd = tupdesc;
+
+		inter_call_data->seg = GinDataLeafPageGetPostingList(page);
+		inter_call_data->lastseg = (GinPostingList *)
+			(((char *) inter_call_data->seg) +
+			 GinDataLeafPageGetPostingListSize(page));
+
+		fctx->user_fctx = inter_call_data;
+
+		MemoryContextSwitchTo(mctx);
+	}
+
+	fctx = SRF_PERCALL_SETUP();
+	inter_call_data = fctx->user_fctx;
+
+	if (inter_call_data->seg != inter_call_data->lastseg)
+	{
+		HeapTuple	resultTuple;
+		Datum		result;
+		Datum		values[3];
+		bool		nulls[3];
+
+		memset(nulls, 0, sizeof(nulls));
+
+		values[0] = ItemPointerGetDatum(&inter_call_data->seg->first);
+		values[1] = UInt16GetDatum(inter_call_data->seg->nbytes);
+		/* TODO: array of decoded item pointers */
+		nulls[2] = true;
+
+		/* Build and return the result tuple. */
+		resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
+		result = HeapTupleGetDatum(resultTuple);
+
+		inter_call_data->seg = GinNextPostingListSegment(inter_call_data->seg);
+
+		SRF_RETURN_NEXT(fctx, result);
+	}
+	else
+		SRF_RETURN_DONE(fctx);
+}
diff --git a/contrib/pageinspect/pageinspect--1.2--1.3.sql b/contrib/pageinspect/pageinspect--1.2--1.3.sql
index 9bc4dde..15eec67 100644
--- a/contrib/pageinspect/pageinspect--1.2--1.3.sql
+++ b/contrib/pageinspect/pageinspect--1.2--1.3.sql
@@ -40,4 +40,41 @@ CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
 	OUT value text)
 RETURNS SETOF record
 AS 'MODULE_PATHNAME', 'brin_page_items'
+
+--
+-- gin_metapage()
+--
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
+
+--
+-- gin_pageopaq()
+--
+CREATE FUNCTION gin_pageopaq(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_pageopaq'
+LANGUAGE C STRICT;
+
+--
+-- gin_dataleafpage()
+--
+CREATE FUNCTION gin_dataleafpage(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_dataleafpage'
 LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect--1.3.sql b/contrib/pageinspect/pageinspect--1.3.sql
index 856dcdf..8b7b096 100644
--- a/contrib/pageinspect/pageinspect--1.3.sql
+++ b/contrib/pageinspect/pageinspect--1.3.sql
@@ -144,3 +144,45 @@ CREATE FUNCTION fsm_page_contents(IN page bytea)
 RETURNS text
 AS 'MODULE_PATHNAME', 'fsm_page_contents'
 LANGUAGE C STRICT;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage()
+--
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
+
+--
+-- gin_pageopaq()
+--
+CREATE FUNCTION gin_pageopaq(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_pageopaq'
+LANGUAGE C STRICT;
+
+--
+-- gin_dataleafpage()
+--
+CREATE FUNCTION gin_dataleafpage(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_dataleafpage'
+LANGUAGE C STRICT;
-- 
1.9.1

#7Peter Geoghegan
pg@heroku.com
In reply to: Peter Geoghegan (#6)
Re: GIN pageinspect functions

On Tue, Nov 18, 2014 at 2:01 PM, Peter Geoghegan <pg@heroku.com> wrote:

Do you intend to fix this up?

BTW, how do you feel about the B-Tree check extension [1]/messages/by-id/CAM3SWZRtV+xmRWLWq6c-x7czvwavFdwFi4St1zz4dDgFH4yN4g@mail.gmail.com -- Peter Geoghegan? It's very
much related to pageinspect -- it's more or less a derivative. I don't
think I'm going to have time (or that there is sufficient review
bandwidth available) to get it into 9.5, but I should post a revision
soon, so it's at least something that's available for use by an
expert. I did some clean-up work on it that is unpublished. It'll
become a more generic extension - "amcheck", per Robert's suggestion.

One unpublished additional feature (that I have to fix a bug in) that
isn't included in [1]/messages/by-id/CAM3SWZRtV+xmRWLWq6c-x7czvwavFdwFi4St1zz4dDgFH4yN4g@mail.gmail.com -- Peter Geoghegan is the idea of checking invariants across B-Tree
pages. So, a scankey should indicate that the greatest (non-highkey)
item on a non-rightmost page comports with the page that it has a
right link to. Without race conditions.

I don't have that swapped into my head at the moment, and so I don't
have a good sense of how hard it'll be to fix the problem I found...

[1]: /messages/by-id/CAM3SWZRtV+xmRWLWq6c-x7czvwavFdwFi4St1zz4dDgFH4yN4g@mail.gmail.com -- Peter Geoghegan
--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Michael Paquier
michael.paquier@gmail.com
In reply to: Peter Geoghegan (#6)
1 attachment(s)
Re: GIN pageinspect functions

On Wed, Nov 19, 2014 at 7:01 AM, Peter Geoghegan <pg@heroku.com> wrote:

On Tue, Nov 4, 2014 at 7:26 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

I think these functions will be quite useful for debugging purpose
and we already have similar function's for other index (btree).

This patch has bitrotted. I attach rebased revision, for the
convenience of others - V1.3 of pageinspect will now incorporate both
GIN stuff, and BRIN stuff. Seems like this patch was affected by the
recent problems with header includes - that's fixed.

Thanks for the updated version! That's a cool timing, I wanted to move
this patch forward these days... I think as well that it would be a
useful addition for pageinspect (and I was in need of them yesterday,
and soon today btw).

Amit wrote:
1. Documentation seems to be missing, other API's exposed
via pageinspect are documented at:
http://www.postgresql.org/docs/devel/static/pageinspect.html

Done.

2.
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
a. Isn't it better to name the function as gin_metap(..) similar to
existing function bt_metap(..)?

I actually liked more gin_metapage_info, a name similar to the
newly-introduced brin indexes.

b. Can this function have a similar signature as bt_metap() which means
it should take input as relname?

That's mostly a matter of taste but I think we should definitely pass
a raw page to it as it is now. This has the advantage to add an extra
check if the page passed is really a meta page of not, something
useful for development.

3. Can gin_dataleafpage() API have similar name and signature as
API bt_page_items() exposed for btree?

What about gin_leafpage_items then?

4. Can we have any better name for gin_pageopaq (other API name's
in this module are self explanatory)?

gin_page_opaque_info? Because we get back information about the opaque
portion of the page. Feel free if you have any better idea.

Updated patch, with some more things improved and cleaned up (addition
of header of ginfuncs.c, addition of array of decoded item pointers
for compressed data leaf pages), is attached.

One last thing not only interesting for this patch: it may be good to
expose DatumGetItemPointer and ItemPointerGetDatum in for extensions
analyzing content of pages. I am not sure where though, a place like
utils/*.h may be useful. Thoughts?
Regards,
--
Michael

Attachments:

20141120_pageinspect_gin_v3.patchapplication/x-patch; name=20141120_pageinspect_gin_v3.patchDownload
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a59de8a..aec5258 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -1,12 +1,13 @@
 # contrib/pageinspect/Makefile
 
 MODULE_big	= pageinspect
-OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o brinfuncs.o $(WIN32RES)
+OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o \
+		  brinfuncs.o ginfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
-DATA = pageinspect--1.3.sql pageinspect--1.0--1.1.sql \
-	pageinspect--1.2--1.3.sql \
-	pageinspect--1.1--1.2.sql pageinspect--unpackaged--1.0.sql
+DATA = pageinspect--1.3.sql pageinspect--1.2--1.3.sql \
+	pageinspect--1.1--1.2.sql pageinspect--1.0--1.1.sql \
+	pageinspect--unpackaged--1.0.sql
 PGFILEDESC = "pageinspect - functions to inspect contents of database pages"
 
 ifdef USE_PGXS
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
new file mode 100644
index 0000000..8927951
--- /dev/null
+++ b/contrib/pageinspect/ginfuncs.c
@@ -0,0 +1,280 @@
+/*
+ * ginfuncs.c
+ *		Functions to investigate the content of GIN indexes
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		contrib/pageinspect/ginfuncs.c
+ */
+#include "postgres.h"
+
+#include "access/gin.h"
+#include "access/gin_private.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+
+#define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
+#define ItemPointerGetDatum(X)	 PointerGetDatum(X)
+
+
+PG_FUNCTION_INFO_V1(gin_metapage_info);
+PG_FUNCTION_INFO_V1(gin_page_opaque_info);
+PG_FUNCTION_INFO_V1(gin_leafpage_items);
+
+typedef struct gin_leafpage_items_state
+{
+	TupleDesc	tupd;
+	GinPostingList *seg;
+	GinPostingList *lastseg;
+} gin_leafpage_items_state;
+
+
+Datum
+gin_metapage_info(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	GinMetaPageData *metadata;
+	HeapTuple	resultTuple;
+	Datum		values[10];
+	bool		nulls[10];
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(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)));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	metadata = GinPageGetMeta(page);
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(metadata->head);
+	values[1] = Int64GetDatum(metadata->tail);
+	values[2] = Int32GetDatum(metadata->tailFreeSize);
+	values[3] = Int64GetDatum(metadata->nPendingPages);
+	values[4] = Int64GetDatum(metadata->nPendingHeapTuples);
+
+	/* statistics, updated by VACUUM */
+	values[5] = Int64GetDatum(metadata->nTotalPages);
+	values[6] = Int64GetDatum(metadata->nEntryPages);
+	values[7] = Int64GetDatum(metadata->nDataPages);
+	values[8] = Int64GetDatum(metadata->nEntries);
+
+	values[9] = Int32GetDatum(metadata->ginVersion);
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+
+Datum
+gin_page_opaque_info(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	TupleDesc	tupdesc;
+	Page		page;
+	GinPageOpaque opaq;
+	HeapTuple	resultTuple;
+	Datum		values[3];
+	bool		nulls[10];
+	Datum		flags[16];
+	int			nflags = 0;
+	uint16		flagbits;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+	if (raw_page_size < BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("input page too small (%d bytes)", raw_page_size)));
+	page = VARDATA(raw_page);
+
+	opaq = (GinPageOpaque) PageGetSpecialPointer(page);
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Convert the flags bitmask to an array of human-readable names */
+	flagbits = opaq->flags;
+	if (flagbits & GIN_DATA)
+		flags[nflags++] = CStringGetTextDatum("data");
+	if (flagbits & GIN_LEAF)
+		flags[nflags++] = CStringGetTextDatum("leaf");
+	if (flagbits & GIN_DELETED)
+		flags[nflags++] = CStringGetTextDatum("deleted");
+	if (flagbits & GIN_META)
+		flags[nflags++] = CStringGetTextDatum("meta");
+	if (flagbits & GIN_LIST)
+		flags[nflags++] = CStringGetTextDatum("list");
+	if (flagbits & GIN_LIST_FULLROW)
+		flags[nflags++] = CStringGetTextDatum("list_fullrow");
+	if (flagbits & GIN_INCOMPLETE_SPLIT)
+		flags[nflags++] = CStringGetTextDatum("incomplete_split");
+	if (flagbits & GIN_COMPRESSED)
+		flags[nflags++] = CStringGetTextDatum("compressed");
+	flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
+				  GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
+	if (flagbits)
+	{
+		/* any flags we don't recognize are printed in hex */
+		flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits));
+	}
+
+	memset(nulls, 0, sizeof(nulls));
+
+	values[0] = Int64GetDatum(opaq->rightlink);
+	values[1] = Int64GetDatum(opaq->maxoff);
+	values[2] = PointerGetDatum(
+		construct_array(flags, nflags, TEXTOID, -1, false, 'i'));
+
+	/* Build and return the result tuple. */
+	resultTuple = heap_form_tuple(tupdesc, values, nulls);
+
+	return HeapTupleGetDatum(resultTuple);
+}
+
+
+Datum
+gin_leafpage_items(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	int			raw_page_size;
+	FuncCallContext *fctx;
+	gin_leafpage_items_state *inter_call_data;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+		MemoryContext mctx;
+		Page		page;
+		GinPageOpaque opaq;
+
+		if (raw_page_size < BLCKSZ)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				  errmsg("input page too small (%d bytes)", raw_page_size)));
+		page = VARDATA(raw_page);
+
+		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("Special size %d, expected %d",
+							   (int) PageGetSpecialSize(page),
+							   (int) MAXALIGN(sizeof(GinPageOpaqueData)))));
+
+		opaq = (GinPageOpaque) PageGetSpecialPointer(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))));
+
+		fctx = SRF_FIRSTCALL_INIT();
+		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+		inter_call_data = palloc(sizeof(gin_leafpage_items_state));
+
+		/* Build a tuple descriptor for our result type */
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		inter_call_data->tupd = tupdesc;
+
+		inter_call_data->seg = GinDataLeafPageGetPostingList(page);
+		inter_call_data->lastseg = (GinPostingList *)
+			(((char *) inter_call_data->seg) +
+			 GinDataLeafPageGetPostingListSize(page));
+
+		fctx->user_fctx = inter_call_data;
+
+		MemoryContextSwitchTo(mctx);
+	}
+
+	fctx = SRF_PERCALL_SETUP();
+	inter_call_data = fctx->user_fctx;
+
+	if (inter_call_data->seg != inter_call_data->lastseg)
+	{
+		HeapTuple	resultTuple;
+		Datum		result;
+		Datum		values[3];
+		bool		nulls[3];
+		int			ndecoded, i;
+		GinPostingList	*cur = inter_call_data->seg;
+		ItemPointer		tids;
+		Datum			*tids_datum;
+
+		memset(nulls, 0, sizeof(nulls));
+
+		values[0] = ItemPointerGetDatum(&cur->first);
+		values[1] = UInt16GetDatum(cur->nbytes);
+
+		/* build an array of decoded item pointers */
+		tids = ginPostingListDecode(cur, &ndecoded);
+		tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum));
+		for (i = 0; i < ndecoded; i++)
+			tids_datum[i] = ItemPointerGetDatum(&tids[i]);
+		values[2] = PointerGetDatum(
+			construct_array(tids_datum, ndecoded, TIDOID,
+							sizeof(ItemPointerData), false, 'i'));
+		pfree(tids_datum);
+		pfree(tids);
+
+		/* Build and return the result tuple. */
+		resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
+		result = HeapTupleGetDatum(resultTuple);
+
+		inter_call_data->seg = GinNextPostingListSegment(cur);
+
+		SRF_RETURN_NEXT(fctx, result);
+	}
+	else
+		SRF_RETURN_DONE(fctx);
+}
diff --git a/contrib/pageinspect/pageinspect--1.2--1.3.sql b/contrib/pageinspect/pageinspect--1.2--1.3.sql
index 9bc4dde..dd37469 100644
--- a/contrib/pageinspect/pageinspect--1.2--1.3.sql
+++ b/contrib/pageinspect/pageinspect--1.2--1.3.sql
@@ -40,4 +40,41 @@ CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
 	OUT value text)
 RETURNS SETOF record
 AS 'MODULE_PATHNAME', 'brin_page_items'
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
 LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect--1.3.sql b/contrib/pageinspect/pageinspect--1.3.sql
index 856dcdf..9697486 100644
--- a/contrib/pageinspect/pageinspect--1.3.sql
+++ b/contrib/pageinspect/pageinspect--1.3.sql
@@ -144,3 +144,45 @@ CREATE FUNCTION fsm_page_contents(IN page bytea)
 RETURNS text
 AS 'MODULE_PATHNAME', 'fsm_page_contents'
 LANGUAGE C STRICT;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT;
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 70517ac..d9e2903 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -302,6 +302,90 @@ brintest-# order by blknum, attnum limit 6;
 
    <varlistentry>
     <term>
+     <function>gin_metapage_info(page bytea) returns record</function>
+     <indexterm>
+      <primary>gin_metapage_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>gin_metapage_info</function> returns information about
+      a <acronym>GIN</acronym> index metapage.  For example:
+<screen>
+test=# SELECT * FROM gin_metapage_info(get_raw_page('gin_index', 0));
+-[ RECORD 1 ]----+-----------
+pending_head     | 4294967295
+pending_tail     | 4294967295
+tail_free_size   | 0
+n_pending_pages  | 0
+n_pending_tuples | 0
+n_total_pages    | 7
+n_entry_pages    | 6
+n_data_pages     | 0
+n_entries        | 693
+version          | 2
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>gin_page_opaque_info(page bytea) returns record</function>
+     <indexterm>
+      <primary>gin_page_opaque_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>gin_page_opaque_info</function> returns information about
+      a <acronym>GIN</acronym> index opaque area, like the page type.
+      For example:
+<screen>
+test=# SELECT * FROM gin_page_opaque_info(get_raw_page('gin_index', 2));
+ rightlink | maxoff |         flags
+-----------+--------+------------------------
+         5 |      0 | {data,leaf,compressed}
+(1 row)
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>gin_leafpage_items(page bytea) returns setof record</function>
+     <indexterm>
+      <primary>gin_leafpage_items</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>gin_leafpage_items</function> returns information about
+      the data stored in a <acronym>GIN</acronym> leaf page.  For example:
+<screen>
+ test=# SELECT first_tid, nbytes,
+               array_length(tids,1) AS num_tids,
+               tids[2] AS second_tid
+        FROM gin_leafpage_items(get_raw_page('gin_index', 3)) LIMIT 5;
+ first_tid | nbytes | num_tids | second_tid
+-----------+--------+----------+------------
+ (149,94)  |    248 |      244 | (0,149)
+ (154,11)  |    248 |      245 | (0,154)
+ (158,62)  |    248 |      244 | (0,158)
+ (163,14)  |    248 |      244 | (0,163)
+ (168,18)  |    248 |      245 | (0,168)
+ (5 rows)
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
      <function>fsm_page_contents(page bytea) returns text</function>
      <indexterm>
       <primary>fsm_page_contents</primary>
#9Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Michael Paquier (#8)
Re: GIN pageinspect functions

On 11/20/2014 05:52 AM, Michael Paquier wrote:

On Wed, Nov 19, 2014 at 7:01 AM, Peter Geoghegan <pg@heroku.com> wrote:

On Tue, Nov 4, 2014 at 7:26 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
1. Documentation seems to be missing, other API's exposed
via pageinspect are documented at:
http://www.postgresql.org/docs/devel/static/pageinspect.html

Done.

2.
+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
a. Isn't it better to name the function as gin_metap(..) similar to
existing function bt_metap(..)?

I actually liked more gin_metapage_info, a name similar to the
newly-introduced brin indexes.

b. Can this function have a similar signature as bt_metap() which means
it should take input as relname?

That's mostly a matter of taste but I think we should definitely pass
a raw page to it as it is now. This has the advantage to add an extra
check if the page passed is really a meta page of not, something
useful for development.

3. Can gin_dataleafpage() API have similar name and signature as
API bt_page_items() exposed for btree?

What about gin_leafpage_items then?

The signature of bt_page_items() isn't a good example to follow. It
existed before the get_raw_page() function, and the other functions that
are designed to work with that, was added. gin_leafpage_items() name
seems fine to me.

4. Can we have any better name for gin_pageopaq (other API name's
in this module are self explanatory)?

gin_page_opaque_info? Because we get back information about the opaque
portion of the page. Feel free if you have any better idea.

Updated patch, with some more things improved and cleaned up (addition
of header of ginfuncs.c, addition of array of decoded item pointers
for compressed data leaf pages), is attached.

This is why I love open source - I post something half-baked, and others
pop up and finish the work ;-). Committed with minor fixes, many thanks!

One last thing not only interesting for this patch: it may be good to
expose DatumGetItemPointer and ItemPointerGetDatum in for extensions
analyzing content of pages. I am not sure where though, a place like
utils/*.h may be useful. Thoughts?

Yeah, maybe. I'll leave that to the next patch that needs it, as long as
there's only one user of it, it doesn't seem worth it.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Jeff Janes
jeff.janes@gmail.com
In reply to: Heikki Linnakangas (#9)
1 attachment(s)
Re: GIN pageinspect functions

On Fri, Nov 21, 2014 at 2:04 AM, Heikki Linnakangas <hlinnakangas@vmware.com

wrote:

On 11/20/2014 05:52 AM, Michael Paquier wrote:

On Wed, Nov 19, 2014 at 7:01 AM, Peter Geoghegan <pg@heroku.com> wrote:

On Tue, Nov 4, 2014 at 7:26 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:
1. Documentation seems to be missing, other API's exposed
via pageinspect are documented at:
http://www.postgresql.org/docs/devel/static/pageinspect.html

Done.

2.

+CREATE FUNCTION gin_metapage(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage'
+LANGUAGE C STRICT;
a. Isn't it better to name the function as gin_metap(..) similar to
existing function bt_metap(..)?

I actually liked more gin_metapage_info, a name similar to the
newly-introduced brin indexes.

b. Can this function have a similar signature as bt_metap() which means

it should take input as relname?

That's mostly a matter of taste but I think we should definitely pass
a raw page to it as it is now. This has the advantage to add an extra
check if the page passed is really a meta page of not, something
useful for development.

3. Can gin_dataleafpage() API have similar name and signature as

API bt_page_items() exposed for btree?

What about gin_leafpage_items then?

The signature of bt_page_items() isn't a good example to follow. It
existed before the get_raw_page() function, and the other functions that
are designed to work with that, was added. gin_leafpage_items() name seems
fine to me.

When I call gin_leafpage_items on a {leaf} page, I get the ERROR:

ERROR: input page is not a compressed GIN data leaf page
DETAIL: Flags 0002, expected 0083

I'm don't know why it won't work on an uncompressed leaf page (or for that
matter, why my index pages are not compressed), but the docs should
probably note the restriction.

Cheers,

Jeff

Attachments:

pageinspect_gin_leaf.patchapplication/octet-stream; name=pageinspect_gin_leaf.patchDownload
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
new file mode 100644
index 313cbae..96bcf32
*** a/doc/src/sgml/pageinspect.sgml
--- b/doc/src/sgml/pageinspect.sgml
*************** test=# SELECT * FROM gin_page_opaque_inf
*** 380,386 ****
      <listitem>
       <para>
        <function>gin_leafpage_items</function> returns information about
!       the data stored in a <acronym>GIN</acronym> leaf page.  For example:
  <screen>
   test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
          FROM gin_leafpage_items(get_raw_page('gin_test_idx', 2));
--- 380,386 ----
      <listitem>
       <para>
        <function>gin_leafpage_items</function> returns information about
!       the data stored in a compressed <acronym>GIN</acronym> data leaf page.  For example:
  <screen>
   test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
          FROM gin_leafpage_items(get_raw_page('gin_test_idx', 2));
#11Bruce Momjian
bruce@momjian.us
In reply to: Jeff Janes (#10)
Re: [HACKERS] GIN pageinspect functions

On Mon, Aug 10, 2015 at 09:14:48AM -0700, Jeff Janes wrote:

When I call gin_leafpage_items on a {leaf} page, I get the ERROR:

ERROR:  input page is not a compressed GIN data leaf page
DETAIL:  Flags 0002, expected 0083

I'm don't know why it won't work on an uncompressed leaf page (or for that
matter, why my index pages are not compressed), but the docs should probably
note the restriction.

Yes, this patch is from nine years ago, but it is still an improvement,
and the GIN page still must be compressed, so patch applied to master:

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))));

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Only you can decide what is important to you.