PostgreSQL Bitwise operators with bit varying “cannot AND bit strings of different sizes”

江枫思渺然 提交于 2020-12-30 07:49:11

问题


I have a varying bitmask field and I want to perform a bitwise AND on it.

PG::Error: ERROR:  cannot AND bit strings of different sizes
SELECT "groups".* FROM "groups"  WHERE (read_roles_bitmask = B'0' OR read_roles_bitmask & B'10' > B'0')

( you need to have bitmasks with varying lengths in your table to get this error. )

I'm expecting the bitwise math to look like the following: 00010 & 100000010 = 00010

I've also tried casting the bitmask to a integer with no luck.

Why does PostgreSQL choke on this?

How should I rewrite this query to play nicely?

I was able to use the following to get bitwise operators working: lpad(read_roles_bitmask::varchar,64,'0')::bigint

However this is limited to 64 bits, is there a better way?


回答1:


The behaviour of the PostgreSQL bit and bit varying types is exceedingly unhelpful, with the way it refuses to extend bitfields for operations, and it right-extends them for casts instead of left-extending them.

It would make sense for Pg to left-extend the smaller operand with zeroes before an AND or OR operation, rather than failing.

You can't use a cast to bit(n) to get the same lengths, because for some insane reason a cast to bit(n) right-pads the argument, making it useless in almost all situations.

You can use something like lpad($1::text, greatest(length($1), length($2)),'0')::bit varying to left-extend a bit field with zeroes to the greater of two lengths. It's cumbersome, but it'll work. I'd recommend writing wrapper functions to contain the mess.

Alternately, consider modifying the bit support code in src/backend/utils/adt/varbit.c to add functions to left-extend and left-truncate bit fields, and functions to do left-extending comparisons. It should be pretty easy based on the existing code.




回答2:


I had a similar issue today. I wanted to do almost exactly the same thing: mask off the least significant two bits of a bitstring and compare the result with a literal value, like this:

status & b'11' > b'01'

(status was my bit varying column).

Initially I tried using Craig's solution, but it got pretty messy pretty quickly because not only does the mask have to be left extended, so does the value I was comparing the result with, since according to postgresql:

t2=> select b'0010' < b'01';
 ?column?
----------
 t
(1 row)

The RHS is right padded to make it the same size as the LHS before applying the < operation.

In the end I solved it like this:

(status << length(status)-2)::bit(2) > b'01'

The nice thing about that is it allows you to extract any set of bits for comparison. For example to get the pair of bits 3rd from the left:

(status << length(status)-6)::bit(2)

You can also use substring to extract an arbitrary set of bits for comparison.




回答3:


1) as mentioned in other answers -

postgres has has some inconvenient/counterintuitive behaviors - 
right padding when casting to bit(n), 
bitwise ops only on similar size,
etc.

2) one workaround is -

double-casting every value - to integer and then to bit(XX)

pros:

- left vs right padding works correctly 
- all the bit-strings have same length for correct bitwise operations
- comparisons work correctly
- bit-masking/casting to get least significant bits

examples:

basic left padding:

select B'0010'::int::bit(22)
0000000000000000000010

bitwise ops:

select B'0010'::int::bit(22) | B'01'::int::bit(22)
0000000000000000000011

comparison:

select B'0010'::int::bit(22) > B'01'::int::bit(22)
true

bit-masking/casting to get three LEAST significant bits:

select B'11010'::int::bit(3)
010

bit-masking/casting to get three MOST significant bits:

select B'11010'::bit(3)
110

UPDATE: use int8 to accommodate longer bit-strings



来源:https://stackoverflow.com/questions/13694129/postgresql-bitwise-operators-with-bit-varying-cannot-and-bit-strings-of-differe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!