Skip to content

Commit ae84426

Browse files
authored
feat: added simple variant of typeid that uses text type (#181)
## Summary This creates a simple variant of typeid sql generation that generates text type, rather than a combined type of varchar + uuid. The reason it was done because a few database tools like SQLC were failing to correctly recognize the type from database during generation, plus it will make it easier for user to just copy the id's from the table and won't have to parse them again. ## How was it tested? I added this in Postgres Docker, and tested it there.
1 parent 2b5bb56 commit ae84426

3 files changed

Lines changed: 155 additions & 9 deletions

File tree

README.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,41 @@ implementation to work – simply use the Postgres instance of your choice.
2525
Once you've installed the TypeID types and functions in your Postgres instance,
2626
you can use it as follows.
2727

28-
To define a new type of typeid with a specific prefix use the `typeid_check` function:
28+
To define a new type of typeid with a specific prefix use the `typeid_
2929
```sql
30-
-- Define a `user_id` type, which is a typeid with type prefix "user".
31-
-- Using `user_id` throughout our schema, gives us type safety by guaranteeing
32-
-- that the type prefix is always "user".
33-
CREATE DOMAIN user_id AS typeid CHECK (typeid_check(value, 'user'));
30+
-- Define a `users` table that uses `user_id` as its primary key.
31+
-- We use the `typeid_generate` function to randomly generate a new typeid of the
32+
-- correct type for each user.
33+
CREATE TABLE users (
34+
"id" user_id not null default typeid_generate('user'),
35+
"name" text,
36+
"email" text
37+
);
38+
39+
-- Now we can insert new uses and have the `id` column automatically generated.
40+
INSERT INTO users ("name", "email") VALUES ('Alice P. Hacker', 'alice@hacker.net');
3441
```
3542

36-
You can now use the newly defined type in your tables. The `typeid_generate` function
37-
makes it possible to automatically a new random typeid for each row:
43+
Or
44+
45+
You can use the typeid_generate_text function to generate a new typeid as a string, and not use the typeid type
3846

3947
```sql
4048
-- Define a `users` table that uses `user_id` as its primary key.
41-
-- We use the `typeid_generate` function to randomly generate a new typeid of the
49+
-- We use the `typeid_generate_text` function to randomly generate a new typeid of the
4250
-- correct type for each user.
51+
-- You will need to manually add the check constraint to the column
4352
CREATE TABLE users (
44-
"id" user_id not null default typeid_generate('user'),
53+
"id" text not null default typeid_generate_text('user') CHECK (typeid_check_text(id, 'user')),
4554
"name" text,
4655
"email" text
4756
);
4857

4958
-- Now we can insert new uses and have the `id` column automatically generated.
5059
INSERT INTO users ("name", "email") VALUES ('Alice P. Hacker', 'alice@hacker.net');
60+
SELECT id FROM users;
61+
-- Result:
62+
-- "user_01hfs6amkdfem8sb6b1xmg7tq7"
5163
```
5264

5365
Note that the database internally encodes typeids as a `(prefix, uuid)` tuple. Because
@@ -68,6 +80,21 @@ SELECT typeid_print(id) AS id, "name", "email" FROM users
6880
WHERE id = typeid_parse('user_01h455vb4pex5vsknk084sn02q');
6981
```
7082

83+
or for the text variant
84+
85+
```sql
86+
-- Insert a user with a specific typeid that might have been generated elsewhere:
87+
INSERT INTO users ("id", "name", "email")
88+
VALUES ('user_01h455vb4pex5vsknk084sn02q', 'Ben Bitdiddle', 'ben@bitdiddle.com');
89+
90+
-- To retrieve the ids as encoded strings, just use the column:
91+
SELECT id AS id, "name", "email" FROM users;
92+
93+
-- You can also use filter in a WHERE clause to filter by typeid:
94+
SELECT typeid_print(id) AS id, "name", "email" FROM users
95+
WHERE id = 'user_01h455vb4pex5vsknk084sn02q';
96+
```
97+
7198
## (Optional) Operator overload
7299
If you'd like to be able to do the following:
73100

sql/03_typeid.sql

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ $$
2323
language plpgsql
2424
volatile;
2525

26+
-- Function that generates a type_id of given type, and returns the parsed typeid as text.
27+
create or replace function typeid_generate_text(prefix text)
28+
returns text
29+
as $$
30+
begin
31+
if (prefix is null) or not (prefix ~ '^[a-z]{0,63}$') then
32+
raise exception 'typeid prefix must match the regular expression [a-z]{0,63}';
33+
end if;
34+
return typeid_print((prefix, uuid_generate_v7())::typeid);
35+
end
36+
$$
37+
language plpgsql
38+
volatile;
39+
2640
-- Function that checks if a typeid is valid, for the given type prefix.
2741
-- It also enforces that the UUID is a v7 UUID.
2842
-- NOTE: we might want to make the version check optional.
@@ -46,6 +60,29 @@ $$
4660
language plpgsql
4761
immutable;
4862

63+
-- Function that checks if a typeid is valid, for the given type_id in text format and type prefix, returns boolean.
64+
-- It also enforces that the UUID is a v7 UUID.
65+
create or replace function typeid_check_text(type_id text, expected_type text)
66+
returns boolean
67+
as $$
68+
declare
69+
prefix text;
70+
tid typeid;
71+
bytes bytea;
72+
ver int;
73+
begin
74+
tid = typeid_parse(type_id);
75+
prefix = (tid).type;
76+
bytes = uuid_send((tid).uuid);
77+
ver = (get_byte(bytes, 6) >> 4)::bit(4)::int;
78+
-- Check that:
79+
-- + The prefix matches the expected type
80+
-- + The UUID version is 7 OR it's the special "nil" UUID
81+
return prefix = expected_type AND (ver = 7 OR (tid).uuid = '00000000-0000-0000-0000-000000000000');
82+
end
83+
$$
84+
language plpgsql
85+
immutable;
4986

5087
-- Function that parses a string into a typeid.
5188
create or replace function typeid_parse(typeid_str text)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-- Start transaction and plan the tests.
2+
BEGIN;
3+
SELECT plan(7);
4+
5+
create table tests (
6+
"tid" text Check(typeid_check_text(tid, 'generated'))
7+
);
8+
-- -- Run tests for typeid_generate_text and typeid_check_text on tests table.
9+
10+
-- - name: generate-text
11+
-- typeid: "generated_00000000000000000000000000"
12+
-- description: "Generate a typeid with a specific prefix using typeid_generate_text"
13+
INSERT INTO tests (tid) VALUES (typeid_generate_text('generated'));
14+
SELECT is(
15+
typeid_check_text((SELECT tid FROM tests), 'generated'),
16+
true,
17+
'Generate typeid text with a specific prefix using typeid_generate_text'
18+
);
19+
20+
-- - name: generate-text-invalid-prefix
21+
-- typeid: "12345_00000000000000000000000000"
22+
-- description: "Attempt to generate a typeid with an invalid prefix"
23+
SELECT throws_ok(
24+
$$ INSERT INTO tests (tid) VALUES (typeid_generate_text('12345')); $$,
25+
'typeid prefix must match the regular expression [a-z]{0,63}',
26+
'Generate typeid text with an invalid prefix should throw an error'
27+
);
28+
29+
-- - name: check-text-valid
30+
-- typeid: "generated_00000000000000000000000000"
31+
-- description: "Check if a generated typeid text is valid"
32+
INSERT INTO tests (tid) VALUES (typeid_generate_text('generated'));
33+
SELECT is(
34+
typeid_check_text((SELECT tid FROM tests limit
35+
1), 'generated'),
36+
true,
37+
'Check if a generated typeid text is valid using typeid_check_text'
38+
);
39+
40+
-- - name: check-text-invalid-prefix
41+
-- typeid: "12345_00000000000000000000000000"
42+
-- description: "Check if a typeid text with an invalid prefix is invalid"
43+
-- INSERT INTO tests (tid) VALUES ('12345_00000000000000000000000000');
44+
SELECT throws_ok(
45+
$$ INSERT into tests (tid) VALUES (typeid_parse('12345_00000000000000000000000000')); $$,
46+
'typeid prefix must match the regular expression [a-z]{0,63}',
47+
'Parse invalid: prefix-underscore'
48+
);
49+
50+
-- - name: generate-text
51+
-- typeid: "generated_00000000000000000000000000"
52+
-- description: "Generate a typeid with a specific prefix using typeid_generate_text"
53+
INSERT INTO tests (tid) VALUES (typeid_generate_text('generated'));
54+
SELECT is(
55+
typeid_check_text((SELECT tid FROM tests limit 1), 'generated'),
56+
true,
57+
'Generate typeid text with a specific prefix using typeid_generate_text'
58+
);
59+
60+
-- - name: generate-text-invalid-prefix
61+
-- typeid: "12345_00000000000000000000000000"
62+
-- description: "Attempt to generate a typeid with an invalid prefix"
63+
SELECT throws_ok(
64+
$$ INSERT INTO tests (tid) VALUES (typeid_generate_text('12345')); $$,
65+
'typeid prefix must match the regular expression [a-z]{0,63}',
66+
'Generate typeid text with an invalid prefix should throw an error'
67+
);
68+
69+
-- - name: check-text-valid
70+
-- typeid: "generated_00000000000000000000000000"
71+
-- description: "Check if a generated typeid text is valid"
72+
INSERT INTO tests (tid) VALUES (typeid_generate_text('generated'));
73+
SELECT is(
74+
typeid_check_text((SELECT tid FROM tests limit 1), 'generated'),
75+
true,
76+
'Check if a generated typeid text is valid using typeid_check_text'
77+
);
78+
79+
80+
-- Finish the tests and clean up.
81+
SELECT * FROM finish();
82+
ROLLBACK;

0 commit comments

Comments
 (0)