change_v33.patch
application/octet-stream
Patch
Format: unified
Series: patch v33
| File | + | − |
|---|---|---|
| src/backend/utils/adt/uuid.c | 96 | 53 |
| src/include/port/win32_port.h | 0 | 14 |
| src/port/win32gettimeofday.c | 0 | 22 |
| src/test/regress/expected/uuid.out | 14 | 1 |
| src/test/regress/sql/uuid.sql | 9 | 1 |
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index f8b8b590216..ac094ac5901 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -13,17 +13,30 @@
#include "postgres.h"
+#include <time.h> /* for clock_gettime() */
+
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
#include "port/pg_bswap.h"
-#include "portability/instr_time.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/sortsupport.h"
#include "utils/timestamp.h"
#include "utils/uuid.h"
+/* helper macros */
+#define NS_PER_S INT64CONST(1000000000)
+#define NS_PER_MS INT64CONST(1000000)
+#define NS_PER_US INT64CONST(1000)
+
+/*
+ * In UUID version 7, we use 12 bits in "rand_a" to store 1/4096 fractions of
+ * sub-millisecond. This is the minimum amount of nanoseconds that guarantees
+ * step of UUID increased clock precision.
+ */
+#define SUBMS_MINIMAL_STEP ((NS_PER_MS / (1 << 12)) + 1)
+
/* sortsupport for uuid */
typedef struct
{
@@ -39,7 +52,7 @@ static int uuid_fast_cmp(Datum x, Datum y, SortSupport ssup);
static bool uuid_abbrev_abort(int memtupcount, SortSupport ssup);
static Datum uuid_abbrev_convert(Datum original, SortSupport ssup);
static inline void uuid_set_version(pg_uuid_t *uuid, unsigned char version);
-static inline uint64 get_real_time_ns_ascending();
+static inline int64 get_real_time_ns_ascending();
Datum
uuid_in(PG_FUNCTION_ARGS)
@@ -404,14 +417,13 @@ uuid_hash_extended(PG_FUNCTION_ARGS)
return hash_any_extended(key->data, UUID_LEN, PG_GETARG_INT64(1));
}
-/*
- * Set magic numbers for a UUID variant 3
- * https://www.rfc-editor.org/rfc/rfc9562
- */
-static inline void uuid_set_version(pg_uuid_t *uuid, unsigned char version)
+/* Set the given UUID version and the variant bits */
+static inline void
+uuid_set_version(pg_uuid_t *uuid, unsigned char version)
{
/* set version field, top four bits */
uuid->data[6] = (uuid->data[6] & 0x0f) | (version << 4);
+
/* set variant field, top two bits are 1, 0 */
uuid->data[8] = (uuid->data[8] & 0x3f) | 0x80;
}
@@ -433,8 +445,8 @@ gen_random_uuid(PG_FUNCTION_ARGS)
errmsg("could not generate random values")));
/*
- * Set magic numbers for a "version 4" (pseudorandom) UUID, see
- * https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4
+ * Set magic numbers for a "version 4" (pseudorandom) UUID and
+ * variant, see https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4
*/
uuid_set_version(uuid, 4);
@@ -442,44 +454,63 @@ gen_random_uuid(PG_FUNCTION_ARGS)
}
/*
- * Aquire nanosecond reading and ensure it is ascending (on this backend)
+ * Get the current timestamp with nanosecond precision for UUID generation.
+ * The returned timestamp is ensured to be at least SUBMS_MINIMAL_STEP greater
+ * than the previous returned timestamp (on this backend).
*/
-static inline uint64 get_real_time_ns_ascending()
+static inline int64
+get_real_time_ns_ascending()
{
- static uint64 previous_ns = 0;
+ static int64 previous_ns = 0;
+ int64 ns;
+
+ /* Get the current real timestamp */
+
+#ifdef WIN32
+ struct timeval tmp;
+
+ gettimeofday(&tp, NULL);
+ ns = tmp.tv_sec * NS_PER_S + tmp.tv_usec * NS_PER_US;
+#else
struct timespec tmp;
- uint64 ns;
- /* We use some bits of nanosecond precision, so we cannot resort to gettimeofday() */
+ /*
+ * We don't use gettimeofday() where available, instead use clock_gettime()
+ * with CLOCK_REALTIME in order to get a high-precision (nanoseconds) real
+ * timestamp.
+ *
+ * Note that a timestamp returned by clock_gettime() with CLOCK_REALTIME
+ * is nanosecond-precision on most Unix-like platforms. On some platforms
+ * such as macOS, it's restricted to microsecond-precision.
+ */
clock_gettime(CLOCK_REALTIME, &tmp);
- ns = tmp.tv_sec * 1000000000L + tmp.tv_nsec;
+ ns = tmp.tv_sec * NS_PER_S + tmp.tv_nsec;
+#endif
- /* minimum amount of ns that guarantees step of UUID increased clock precision */
-#define SUB_MILLISECOND_STEP ((NS_PER_MS / (1 << 12)) + 1)
- if (previous_ns + SUB_MILLISECOND_STEP >= ns)
- ns = previous_ns + SUB_MILLISECOND_STEP;
+ /* Guarantee the minimal step advancement of the timestamp */
+ if (previous_ns + SUBMS_MINIMAL_STEP >= ns)
+ ns = previous_ns + SUBMS_MINIMAL_STEP;
previous_ns = ns;
return ns;
}
/*
- * Generate UUID version 7 per RFC 9562.
+ * Generate UUID version 7 per RFC 9562, with the given timestamp.
*
- * Monotonicity (regarding generation on given backend) is ensured with method
- * "Replace Leftmost Random Bits with Increased Clock Precision (Method 3)".
- * We use 12 bits in "rand_a" bits to store 1/4096 fractions of millisecond.
- * Usage of pg_testtime indicates that such precision is available on most
- * systems. If timestamp is not advancing between two consecutive UUID
- * generations, previous timestamp is incremented and used instead of current
- * timestamp.
+ * UUID version 7 consists of a Unix timestamp in milliseconds (48 bits) and
+ * 74 random bits, excluding the required version and variant bits. To ensure
+ * monotonicity in scenarios of high-frequency UUID generation, we employ the
+ * method "Replace Leftmost Random Bits with Increased Clock Precision (Method 3)".
+ * This method utilizes 12 bits from the "rand_a" bits to store a 1/4096
+ * (or 2^12) fraction of sub-millisecond precision.
*/
-static Datum
-generate_uuidv7(uint64 ns)
+static pg_attribute_always_inline pg_uuid_t *
+generate_uuidv7(int64 ns)
{
pg_uuid_t *uuid = palloc(UUID_LEN);
- uint64 unix_ts_ms;
- uint16 increased_clock_precision;
+ int64 unix_ts_ms;
+ int32 increased_clock_precision;
unix_ts_ms = ns / NS_PER_MS;
@@ -494,6 +525,7 @@ generate_uuidv7(uint64 ns)
/* sub-millisecond timestamp fraction (12 bits) */
increased_clock_precision = ((ns % NS_PER_MS) * (1 << 12)) / NS_PER_MS;
+ /* Fill the increased clock precision to "rand_a" bits */
uuid->data[6] = (unsigned char) (increased_clock_precision >> 8);
uuid->data[7] = (unsigned char) (increased_clock_precision);
@@ -504,51 +536,62 @@ generate_uuidv7(uint64 ns)
errmsg("could not generate random values")));
/*
- * Set magic numbers for a "version 7" (pseudorandom) UUID, see
- * https://www.rfc-editor.org/rfc/rfc9562#name-version-field
+ * Set magic numbers for a "version 7" (pseudorandom) UUID and
+ * variant, see https://www.rfc-editor.org/rfc/rfc9562#name-version-field
*/
uuid_set_version(uuid, 7);
- PG_RETURN_UUID_P(uuid);
+ return uuid;
}
/*
- * Entry point for uuidv7()
+ * Generate UUID version 7 with the current timestamp.
*/
Datum
uuidv7(PG_FUNCTION_ARGS)
{
- return generate_uuidv7(get_real_time_ns_ascending());
+ pg_uuid_t *uuid = generate_uuidv7(get_real_time_ns_ascending());
+
+ PG_RETURN_UUID_P(uuid);
}
/*
- * Entry point for uuidv7(interval)
+ * Similar to uuidv7() but with the timestamp adjusted by the given interval.
*/
Datum
uuidv7_interval(PG_FUNCTION_ARGS)
{
- uint64 ns = get_real_time_ns_ascending();
+ Interval *span = PG_GETARG_INTERVAL_P(0);
+ TimestampTz ts;
+ pg_uuid_t *uuid;
+ int64 ns = get_real_time_ns_ascending();
+
/*
- * We are given a time shift interval as an argument.
- * The interval represent days, monthes and years, that are not fixed
- * number of nanoseconds. To make correct computations we call
- * timestamptz_pl_interval() with corresponding logic. This logic is
- * implemented with microsecond precision. So we carry nanoseconds
- * between computations.
+ * Shift the current timestamp by the given interval. To make correct
+ * calculating the time shift, we convert the UNIX epoch to TimestampTz
+ * and use timestamptz_pl_interval(). Since this calculation is done with
+ * microsecond precision, we carry back the nanoseconds.
*/
- Interval *span = PG_GETARG_INTERVAL_P(0);
- /* Convert time part of UUID to Timestamptz (ms since Postgres epoch) */
- TimestampTz ts = (TimestampTz) (ns / 1000) -
+
+ ts = (TimestampTz) (ns / NS_PER_US) -
(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
/* Copmute time shift */
ts = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,
- TimestampTzGetDatum(ts),
- IntervalPGetDatum(span)));
- /* Convert TimestampTz back and carry nanoseconds. */
+ TimestampTzGetDatum(ts),
+ IntervalPGetDatum(span)));
+
+ /*
+ * Convert a TimestampTz value back to an UNIX epoch and carry back
+ * nanoseconds.
+ */
ns = (ts + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC)
- * 1000 + ns % 1000;
- return generate_uuidv7(ns);
+ * NS_PER_US + ns % NS_PER_US;
+
+ /* Generate an UUID */
+ uuid = generate_uuidv7(ns);
+
+ PG_RETURN_UUID_P(uuid);
}
/*
@@ -603,7 +646,7 @@ uuid_extract_timestamp(PG_FUNCTION_ARGS)
+ (((uint64) uuid->data[0]) << 40);
/* convert ms to us, then adjust */
- ts = (TimestampTz) (tms * 1000) -
+ ts = (TimestampTz) (tms * NS_PER_US) -
(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
PG_RETURN_TIMESTAMPTZ(ts);
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index d343e6c1875..7789e0431aa 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -184,20 +184,6 @@
#ifdef _MSC_VER
/* Last parameter not used */
extern int gettimeofday(struct timeval *tp, void *tzp);
-
-/*
- * Windows implementation is limited to CLOCK_REALTIME
- */
-typedef enum {
- CLOCK_REALTIME
-} clockid_t;
-
-#include <time.h> /* for timespec */
-
-extern int clock_gettime(clockid_t clock_id, struct timespec *tp);
-#else
-/* MinGW */
-#include "pthread_time.h"
#endif
/* for setitimer in backend/port/win32/timer.c */
diff --git a/src/port/win32gettimeofday.c b/src/port/win32gettimeofday.c
index 93ec3bf731c..1e00f7ee149 100644
--- a/src/port/win32gettimeofday.c
+++ b/src/port/win32gettimeofday.c
@@ -41,7 +41,6 @@ static const unsigned __int64 epoch = UINT64CONST(116444736000000000);
*/
#define FILETIME_UNITS_PER_SEC 10000000L
#define FILETIME_UNITS_PER_USEC 10
-#define FILETIME_UNITS_TO_NS 100L
/*
@@ -74,24 +73,3 @@ gettimeofday(struct timeval *tp, void *tzp)
return 0;
}
-
-/*
- * This function is ported for UUID purposes.
- */
-int
-clock_gettime(clockid_t clock_id, struct timespec *tp)
-{
- Assert(clock_id == CLOCK_REALTIME);
-
- FILETIME file_time;
- ULARGE_INTEGER ularge;
- GetSystemTimePreciseAsFileTime(&file_time);
- ularge.LowPart = file_time.dwLowDateTime;
- ularge.HighPart = file_time.dwHighDateTime;
-
- tp->tv_sec = (long) ((ularge.QuadPart - epoch) / FILETIME_UNITS_PER_SEC);
- tp->tv_nsec = (long) (((ularge.QuadPart - epoch) % FILETIME_UNITS_PER_SEC)
- * FILETIME_UNITS_TO_NS);
-
- return 0;
-}
\ No newline at end of file
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index aa6224e81bb..bd83f6b0763 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -10,6 +10,11 @@ CREATE TABLE guid2
guid_field UUID,
text_field TEXT DEFAULT(now())
);
+CREATE TABLE guid3
+(
+ id SERIAL,
+ guid_field UUID
+);
-- inserting invalid data tests
-- too long
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111F');
@@ -189,6 +194,14 @@ SELECT count(DISTINCT guid_field) FROM guid1;
3
(1 row)
+-- test sortability of v7
+INSERT INTO guid3 (guid_field) SELECT uuidv7() FROM generate_series(1, 10);
+SELECT array_agg(id ORDER BY guid_field) FROM guid3;
+ array_agg
+------------------------
+ {1,2,3,4,5,6,7,8,9,10}
+(1 row)
+
-- extract functions
-- version
SELECT uuid_extract_version('11111111-1111-5111-8111-111111111111'); -- 5
@@ -247,4 +260,4 @@ SELECT uuid_extract_timestamp('11111111-1111-1111-1111-111111111111'); -- null
(1 row)
-- clean up
-DROP TABLE guid1, guid2 CASCADE;
+DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index eec7f160f81..8e54217a75c 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -10,6 +10,11 @@ CREATE TABLE guid2
guid_field UUID,
text_field TEXT DEFAULT(now())
);
+CREATE TABLE guid3
+(
+ id SERIAL,
+ guid_field UUID
+);
-- inserting invalid data tests
-- too long
@@ -98,6 +103,9 @@ INSERT INTO guid1 (guid_field) VALUES (uuidv7());
INSERT INTO guid1 (guid_field) VALUES (uuidv7(INTERVAL '1 day'));
SELECT count(DISTINCT guid_field) FROM guid1;
+-- test sortability of v7
+INSERT INTO guid3 (guid_field) SELECT uuidv7() FROM generate_series(1, 10);
+SELECT array_agg(id ORDER BY guid_field) FROM guid3;
-- extract functions
@@ -116,4 +124,4 @@ SELECT uuid_extract_timestamp('11111111-1111-1111-1111-111111111111'); -- null
-- clean up
-DROP TABLE guid1, guid2 CASCADE;
+DROP TABLE guid1, guid2, guid3 CASCADE;