Last active
March 30, 2020 13:45
-
-
Save hmarr/0b5cdf8700876c56ae7e to your computer and use it in GitHub Desktop.
Random-looking id generation in Postgres
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE OR REPLACE FUNCTION base32_encode(n bigint, min_width int DEFAULT 8) RETURNS text AS $$ | |
DECLARE | |
alphabet CONSTANT text := '0123456789abcdefghjkmnpqrstvwxyz'; | |
output text := ''; | |
BEGIN | |
IF n < 0 THEN | |
RAISE EXCEPTION 'base32_encode called with negative value'; | |
END IF; | |
WHILE n != 0 LOOP | |
output := substr(alphabet, 1 + (n % 32)::int, 1) || output; | |
n := n / 32; | |
END LOOP; | |
IF char_length(output) < min_width THEN | |
output := lpad(output, min_width, '0'); | |
END IF; | |
RETURN output; | |
END; | |
$$ LANGUAGE plpgsql IMMUTABLE STRICT; | |
CREATE OR REPLACE FUNCTION feistel_cipher(value bigint, key int DEFAULT 1065940451) returns bigint AS $$ | |
DECLARE | |
-- Constants that control the size of the input and output. | |
num_bits CONSTANT int := 38; | |
max_piece_val CONSTANT bigint := power(2, num_bits / 2)::bigint - 1; | |
-- Constants for the round function (a linear congruential generator) | |
round_f_inc CONSTANT bigint := key % max_piece_val; | |
round_f_mul CONSTANT bigint := (power(2, 31) - key)::bigint % max_piece_val; | |
l1 bigint; | |
l2 bigint; | |
r1 bigint; | |
r2 bigint; | |
round int := 0; | |
BEGIN | |
-- Pull out the n/2 most significant bits by shifting and masking | |
l1 := (value >> (num_bits / 2)) & max_piece_val; | |
-- Pull out the n/2 least significant bits by masking | |
r1 := value & max_piece_val; | |
WHILE round < 3 LOOP | |
l2 := r1; | |
r2 := l1 # ((round_f_mul * r1 + round_f_inc) % max_piece_val)::bigint; | |
l1 := l2; | |
r1 := r2; | |
round := round + 1; | |
END LOOP; | |
RETURN ((l1::bigint << (num_bits / 2)) + r1); | |
END; | |
$$ LANGUAGE plpgsql STRICT IMMUTABLE; | |
CREATE OR REPLACE FUNCTION generate_id(seqname text) RETURNS text AS $$ | |
DECLARE | |
seqval bigint; | |
BEGIN | |
SELECT nextval(seqname) INTO seqval; | |
RETURN base32_encode(feistel_cipher(seqval, hashtext(seqname))); | |
END; | |
$$ LANGUAGE plpgsql STRICT IMMUTABLE; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment