mirror of
https://github.com/postgres/postgres.git
synced 2026-03-28 21:33:14 -04:00
This adds support for base32hex encoding and decoding, as defined in RFC 4648 Section 7. Unlike standard base32, base32hex uses the extended hex alphabet (0-9, A-V) which preserves the lexicographical order of the encoded data. This is particularly useful for representing UUIDv7 values in a compact string format while maintaining their time-ordered sort property. The encode() function produces output padded with '=', while decode() accepts both padded and unpadded input. Following the behavior of other encoding types, decoding is case-insensitive. Suggested-by: Sergey Prokhorenko <sergeyprokhorenko@yahoo.com.au> Author: Andrey Borodin <x4mmm@yandex-team.ru> Co-authored-by: Aleksander Alekseev <aleksander@tigerdata.com> Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com> Reviewed-by: Илья Чердаков <i.cherdakov.pg@gmail.com> Reviewed-by: Chengxi Sun <chengxisun92@gmail.com> Reviewed-by: Chao Li <li.evan.chao@gmail.com> Discussion: https://postgr.es/m/CAJ7c6TOramr1UTLcyB128LWMqita1Y7%3Darq3KHaU%3Dqikf5yKOQ%40mail.gmail.com
162 lines
6.4 KiB
SQL
162 lines
6.4 KiB
SQL
-- regression test for the uuid datatype
|
|
-- creating test tables
|
|
CREATE TABLE guid1
|
|
(
|
|
guid_field UUID,
|
|
text_field TEXT DEFAULT(now())
|
|
);
|
|
CREATE TABLE guid2
|
|
(
|
|
guid_field UUID,
|
|
text_field TEXT DEFAULT(now())
|
|
);
|
|
CREATE TABLE guid3
|
|
(
|
|
id SERIAL,
|
|
guid_field UUID,
|
|
guid_encoded text GENERATED ALWAYS AS (encode(guid_field::bytea, 'base32hex')) STORED
|
|
);
|
|
|
|
-- inserting invalid data tests
|
|
-- too long
|
|
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111F');
|
|
-- too short
|
|
INSERT INTO guid1(guid_field) VALUES('{11111111-1111-1111-1111-11111111111}');
|
|
-- valid data but invalid format
|
|
INSERT INTO guid1(guid_field) VALUES('111-11111-1111-1111-1111-111111111111');
|
|
INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222 ');
|
|
-- invalid data
|
|
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-G111-111111111111');
|
|
INSERT INTO guid1(guid_field) VALUES('11+11111-1111-1111-1111-111111111111');
|
|
|
|
-- test non-error-throwing API
|
|
SELECT pg_input_is_valid('11', 'uuid');
|
|
SELECT * FROM pg_input_error_info('11', 'uuid');
|
|
|
|
--inserting three input formats
|
|
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
|
|
INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}');
|
|
INSERT INTO guid1(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e');
|
|
|
|
-- retrieving the inserted data
|
|
SELECT guid_field FROM guid1;
|
|
|
|
-- ordering test
|
|
SELECT guid_field FROM guid1 ORDER BY guid_field ASC;
|
|
SELECT guid_field FROM guid1 ORDER BY guid_field DESC;
|
|
|
|
-- = operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e';
|
|
|
|
-- <> operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111';
|
|
|
|
-- < operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field < '22222222-2222-2222-2222-222222222222';
|
|
|
|
-- <= operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222';
|
|
|
|
-- > operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field > '22222222-2222-2222-2222-222222222222';
|
|
|
|
-- >= operator test
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field >= '22222222-2222-2222-2222-222222222222';
|
|
|
|
-- btree and hash index creation test
|
|
CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
|
|
CREATE INDEX guid1_hash ON guid1 USING HASH (guid_field);
|
|
|
|
-- unique index test
|
|
CREATE UNIQUE INDEX guid1_unique_BTREE ON guid1 USING BTREE (guid_field);
|
|
|
|
EXPLAIN (COSTS OFF)
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111' OR
|
|
guid_field <> '3f3e3c3b-3a30-3938-3736-353433a2313e';
|
|
EXPLAIN (COSTS OFF)
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222' OR
|
|
guid_field <= '11111111111111111111111111111111' OR
|
|
guid_field <= '3f3e3c3b-3a30-3938-3736-353433a2313e';
|
|
EXPLAIN (COSTS OFF)
|
|
SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e' OR
|
|
guid_field = '11111111111111111111111111111111';
|
|
|
|
-- should fail
|
|
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
|
|
|
|
-- check to see whether the new indexes are actually there
|
|
SELECT count(*) FROM pg_class WHERE relkind='i' AND relname LIKE 'guid%';
|
|
|
|
-- populating the test tables with additional records
|
|
INSERT INTO guid1(guid_field) VALUES('44444444-4444-4444-4444-444444444444');
|
|
INSERT INTO guid2(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
|
|
INSERT INTO guid2(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}');
|
|
INSERT INTO guid2(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e');
|
|
|
|
-- join test
|
|
SELECT COUNT(*) FROM guid1 g1 INNER JOIN guid2 g2 ON g1.guid_field = g2.guid_field;
|
|
SELECT COUNT(*) FROM guid1 g1 LEFT JOIN guid2 g2 ON g1.guid_field = g2.guid_field WHERE g2.guid_field IS NULL;
|
|
|
|
-- generation test
|
|
TRUNCATE guid1;
|
|
INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
|
|
INSERT INTO guid1 (guid_field) VALUES (gen_random_uuid());
|
|
SELECT count(DISTINCT guid_field) FROM guid1;
|
|
|
|
-- test of uuidv4() alias
|
|
TRUNCATE guid1;
|
|
INSERT INTO guid1 (guid_field) VALUES (uuidv4());
|
|
INSERT INTO guid1 (guid_field) VALUES (uuidv4());
|
|
SELECT count(DISTINCT guid_field) FROM guid1;
|
|
|
|
-- generation test for v7
|
|
TRUNCATE guid1;
|
|
INSERT INTO guid1 (guid_field) VALUES (uuidv7());
|
|
INSERT INTO guid1 (guid_field) VALUES (uuidv7());
|
|
INSERT INTO guid1 (guid_field) VALUES (uuidv7(INTERVAL '1 day'));
|
|
SELECT count(DISTINCT guid_field) FROM guid1;
|
|
|
|
-- test sortability of v7
|
|
INSERT INTO guid3 (guid_field) VALUES ('00000000-0000-0000-0000-000000000000'::uuid);
|
|
INSERT INTO guid3 (guid_field) SELECT uuidv7() FROM generate_series(1, 10);
|
|
INSERT INTO guid3 (guid_field) VALUES ('ffffffff-ffff-ffff-ffff-ffffffffffff'::uuid);
|
|
SELECT array_agg(id ORDER BY guid_field) FROM guid3;
|
|
|
|
-- make sure base32hex encoding works with UUIDs and preserves ordering
|
|
SELECT array_agg(id ORDER BY guid_encoded) FROM guid3;
|
|
|
|
-- Check the timestamp offsets for v7.
|
|
--
|
|
-- generate UUIDv7 values with timestamps ranging from 1970 (the Unix epoch year)
|
|
-- to 10888 (one year before the maximum possible year), and then verify that
|
|
-- the extracted timestamps from these UUIDv7 values have not overflowed.
|
|
WITH uuidts AS (
|
|
SELECT y, ts as ts, lag(ts) OVER (ORDER BY y) AS prev_ts
|
|
FROM (SELECT y, uuid_extract_timestamp(uuidv7((y || ' years')::interval)) AS ts
|
|
FROM generate_series(1970 - extract(year from now())::int, 10888 - extract(year from now())::int) y)
|
|
)
|
|
SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts;
|
|
|
|
-- extract functions
|
|
|
|
-- version
|
|
SELECT uuid_extract_version('11111111-1111-5111-8111-111111111111'); -- 5
|
|
SELECT uuid_extract_version(gen_random_uuid()); -- 4
|
|
SELECT uuid_extract_version('11111111-1111-1111-1111-111111111111'); -- null
|
|
SELECT uuid_extract_version(uuidv4()); -- 4
|
|
SELECT uuid_extract_version(uuidv7()); -- 7
|
|
|
|
-- timestamp
|
|
SELECT uuid_extract_timestamp('C232AB00-9414-11EC-B3C8-9F6BDECED846') = 'Tuesday, February 22, 2022 2:22:22.00 PM GMT+05:00'; -- RFC 9562 test vector for v1
|
|
SELECT uuid_extract_timestamp('017F22E2-79B0-7CC3-98C4-DC0C0C07398F') = 'Tuesday, February 22, 2022 2:22:22.00 PM GMT+05:00'; -- RFC 9562 test vector for v7
|
|
SELECT uuid_extract_timestamp(gen_random_uuid()); -- null
|
|
SELECT uuid_extract_timestamp('11111111-1111-1111-1111-111111111111'); -- null
|
|
|
|
-- casts
|
|
SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid::bytea;
|
|
SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
|
|
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
|
|
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
|
|
|
|
-- clean up
|
|
DROP TABLE guid1, guid2, guid3 CASCADE;
|