Why unsigned integer is not available in PostgreSQL?

Viewed 105159
6 Answers

The talk about DOMAINS is interesting but not relevant to the only possible origin of that question. The desire for unsigned ints is to double the range of ints with the same number of bits, it's an efficiency argument, not the desire to exclude negative numbers, everybody knows how to add a check constraint.

When asked by someone about it, Tome Lane stated:

Basically, there is zero chance this will happen unless you can find a way of fitting them into the numeric promotion hierarchy that doesn't break a lot of existing applications. We have looked at this more than once, if memory serves, and failed to come up with a workable design that didn't seem to violate the POLA.

What is the "POLA"? Google gave me 10 results that are meaningless. Not sure if it's politically incorrect thought and therefore censored. Why would this search term not yield any result? Whatever.

You can implement unsigned ints as extension types without too much trouble. If you do it with C-functions, then there will be about no performance penalties at all. You won't need to extend the parser to deal with literals because PgSQL has such an easy way to interpret strings as literals, just write '4294966272'::uint4 as your literals. Casts shouldn't be a huge deal either. You don't even need to do range exceptions, you can just treat the semantics of '4294966273'::uint4::int as -1024. Or you can throw an error.

If I wanted this, I would have done it. But since I'm using Java on the other side of SQL, to me it is of little value since Java doesn't have those unsigned integers either. So I gain nothing. I'm already annoyed if I get a BigInteger from a bigint column, when it should fit into long.

Another thing, if I did have the need to store 32 bit or 64 bit types, I can use PostgreSQL int4 or int8 respectively, just remembering that the natural order or arithmetic won't work reliably. But storing and retrieving is unaffected by that.


Here is how I can implement a simple unsigned int8:

First I will use

CREATE TYPE name (
    INPUT = uint8_in,
    OUTPUT = uint8_out
    [, RECEIVE = uint8_receive ]
    [, SEND = uint8_send ]
    [, ANALYZE = uint8_analyze ]
    , INTERNALLENGTH = 8
    , PASSEDBYVALUE ]
    , ALIGNMENT = 8
    , STORAGE = plain
    , CATEGORY = N
    , PREFERRED = false
    , DEFAULT = null
)

the minimal 2 functions uint8_in and uint8_out I must first define.

CREATE FUNCTION uint8_in(cstring)
    RETURNS uint8
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION uint64_out(complex)
    RETURNS cstring
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

need to implement this in C uint8_funcs.c. So I go use the complex example from here and make it simple:

PG_FUNCTION_INFO_V1(complex_in);

Datum complex_in(PG_FUNCTION_ARGS) {
    char       *str = PG_GETARG_CSTRING(0);
    uint64_t   result;

    if(sscanf(str, "%llx" , &result) != 1)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for uint8: \"%s\"", str)));

    return (Datum)SET_8_BYTES(result);
}

ah well, or you can just find it done already.

According to the latest documentation, the signed integer is supported but no unsigned integer in the table. However, the serial type is kind of similar to unsigned except it starts from 1 not from zero. But the upper limit is the same as signed. So the system truly does not have unsigned support. As pointed out by Peter, the door is open to implement the unsigned version. The code may have to be updated a lot, just too much work from my experience working with C programming.

https://www.postgresql.org/docs/10/datatype-numeric.html

integer     4 bytes     typical choice for integer  -2147483648 to +2147483647
serial      4 bytes     autoincrementing integer    1 to 2147483647

Postgres does have an unsigned integer type that is unbeknownst to many: OID.

The oid type is currently implemented as an unsigned four-byte integer. […]

The oid type itself has few operations beyond comparison. It can be cast to integer, however, and then manipulated using the standard integer operators. (Beware of possible signed-versus-unsigned confusion if you do this.)

It is not a numeric type though, and trying to do any arithmetic (or even bitwise operations) with it is going to fail. Also, it's just 4 bytes (INTEGER), there is no corresponding 8 byte (BIGINT) unsigned type.

So it's not really a good idea to use this yourself, and I agree with all the other answers that in a Postgresql database design you should always use an INTEGER or BIGINT column for your serial primary key - having it start in the negative (MINVALUE) or allowing it to wrap around (CYCLE) if you want to exhaust the full domain.

However, it is quite useful for input/output conversion, like your migration from another DBMS. Inserting the value 2147483648 into an integer column will lead to an "ERROR: integer out of range", while using the expression 2147483648::OID works just fine.
Similarly, when selecting an integer column as text with mycolumn::TEXT, you will get negative values at some point, but with mycolumn::OID::TEXT you will always get a natural number.

See an example at dbfiddle.uk.

Related