# Decoding infomasks

> Come on, admit it: you&#x27;ve always wanted to display the infomask bits from a tuple header in a human-readable manner, but you&#x27;ve never gotten around to it and y…

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`).

---
[View this page online](https://www.commandprompt.com/blog/decoding_infomasks/)