From 45f3f833628a464e77f94b3c7bb4a74f15d3ecfc Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Tue, 10 Sep 2019 19:55:55 +0800 Subject: [PATCH v5] Introduce heap_tuple_infomask_flags to decode t_infomask and t_infomask2 --- contrib/pageinspect/Makefile | 2 +- contrib/pageinspect/expected/page.out | 136 ++++++++++++++++++ contrib/pageinspect/heapfuncs.c | 112 +++++++++++++++ contrib/pageinspect/pageinspect--1.7--1.8.sql | 15 ++ contrib/pageinspect/pageinspect.control | 2 +- contrib/pageinspect/sql/page.sql | 32 +++++ doc/src/sgml/pageinspect.sgml | 39 +++++ 7 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 contrib/pageinspect/pageinspect--1.7--1.8.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index e5a581f141..cfe01297fb 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 hashfuncs.o $(WIN32RES) EXTENSION = pageinspect -DATA = pageinspect--1.6--1.7.sql \ +DATA = pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \ pageinspect--1.5.sql pageinspect--1.5--1.6.sql \ pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \ pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \ diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out index 3fcd9fbe6d..4dea50a67c 100644 --- a/contrib/pageinspect/expected/page.out +++ b/contrib/pageinspect/expected/page.out @@ -82,6 +82,142 @@ SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0)); (1 row) +-- If we freeze the only tuple on test1, the infomask should +-- always be the same in all test runs. we show raw flags by +-- default: HEAP_XMIN_COMMITTED and HEAP_XMIN_INVALID. +VACUUM FREEZE test1; +SELECT t_infomask, t_infomask2, unnest(flags) +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags) +ORDER BY 3; + t_infomask | t_infomask2 | unnest +------------+-------------+--------------------- + 2816 | 2 | HEAP_XMAX_INVALID + 2816 | 2 | HEAP_XMIN_COMMITTED + 2816 | 2 | HEAP_XMIN_INVALID +(3 rows) + +-- output the decoded flag HEAP_XMIN_FROZEN instead +SELECT t_infomask, t_infomask2, unnest(flags) +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags) +ORDER BY 3; + t_infomask | t_infomask2 | unnest +------------+-------------+------------------- + 2816 | 2 | HEAP_XMAX_INVALID + 2816 | 2 | HEAP_XMIN_FROZEN +(2 rows) + +-- test for HEAP_LOCKED_UPGRADED +SELECT unnest(heap_tuple_infomask_flags(x'1080'::integer, 0, true)) ORDER BY 1; + unnest +---------------------- + HEAP_LOCKED_UPGRADED +(1 row) + +-- test for all flags of both t_infomask and t_infomask2 +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false)) ORDER BY 1; + unnest +----------------------- + HEAP_COMBOCID + HEAP_HASEXTERNAL + HEAP_HASNULL + HEAP_HASOID_OLD + HEAP_HASVARWIDTH + HEAP_HOT_UPDATED + HEAP_KEYS_UPDATED + HEAP_MOVED_IN + HEAP_MOVED_OFF + HEAP_ONLY_TUPLE + HEAP_UPDATED + HEAP_XMAX_COMMITTED + HEAP_XMAX_EXCL_LOCK + HEAP_XMAX_INVALID + HEAP_XMAX_IS_MULTI + HEAP_XMAX_KEYSHR_LOCK + HEAP_XMAX_LOCK_ONLY + HEAP_XMIN_COMMITTED + HEAP_XMIN_INVALID +(19 rows) + +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true)) ORDER BY 1; + unnest +--------------------- + HEAP_COMBOCID + HEAP_HASEXTERNAL + HEAP_HASNULL + HEAP_HASOID_OLD + HEAP_HASVARWIDTH + HEAP_HOT_UPDATED + HEAP_KEYS_UPDATED + HEAP_MOVED + HEAP_ONLY_TUPLE + HEAP_UPDATED + HEAP_XMAX_COMMITTED + HEAP_XMAX_INVALID + HEAP_XMAX_IS_MULTI + HEAP_XMAX_LOCK_ONLY + HEAP_XMAX_SHR_LOCK + HEAP_XMIN_FROZEN +(16 rows) + +-- same result as specifying all flags +SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) ORDER BY 1; + unnest +----------------------- + HEAP_COMBOCID + HEAP_HASEXTERNAL + HEAP_HASNULL + HEAP_HASOID_OLD + HEAP_HASVARWIDTH + HEAP_HOT_UPDATED + HEAP_KEYS_UPDATED + HEAP_MOVED_IN + HEAP_MOVED_OFF + HEAP_ONLY_TUPLE + HEAP_UPDATED + HEAP_XMAX_COMMITTED + HEAP_XMAX_EXCL_LOCK + HEAP_XMAX_INVALID + HEAP_XMAX_IS_MULTI + HEAP_XMAX_KEYSHR_LOCK + HEAP_XMAX_LOCK_ONLY + HEAP_XMIN_COMMITTED + HEAP_XMIN_INVALID +(19 rows) + +SELECT unnest(heap_tuple_infomask_flags(-1, -1, true)) ORDER BY 1; + unnest +--------------------- + HEAP_COMBOCID + HEAP_HASEXTERNAL + HEAP_HASNULL + HEAP_HASOID_OLD + HEAP_HASVARWIDTH + HEAP_HOT_UPDATED + HEAP_KEYS_UPDATED + HEAP_MOVED + HEAP_ONLY_TUPLE + HEAP_UPDATED + HEAP_XMAX_COMMITTED + HEAP_XMAX_INVALID + HEAP_XMAX_IS_MULTI + HEAP_XMAX_LOCK_ONLY + HEAP_XMAX_SHR_LOCK + HEAP_XMIN_FROZEN +(16 rows) + +-- output no flags +SELECT unnest(heap_tuple_infomask_flags(0, 0, false)) ORDER BY 1; + unnest +-------- +(0 rows) + +SELECT unnest(heap_tuple_infomask_flags(0, 0, true)) ORDER BY 1; + unnest +-------- +(0 rows) + DROP TABLE test1; -- check that using any of these functions with a partitioned table or index -- would fail diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c index 64a6e351d5..3764bf375c 100644 --- a/contrib/pageinspect/heapfuncs.c +++ b/contrib/pageinspect/heapfuncs.c @@ -33,6 +33,7 @@ #include "catalog/pg_am_d.h" #include "catalog/pg_type.h" #include "miscadmin.h" +#include "port/pg_bitutils.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/rel.h" @@ -494,3 +495,114 @@ tuple_data_split(PG_FUNCTION_ARGS) PG_RETURN_ARRAYTYPE_P(res); } + +/* + * heap_tuple_infomask_flags + * + * Decode an infomask, per htup_details.c, into human readable + * form. For detail of masks see access/htup_details.h. + */ +PG_FUNCTION_INFO_V1(heap_tuple_infomask_flags); + +Datum +heap_tuple_infomask_flags(PG_FUNCTION_ARGS) +{ + uint16 t_infomask = PG_GETARG_INT16(0); + uint16 t_infomask2 = PG_GETARG_INT16(1); + bool decode_combined = PG_GETARG_BOOL(2); + int cnt = 0; + ArrayType *a; + int bitcnt; + Datum *d; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + bitcnt = pg_popcount((const char *) &t_infomask, sizeof(uint16)) + + pg_popcount((const char *) &t_infomask2, sizeof(uint16)); + + /* If no flags, return an empty array */ + if (bitcnt <= 0) + PG_RETURN_POINTER(construct_empty_array(TEXTOID)); + + d = (Datum *) palloc0(sizeof(Datum) * bitcnt); + + /* decode t_infomask */ + if ((t_infomask & HEAP_HASNULL) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_HASNULL"); + if ((t_infomask & HEAP_HASVARWIDTH) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_HASVARWIDTH"); + if ((t_infomask & HEAP_HASEXTERNAL) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_HASEXTERNAL"); + if ((t_infomask & HEAP_HASOID_OLD) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_HASOID_OLD"); + if ((t_infomask & HEAP_COMBOCID) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_COMBOCID"); + if ((t_infomask & HEAP_XMAX_COMMITTED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_COMMITTED"); + if ((t_infomask & HEAP_XMAX_INVALID) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_INVALID"); + if ((t_infomask & HEAP_UPDATED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_UPDATED"); + + /* decode combined masks of t_infomaks */ + if (decode_combined && + (t_infomask & HEAP_XMAX_SHR_LOCK) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_SHR_LOCK"); + else + { + if ((t_infomask & HEAP_XMAX_EXCL_LOCK) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_EXCL_LOCK"); + if ((t_infomask & HEAP_XMAX_KEYSHR_LOCK) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_KEYSHR_LOCK"); + } + + if (decode_combined && + (t_infomask & HEAP_XMIN_FROZEN) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMIN_FROZEN"); + else + { + if ((t_infomask & HEAP_XMIN_COMMITTED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMIN_COMMITTED"); + if ((t_infomask & HEAP_XMIN_INVALID) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMIN_INVALID"); + } + + if (decode_combined && + (t_infomask & HEAP_MOVED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_MOVED"); + else + { + if ((t_infomask & HEAP_MOVED_IN) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_MOVED_IN"); + if ((t_infomask & HEAP_MOVED_OFF) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_MOVED_OFF"); + } + + if (decode_combined && + HEAP_LOCKED_UPGRADED(t_infomask)) + d[cnt++] = CStringGetTextDatum("HEAP_LOCKED_UPGRADED"); + else + { + if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_LOCK_ONLY"); + if ((t_infomask & HEAP_XMAX_IS_MULTI) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_XMAX_IS_MULTI"); + } + + /* decode t_infomask2 */ + if ((t_infomask2 & HEAP_KEYS_UPDATED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_KEYS_UPDATED"); + if ((t_infomask2 & HEAP_HOT_UPDATED) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_HOT_UPDATED"); + if ((t_infomask2 & HEAP_ONLY_TUPLE) != 0) + d[cnt++] = CStringGetTextDatum("HEAP_ONLY_TUPLE"); + + a = construct_array(d, cnt, TEXTOID, -1, false, 'i'); + + pfree(d); + + PG_RETURN_POINTER(a); +} diff --git a/contrib/pageinspect/pageinspect--1.7--1.8.sql b/contrib/pageinspect/pageinspect--1.7--1.8.sql new file mode 100644 index 0000000000..7e85677d6c --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql @@ -0,0 +1,15 @@ +/* contrib/pageinspect/pageinspect--1.7--1.8.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.8'" to load this file. \quit + +-- +-- heap_tuple_infomask_flags() +-- +CREATE FUNCTION heap_tuple_infomask_flags( + t_infomask integer, + t_infomask2 integer, + decode_combined boolean DEFAULT false) +RETURNS text[] +AS 'MODULE_PATHNAME', 'heap_tuple_infomask_flags' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control index dcfc61f22d..f8cdf526c6 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.7' +default_version = '1.8' module_pathname = '$libdir/pageinspect' relocatable = true diff --git a/contrib/pageinspect/sql/page.sql b/contrib/pageinspect/sql/page.sql index 8ac9991837..8d3b69aa2b 100644 --- a/contrib/pageinspect/sql/page.sql +++ b/contrib/pageinspect/sql/page.sql @@ -31,6 +31,38 @@ SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bi SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0)); +-- If we freeze the only tuple on test1, the infomask should +-- always be the same in all test runs. we show raw flags by +-- default: HEAP_XMIN_COMMITTED and HEAP_XMIN_INVALID. +VACUUM FREEZE test1; + +SELECT t_infomask, t_infomask2, unnest(flags) +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags) +ORDER BY 3; + +-- output the decoded flag HEAP_XMIN_FROZEN instead +SELECT t_infomask, t_infomask2, unnest(flags) +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags) +ORDER BY 3; + +-- test for HEAP_LOCKED_UPGRADED +SELECT unnest(heap_tuple_infomask_flags(x'1080'::integer, 0, true)) ORDER BY 1; + +-- test for all flags of both t_infomask and t_infomask2 +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false)) ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true)) ORDER BY 1; + +-- same result as specifying all flags +SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(-1, -1, true)) ORDER BY 1; + +-- output no flags +SELECT unnest(heap_tuple_infomask_flags(0, 0, false)) ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(0, 0, true)) ORDER BY 1; + + DROP TABLE test1; -- check that using any of these functions with a partitioned table or index diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml index 7a767b25ea..367b6327b7 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -184,6 +184,10 @@ test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0)); src/include/access/htup_details.h for explanations of the fields returned. + + The heap_tuple_infomask_flags function can be used to unpack the + recognized bits of the infomasks of heap tuples. + @@ -236,6 +240,41 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class + + + + heap_tuple_infomask_flags(t_infomask integer, t_infomask2 integer, decode_combined bool) returns text[] + + heap_tuple_infomask_flags + + + + + heap_tuple_infomask_flags decodes the + t_infomask and + t_infomask2 returned by + heap_page_items into a human-readable + array of flag names. This can be used to see the tuple hint + bits etc. For example: + +test=# SELECT heap_tuple_infomask_flags(t_infomask, t_infomask2, true) FROM heap_page_items(get_raw_page('pg_class', 0)); + + This function should be called with the same arguments as the return + attributes of heap_page_items. + + + If decode_combined is true, + combination flags like HEAP_XMIN_FROZEN are + output instead of raw flags, HEAP_XMIN_COMMITTED + and HEAP_XMIN_INVALID. Default value is + false. + + + For the meaning of these flags see + src/include/access/htup_details.h + + + -- 2.22.0