Decoding infomasks

Come on, admit it: you've always wanted to display the infomask bits from a tuple header in a human-readable manner, but you've never gotten around to it and you still keep htup.h in display while you peek around tuples.

Fortunately, that time is now past! Here's a short and simple recipe to decode the bits for your reading pleasure. Gone is the htup.h cheat sheet. Here's what you need:

create type infomask_bit_desc as (mask varbit, symbol text);

create or replace function infomask(msk int, which int) returns text
language plpgsql as $$
declare
        r infomask_bit_desc;
        str text = '';
        append_bar bool = false;
begin
        for r in select * from infomask_bits(which) loop
                if (msk::bit(16) & r.mask)::int <> 0 then
                        if append_bar then
                                str = str || '|';
                        end if;
                        append_bar = true;
                        str = str || r.symbol;
                end if;
        end loop;
        return str;
end;
$$ ;

create or replace function infomask_bits(which int)
returns setof infomask_bit_desc
language plpgsql as $$
begin
        if which = 1 then
                return query values
                (x'8000'::varbit, 'MOVED_IN'),
                (x'4000', 'MOVED_OFF'),
                (x'2000', 'UPDATED'),
                (x'1000', 'XMAX_IS_MULTI'),
                (x'0800', 'XMAX_INVALID'),
                (x'0400', 'XMAX_COMMITTED'),
                (x'0200', 'XMIN_INVALID'),
                (x'0100', 'XMIN_COMMITTED'),
                (x'0080', 'XMAX_LOCK_ONLY'),
                (x'0040', 'EXCL_LOCK'),
                (x'0020', 'COMBOCID'),
                (x'0010', 'XMAX_KEYSHR_LOCK'),
                (x'0008', 'HASOID'),
                (x'0004', 'HASEXTERNAL'),
                (x'0002', 'HASVARWIDTH'),
                (x'0001', 'HASNULL');
        elsif which = 2 then
                return query values
                (x'2000'::varbit, 'UPDATE_KEY_REVOKED'),
                (x'4000', 'HOT_UPDATED'),
                (x'8000', 'HEAP_ONLY_TUPLE');
        end if;
end;
$$;
You can now use it like this:
alvherre=# select lp, t_xmin, t_xmax, t_ctid,
       infomask(t_infomask, 1) as infomask,
       infomask(t_infomask2, 2) as infomask2
from heap_page_items(get_raw_page('foo', 0));
lp t_xmin t_xmax t_ctid infomask infomask2
1 702 2 (0,2) XMAX_IS_MULTI|XMAX_COMMITTED|XMIN_COMMITTED|XMAX_KEYSHR_LOCK UPDATE_KEY_INTACT|HOT_UPDATED
2 704 704 (0,3) UPDATED|XMAX_COMMITTED|XMIN_COMMITTED|COMBOCID UPDATE_KEY_INTACT|HOT_UPDATED|HEAP_ONLY_TUPLE
3 704 0 (0,3) UPDATED|XMAX_INVALID|XMIN_COMMITTED HEAP_ONLY_TUPLE

Okay, I cheated -- there are bits here that are not part of stock Postgres, but are part of my keylocks patch. It's trivial to edit them out (just make sure you remember to change HEAP_IS_NOT_UPDATE to HEAP_SHARE_LOCK).