В письме от 30 сентября 2015 13:49:00 пользователь Michael Paquier написал:
>
> - errmsg("input page too small (%d bytes)",
> raw_page_size)));
> + errmsg("input page too small (%d
> bytes)", raw_page_size)));
> Please be careful of spurious diffs. Those just make the life of committers
> more difficult than it already is.
Oh, that's not diff. That's I've fixed indent on the code I did not write :-)
> + <para>
> + General idea about output columns: <function>lp_*</function>
> attributes
> + are about where tuple is located inside the page;
> + <function>t_xmin</function>, <function>t_xmax</function>,
> + <function>t_field3</function>, <function>t_ctid</function> are about
> + visibility of this tuplue inside or outside of the transaction;
> + <function>t_infomask2</function>, <function>t_infomask</function> has
> some
> + information about properties of attributes stored in tuple data.
> + <function>t_hoff</function> sais where tuple data begins and
> + <function>t_bits</function> sais which attributes are NULL and which
> are
> + not. Please notice that t_bits here is not an actual value that is
> stored
> + in tuple data, but it's text representation with '0' and '1'
> charactrs.
> + </para>
> I would remove that as well. htup_details.h contains all this information.
Made it even more shorter. Just links and comments about representation of
t_bits.
> +<screen>
> +test=# select * from heap_page_item_attrs(get_raw_page('pg_range',
> 0),'pg_range'::regclass);
> +[loooooong tuple data]
> This example is too large in character per lines, I think that you should
> cut a major part of the fields, why not just keeping lp and t_attrs for
> example.
Did id.
>
> + <tbody>
> + <row>
> + <entry><structfield>rel_oid</structfield></entry>
> + <entry><type>oid</type></entry>
> + <entry>OID of the relation, of the tuple we want to split</entry>
> + </row>
> +
> + <row>
> + <entry><structfield>tuple_data</structfield></entry>
> + <entry><type>bytea</type></entry>
> + <entry>tuple raw data to split
> + </entry>
> + </row>
> In the description of tuple_data_split, I am not sure it is worth listing
> all the argument of the function like that. IMO, we should just say: those
> are the fields returned by "heap_page_items". tuple_data should as well be
> renamed to t_data.
Did it.
>
> + tuple attributes instead of one peace of raw tuple data. All other
> return
> This is not that "peaceful" to me. It should be "piece" :)
Oops ;-)
> + values[13] = PointerGetDatum(tuple_data_bytea);
> + nulls[13] = false;
> There is no point to set nulls[13] here.
Oh, you are right!
>
> +<screen>
> +test=# select tuple_data_split('pg_range'::regclass,
> '\x400f00001700000000000000ba0700004a0f0000520f0000'::bytea, 2304, 6, null);
> + tuple_data_split
> +---------------------------------------------------------------------------
> ------------ +
> {"\\x400f0000","\\x17000000","\\x00000000","\\xba070000","\\x4a0f0000","\\x5
> 20f0000"} +(1 row)
> This would be more demonstrative if combined with heap_page_items, like
> that for example:
> SELECT tuple_data_split('pg_class'::regclass, t_data, t_infomask,
> t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_class', 0));
> And actually this query crashes.
Oh... It crached because I did not check that t_data can actually be NULL.
There also was a bug in original pageinspect, that did not get t_bits right
when there was OID in the tuple. I've fixed it too.
Here is next patch in attachment.
--
Nikolay Shaplov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index aec5258..e4bc1af 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -5,7 +5,7 @@ OBJS = rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o \
brinfuncs.o ginfuncs.o $(WIN32RES)
EXTENSION = pageinspect
-DATA = pageinspect--1.3.sql pageinspect--1.2--1.3.sql \
+DATA = pageinspect--1.4.sql pageinspect--1.3--1.4.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"
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 8d1666c..4fd3087 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -29,7 +29,14 @@
#include "funcapi.h"
#include "utils/builtins.h"
#include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/rel.h"
+#include "catalog/pg_type.h"
+Datum split_tuple_data(char *tuple_data, uint16 tuple_data_len,
+ TupleDesc tuple_desc, uint16 t_infomask,
+ uint16 t_infomask2, bits8 *t_bits, bool
+ do_detoast);
/*
* bits_to_text
@@ -122,8 +129,8 @@ heap_page_items(PG_FUNCTION_ARGS)
HeapTuple resultTuple;
Datum result;
ItemId id;
- Datum values[13];
- bool nulls[13];
+ Datum values[14];
+ bool nulls[14];
uint16 lp_offset;
uint16 lp_flags;
uint16 lp_len;
@@ -154,7 +161,8 @@ heap_page_items(PG_FUNCTION_ARGS)
lp_offset + lp_len <= raw_page_size)
{
HeapTupleHeader tuphdr;
- int bits_len;
+ bytea *tuple_data_bytea;
+ int tuple_data_len;
/* Extract information from the tuple header */
@@ -168,6 +176,13 @@ heap_page_items(PG_FUNCTION_ARGS)
values[9] = UInt32GetDatum(tuphdr->t_infomask);
values[10] = UInt8GetDatum(tuphdr->t_hoff);
+ /* Copy raw tuple data into bytea attribute */
+ tuple_data_len = lp_len - tuphdr->t_hoff;
+ tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ);
+ SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ);
+ memcpy(VARDATA(tuple_data_bytea), (char *) tuphdr + tuphdr->t_hoff,
+ tuple_data_len);
+ values[13] = PointerGetDatum(tuple_data_bytea);
/*
* We already checked that the item is completely within the raw
* page passed to us, with the length given in the line pointer.
@@ -180,11 +195,11 @@ heap_page_items(PG_FUNCTION_ARGS)
{
if (tuphdr->t_infomask & HEAP_HASNULL)
{
- bits_len = tuphdr->t_hoff -
- offsetof(HeapTupleHeaderData, t_bits);
+ int bits_len =
+ ((tuphdr->t_infomask2 & HEAP_NATTS_MASK)/8 + 1)*8;
values[11] = CStringGetTextDatum(
- bits_to_text(tuphdr->t_bits, bits_len * 8));
+ bits_to_text(tuphdr->t_bits, bits_len));
}
else
nulls[11] = true;
@@ -208,7 +223,7 @@ heap_page_items(PG_FUNCTION_ARGS)
*/
int i;
- for (i = 4; i <= 12; i++)
+ for (i = 4; i <= 13; i++)
nulls[i] = true;
}
@@ -223,3 +238,222 @@ heap_page_items(PG_FUNCTION_ARGS)
else
SRF_RETURN_DONE(fctx);
}
+
+PG_FUNCTION_INFO_V1(tuple_data_split);
+Datum
+tuple_data_split(PG_FUNCTION_ARGS)
+{
+ Oid rel_oid;
+ bytea *raw_data;
+ uint16 t_infomask;
+ uint16 t_infomask2;
+ text *t_bits_str;
+ RelationData *rel;
+ TupleDesc tuple_desc;
+ bool do_detoast = false;
+
+ bits8 *t_bits = NULL;
+ Datum res;
+
+ rel_oid = PG_GETARG_OID(0);
+ raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
+ t_infomask = PG_GETARG_INT16(2);
+ t_infomask2 = PG_GETARG_INT16(3);
+ t_bits_str = PG_ARGISNULL(4) ? NULL : PG_GETARG_TEXT_P(4);
+ if (PG_NARGS()>=6)
+ do_detoast = PG_GETARG_BOOL(5);
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use raw page functions")));
+
+ if (!raw_data)
+ PG_RETURN_NULL();
+
+ /*
+ * Here we converting t_bits string back to the bits8 array,
+ * as it really is in the tuple header
+ */
+ if (t_infomask & HEAP_HASNULL)
+ {
+ int bits_str_len;
+ int bits_len;
+ char *p;
+ int off;
+ char byte = 0;
+
+ bits_len = (t_infomask2 & HEAP_NATTS_MASK) / 8 + 1;
+ if (!t_bits_str)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("t_bits argument is NULL, though we expect it to be NOT NULL and %i character long",
+ bits_len * 8)));
+
+ bits_str_len = VARSIZE(t_bits_str) - VARHDRSZ;
+ if (bits_str_len % 8)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("t_bits argument length should be multiple of eight")));
+
+ if (bits_len * 8 != bits_str_len)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("wrong t_bits argument length. Expected %i, actual is %i",
+ bits_len * 8, bits_str_len)));
+
+ t_bits = palloc(bits_len + 1);
+ p = (char *) t_bits_str + VARHDRSZ;
+ off = 0;
+
+ while (off<bits_str_len)
+ {
+ if (!(off % 8))
+ byte = 0;
+
+ if (( p[off] == '0') || (p[off] == '1'))
+ byte = byte | ( (p[off]-'0')<<off % 8);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("illegal character '%c' in t_bits string", p[off])));
+
+ if (off % 8 == 7)
+ t_bits[off / 8] = byte;
+
+ off++;
+ }
+ }
+ else
+ {
+ if (t_bits_str)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("t_bits string is expected to be NULL, but instead it is %i bytes length",
+ VARSIZE(t_bits_str) - VARHDRSZ)));
+ }
+
+ /* Getting tuple descriptor from relation OID */
+ rel = relation_open(rel_oid, NoLock);
+ tuple_desc = CreateTupleDescCopyConstr(rel->rd_att);
+ relation_close(rel, NoLock);
+
+ /* Splitting tuple data */
+ res = split_tuple_data((char *) raw_data + VARHDRSZ,
+ VARSIZE(raw_data) - VARHDRSZ, tuple_desc,
+ t_infomask, t_infomask2, t_bits,
+ do_detoast);
+
+ PG_RETURN_ARRAYTYPE_P(res);
+}
+
+
+Datum
+split_tuple_data(char *tuple_data, uint16 tuple_data_len, TupleDesc tuple_desc,
+ uint16 t_infomask, uint16 t_infomask2, bits8 *t_bits,
+ bool do_detoast)
+{
+ ArrayBuildState *raw_attrs;
+ int nattrs;
+ int i;
+ int off;
+
+ /*
+ * Here we reimplement the basic functionality of nocachegetattr from
+ * backend/access/common/heaptuple.c witch is basically used for fetching
+ * attributes from tuple when it is not cached. We can not use
+ * nocachegetattr here directly, because we should ignore all cache
+ * optimizations and other stuff just get binary data as it is.
+ */
+
+ raw_attrs = initArrayResult(BYTEAOID,CurrentMemoryContext,0);
+ off = 0;
+ nattrs = tuple_desc->natts;
+
+ if (nattrs < (t_infomask2 & HEAP_NATTS_MASK))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("data corruption: number of attributes in tuple header is greater than number of attributes in tuple descripor")));
+
+ for(i=0; i < nattrs; i++)
+ {
+ Form_pg_attribute attr;
+ Datum raw_attr = PointerGetDatum(NULL);
+ bool is_null;
+
+ attr = tuple_desc->attrs[i];
+ is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
+ /*
+ * Tuple header can specify less attributes then tuple descriptor
+ * as ALTER TABLE ADD COLUMN without DEFAULT keyword does not
+ * actually change tuples in pages, so attributes with numbers greater
+ * than t_infomask2 & HEAP_NATTS_MASK should be treated as NULL
+ */
+ if (i >= (t_infomask2 & HEAP_NATTS_MASK) )
+ is_null = true;
+
+ if (!is_null)
+ {
+ int len;
+ bytea *attr_data;
+
+ if (attr->attlen == -1)
+ {
+ off = att_align_pointer(off, tuple_desc->attrs[i]->attalign, -1,
+ tuple_data + off);
+ /*
+ * As VARSIZE_ANY throws an exception if it can't properly detect
+ * type of external storage in macros VARTAG_SIZE, so we repeat
+ * this check here to preform nicer error handling
+ */
+ if (VARATT_IS_1B_E(tuple_data + off) &&
+ VARTAG_EXTERNAL(tuple_data + off) != VARTAG_INDIRECT &&
+ VARTAG_EXTERNAL(tuple_data + off) != VARTAG_ONDISK)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("data corruption: First byte of varlen attr seems to be corrupted")));
+ }
+
+ len = VARSIZE_ANY(tuple_data + off);
+ }
+ else
+ {
+ off = att_align_nominal(off, tuple_desc->attrs[i]->attalign);
+ len = attr->attlen;
+ }
+
+ if (tuple_data_len < off + len)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("data corruption: Iterating over tuple data reached out of actual tuple size")));
+
+ attr_data = (bytea *) palloc(len + VARHDRSZ);
+ SET_VARSIZE(attr_data, len + VARHDRSZ);
+ memcpy(VARDATA(attr_data), tuple_data + off, len);
+ raw_attr = PointerGetDatum(attr_data);
+
+ if ( attr->attlen == -1 && do_detoast)
+ {
+ Datum raw_attr_copy;
+ raw_attr_copy = PointerGetDatum(
+ PG_DETOAST_DATUM_COPY(tuple_data + off));
+ pfree(attr_data);
+ raw_attr = raw_attr_copy;
+ }
+
+ off = att_addlength_pointer(off, tuple_desc->attrs[i]->attlen,
+ tuple_data + off);
+ }
+
+ raw_attrs = accumArrayResult(raw_attrs, raw_attr, is_null, BYTEAOID,
+ CurrentMemoryContext);
+ }
+
+ if (tuple_data_len != off)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("data corruption: Iterating over tuple data did not actualy reach tuple end")));
+
+ return makeArrayResult(raw_attrs, CurrentMemoryContext);
+}
diff --git a/contrib/pageinspect/pageinspect--1.3--1.4.sql b/contrib/pageinspect/pageinspect--1.3--1.4.sql
new file mode 100644
index 0000000..4b1d140
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.3--1.4.sql
@@ -0,0 +1,82 @@
+/* contrib/pageinspect/pageinspect--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.4'" to load this file. \quit
+
+--
+-- heap_page_items()
+--
+DROP FUNCTION heap_page_items(bytea);
+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,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(t_oid rel_oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(t_oid rel_oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass, IN do_detoast bool,
+ 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,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record
+AS 'SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, t_infomask2, t_infomask, t_hoff, t_bits, t_oid, tuple_data_split(rel_oid, t_data, t_infomask, t_infomask2, t_bits, do_detoast) as t_attrs from heap_page_items(page)'
+LANGUAGE SQL;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN heap_oid regclass,
+ 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,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record
+AS 'SELECT * from heap_page_item_attrs(page, heap_oid, false)'
+LANGUAGE SQL;
diff --git a/contrib/pageinspect/pageinspect--1.3.sql b/contrib/pageinspect/pageinspect--1.3.sql
deleted file mode 100644
index a99e058..0000000
--- a/contrib/pageinspect/pageinspect--1.3.sql
+++ /dev/null
@@ -1,189 +0,0 @@
-/* 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;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_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_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.4.sql b/contrib/pageinspect/pageinspect--1.4.sql
new file mode 100644
index 0000000..4599fdd
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.4.sql
@@ -0,0 +1,246 @@
+/* contrib/pageinspect/pageinspect--1.4.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,
+ OUT t_data bytea
+ )
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass, IN do_detoast bool,
+ 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,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record
+AS 'SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, t_infomask2, t_infomask, t_hoff, t_bits, t_oid, tuple_data_split(rel_oid, t_data, t_infomask, t_infomask2, t_bits, do_detoast) as t_attrs from heap_page_items(page)'
+LANGUAGE SQL;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN heap_oid regclass,
+ 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,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record
+AS 'SELECT * from heap_page_item_attrs(page, heap_oid, false)'
+LANGUAGE SQL;
+
+--
+-- 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;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_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_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.control b/contrib/pageinspect/pageinspect.control
index a9dab33..68c7d61 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.3'
+default_version = '1.4'
module_pathname = '$libdir/pageinspect'
relocatable = true
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index b95cc81..7676dfa 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -93,19 +93,170 @@ test=# SELECT * FROM page_header(get_raw_page('pg_class', 0));
<listitem>
<para>
<function>heap_page_items</function> shows all line pointers on a heap
- page. For those line pointers that are in use, tuple headers are also
- shown. All tuples are shown, whether or not the tuples were visible to
- an MVCC snapshot at the time the raw page was copied.
+ page. For those line pointers that are in use,
+ <function>heap_page_items</function> also shows tuple headers and raw
+ tuple data. All tuples are shown, whether or not the tuples were visible
+ to an MVCC snapshot at the time the raw page was copied.
</para>
<para>
A heap page image obtained with <function>get_raw_page</function> should
be passed as argument. For example:
<screen>
-test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));
+
+test=# select * from heap_page_items(get_raw_page('pg_range', 0));
+ lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
+----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+----------------------------------------------------
+ 1 | 8144 | 1 | 48 | 1 | 0 | 0 | (0,1) | 6 | 2304 | 24 | | | \x400f00001700000000000000ba0700004a0f0000520f0000
+ 2 | 8096 | 1 | 48 | 1 | 0 | 0 | (0,2) | 6 | 2304 | 24 | | | \x420f0000a406000000000000350c000000000000540f0000
+ 3 | 8048 | 1 | 48 | 1 | 0 | 0 | (0,3) | 6 | 2304 | 24 | | | \x440f00005a04000000000000380c000000000000590f0000
+ 4 | 8000 | 1 | 48 | 1 | 0 | 0 | (0,4) | 6 | 2304 | 24 | | | \x460f0000a004000000000000370c0000000000005a0f0000
+ 5 | 7952 | 1 | 48 | 1 | 0 | 0 | (0,5) | 6 | 2304 | 24 | | | \x480f00003a04000000000000320c00004b0f0000550f0000
+ 6 | 7904 | 1 | 48 | 1 | 0 | 0 | (0,6) | 6 | 2304 | 24 | | | \x560f00001400000000000000340c0000580f0000530f0000
+(6 rows)
+
+</screen>
+ </para>
+ <para>
+ Attributes returned by <function>heap_page_items</function> are values
+ from tuple header, and from page item identifiers that points to the
+ tuple. Detailed description of these attributes can be found at
+ <xref linkend="storage-page-layout">, <xref linkend="ddl-system-columns">
+ and source in code: <filename>src/include/storage/itemid.h</>,
+ <filename>src/include/access/htup_details.h</>.
+ </para>
+ <para>
+ Please notice that <function>t_bits</function> in tuple header structure
+ is a binary bitmap, but <function>heap_page_items</function> returns
+ <function>t_bits</function> as human readable text representation of
+ original <function>t_bits</function> bitmap.
+ </para>
+ <para>
+ Last attribute <function>t_data</function> contains raw tuple data stored
+ as <function>bytea</function> binary.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
+ <varlistentry>
+ <term>
+ <function>tuple_data_split(rel_oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) returs bytea[]</function>
+ <indexterm>
+ <primary>tuple_data_split</primary>
+ </indexterm>
+ </term>
+ <term>
+ <function>tuple_data_split(rel_oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) returs bytea[]</function>
+ </term>
+ <listitem>
+ <para>
+ <function>tuple_data_split</function> splits tuple data into attributes in
+ the same way as <function>nocachegetattr</function> function do it in
+ postgres internals. Returns array of bytea.
+<screen>
+test=# SELECT tuple_data_split('pg_range'::regclass, t_data, t_infomask, t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_range', 0));
+ tuple_data_split
+---------------------------------------------------------------------------------------
+ {"\\x400f0000","\\x17000000","\\x00000000","\\xba070000","\\x4a0f0000","\\x520f0000"}
+ {"\\x420f0000","\\xa4060000","\\x00000000","\\x350c0000","\\x00000000","\\x540f0000"}
+ {"\\x440f0000","\\x5a040000","\\x00000000","\\x380c0000","\\x00000000","\\x590f0000"}
+ {"\\x460f0000","\\xa0040000","\\x00000000","\\x370c0000","\\x00000000","\\x5a0f0000"}
+ {"\\x480f0000","\\x3a040000","\\x00000000","\\x320c0000","\\x4b0f0000","\\x550f0000"}
+ {"\\x560f0000","\\x14000000","\\x00000000","\\x340c0000","\\x580f0000","\\x530f0000"}
+(6 rows)
</screen>
- See <filename>src/include/storage/itemid.h</> and
- <filename>src/include/access/htup_details.h</> for explanations of the fields
- returned.
+ </para>
+
+ <para>
+ <function>tuple_data_split</function> takes OID of the relation whose
+ tuple data we going to split as a first argument. The rest arguments
+ should contains <function>t_data</function>,
+ <function>t_infomask</function>, <function>t_infomask2</function> and
+ <function>t_bits</function> as they are returned by
+ <function>heap_page_items</function>. There is also one optional argument
+ <function>do_detoast</function>, if it is set to true,
+ <function>tuple_data_split</function> will deTOAST and uncompress
+ attribute values if they were TOASTed or compressed. See
+ <xref linkend="storage-toast">
+ </para>
+ <para>
+ In most cases you will not need <function>tuple_data_split</function>
+ itself, consider using <function>heap_page_item_attrs</function> for
+ viewing page data with split attributes.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>heap_page_item_attrs(page bytea, rel_oid regclass) returns record</function>
+ <indexterm>
+ <primary>heap_page_item_attrs</primary>
+ </indexterm>
+ </term>
+ <term>
+ <function>heap_page_item_attrs(page bytea, rel_oid regclass, do_detoast bool) returns record</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>heap_page_item_attrs</function> is actually a clone of
+ <function>heap_page_items</function>, with one difference:
+ <function>heap_page_item_attrs</function> returns array of raw values of
+ tuple attributes instead of one piece of raw tuple data. All other return
+ columns are same as in <function>heap_page_items</function>.
+<screen>
+test=# select lp, t_attrs from heap_page_item_attrs(get_raw_page('pg_range',0),'pg_range'::regclass);
+ lp | t_attrs
+----+---------------------------------------------------------------------------------------
+ 1 | {"\\x400f0000","\\x17000000","\\x00000000","\\xba070000","\\x4a0f0000","\\x520f0000"}
+ 2 | {"\\x420f0000","\\xa4060000","\\x00000000","\\x350c0000","\\x00000000","\\x540f0000"}
+ 3 | {"\\x440f0000","\\x5a040000","\\x00000000","\\x380c0000","\\x00000000","\\x590f0000"}
+ 4 | {"\\x460f0000","\\xa0040000","\\x00000000","\\x370c0000","\\x00000000","\\x5a0f0000"}
+ 5 | {"\\x480f0000","\\x3a040000","\\x00000000","\\x320c0000","\\x4b0f0000","\\x550f0000"}
+ 6 | {"\\x560f0000","\\x14000000","\\x00000000","\\x340c0000","\\x580f0000","\\x530f0000"}
+(6 rows)
+</screen>
+ </para>
+ <para>
+ <function>heap_page_item_attrs</function> takes following argiments:
+ <informaltable>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Argument</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>page</structfield></entry>
+ <entry><type>bytea</type></entry>
+ <entry>page raw data, usually returned by <function>get_raw_page</function></entry>
+ </row>
+
+ <row>
+ <entry><structfield>rel_oid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry>OID of the relation, whose page we want to parse</entry>
+ </row>
+
+ <row>
+ <entry><structfield>do_detoast</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry>optional attribute, if <function>do_detoast</function> is set to
+ true, <function>heap_page_item_attrs</function> will not only split
+ tuple data into attributes, but also try to deTOAST and uncompress
+ attribute value if it was TOASTed or compressed.
+ See <xref linkend="storage-toast">
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+
</para>
</listitem>
</varlistentry>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers