Last active
April 2, 2020 13:47
-
-
Save elnygren/434bbe434e4c5cba7b94038bed9dfb30 to your computer and use it in GitHub Desktop.
PLPGSQL Order ID Calculation
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
-- needed for `gen_random_bytes` | |
CREATE EXTENSION IF NOT EXISTS pgcrypto; | |
/** | |
Generate a unique Order ID based on current_timestamp, base32 and two bits of entropy. | |
If two Orders are created at the exact same millisecond, we rely on the entropy to provide uniqueness. | |
Usage: | |
> select * from generate_order_id(); | |
generate_order_id | |
------------------- | |
01E43H2JAG | |
*/ | |
CREATE OR REPLACE FUNCTION generate_order_id() | |
RETURNS TEXT | |
AS $$ | |
DECLARE | |
-- Crockford's Base32 | |
encoding BYTEA = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; | |
timestamp BYTEA = '\\000\\000\\000\\000\\000\\000'; | |
output TEXT = ''; | |
unix_time BIGINT; | |
ulid BYTEA; | |
BEGIN | |
-- 6 timestamp bytes | |
unix_time = (EXTRACT(EPOCH FROM current_timestamp) * 1000)::BIGINT; | |
timestamp = SET_BYTE(timestamp, 0, (unix_time >> 40)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 1, (unix_time >> 32)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 2, (unix_time >> 24)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 3, (unix_time >> 16)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 4, (unix_time >> 8)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 5, unix_time::BIT(8)::INTEGER); | |
-- Entropy to make collisions rare | |
ulid = timestamp || gen_random_bytes(2); | |
-- Encode the timestamp | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 0) & 224) >> 5)); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 0) & 31))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 1) & 248) >> 3)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 1) & 7) << 2) | ((GET_BYTE(ulid, 2) & 192) >> 6))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 2) & 62) >> 1)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 2) & 1) << 4) | ((GET_BYTE(ulid, 3) & 240) >> 4))); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 3) & 15) << 1) | ((GET_BYTE(ulid, 4) & 128) >> 7))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 4) & 124) >> 2)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 4) & 3) << 3) | ((GET_BYTE(ulid, 5) & 224) >> 5))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 5) & 31))); | |
return output; | |
END | |
$$ LANGUAGE PLPGSQL; |
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
-- Mostly based on pgulid, which is based on oklog, hence the license. | |
-- | |
-- https://github.com/geckoboard/pgulid/blob/master/pgulid.sql | |
-- https://github.com/oklog/ulid | |
-- https://github.com/ulid/spec | |
-- | |
-- Copyright 2016 The Oklog Authors | |
-- Licensed under the Apache License, Version 2.0 (the "License"); | |
-- you may not use this file except in compliance with the License. | |
-- You may obtain a copy of the License at | |
-- | |
-- http://www.apache.org/licenses/LICENSE-2.0 | |
-- | |
-- Unless required by applicable law or agreed to in writing, software | |
-- distributed under the License is distributed on an "AS IS" BASIS, | |
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
-- See the License for the specific language governing permissions and | |
-- limitations under the License. | |
CREATE EXTENSION IF NOT EXISTS pgcrypto; | |
CREATE SEQUENCE IF NOT EXISTS public.order_id_gen_seq; | |
SELECT * FROM generate_order_id(); | |
CREATE OR REPLACE FUNCTION generate_order_id() | |
RETURNS TEXT | |
AS $$ | |
DECLARE | |
-- Crockford's Base32 | |
encoding BYTEA = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; | |
timestamp BYTEA = E'\\000\\000\\000\\000\\000\\000'; | |
output TEXT = ''; | |
unix_time BIGINT; | |
ulid BYTEA; | |
BEGIN | |
-- 6 timestamp bytes | |
unix_time = (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT; | |
timestamp = SET_BYTE(timestamp, 0, (unix_time >> 40)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 1, (unix_time >> 32)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 2, (unix_time >> 24)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 3, (unix_time >> 16)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 4, (unix_time >> 8)::BIT(8)::INTEGER); | |
timestamp = SET_BYTE(timestamp, 5, unix_time::BIT(8)::INTEGER); | |
ulid = timestamp; | |
-- Encode the timestamp, this is a hand optimised unrolled loop that all the ULID masters use | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 0) & 224) >> 5)); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 0) & 31))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 1) & 248) >> 3)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 1) & 7) << 2) | ((GET_BYTE(ulid, 2) & 192) >> 6))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 2) & 62) >> 1)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 2) & 1) << 4) | ((GET_BYTE(ulid, 3) & 240) >> 4))); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 3) & 15) << 1) | ((GET_BYTE(ulid, 4) & 128) >> 7))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 4) & 124) >> 2)); | |
output = output || CHR(GET_BYTE(encoding, ((GET_BYTE(ulid, 4) & 3) << 3) | ((GET_BYTE(ulid, 5) & 224) >> 5))); | |
output = output || CHR(GET_BYTE(encoding, (GET_BYTE(ulid, 5) & 31))); | |
-- add value from sequence to prevent collisions | |
output = output || nextval('order_id_gen_seq') % 99; | |
RETURN output; | |
END; | |
$$ | |
LANGUAGE plpgsql | |
VOLATILE; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
01E43H2JAG
still feels like a bit too long order ID ?