|
| 1 | +/* |
| 2 | +** SQLite extension for UUIDv7. |
| 3 | +** Adapted from PostgreSQL implementation. |
| 4 | +** Original source: |
| 5 | +** <https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/uuid.c> |
| 6 | +*/ |
| 7 | +#include "sqlite3ext.h" |
| 8 | +SQLITE_EXTENSION_INIT1 |
| 9 | +#include <assert.h> |
| 10 | +#include <string.h> |
| 11 | +#include <time.h> |
| 12 | +#include <stdint.h> |
| 13 | + |
| 14 | +/* |
| 15 | +** We use a local implementation of isxdigit to avoid a dependency on <ctype.h>. |
| 16 | +*/ |
| 17 | +static int sqlite3Isxdigit(int c){ |
| 18 | + return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F'); |
| 19 | +} |
| 20 | + |
| 21 | +/* |
| 22 | +** Helper to convert hex to int. |
| 23 | +*/ |
| 24 | +static unsigned char sqlite3UuidHexToInt(int h){ |
| 25 | + assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); |
| 26 | + if( h>='0' && h<='9' ) return h - '0'; |
| 27 | + if( h>='a' && h<='f' ) return h - 'a' + 10; |
| 28 | + return h - 'A' + 10; |
| 29 | +} |
| 30 | + |
| 31 | +/* |
| 32 | +** Convert blob to UUID string. |
| 33 | +*/ |
| 34 | +static void sqlite3UuidBlobToStr(const unsigned char *aBlob, unsigned char *zStr){ |
| 35 | + static const char zDigits[] = "0123456789abcdef"; |
| 36 | + int i, k; |
| 37 | + unsigned char x; |
| 38 | + k = 0; |
| 39 | + for(i=0, k=0x550; i<16; i++, k=k>>1){ |
| 40 | + if( k&1 ){ |
| 41 | + zStr[0] = '-'; |
| 42 | + zStr++; |
| 43 | + } |
| 44 | + x = aBlob[i]; |
| 45 | + zStr[0] = zDigits[x>>4]; |
| 46 | + zStr[1] = zDigits[x&0xf]; |
| 47 | + zStr += 2; |
| 48 | + } |
| 49 | + *zStr = 0; |
| 50 | +} |
| 51 | + |
| 52 | +/* |
| 53 | +** UUIDv7 Generation Logic |
| 54 | +*/ |
| 55 | + |
| 56 | +#define NS_PER_S 1000000000LL |
| 57 | +#define NS_PER_MS 1000000LL |
| 58 | + |
| 59 | +/* |
| 60 | +** UUID version 7 uses 12 bits in "rand_a" to store 1/4096 (or 2^12) fractions of |
| 61 | +** sub-millisecond. |
| 62 | +*/ |
| 63 | +#define SUBMS_BITS 12 |
| 64 | +#define SUBMS_MINIMAL_STEP_NS ((NS_PER_MS / (1 << 12)) + 1) |
| 65 | + |
| 66 | +static int64_t get_real_time_ns_ascending(void) |
| 67 | +{ |
| 68 | + static int64_t last_time_ns = 0; |
| 69 | + struct timespec ts; |
| 70 | + int64_t now_ns; |
| 71 | + |
| 72 | + clock_gettime(CLOCK_REALTIME, &ts); |
| 73 | + now_ns = (int64_t)ts.tv_sec * NS_PER_S + ts.tv_nsec; |
| 74 | + |
| 75 | + /* |
| 76 | + * If the clock moved backwards or stalled, we must advance it to |
| 77 | + * maintain monotonicity as required by UUIDv7. |
| 78 | + */ |
| 79 | + if (now_ns <= last_time_ns) |
| 80 | + { |
| 81 | + now_ns = last_time_ns + SUBMS_MINIMAL_STEP_NS; |
| 82 | + } |
| 83 | + |
| 84 | + last_time_ns = now_ns; |
| 85 | + return now_ns; |
| 86 | +} |
| 87 | + |
| 88 | +static void generate_uuidv7(unsigned char *uuid_out) |
| 89 | +{ |
| 90 | + int64_t current_ns; |
| 91 | + uint64_t unix_ts_ms; |
| 92 | + uint32_t sub_ms; |
| 93 | + |
| 94 | + current_ns = get_real_time_ns_ascending(); |
| 95 | + |
| 96 | + unix_ts_ms = current_ns / NS_PER_MS; |
| 97 | + /* Calculate sub-millisecond fraction, scaled to 12 bits (4096) */ |
| 98 | + sub_ms = (uint32_t)(((current_ns % NS_PER_MS) * 4096) / NS_PER_MS); |
| 99 | + |
| 100 | + /* Fill with random data first */ |
| 101 | + sqlite3_randomness(16, uuid_out); |
| 102 | + |
| 103 | + /* |
| 104 | + ** uuid[0-5]: unix_ts_ms (48 bits) |
| 105 | + ** uuid[6]: ver (4 bits) | rand_a (4 bits from sub_ms) |
| 106 | + ** uuid[7]: rand_a (8 bits from sub_ms) |
| 107 | + ** uuid[8]: var (2 bits) | rand_b (6 bits) |
| 108 | + */ |
| 109 | + |
| 110 | + /* Encode timestamp (big-endian) */ |
| 111 | + uuid_out[0] = (unsigned char)(unix_ts_ms >> 40); |
| 112 | + uuid_out[1] = (unsigned char)(unix_ts_ms >> 32); |
| 113 | + uuid_out[2] = (unsigned char)(unix_ts_ms >> 24); |
| 114 | + uuid_out[3] = (unsigned char)(unix_ts_ms >> 16); |
| 115 | + uuid_out[4] = (unsigned char)(unix_ts_ms >> 8); |
| 116 | + uuid_out[5] = (unsigned char)(unix_ts_ms); |
| 117 | + |
| 118 | + /* Version 7 and top 4 bits of sub_ms */ |
| 119 | + uuid_out[6] = 0x70 | ((sub_ms >> 8) & 0x0F); |
| 120 | + |
| 121 | + /* Lower 8 bits of sub_ms */ |
| 122 | + uuid_out[7] = (unsigned char)(sub_ms & 0xFF); |
| 123 | + |
| 124 | + /* Variant 1 (0b10xx) */ |
| 125 | + uuid_out[8] = (uuid_out[8] & 0x3F) | 0x80; |
| 126 | +} |
| 127 | + |
| 128 | +static void sqlite3Uuid7Func( |
| 129 | + sqlite3_context *context, |
| 130 | + int argc, |
| 131 | + sqlite3_value **argv |
| 132 | +){ |
| 133 | + unsigned char aBlob[16]; |
| 134 | + unsigned char zStr[37]; |
| 135 | + (void)argc; |
| 136 | + (void)argv; |
| 137 | + |
| 138 | + generate_uuidv7(aBlob); |
| 139 | + sqlite3UuidBlobToStr(aBlob, zStr); |
| 140 | + sqlite3_result_text(context, (char*)zStr, 36, SQLITE_TRANSIENT); |
| 141 | +} |
| 142 | + |
| 143 | +#ifdef _WIN32 |
| 144 | +__declspec(dllexport) |
| 145 | +#endif |
| 146 | +int sqlite3_uuid7_init( |
| 147 | + sqlite3 *db, |
| 148 | + char **pzErrMsg, |
| 149 | + const sqlite3_api_routines *pApi |
| 150 | +){ |
| 151 | + int rc = SQLITE_OK; |
| 152 | + SQLITE_EXTENSION_INIT2(pApi); |
| 153 | + (void)pzErrMsg; |
| 154 | + rc = sqlite3_create_function_v2(db, "uuid7", 0, SQLITE_UTF8|SQLITE_INNOCUOUS, 0, |
| 155 | + sqlite3Uuid7Func, 0, 0, 0); |
| 156 | + return rc; |
| 157 | +} |
0 commit comments