Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use C code from pgsql-hackers #15

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions pg_uuidv7.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,107 @@ PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(uuid_generate_v7);

#define UUID_RND_CACHE_LEN 64
int rnd_cache_ptr = UUID_RND_CACHE_LEN;
unsigned char random_cache[UUID_RND_CACHE_LEN];

static bool
cached_strong_random(void *buf, size_t len)
{
if (len + rnd_cache_ptr >= UUID_RND_CACHE_LEN)
{
if (!pg_strong_random(random_cache, UUID_RND_CACHE_LEN))
return false;
rnd_cache_ptr = 0;
}
memcpy(buf, &random_cache[rnd_cache_ptr], len);
rnd_cache_ptr += len;
return true;
}

static uint32_t sequence_counter;
static uint64_t previous_timestamp = 0;

Datum uuid_generate_v7(PG_FUNCTION_ARGS)
{
pg_uuid_t *uuid = palloc(UUID_LEN);
struct timespec ts;
pg_uuid_t *uuid = palloc(UUID_LEN);
uint64_t tms;
struct timespec ts;

if (!pg_strong_random(uuid, UUID_LEN))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random values")));

/*
* Set first 48 bits to unix epoch timestamp
*/
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not get CLOCK_REALTIME")));

tms = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);
tms = pg_hton64(tms << 16);
memcpy(&uuid->data[0], &tms, 6);

if (tms <= previous_timestamp)
{
/* Time did not increment from the previous generation, we must increment counter */
++sequence_counter;
if (sequence_counter > 0x3ffff)
{
/* We only have 18-bit counter */
sequence_counter = 0;
previous_timestamp++;
}

/* protection from leap backward */
tms = previous_timestamp;

/* fill everything after the timestamp and counter with random bytes */
if (!cached_strong_random(&uuid->data[8], UUID_LEN - 8))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random values")));

/* most significant 4 bits of 18-bit counter */
uuid->data[6] = (unsigned char)(sequence_counter >> 14);
/* next 8 bits */
uuid->data[7] = (unsigned char)(sequence_counter >> 6);
/* least significant 6 bits */
uuid->data[8] = (unsigned char)(sequence_counter);
}
else
{
/* fill everything after the timestamp with random bytes */
if (!cached_strong_random(&uuid->data[6], UUID_LEN - 6))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random values")));

/*
* Left-most counter bits are initialized as zero for the sole purpose
* of guarding against counter rollovers.
* See section "Fixed-Length Dedicated Counter Seeding"
* https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-09#monotonicity_counters
*/
uuid->data[6] = (uuid->data[6] & 0xf7);

sequence_counter = ((uint32_t)uuid->data[8] & 0x3f) +
(((uint32_t)uuid->data[7]) << 6) +
(((uint32_t)uuid->data[6] & 0x0f) << 14);

previous_timestamp = tms;
}

/* Fill in time part */
uuid->data[0] = (unsigned char)(tms >> 40);
uuid->data[1] = (unsigned char)(tms >> 32);
uuid->data[2] = (unsigned char)(tms >> 24);
uuid->data[3] = (unsigned char)(tms >> 16);
uuid->data[4] = (unsigned char)(tms >> 8);
uuid->data[5] = (unsigned char)tms;

/*
* Set magic numbers for a "version 7" UUID, see
* https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-00.html#name-uuid-version-7
* Set magic numbers for a "version 7" (pseudorandom) UUID, see
* http://tools.ietf.org/html/rfc ???
* https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format#name-creating-a-uuidv7-value
*/
uuid->data[6] = (uuid->data[6] & 0x0f) | 0x70; /* 4 bit version [0111] */
uuid->data[8] = (uuid->data[8] & 0x3f) | 0x80; /* 2 bit variant [10] */
/* set version field, top four bits are 0, 1, 1, 1 */
uuid->data[6] = (uuid->data[6] & 0x0f) | 0x70;
/* set variant field, top two bits are 1, 0 */
uuid->data[8] = (uuid->data[8] & 0x3f) | 0x80;

PG_RETURN_UUID_P(uuid);
}
Expand Down