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..6a09d46a57 100644 --- a/contrib/pageinspect/expected/page.out +++ b/contrib/pageinspect/expected/page.out @@ -82,6 +82,186 @@ 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, flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags); + t_infomask | t_infomask2 | flags +------------+-------------+----------------------------------------------------------- + 2816 | 2 | {HEAP_XMAX_INVALID,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID} +(1 row) + +-- output the decoded flag HEAP_XMIN_FROZEN instead +SELECT t_infomask, t_infomask2, flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags); + t_infomask | t_infomask2 | flags +------------+-------------+-------------------------------------- + 2816 | 2 | {HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN} +(1 row) + +-- tests for decoding of combined flags +-- HEAP_XMAX_SHR_LOCK = (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK) +SELECT heap_tuple_infomask_flags(x'0050'::int, 0, true); + heap_tuple_infomask_flags +--------------------------- + {HEAP_XMAX_SHR_LOCK} +(1 row) + +SELECT heap_tuple_infomask_flags(x'0050'::int, 0, false); + heap_tuple_infomask_flags +--------------------------------------------- + {HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_KEYSHR_LOCK} +(1 row) + +-- HEAP_XMIN_FROZEN = (HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID) +SELECT heap_tuple_infomask_flags(x'0300'::int, 0, true); + heap_tuple_infomask_flags +--------------------------- + {HEAP_XMIN_FROZEN} +(1 row) + +SELECT heap_tuple_infomask_flags(x'0300'::int, 0, false); + heap_tuple_infomask_flags +----------------------------------------- + {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID} +(1 row) + +-- HEAP_MOVED = (HEAP_MOVED_IN | HEAP_MOVED_OFF) +SELECT heap_tuple_infomask_flags(x'C000'::int, 0, true); + heap_tuple_infomask_flags +--------------------------- + {HEAP_MOVED} +(1 row) + +SELECT heap_tuple_infomask_flags(x'C000'::int, 0, false); + heap_tuple_infomask_flags +-------------------------------- + {HEAP_MOVED_IN,HEAP_MOVED_OFF} +(1 row) + +-- HEAP_LOCKED_UPGRADED = (HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY) +SELECT heap_tuple_infomask_flags(x'1080'::int, 0, true); + heap_tuple_infomask_flags +--------------------------- + {HEAP_LOCKED_UPGRADED} +(1 row) + +SELECT heap_tuple_infomask_flags(x'1080'::int, 0, false); + heap_tuple_infomask_flags +------------------------------------------ + {HEAP_XMAX_LOCK_ONLY,HEAP_XMAX_IS_MULTI} +(1 row) + +-- test all flags of t_infomask and t_infomask2 +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int, false)) + AS flags ORDER BY 1; + flags +----------------------- + 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'::int, x'FFFF'::int, true)) + AS flags ORDER BY 1; + flags +--------------------- + 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) + +SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) + AS flags ORDER BY 1; + flags +----------------------- + 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)) + AS flags ORDER BY 1; + flags +--------------------- + 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) + +-- no flags +SELECT unnest(heap_tuple_infomask_flags(0, 0, false)); + unnest +-------- +(0 rows) + +SELECT unnest(heap_tuple_infomask_flags(0, 0, true)); + 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..68f16cd400 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,111 @@ tuple_data_split(PG_FUNCTION_ARGS) PG_RETURN_ARRAYTYPE_P(res); } + +/* + * heap_tuple_infomask_flags + * + * Decode into a human-readable format t_infomask and t_infomask2 associated + * to a tuple. All the flags are described in 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"); + + Assert(cnt <= bitcnt); + 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..0319b5fa11 100644 --- a/contrib/pageinspect/sql/page.sql +++ b/contrib/pageinspect/sql/page.sql @@ -31,6 +31,48 @@ 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, flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags); + +-- output the decoded flag HEAP_XMIN_FROZEN instead +SELECT t_infomask, t_infomask2, flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags); + +-- tests for decoding of combined flags +-- HEAP_XMAX_SHR_LOCK = (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK) +SELECT heap_tuple_infomask_flags(x'0050'::int, 0, true); +SELECT heap_tuple_infomask_flags(x'0050'::int, 0, false); +-- HEAP_XMIN_FROZEN = (HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID) +SELECT heap_tuple_infomask_flags(x'0300'::int, 0, true); +SELECT heap_tuple_infomask_flags(x'0300'::int, 0, false); +-- HEAP_MOVED = (HEAP_MOVED_IN | HEAP_MOVED_OFF) +SELECT heap_tuple_infomask_flags(x'C000'::int, 0, true); +SELECT heap_tuple_infomask_flags(x'C000'::int, 0, false); +-- HEAP_LOCKED_UPGRADED = (HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY) +SELECT heap_tuple_infomask_flags(x'1080'::int, 0, true); +SELECT heap_tuple_infomask_flags(x'1080'::int, 0, false); + +-- test all flags of t_infomask and t_infomask2 +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int, false)) + AS flags ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int, true)) + AS flags ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) + AS flags ORDER BY 1; +SELECT unnest(heap_tuple_infomask_flags(-1, -1, true)) + AS flags ORDER BY 1; + +-- no flags +SELECT unnest(heap_tuple_infomask_flags(0, 0, false)); +SELECT unnest(heap_tuple_infomask_flags(0, 0, true)); + 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..a7da3364a1 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -184,6 +184,11 @@ 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 flag bits of t_infomask + and t_infomask2 for heap tuples. + @@ -236,6 +241,42 @@ 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. For example: + +test=# SELECT t_ctid, heap_tuple_infomask_flags(t_infomask, t_infomask2) AS flags + FROM heap_page_items(get_raw_page('pg_class', 0)) + WHERE t_infomask IS NOT NULL OR t_infomask2 IS NOT NULL; + + This function should be called with the same arguments as the return + attributes of heap_page_items. + + + If decode_combined is true, + combined flags like HEAP_XMIN_FROZEN are + returned instead of raw flags (HEAP_XMIN_COMMITTED + and HEAP_XMIN_INVALID in this case). Default value + is false. + + + See src/include/access/htup_details.h for + explanations of the flag names returned. + + +