From 18fca9c59211dd4dc146a6e2ffbbf5200ecb0d95 Mon Sep 17 00:00:00 2001 From: Jochen Schalanda Date: Wed, 30 Jul 2025 16:06:03 +0200 Subject: [PATCH] feat(sql): implement TypeID specification 0.3.0 in typeid-sql --- typeid/typeid-sql/README.md | 21 +++++++ typeid/typeid-sql/package.json | 6 ++ typeid/typeid-sql/sql/03_typeid.sql | 27 +++++---- .../supabase/tests/03_typed_text.test.sql | 51 ++++++++++++----- .../supabase/tests/03_typeid.test.sql | 56 +++++++++++++------ 5 files changed, 118 insertions(+), 43 deletions(-) diff --git a/typeid/typeid-sql/README.md b/typeid/typeid-sql/README.md index 29f275fa..11b89d71 100644 --- a/typeid/typeid-sql/README.md +++ b/typeid/typeid-sql/README.md @@ -144,6 +144,27 @@ Then you can add in [the operator overload functions for typeid](https://github. Some users have reported issues with the above operator when using Rails and ActiveRecord – we recommend removing `COMMUTATOR` from the operator definition if you encounter issues. +## Tests + +The tests in [`supabase/tests/`](./supabase/tests/) are using [pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) as test runner. + +You need to install the development dependencies: +```sh +yarn install +``` + +Afterwards you can run the tests with the command: +```sh +yarn test +``` + +If you need to reset the database after changing the migrations in [`sql/`](./sql/), you can do so with the following command: +```sh +yarn test:reset +``` + +See also [Testing Your Database](https://supabase.com/docs/guides/database/testing) for more details. + ## Future work (contributions welcome) - Include examples not just for Postgres, but for other databases like MySQL as well. diff --git a/typeid/typeid-sql/package.json b/typeid/typeid-sql/package.json index 7a5aca17..8a052875 100644 --- a/typeid/typeid-sql/package.json +++ b/typeid/typeid-sql/package.json @@ -3,5 +3,11 @@ "version": "0.1.0", "devDependencies": { "supabase": "^1.153.4" + }, + "scripts": { + "test": "supabase start && supabase test db && supabase stop", + "test:start": "supabase start && supabase test db", + "test:stop": "supabase stop", + "test:reset": "supabase db reset" } } diff --git a/typeid/typeid-sql/sql/03_typeid.sql b/typeid/typeid-sql/sql/03_typeid.sql index b827707a..9dd38f57 100644 --- a/typeid/typeid-sql/sql/03_typeid.sql +++ b/typeid/typeid-sql/sql/03_typeid.sql @@ -8,13 +8,13 @@ create type "typeid" as ("type" varchar(63), "uuid" uuid); -- Function that generates a random typeid of the given type. --- This depends on the `uuid_generate_v7` function defined in `uuid_v7.sql`. +-- This depends on the `uuid_generate_v7` function defined in `01_uuidv7.sql`. create or replace function typeid_generate(prefix text) returns typeid as $$ begin - if (prefix is null) or not (prefix ~ '^[a-z]{0,63}$') then - raise exception 'typeid prefix must match the regular expression [a-z]{0,63}'; + if (prefix is null) or not (prefix ~ '^([a-z]([a-z_]{0,61}[a-z])?)?$') then + raise exception 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$'; end if; return (prefix, uuid_generate_v7())::typeid; end @@ -27,8 +27,8 @@ create or replace function typeid_generate_text(prefix text) returns text as $$ begin - if (prefix is null) or not (prefix ~ '^[a-z]{0,63}$') then - raise exception 'typeid prefix must match the regular expression [a-z]{0,63}'; + if (prefix is null) or not (prefix ~ '^([a-z]([a-z_]{0,61}[a-z])?)?$') then + raise exception 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$'; end if; return typeid_print((prefix, uuid_generate_v7())::typeid); end @@ -73,6 +73,7 @@ as $$ declare prefix text; suffix text; + matches text[]; begin if (typeid_str is null) then return null; @@ -80,14 +81,18 @@ begin if position('_' in typeid_str) = 0 then return ('', base32_decode(typeid_str))::typeid; end if; - prefix = split_part(typeid_str, '_', 1); - suffix = split_part(typeid_str, '_', 2); + matches = regexp_match(typeid_str, '^(.*)_(.*)$'); + if array_length(matches, 1) != 2 then + raise exception 'invalid typeid'; + end if; + prefix = matches[1]; + suffix = matches[2]; if prefix is null or prefix = '' then raise exception 'typeid prefix cannot be empty with a delimiter'; end if; -- prefix must match the regular expression [a-z]{0,63} - if not prefix ~ '^[a-z]{0,63}$' then - raise exception 'typeid prefix must match the regular expression [a-z]{0,63}'; + if not prefix ~ '^([a-z]([a-z_]{0,61}[a-z])?)?$' then + raise exception 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$'; end if; return (prefix, base32_decode(suffix))::typeid; @@ -109,8 +114,8 @@ begin end if; prefix = (tid).type; suffix = base32_encode((tid).uuid); - if (prefix is null) or not (prefix ~ '^[a-z]{0,63}$') then - raise exception 'typeid prefix must match the regular expression [a-z]{0,63}'; + if (prefix is null) or not (prefix ~ '^([a-z]([a-z_]{0,61}[a-z])?)?$') then + raise exception 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$'; end if; if prefix = '' then return suffix; diff --git a/typeid/typeid-sql/supabase/tests/03_typed_text.test.sql b/typeid/typeid-sql/supabase/tests/03_typed_text.test.sql index e6b9c077..d07a6a61 100644 --- a/typeid/typeid-sql/supabase/tests/03_typed_text.test.sql +++ b/typeid/typeid-sql/supabase/tests/03_typed_text.test.sql @@ -1,10 +1,15 @@ -- Start transaction and plan the tests. BEGIN; -SELECT plan(7); +SELECT plan(9); create table tests ( "tid" text CHECK(typeid_check_text(tid, 'generated')) ); + +create table tests_underscores ( + "tid" text CHECK(typeid_check_text(tid, 'ge_ne_ra_ted')) +); + -- -- Run tests for typeid_generate_text and typeid_check_text on tests table. -- - name: generate-text @@ -17,12 +22,40 @@ SELECT is( 'Generate typeid text with a specific prefix using typeid_generate_text' ); +-- - name: generate-text-underscore +-- typeid: "ge_ne_ra_ted_00000000000000000000000000" +-- description: "Generate a typeid with a specific prefix with multiple underscores using typeid_generate_text" +INSERT INTO tests_underscores (tid) VALUES (typeid_generate_text('ge_ne_ra_ted')); +SELECT is( + typeid_check_text((SELECT tid FROM tests_underscores), 'ge_ne_ra_ted'), + true, + 'Generate typeid text with a specific prefix with multiple underscores using typeid_generate_text' +); + -- - name: generate-text-invalid-prefix -- typeid: "12345_00000000000000000000000000" -- description: "Attempt to generate a typeid with an invalid prefix" SELECT throws_ok( $$ INSERT INTO tests (tid) VALUES (typeid_generate_text('12345')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', + 'Generate typeid text with an invalid prefix should throw an error' +); + +-- - name: generate-text-invalid-prefix-leading-underscore +-- typeid: "_generated_00000000000000000000000000" +-- description: "Attempt to generate a typeid with an invalid prefix with leading underscore" +SELECT throws_ok( + $$ INSERT INTO tests (tid) VALUES (typeid_generate_text('_generated')); $$, + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', + 'Generate typeid text with an invalid prefix should throw an error' +); + +-- - name: generate-text-invalid-prefix-trailing-underscore +-- typeid: "generated__00000000000000000000000000" +-- description: "Attempt to generate a typeid with an invalid prefix with trailing underscore" +SELECT throws_ok( + $$ INSERT INTO tests (tid) VALUES (typeid_generate_text('generated_')); $$, + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Generate typeid text with an invalid prefix should throw an error' ); @@ -43,7 +76,7 @@ SELECT is( -- INSERT INTO tests (tid) VALUES ('12345_00000000000000000000000000'); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('12345_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-underscore' ); @@ -62,20 +95,10 @@ SELECT is( -- description: "Attempt to generate a typeid with an invalid prefix" SELECT throws_ok( $$ INSERT INTO tests (tid) VALUES (typeid_generate_text('12345')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Generate typeid text with an invalid prefix should throw an error' ); --- - name: check-text-valid --- typeid: "generated_00000000000000000000000000" --- description: "Check if a generated typeid text is valid" -INSERT INTO tests (tid) VALUES (typeid_generate_text('generated')); -SELECT is( - typeid_check_text((SELECT tid FROM tests limit 1), 'generated'), - true, - 'Check if a generated typeid text is valid using typeid_check_text' -); - -- Finish the tests and clean up. SELECT * FROM finish(); diff --git a/typeid/typeid-sql/supabase/tests/03_typeid.test.sql b/typeid/typeid-sql/supabase/tests/03_typeid.test.sql index de901b87..90d38648 100644 --- a/typeid/typeid-sql/supabase/tests/03_typeid.test.sql +++ b/typeid/typeid-sql/supabase/tests/03_typeid.test.sql @@ -1,6 +1,6 @@ -- Start transaction and plan the tests. BEGIN; -SELECT plan(41); +SELECT plan(44); create table tests ( "tid" typeid @@ -113,6 +113,26 @@ SELECT is( 'Print valid: valid-alphabet' ); +-- - name: valid-prefix-underscores +-- typeid: "pre_fix_0123456789abcdefghjkmnpqrs" +-- prefix: "pre_fix" +-- uuid: "0110c853-1d09-52d8-d73e-1194e95b5f19" +SELECT is( + typeid_parse('pre_fix_0123456789abcdefghjkmnpqrs'), + ('pre_fix', '0110c853-1d09-52d8-d73e-1194e95b5f19')::typeid, + 'Parse valid: valid-prefix-underscores' +); +SELECT is( + typeid_print(('pre_fix', '0110c853-1d09-52d8-d73e-1194e95b5f19')), + 'pre_fix_0123456789abcdefghjkmnpqrs', + 'Print valid: valid-alphabet-underscores' +); +SELECT is( + typeid_print(('pre_____fix', '0110c853-1d09-52d8-d73e-1194e95b5f19')), + 'pre_____fix_0123456789abcdefghjkmnpqrs', + 'Print valid: valid-alphabet-underscores' +); + -- - name: valid-uuidv7 -- typeid: "prefix_01h455vb4pex5vsknk084sn02q" -- prefix: "prefix" @@ -135,13 +155,13 @@ SELECT is( -- description: "The prefix should be lowercase with no uppercase letters" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('PREFIX_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-uppercase' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate('PREFIX')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-uppercase' ); @@ -150,13 +170,13 @@ SELECT throws_ok( -- description: "The prefix can't have numbers, it needs to be alphabetic" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('12345_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-numeric' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate('12345')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-numeric' ); @@ -165,28 +185,28 @@ SELECT throws_ok( -- description: "The prefix can't have symbols, it needs to be alphabetic" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('pre.fix_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-period' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate('pre.fix')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-period' ); -- - name: prefix-underscore -- typeid: "pre_fix_00000000000000000000000000" --- description: "The prefix can't have symbols, it needs to be alphabetic" +-- description: "The prefix can't have leading or trailing underscores" SELECT throws_ok( - $$ INSERT into tests (tid) VALUES (typeid_parse('pre_fix_00000000000000000000000000')); $$, - 'typeid suffix must be 26 characters', + $$ INSERT into tests (tid) VALUES (typeid_generate('_prefix')); $$, + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-underscore' ); SELECT throws_ok( - $$ INSERT into tests (tid) VALUES (typeid_generate('pre_fix')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + $$ INSERT into tests (tid) VALUES (typeid_generate('prefix_')); $$, + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-underscore' ); @@ -195,13 +215,13 @@ SELECT throws_ok( -- description: "The prefix can only have ascii letters" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('préfix_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-non-ascii' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate('préfix')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-non-ascii' ); @@ -210,13 +230,13 @@ SELECT throws_ok( -- description: "The prefix can't have any spaces" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse(' prefix_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-spaces' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate(' prefix')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-spaces' ); @@ -226,13 +246,13 @@ SELECT throws_ok( -- description: "The prefix can't be 64 characters, it needs to be 63 characters or less" SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_parse('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl_00000000000000000000000000')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-64-chars' ); SELECT throws_ok( $$ INSERT into tests (tid) VALUES (typeid_generate('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl')); $$, - 'typeid prefix must match the regular expression [a-z]{0,63}', + 'typeid prefix must match the regular expression ^([a-z]([a-z_]{0,61}[a-z])?)?$', 'Parse invalid: prefix-64-chars' );