Add pageinspect functions for inspecting GIN indexes.
authorHeikki Linnakangas <[email protected]>
Fri, 21 Nov 2014 09:46:50 +0000 (11:46 +0200)
committerHeikki Linnakangas <[email protected]>
Fri, 21 Nov 2014 09:58:07 +0000 (11:58 +0200)
Patch by me, Peter Geoghegan and Michael Paquier, reviewed by Amit Kapila.

contrib/pageinspect/Makefile
contrib/pageinspect/ginfuncs.c [new file with mode: 0644]
contrib/pageinspect/pageinspect--1.2--1.3.sql
contrib/pageinspect/pageinspect--1.3.sql
doc/src/sgml/pageinspect.sgml

index a59de8aba9e11a94138dbacae336b924b45156f0..aec5258a1bbc2aac1d54a0a57504a0eb5d47495a 100644 (file)
@@ -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 (file)
index 0000000..6c47d1c
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * 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);
+
+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);
+}
+
+typedef struct gin_leafpage_items_state
+{
+   TupleDesc   tupd;
+   GinPostingList *seg;
+   GinPostingList *lastseg;
+}  gin_leafpage_items_state;
+
+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)
+   {
+       GinPostingList *cur = inter_call_data->seg;
+       HeapTuple   resultTuple;
+       Datum       result;
+       Datum       values[3];
+       bool        nulls[3];
+       int         ndecoded,
+                   i;
+       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, 's'));
+       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);
+}
index 9bc4dded0f42c0e4fe5516e6a0760c60d36789de..9675526fbb9528034a403734d9497ad879d4e37b 100644 (file)
@@ -41,3 +41,41 @@ CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
 RETURNS SETOF record
 AS 'MODULE_PATHNAME', 'brin_page_items'
 LANGUAGE C STRICT;
+
+--
+-- 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;
index 856dcdfb592914deea02f7a7a1e8dc544a475a33..969748675aa6cf7a10aa12dd6bef058f7e1a7f63 100644 (file)
@@ -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;
index 70517ac4e174be60a725db92775032fbf9b9b5c3..4cb6d536dd3b817a1d3ded8cbc019908fa9a4fb5 100644 (file)
@@ -300,6 +300,91 @@ brintest-# order by blknum, attnum limit 6;
     </listitem>
    </varlistentry>
 
+   <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, tids[0:5] as some_tids
+        FROM gin_leafpage_items(get_raw_page('gin_test_idx', 2));
+ first_tid | nbytes |                        some_tids                       
+  
+-----------+--------+----------------------------------------------------------
+ (8,41)    |    244 | {"(8,41)","(8,43)","(8,44)","(8,45)","(8,46)"}
+ (10,45)   |    248 | {"(10,45)","(10,46)","(10,47)","(10,48)","(10,49)"}
+ (12,52)   |    248 | {"(12,52)","(12,53)","(12,54)","(12,55)","(12,56)"}
+ (14,59)   |    320 | {"(14,59)","(14,60)","(14,61)","(14,62)","(14,63)"}
+ (167,16)  |    376 | {"(167,16)","(167,17)","(167,18)","(167,19)","(167,20)"}
+ (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>fsm_page_contents(page bytea) returns text</function>