mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
pageinspect: Add bt_page_items function with bytea argument
Author: Tomas Vondra <tomas.vondra@2ndquadrant.com> Reviewed-by: Ashutosh Sharma <ashu.coek88@gmail.com>
This commit is contained in:
@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
PG_FUNCTION_INFO_V1(bt_metap);
|
PG_FUNCTION_INFO_V1(bt_metap);
|
||||||
PG_FUNCTION_INFO_V1(bt_page_items);
|
PG_FUNCTION_INFO_V1(bt_page_items);
|
||||||
|
PG_FUNCTION_INFO_V1(bt_page_items_bytea);
|
||||||
PG_FUNCTION_INFO_V1(bt_page_stats);
|
PG_FUNCTION_INFO_V1(bt_page_stats);
|
||||||
|
|
||||||
#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
|
#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
|
||||||
@ -235,14 +236,6 @@ bt_page_stats(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_DATUM(result);
|
PG_RETURN_DATUM(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------
|
|
||||||
* bt_page_items()
|
|
||||||
*
|
|
||||||
* Get IndexTupleData set in a btree page
|
|
||||||
*
|
|
||||||
* Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
|
|
||||||
*-------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* cross-call data structure for SRF
|
* cross-call data structure for SRF
|
||||||
@ -253,14 +246,72 @@ struct user_args
|
|||||||
OffsetNumber offset;
|
OffsetNumber offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*-------------------------------------------------------
|
||||||
|
* bt_page_print_tuples()
|
||||||
|
*
|
||||||
|
* Form a tuple describing index tuple at a given offset
|
||||||
|
* ------------------------------------------------------
|
||||||
|
*/
|
||||||
|
static Datum
|
||||||
|
bt_page_print_tuples(FuncCallContext *fctx, Page page, OffsetNumber offset)
|
||||||
|
{
|
||||||
|
char *values[6];
|
||||||
|
HeapTuple tuple;
|
||||||
|
ItemId id;
|
||||||
|
IndexTuple itup;
|
||||||
|
int j;
|
||||||
|
int off;
|
||||||
|
int dlen;
|
||||||
|
char *dump;
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
id = PageGetItemId(page, offset);
|
||||||
|
|
||||||
|
if (!ItemIdIsValid(id))
|
||||||
|
elog(ERROR, "invalid ItemId");
|
||||||
|
|
||||||
|
itup = (IndexTuple) PageGetItem(page, id);
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
values[j++] = psprintf("%d", offset);
|
||||||
|
values[j++] = psprintf("(%u,%u)",
|
||||||
|
ItemPointerGetBlockNumberNoCheck(&itup->t_tid),
|
||||||
|
ItemPointerGetOffsetNumberNoCheck(&itup->t_tid));
|
||||||
|
values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
|
||||||
|
values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
|
||||||
|
values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
|
||||||
|
|
||||||
|
ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
|
||||||
|
dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
|
||||||
|
dump = palloc0(dlen * 3 + 1);
|
||||||
|
values[j] = dump;
|
||||||
|
for (off = 0; off < dlen; off++)
|
||||||
|
{
|
||||||
|
if (off > 0)
|
||||||
|
*dump++ = ' ';
|
||||||
|
sprintf(dump, "%02x", *(ptr + off) & 0xff);
|
||||||
|
dump += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
|
||||||
|
|
||||||
|
return HeapTupleGetDatum(tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------
|
||||||
|
* bt_page_items()
|
||||||
|
*
|
||||||
|
* Get IndexTupleData set in a btree page
|
||||||
|
*
|
||||||
|
* Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
|
||||||
|
*-------------------------------------------------------
|
||||||
|
*/
|
||||||
Datum
|
Datum
|
||||||
bt_page_items(PG_FUNCTION_ARGS)
|
bt_page_items(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
text *relname = PG_GETARG_TEXT_PP(0);
|
text *relname = PG_GETARG_TEXT_PP(0);
|
||||||
uint32 blkno = PG_GETARG_UINT32(1);
|
uint32 blkno = PG_GETARG_UINT32(1);
|
||||||
Datum result;
|
Datum result;
|
||||||
char *values[6];
|
|
||||||
HeapTuple tuple;
|
|
||||||
FuncCallContext *fctx;
|
FuncCallContext *fctx;
|
||||||
MemoryContext mctx;
|
MemoryContext mctx;
|
||||||
struct user_args *uargs;
|
struct user_args *uargs;
|
||||||
@ -345,47 +396,8 @@ bt_page_items(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
if (fctx->call_cntr < fctx->max_calls)
|
if (fctx->call_cntr < fctx->max_calls)
|
||||||
{
|
{
|
||||||
ItemId id;
|
result = bt_page_print_tuples(fctx, uargs->page, uargs->offset);
|
||||||
IndexTuple itup;
|
uargs->offset++;
|
||||||
int j;
|
|
||||||
int off;
|
|
||||||
int dlen;
|
|
||||||
char *dump;
|
|
||||||
char *ptr;
|
|
||||||
|
|
||||||
id = PageGetItemId(uargs->page, uargs->offset);
|
|
||||||
|
|
||||||
if (!ItemIdIsValid(id))
|
|
||||||
elog(ERROR, "invalid ItemId");
|
|
||||||
|
|
||||||
itup = (IndexTuple) PageGetItem(uargs->page, id);
|
|
||||||
|
|
||||||
j = 0;
|
|
||||||
values[j++] = psprintf("%d", uargs->offset);
|
|
||||||
values[j++] = psprintf("(%u,%u)",
|
|
||||||
ItemPointerGetBlockNumberNoCheck(&itup->t_tid),
|
|
||||||
ItemPointerGetOffsetNumberNoCheck(&itup->t_tid));
|
|
||||||
values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
|
|
||||||
values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
|
|
||||||
values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
|
|
||||||
|
|
||||||
ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
|
|
||||||
dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
|
|
||||||
dump = palloc0(dlen * 3 + 1);
|
|
||||||
values[j] = dump;
|
|
||||||
for (off = 0; off < dlen; off++)
|
|
||||||
{
|
|
||||||
if (off > 0)
|
|
||||||
*dump++ = ' ';
|
|
||||||
sprintf(dump, "%02x", *(ptr + off) & 0xff);
|
|
||||||
dump += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
|
|
||||||
result = HeapTupleGetDatum(tuple);
|
|
||||||
|
|
||||||
uargs->offset = uargs->offset + 1;
|
|
||||||
|
|
||||||
SRF_RETURN_NEXT(fctx, result);
|
SRF_RETURN_NEXT(fctx, result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -396,6 +408,90 @@ bt_page_items(PG_FUNCTION_ARGS)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------
|
||||||
|
* bt_page_items_bytea()
|
||||||
|
*
|
||||||
|
* Get IndexTupleData set in a btree page
|
||||||
|
*
|
||||||
|
* Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1));
|
||||||
|
*-------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
Datum
|
||||||
|
bt_page_items_bytea(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
||||||
|
Datum result;
|
||||||
|
FuncCallContext *fctx;
|
||||||
|
struct user_args *uargs;
|
||||||
|
int raw_page_size;
|
||||||
|
|
||||||
|
if (!superuser())
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||||
|
(errmsg("must be superuser to use pageinspect functions"))));
|
||||||
|
|
||||||
|
if (SRF_IS_FIRSTCALL())
|
||||||
|
{
|
||||||
|
BTPageOpaque opaque;
|
||||||
|
MemoryContext mctx;
|
||||||
|
TupleDesc tupleDesc;
|
||||||
|
|
||||||
|
raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
|
||||||
|
|
||||||
|
if (raw_page_size < SizeOfPageHeaderData)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("input page too small (%d bytes)", raw_page_size)));
|
||||||
|
|
||||||
|
fctx = SRF_FIRSTCALL_INIT();
|
||||||
|
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
|
||||||
|
|
||||||
|
uargs = palloc(sizeof(struct user_args));
|
||||||
|
|
||||||
|
uargs->page = VARDATA(raw_page);
|
||||||
|
|
||||||
|
uargs->offset = FirstOffsetNumber;
|
||||||
|
|
||||||
|
opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
|
||||||
|
|
||||||
|
if (P_ISMETA(opaque))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("block is a meta page")));
|
||||||
|
|
||||||
|
if (P_ISDELETED(opaque))
|
||||||
|
elog(NOTICE, "page is deleted");
|
||||||
|
|
||||||
|
fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
|
||||||
|
|
||||||
|
/* Build a tuple descriptor for our result type */
|
||||||
|
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
||||||
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
|
fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc);
|
||||||
|
|
||||||
|
fctx->user_fctx = uargs;
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(mctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fctx = SRF_PERCALL_SETUP();
|
||||||
|
uargs = fctx->user_fctx;
|
||||||
|
|
||||||
|
if (fctx->call_cntr < fctx->max_calls)
|
||||||
|
{
|
||||||
|
result = bt_page_print_tuples(fctx, uargs->page, uargs->offset);
|
||||||
|
uargs->offset++;
|
||||||
|
SRF_RETURN_NEXT(fctx, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pfree(uargs);
|
||||||
|
SRF_RETURN_DONE(fctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------
|
/* ------------------------------------------------
|
||||||
* bt_metap()
|
* bt_metap()
|
||||||
|
@ -42,4 +42,17 @@ data | 01 00 00 00 00 00 00 01
|
|||||||
|
|
||||||
SELECT * FROM bt_page_items('test1_a_idx', 2);
|
SELECT * FROM bt_page_items('test1_a_idx', 2);
|
||||||
ERROR: block number out of range
|
ERROR: block number out of range
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0));
|
||||||
|
ERROR: block is a meta page
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1));
|
||||||
|
-[ RECORD 1 ]-----------------------
|
||||||
|
itemoffset | 1
|
||||||
|
ctid | (0,1)
|
||||||
|
itemlen | 16
|
||||||
|
nulls | f
|
||||||
|
vars | f
|
||||||
|
data | 01 00 00 00 00 00 00 01
|
||||||
|
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
|
||||||
|
ERROR: block number 2 is out of range for relation "test1_a_idx"
|
||||||
DROP TABLE test1;
|
DROP TABLE test1;
|
||||||
|
@ -83,3 +83,17 @@ CREATE FUNCTION page_checksum(IN page bytea, IN blkno int4)
|
|||||||
RETURNS smallint
|
RETURNS smallint
|
||||||
AS 'MODULE_PATHNAME', 'page_checksum'
|
AS 'MODULE_PATHNAME', 'page_checksum'
|
||||||
LANGUAGE C STRICT PARALLEL SAFE;
|
LANGUAGE C STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- bt_page_items_bytea()
|
||||||
|
--
|
||||||
|
CREATE FUNCTION bt_page_items(IN page bytea,
|
||||||
|
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_bytea'
|
||||||
|
LANGUAGE C STRICT PARALLEL SAFE;
|
||||||
|
@ -14,4 +14,8 @@ SELECT * FROM bt_page_items('test1_a_idx', 0);
|
|||||||
SELECT * FROM bt_page_items('test1_a_idx', 1);
|
SELECT * FROM bt_page_items('test1_a_idx', 1);
|
||||||
SELECT * FROM bt_page_items('test1_a_idx', 2);
|
SELECT * FROM bt_page_items('test1_a_idx', 2);
|
||||||
|
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0));
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1));
|
||||||
|
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
|
||||||
|
|
||||||
DROP TABLE test1;
|
DROP TABLE test1;
|
||||||
|
@ -333,6 +333,38 @@ test=# SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
<function>bt_page_items(page bytea) returns setof record</function>
|
||||||
|
<indexterm>
|
||||||
|
<primary>bt_page_items</primary>
|
||||||
|
</indexterm>
|
||||||
|
</term>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It is also possible to pass a page to <function>bt_page_items</function>
|
||||||
|
as a <type>bytea</> value. A page image obtained
|
||||||
|
with <function>get_raw_page</function> should be passed as argument. So
|
||||||
|
the last example could also be rewritten like this:
|
||||||
|
<screen>
|
||||||
|
test=# SELECT * FROM bt_page_items(get_raw_page('pg_cast_oid_index', 1));
|
||||||
|
itemoffset | ctid | itemlen | nulls | vars | data
|
||||||
|
------------+---------+---------+-------+------+-------------
|
||||||
|
1 | (0,1) | 12 | f | f | 23 27 00 00
|
||||||
|
2 | (0,2) | 12 | f | f | 24 27 00 00
|
||||||
|
3 | (0,3) | 12 | f | f | 25 27 00 00
|
||||||
|
4 | (0,4) | 12 | f | f | 26 27 00 00
|
||||||
|
5 | (0,5) | 12 | f | f | 27 27 00 00
|
||||||
|
6 | (0,6) | 12 | f | f | 28 27 00 00
|
||||||
|
7 | (0,7) | 12 | f | f | 29 27 00 00
|
||||||
|
8 | (0,8) | 12 | f | f | 2a 27 00 00
|
||||||
|
</screen>
|
||||||
|
All the other details are the same as explained in the previous item.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
|
@ -176,6 +176,7 @@ typedef struct BTMetaPageData
|
|||||||
#define P_ISLEAF(opaque) ((opaque)->btpo_flags & BTP_LEAF)
|
#define P_ISLEAF(opaque) ((opaque)->btpo_flags & BTP_LEAF)
|
||||||
#define P_ISROOT(opaque) ((opaque)->btpo_flags & BTP_ROOT)
|
#define P_ISROOT(opaque) ((opaque)->btpo_flags & BTP_ROOT)
|
||||||
#define P_ISDELETED(opaque) ((opaque)->btpo_flags & BTP_DELETED)
|
#define P_ISDELETED(opaque) ((opaque)->btpo_flags & BTP_DELETED)
|
||||||
|
#define P_ISMETA(opaque) ((opaque)->btpo_flags & BTP_META)
|
||||||
#define P_ISHALFDEAD(opaque) ((opaque)->btpo_flags & BTP_HALF_DEAD)
|
#define P_ISHALFDEAD(opaque) ((opaque)->btpo_flags & BTP_HALF_DEAD)
|
||||||
#define P_IGNORE(opaque) ((opaque)->btpo_flags & (BTP_DELETED|BTP_HALF_DEAD))
|
#define P_IGNORE(opaque) ((opaque)->btpo_flags & (BTP_DELETED|BTP_HALF_DEAD))
|
||||||
#define P_HAS_GARBAGE(opaque) ((opaque)->btpo_flags & BTP_HAS_GARBAGE)
|
#define P_HAS_GARBAGE(opaque) ((opaque)->btpo_flags & BTP_HAS_GARBAGE)
|
||||||
|
Reference in New Issue
Block a user