Skip to content

Commit

Permalink
feat: uuid - support for UUIDv7 (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
nghduc97 authored Jun 12, 2024
1 parent 425c60f commit 1ba95e4
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 12 deletions.
34 changes: 33 additions & 1 deletion docs/uuid.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# uuid: Universally Unique IDentifiers (UUIDs) in SQLite

Limited support for [RFC 4122](https://www.ietf.org/rfc/rfc4122.txt) compliant UUIDs:
Limited support for [RFC 4122](https://www.ietf.org/rfc/rfc4122.txt) and [RFC 9562](https://datatracker.ietf.org/doc/rfc9562/) compliant UUIDs:

- Generate a version 4 (random) UUID.
- Generate a version 7 (time-ordered, random) UUID.
- Convert a 16-byte blob into a well-formed UUID string and vice versa.

Adapted from [uuid.c](https://sqlite.org/src/file/ext/misc/uuid.c) by D. Richard Hipp.
Expand All @@ -21,6 +22,18 @@ sqlite> select gen_random_uuid();
8d144638-3baf-4901-a554-b541142c152b
```

<h3 name="uuid7"><code>uuid7()</code></h3>

Generate a version 7 (time-ordered, random) UUID.

```
sqlite> select uuid7();
018ff383-3e37-7615-b764-c241f544e573
sqlite> select uuid7();
018ff383-94fd-70fa-8da6-339180b8e15d
```

<h3 name="uuid_str"><code>uuid_str(X)</code></h3>

Converts a UUID `X` into a well-formed UUID string. `X` can be either a string or a blob.
Expand All @@ -39,13 +52,32 @@ sqlite> select hex(uuid_blob(uuid4()));
7192B1B452964E809500CF0364476CD3
```

<h3 name="uuid7_timestamp_ms"><code>uuid7_timestamp_ms(X)</code></h3>

Extract unix timestamp in miliseconds from version 7 UUID `X`. Returns `NULL` if the detected UUID version is not 7.

```
sqlite> SELECT uuid7();
018ff38a-a5c9-712d-bc80-0550b3ad41a2
sqlite> SELECT uuid7_timestamp_ms('018ff38a-a5c9-712d-bc80-0550b3ad41a2');
1717777901001
sqlite> SELECT datetime(uuid7_timestamp_ms('018ff38a-a5c9-712d-bc80-0550b3ad41a2') / 1000, 'unixepoch');
2024-06-07 16:31:41
sqlite> SELECT uuid7_timestamp_ms(uuid4()) IS NULL;
1
```

## Installation and Usage

SQLite command-line interface:

```
sqlite> .load ./uuid
sqlite> select uuid4();
sqlite> select uuid7();
```

See [How to Install an Extension](install.md) for usage with IDE, Python, etc.
Expand Down
76 changes: 66 additions & 10 deletions src/uuid/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
// Universally Unique IDentifiers (UUIDs) in SQLite

/*
* This SQLite extension implements functions that handling RFC-4122 UUIDs
* Three SQL functions are implemented:
* This SQLite extension implements functions that handling RFC-4122 for UUIDv4
* and RFC-9562 for UUIDv7
*
* uuid4() - generate a version 4 UUID as a string
* uuid_str(X) - convert a UUID X into a well-formed UUID string
* uuid_blob(X) - convert a UUID X into a 16-byte blob
* Five SQL functions are implemented:
*
* The output from uuid4() and uuid_str(X) are always well-formed RFC-4122
* uuid4() - generate a version 4 UUID as a string
* uuid7() - generate a version 7 UUID as a string
* uuid_str(X) - convert a UUID X into a well-formed UUID string
* uuid_blob(X) - convert a UUID X into a 16-byte blob
* uuid7_timestamp_ms(X) - extract unix timestamp in miliseconds
* from version 7 UUID X.
*
* The output from uuid4(), uuid7() and uuid_str(X) are always well-formed RFC-4122
* UUID strings in this format:
*
* xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Expand Down Expand Up @@ -58,6 +63,7 @@
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>

#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT3
Expand Down Expand Up @@ -158,9 +164,9 @@ static const unsigned char* sqlite3_uuid_input_to_blob(sqlite3_value* pIn, /* In
}

/*
* uuid_generate generates a version 4 UUID as a string
* uuid_v4_generate generates a version 4 UUID as a string
*/
static void uuid_generate(sqlite3_context* context, int argc, sqlite3_value** argv) {
static void uuid_v4_generate(sqlite3_context* context, int argc, sqlite3_value** argv) {
unsigned char aBlob[16];
unsigned char zStr[37];
(void)argc;
Expand All @@ -172,6 +178,54 @@ static void uuid_generate(sqlite3_context* context, int argc, sqlite3_value** ar
sqlite3_result_text(context, (char*)zStr, 36, SQLITE_TRANSIENT);
}

/*
* uuid_v7_generate generates a version 7 UUID as a string
*/
static void uuid_v7_generate(sqlite3_context* context, int argc, sqlite3_value** argv) {
unsigned char aBlob[16];
unsigned char zStr[37];
(void)argc;
(void)argv;

struct timespec ts;
timespec_get(&ts, TIME_UTC);
unsigned long long timestampMs = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000;

sqlite3_randomness(16, aBlob);
aBlob[0] = timestampMs >> 40;
aBlob[1] = timestampMs >> 32;
aBlob[2] = timestampMs >> 24;
aBlob[3] = timestampMs >> 16;
aBlob[4] = timestampMs >> 8;
aBlob[5] = timestampMs;
aBlob[6] = (aBlob[6] & 0x0f) + 0x70;
aBlob[8] = (aBlob[8] & 0x3f) + 0x80;
sqlite3_uuid_blob_to_str(aBlob, zStr);
sqlite3_result_text(context, (char*)zStr, 36, SQLITE_TRANSIENT);
}

/*
* uuid_v7_extract_timestamp_ms extract unix timestamp in miliseconds
* from a version 7 UUID.
* X can be either a string or a blob.
* If X is not a version 7 UUID, return NULL.
*/
static void uuid_v7_extract_timestamp_ms(sqlite3_context* context, int argc, sqlite3_value** argv) {
unsigned char aBlob[16];
const unsigned char* pBlob;
(void)argc;
pBlob = sqlite3_uuid_input_to_blob(argv[0], aBlob);
if (pBlob == 0 || (pBlob[6] >> 4) != 7)
return;

unsigned long long timestampMs = 0;
for (size_t i = 0; i < 6; ++i) {
timestampMs = (timestampMs << 8) + pBlob[i];
}

sqlite3_result_int64(context, timestampMs);
}

/*
* uuid_str converts a UUID X into a well-formed UUID string.
* X can be either a string or a blob.
Expand Down Expand Up @@ -205,9 +259,11 @@ static void uuid_blob(sqlite3_context* context, int argc, sqlite3_value** argv)
int uuid_init(sqlite3* db) {
static const int flags = SQLITE_UTF8 | SQLITE_INNOCUOUS;
static const int det_flags = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
sqlite3_create_function(db, "uuid4", 0, flags, 0, uuid_generate, 0, 0);
sqlite3_create_function(db, "uuid4", 0, flags, 0, uuid_v4_generate, 0, 0);
sqlite3_create_function(db, "uuid7", 0, flags, 0, uuid_v7_generate, 0, 0);
sqlite3_create_function(db, "uuid7_timestamp_ms", 1, det_flags, 0, uuid_v7_extract_timestamp_ms, 0, 0);
/* for postgresql compatibility */
sqlite3_create_function(db, "gen_random_uuid", 0, flags, 0, uuid_generate, 0, 0);
sqlite3_create_function(db, "gen_random_uuid", 0, flags, 0, uuid_v4_generate, 0, 0);
sqlite3_create_function(db, "uuid_str", 1, det_flags, 0, uuid_str, 0, 0);
sqlite3_create_function(db, "uuid_blob", 1, det_flags, 0, uuid_blob, 0, 0);
return SQLITE_OK;
Expand Down
17 changes: 16 additions & 1 deletion test/uuid.sql
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,19 @@ select '12', typeof(uuid_blob('d5a80b20-0d8f-11e5-b8cb-080027b6ec40')) = 'blob';
select '13', typeof(uuid_blob(uuid4())) = 'blob';
select '14', uuid_blob('hello') is null;
select '15', uuid_blob('') is null;
select '16', uuid_blob(null) is null;
select '16', uuid_blob(null) is null;

-- uuid7
select '17', uuid7() like '________-____-7___-____-____________';

-- uuid7_timestamp_ms
select '18', uuid7_timestamp_ms('018ff38a-a5c9-712d-bc80-0550b3ad41a2') = 1717777901001;
select '19', uuid7_timestamp_ms('00000000-0000-7000-0000-000000000000') = 0;
select '20', uuid7_timestamp_ms('ffffffff-ffff-7000-0000-000000000000') = 281474976710655;
select '21', typeof(uuid7_timestamp_ms('018ff38a-a5c9-712d-bc80-0550b3ad41a2')) = 'integer';
select '22', typeof(uuid7_timestamp_ms(uuid7())) = 'integer';
select '23', uuid7_timestamp_ms('hello') is null;
select '24', uuid7_timestamp_ms('') is null;
select '25', uuid7_timestamp_ms(null) is null;
select '26', uuid7_timestamp_ms('b2df66e7-bd9a-45f4-8c0d-b9fd73cc9f18') is null;
select '27', uuid7_timestamp_ms(uuid4()) is null;

0 comments on commit 1ba95e4

Please sign in to comment.