-
-
Save fabiolimace/e3c3d354d1afe0b3175f65be2d962523 to your computer and use it in GitHub Desktop.
DROP VIEW IF EXISTS uuid7; | |
CREATE VIEW uuid7 AS | |
WITH unixtime AS ( | |
SELECT CAST((STRFTIME('%s') * 1000) + ((STRFTIME('%f') * 1000) % 1000) AS INTEGER) AS time | |
-- SELECT CAST((UNIXEPOCH('subsec') * 1000) AS INTEGER) AS time -- for SQLite v3.38.0 (2022) | |
) | |
SELECT PRINTF('%08x-%04x-%04x-%04x-%012x', | |
(select time from unixtime) >> 16, | |
(select time from unixtime) & 0xffff, | |
ABS(RANDOM()) % 0x0fff + 0x7000, | |
ABS(RANDOM()) % 0x3fff + 0x8000, | |
ABS(RANDOM()) >> 16) AS next; | |
-- EXAMPLE: | |
-- | |
-- sqlite> SELECT next FROM uuid7; | |
-- 01901973-f202-71ca-9a22-14e7146dab85 | |
-- | |
-- DROP TABLE IF EXISTS my_table; | |
CREATE TABLE my_table ( | |
uuid TEXT PRIMARY KEY, | |
created DATE NOT NULL, | |
content TEXT NOT NULL, | |
CONSTRAINT check_my_table_uuid CHECK (uuid REGEXP '[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}') | |
); | |
DROP TRIGGER IF EXISTS trigger_after_insert_on_my_table; | |
CREATE TRIGGER trigger_after_insert_on_my_table | |
AFTER INSERT ON my_table FOR EACH ROW WHEN NEW.uuid IS NULL | |
BEGIN | |
UPDATE my_table SET uuid = (SELECT next FROM uuid7) WHERE ROWID = NEW.ROWID; | |
END; | |
-- NOTES: | |
-- 1. The trigger generates a UUIDv7 when none is provided in the insert statement. | |
-- 2. The check constraint verifies if the provided UUID is valid on updates and inserts. | |
-- 3. Unfortunately, we can't use a sub-query as DEFAULT value for the PK. That's why we use the trigger. | |
-- Insert some lines using the trigger | |
INSERT INTO my_table VALUES(NULL, DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES(NULL, DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES(NULL, DATETIME(), 'Hello, World!'); | |
-- Insert more lines using the view (ignoring the trigger) | |
INSERT INTO my_table VALUES((SELECT next FROM uuid7), DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES((SELECT next FROM uuid7), DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES((SELECT next FROM uuid7), DATETIME(), 'Hello, World!'); | |
-- Insert more lines providing UUIDs (also ignoring the trigger) | |
INSERT INTO my_table VALUES('99f30c47-d5be-4803-9f2b-95c232f89877', DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES('d38ae560-321f-408c-a70b-a5ad8f27d346', DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES('c78a27ec-b065-4af1-9619-7a676d34275a', DATETIME(), 'Hello, World!'); | |
-- Try to insert lines with invalid UUIDs; they must throw check error | |
INSERT INTO my_table VALUES('', DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES('asdfasdfasdf', DATETIME(), 'Hello, World!'); | |
INSERT INTO my_table VALUES('0d56252d-51a6-4d1b-8ddc----------', DATETIME(), 'Hello, World!'); | |
SELECT * FROM my_table; | |
-- 01901a7a-3f0b-714e-b696-6d48d23a262e|2024-06-15 05:58:57|Hello, World! | |
-- 01901a7a-3f0b-76fb-82cd-17b01c4e7cfe|2024-06-15 05:58:57|Hello, World! | |
-- 01901a7a-3f0b-7a42-a03b-3055c708f422|2024-06-15 05:58:57|Hello, World! | |
-- 01901a7a-3f0b-7ad1-b2b9-0d4ead036759|2024-06-15 05:58:57|Hello, World! | |
-- 01901a7a-3f0b-7076-ae04-693bd8f276aa|2024-06-15 05:58:57|Hello, World! | |
-- 01901a7a-3f0c-7e05-bae2-4792b2e3192b|2024-06-15 05:58:57|Hello, World! | |
-- 99f30c47-d5be-4803-9f2b-95c232f89877|2024-06-15 05:58:57|Hello, World! | |
-- d38ae560-321f-408c-a70b-a5ad8f27d346|2024-06-15 05:58:57|Hello, World! | |
-- c78a27ec-b065-4af1-9619-7a676d34275a|2024-06-15 05:58:57|Hello, World! | |
Hi @brettinternet !
Unfortunately we can't use a BEFORE INSERT
trigger to populate the UUID field before the insert. That's why we use the AFTER INSERT
.
In the example, the primary key has no NOT NULL
constraint in the UUID field. The trigger catches inserts with null UUIDs and replaces it with the next value generated in the view.
The insert trigger acts like a DEFAULT
keyword as in this fake snippet:
CREATE TABLE my_table (
uuid TEXT PRIMARY KEY DEFAULT (SELECT next FROM uuid7), ...
If you're using the NOT NULL
constraint in the UUID field, you can't use the insert trigger. You can only do insert specifying a non-null UUID:
INSERT INTO my_table VALUES((SELECT next FROM uuid7), DATETIME(), 'Hello, World!');
Let me know if you find a way to have both the trigger and the NOT NULL
constraint. I've searched a lot but couldn't find a way of doing it.
Thanks for your feedback!
Two updates:
- Replace
FORMAT
withPRINTF
for older versions of SQLite, e.g. SQLite version 3.37.2 2022-01-06 13:25:41. - Add
FOR EACH ROW
in trigger definition, which is optional, but it's also a good practice.
Hi @fabiolimace, I see now. Thanks for sharing this and explaining your solution!
This doesn't work for me. I get this error: