LCOV - code coverage report
Current view: top level - src/backend/utils/adt - uuid.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 196 215 91.2 %
Date: 2025-04-01 14:15:22 Functions: 25 27 92.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * uuid.c
       4             :  *    Functions for the built-in type "uuid".
       5             :  *
       6             :  * Copyright (c) 2007-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *    src/backend/utils/adt/uuid.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : 
      14             : #include "postgres.h"
      15             : 
      16             : #include <time.h>             /* for clock_gettime() */
      17             : 
      18             : #include "common/hashfn.h"
      19             : #include "lib/hyperloglog.h"
      20             : #include "libpq/pqformat.h"
      21             : #include "port/pg_bswap.h"
      22             : #include "utils/fmgrprotos.h"
      23             : #include "utils/guc.h"
      24             : #include "utils/sortsupport.h"
      25             : #include "utils/timestamp.h"
      26             : #include "utils/uuid.h"
      27             : 
      28             : /* helper macros */
      29             : #define NS_PER_S    INT64CONST(1000000000)
      30             : #define NS_PER_MS   INT64CONST(1000000)
      31             : #define NS_PER_US   INT64CONST(1000)
      32             : #define US_PER_MS   INT64CONST(1000)
      33             : 
      34             : /*
      35             :  * UUID version 7 uses 12 bits in "rand_a" to store  1/4096 (or 2^12) fractions of
      36             :  * sub-millisecond. While most Unix-like platforms provide nanosecond-precision
      37             :  * timestamps, some systems only offer microsecond precision, limiting us to 10
      38             :  * bits of sub-millisecond information. For example, on macOS, real time is
      39             :  * truncated to microseconds. Additionally, MSVC uses the ported version of
      40             :  * gettimeofday() that returns microsecond precision.
      41             :  *
      42             :  * On systems with only 10 bits of sub-millisecond precision, we still use
      43             :  * 1/4096 parts of a millisecond, but fill lower 2 bits with random numbers
      44             :  * (see generate_uuidv7() for details).
      45             :  *
      46             :  * SUBMS_MINIMAL_STEP_NS defines the minimum number of nanoseconds that guarantees
      47             :  * an increase in the UUID's clock precision.
      48             :  */
      49             : #if defined(__darwin__) || defined(_MSC_VER)
      50             : #define SUBMS_MINIMAL_STEP_BITS 10
      51             : #else
      52             : #define SUBMS_MINIMAL_STEP_BITS 12
      53             : #endif
      54             : #define SUBMS_BITS  12
      55             : #define SUBMS_MINIMAL_STEP_NS ((NS_PER_MS / (1 << SUBMS_MINIMAL_STEP_BITS)) + 1)
      56             : 
      57             : /* sortsupport for uuid */
      58             : typedef struct
      59             : {
      60             :     int64       input_count;    /* number of non-null values seen */
      61             :     bool        estimating;     /* true if estimating cardinality */
      62             : 
      63             :     hyperLogLogState abbr_card; /* cardinality estimator */
      64             : } uuid_sortsupport_state;
      65             : 
      66             : static void string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext);
      67             : static int  uuid_internal_cmp(const pg_uuid_t *arg1, const pg_uuid_t *arg2);
      68             : static int  uuid_fast_cmp(Datum x, Datum y, SortSupport ssup);
      69             : static bool uuid_abbrev_abort(int memtupcount, SortSupport ssup);
      70             : static Datum uuid_abbrev_convert(Datum original, SortSupport ssup);
      71             : static inline void uuid_set_version(pg_uuid_t *uuid, unsigned char version);
      72             : static inline int64 get_real_time_ns_ascending();
      73             : static pg_uuid_t *generate_uuidv7(uint64 unix_ts_ms, uint32 sub_ms);
      74             : 
      75             : Datum
      76      585920 : uuid_in(PG_FUNCTION_ARGS)
      77             : {
      78      585920 :     char       *uuid_str = PG_GETARG_CSTRING(0);
      79             :     pg_uuid_t  *uuid;
      80             : 
      81      585920 :     uuid = (pg_uuid_t *) palloc(sizeof(*uuid));
      82      585920 :     string_to_uuid(uuid_str, uuid, fcinfo->context);
      83      585884 :     PG_RETURN_UUID_P(uuid);
      84             : }
      85             : 
      86             : Datum
      87        4704 : uuid_out(PG_FUNCTION_ARGS)
      88             : {
      89        4704 :     pg_uuid_t  *uuid = PG_GETARG_UUID_P(0);
      90             :     static const char hex_chars[] = "0123456789abcdef";
      91             :     char       *buf,
      92             :                *p;
      93             :     int         i;
      94             : 
      95             :     /* counts for the four hyphens and the zero-terminator */
      96        4704 :     buf = palloc(2 * UUID_LEN + 5);
      97        4704 :     p = buf;
      98       79968 :     for (i = 0; i < UUID_LEN; i++)
      99             :     {
     100             :         int         hi;
     101             :         int         lo;
     102             : 
     103             :         /*
     104             :          * We print uuid values as a string of 8, 4, 4, 4, and then 12
     105             :          * hexadecimal characters, with each group is separated by a hyphen
     106             :          * ("-"). Therefore, add the hyphens at the appropriate places here.
     107             :          */
     108       75264 :         if (i == 4 || i == 6 || i == 8 || i == 10)
     109       18816 :             *p++ = '-';
     110             : 
     111       75264 :         hi = uuid->data[i] >> 4;
     112       75264 :         lo = uuid->data[i] & 0x0F;
     113             : 
     114       75264 :         *p++ = hex_chars[hi];
     115       75264 :         *p++ = hex_chars[lo];
     116             :     }
     117        4704 :     *p = '\0';
     118             : 
     119        4704 :     PG_RETURN_CSTRING(buf);
     120             : }
     121             : 
     122             : /*
     123             :  * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
     124             :  * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
     125             :  * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
     126             :  * digits, is the only one used for output.)
     127             :  */
     128             : static void
     129      585920 : string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
     130             : {
     131      585920 :     const char *src = source;
     132      585920 :     bool        braces = false;
     133             :     int         i;
     134             : 
     135      585920 :     if (src[0] == '{')
     136             :     {
     137          24 :         src++;
     138          24 :         braces = true;
     139             :     }
     140             : 
     141     9960226 :     for (i = 0; i < UUID_LEN; i++)
     142             :     {
     143             :         char        str_buf[3];
     144             : 
     145     9374342 :         if (src[0] == '\0' || src[1] == '\0')
     146          36 :             goto syntax_error;
     147     9374330 :         memcpy(str_buf, src, 2);
     148     9374330 :         if (!isxdigit((unsigned char) str_buf[0]) ||
     149     9374318 :             !isxdigit((unsigned char) str_buf[1]))
     150          24 :             goto syntax_error;
     151             : 
     152     9374306 :         str_buf[2] = '\0';
     153     9374306 :         uuid->data[i] = (unsigned char) strtoul(str_buf, NULL, 16);
     154     9374306 :         src += 2;
     155     9374306 :         if (src[0] == '-' && (i % 2) == 1 && i < UUID_LEN - 1)
     156     1935434 :             src++;
     157             :     }
     158             : 
     159      585884 :     if (braces)
     160             :     {
     161          18 :         if (*src != '}')
     162           6 :             goto syntax_error;
     163          12 :         src++;
     164             :     }
     165             : 
     166      585878 :     if (*src != '\0')
     167           6 :         goto syntax_error;
     168             : 
     169      585872 :     return;
     170             : 
     171          48 : syntax_error:
     172          48 :     ereturn(escontext,,
     173             :             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     174             :              errmsg("invalid input syntax for type %s: \"%s\"",
     175             :                     "uuid", source)));
     176             : }
     177             : 
     178             : Datum
     179           0 : uuid_recv(PG_FUNCTION_ARGS)
     180             : {
     181           0 :     StringInfo  buffer = (StringInfo) PG_GETARG_POINTER(0);
     182             :     pg_uuid_t  *uuid;
     183             : 
     184           0 :     uuid = (pg_uuid_t *) palloc(UUID_LEN);
     185           0 :     memcpy(uuid->data, pq_getmsgbytes(buffer, UUID_LEN), UUID_LEN);
     186           0 :     PG_RETURN_POINTER(uuid);
     187             : }
     188             : 
     189             : Datum
     190           0 : uuid_send(PG_FUNCTION_ARGS)
     191             : {
     192           0 :     pg_uuid_t  *uuid = PG_GETARG_UUID_P(0);
     193             :     StringInfoData buffer;
     194             : 
     195           0 :     pq_begintypsend(&buffer);
     196           0 :     pq_sendbytes(&buffer, uuid->data, UUID_LEN);
     197           0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buffer));
     198             : }
     199             : 
     200             : /* internal uuid compare function */
     201             : static int
     202    41775396 : uuid_internal_cmp(const pg_uuid_t *arg1, const pg_uuid_t *arg2)
     203             : {
     204    41775396 :     return memcmp(arg1->data, arg2->data, UUID_LEN);
     205             : }
     206             : 
     207             : Datum
     208       84398 : uuid_lt(PG_FUNCTION_ARGS)
     209             : {
     210       84398 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     211       84398 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     212             : 
     213       84398 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) < 0);
     214             : }
     215             : 
     216             : Datum
     217       17046 : uuid_le(PG_FUNCTION_ARGS)
     218             : {
     219       17046 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     220       17046 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     221             : 
     222       17046 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) <= 0);
     223             : }
     224             : 
     225             : Datum
     226      154458 : uuid_eq(PG_FUNCTION_ARGS)
     227             : {
     228      154458 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     229      154458 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     230             : 
     231      154458 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) == 0);
     232             : }
     233             : 
     234             : Datum
     235       12378 : uuid_ge(PG_FUNCTION_ARGS)
     236             : {
     237       12378 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     238       12378 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     239             : 
     240       12378 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) >= 0);
     241             : }
     242             : 
     243             : Datum
     244       16290 : uuid_gt(PG_FUNCTION_ARGS)
     245             : {
     246       16290 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     247       16290 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     248             : 
     249       16290 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) > 0);
     250             : }
     251             : 
     252             : Datum
     253          18 : uuid_ne(PG_FUNCTION_ARGS)
     254             : {
     255          18 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     256          18 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     257             : 
     258          18 :     PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) != 0);
     259             : }
     260             : 
     261             : /* handler for btree index operator */
     262             : Datum
     263        9308 : uuid_cmp(PG_FUNCTION_ARGS)
     264             : {
     265        9308 :     pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
     266        9308 :     pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
     267             : 
     268        9308 :     PG_RETURN_INT32(uuid_internal_cmp(arg1, arg2));
     269             : }
     270             : 
     271             : /*
     272             :  * Sort support strategy routine
     273             :  */
     274             : Datum
     275         384 : uuid_sortsupport(PG_FUNCTION_ARGS)
     276             : {
     277         384 :     SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
     278             : 
     279         384 :     ssup->comparator = uuid_fast_cmp;
     280         384 :     ssup->ssup_extra = NULL;
     281             : 
     282         384 :     if (ssup->abbreviate)
     283             :     {
     284             :         uuid_sortsupport_state *uss;
     285             :         MemoryContext oldcontext;
     286             : 
     287         308 :         oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt);
     288             : 
     289         308 :         uss = palloc(sizeof(uuid_sortsupport_state));
     290         308 :         uss->input_count = 0;
     291         308 :         uss->estimating = true;
     292         308 :         initHyperLogLog(&uss->abbr_card, 10);
     293             : 
     294         308 :         ssup->ssup_extra = uss;
     295             : 
     296         308 :         ssup->comparator = ssup_datum_unsigned_cmp;
     297         308 :         ssup->abbrev_converter = uuid_abbrev_convert;
     298         308 :         ssup->abbrev_abort = uuid_abbrev_abort;
     299         308 :         ssup->abbrev_full_comparator = uuid_fast_cmp;
     300             : 
     301         308 :         MemoryContextSwitchTo(oldcontext);
     302             :     }
     303             : 
     304         384 :     PG_RETURN_VOID();
     305             : }
     306             : 
     307             : /*
     308             :  * SortSupport comparison func
     309             :  */
     310             : static int
     311    41481500 : uuid_fast_cmp(Datum x, Datum y, SortSupport ssup)
     312             : {
     313    41481500 :     pg_uuid_t  *arg1 = DatumGetUUIDP(x);
     314    41481500 :     pg_uuid_t  *arg2 = DatumGetUUIDP(y);
     315             : 
     316    41481500 :     return uuid_internal_cmp(arg1, arg2);
     317             : }
     318             : 
     319             : /*
     320             :  * Callback for estimating effectiveness of abbreviated key optimization.
     321             :  *
     322             :  * We pay no attention to the cardinality of the non-abbreviated data, because
     323             :  * there is no equality fast-path within authoritative uuid comparator.
     324             :  */
     325             : static bool
     326        2322 : uuid_abbrev_abort(int memtupcount, SortSupport ssup)
     327             : {
     328        2322 :     uuid_sortsupport_state *uss = ssup->ssup_extra;
     329             :     double      abbr_card;
     330             : 
     331        2322 :     if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating)
     332        2130 :         return false;
     333             : 
     334         192 :     abbr_card = estimateHyperLogLog(&uss->abbr_card);
     335             : 
     336             :     /*
     337             :      * If we have >100k distinct values, then even if we were sorting many
     338             :      * billion rows we'd likely still break even, and the penalty of undoing
     339             :      * that many rows of abbrevs would probably not be worth it.  Stop even
     340             :      * counting at that point.
     341             :      */
     342         192 :     if (abbr_card > 100000.0)
     343             :     {
     344           0 :         if (trace_sort)
     345           0 :             elog(LOG,
     346             :                  "uuid_abbrev: estimation ends at cardinality %f"
     347             :                  " after " INT64_FORMAT " values (%d rows)",
     348             :                  abbr_card, uss->input_count, memtupcount);
     349           0 :         uss->estimating = false;
     350           0 :         return false;
     351             :     }
     352             : 
     353             :     /*
     354             :      * Target minimum cardinality is 1 per ~2k of non-null inputs.  0.5 row
     355             :      * fudge factor allows us to abort earlier on genuinely pathological data
     356             :      * where we've had exactly one abbreviated value in the first 2k
     357             :      * (non-null) rows.
     358             :      */
     359         192 :     if (abbr_card < uss->input_count / 2000.0 + 0.5)
     360             :     {
     361          96 :         if (trace_sort)
     362           0 :             elog(LOG,
     363             :                  "uuid_abbrev: aborting abbreviation at cardinality %f"
     364             :                  " below threshold %f after " INT64_FORMAT " values (%d rows)",
     365             :                  abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count,
     366             :                  memtupcount);
     367          96 :         return true;
     368             :     }
     369             : 
     370          96 :     if (trace_sort)
     371           0 :         elog(LOG,
     372             :              "uuid_abbrev: cardinality %f after " INT64_FORMAT
     373             :              " values (%d rows)", abbr_card, uss->input_count, memtupcount);
     374             : 
     375          96 :     return false;
     376             : }
     377             : 
     378             : /*
     379             :  * Conversion routine for sortsupport.  Converts original uuid representation
     380             :  * to abbreviated key representation.  Our encoding strategy is simple -- pack
     381             :  * the first `sizeof(Datum)` bytes of uuid data into a Datum (on little-endian
     382             :  * machines, the bytes are stored in reverse order), and treat it as an
     383             :  * unsigned integer.
     384             :  */
     385             : static Datum
     386     3384150 : uuid_abbrev_convert(Datum original, SortSupport ssup)
     387             : {
     388     3384150 :     uuid_sortsupport_state *uss = ssup->ssup_extra;
     389     3384150 :     pg_uuid_t  *authoritative = DatumGetUUIDP(original);
     390             :     Datum       res;
     391             : 
     392     3384150 :     memcpy(&res, authoritative->data, sizeof(Datum));
     393     3384150 :     uss->input_count += 1;
     394             : 
     395     3384150 :     if (uss->estimating)
     396             :     {
     397             :         uint32      tmp;
     398             : 
     399             : #if SIZEOF_DATUM == 8
     400     3384150 :         tmp = (uint32) res ^ (uint32) ((uint64) res >> 32);
     401             : #else                           /* SIZEOF_DATUM != 8 */
     402             :         tmp = (uint32) res;
     403             : #endif
     404             : 
     405     3384150 :         addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp)));
     406             :     }
     407             : 
     408             :     /*
     409             :      * Byteswap on little-endian machines.
     410             :      *
     411             :      * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer
     412             :      * 3-way comparator) works correctly on all platforms.  If we didn't do
     413             :      * this, the comparator would have to call memcmp() with a pair of
     414             :      * pointers to the first byte of each abbreviated key, which is slower.
     415             :      */
     416     3384150 :     res = DatumBigEndianToNative(res);
     417             : 
     418     3384150 :     return res;
     419             : }
     420             : 
     421             : /* hash index support */
     422             : Datum
     423        2422 : uuid_hash(PG_FUNCTION_ARGS)
     424             : {
     425        2422 :     pg_uuid_t  *key = PG_GETARG_UUID_P(0);
     426             : 
     427        2422 :     return hash_any(key->data, UUID_LEN);
     428             : }
     429             : 
     430             : Datum
     431          60 : uuid_hash_extended(PG_FUNCTION_ARGS)
     432             : {
     433          60 :     pg_uuid_t  *key = PG_GETARG_UUID_P(0);
     434             : 
     435          60 :     return hash_any_extended(key->data, UUID_LEN, PG_GETARG_INT64(1));
     436             : }
     437             : 
     438             : /*
     439             :  * Set the given UUID version and the variant bits
     440             :  */
     441             : static inline void
     442       53640 : uuid_set_version(pg_uuid_t *uuid, unsigned char version)
     443             : {
     444             :     /* set version field, top four bits */
     445       53640 :     uuid->data[6] = (uuid->data[6] & 0x0f) | (version << 4);
     446             : 
     447             :     /* set variant field, top two bits are 1, 0 */
     448       53640 :     uuid->data[8] = (uuid->data[8] & 0x3f) | 0x80;
     449       53640 : }
     450             : 
     451             : /*
     452             :  * Generate UUID version 4.
     453             :  *
     454             :  * All UUID bytes are filled with strong random numbers except version and
     455             :  * variant bits.
     456             :  */
     457             : Datum
     458          42 : gen_random_uuid(PG_FUNCTION_ARGS)
     459             : {
     460          42 :     pg_uuid_t  *uuid = palloc(UUID_LEN);
     461             : 
     462          42 :     if (!pg_strong_random(uuid, UUID_LEN))
     463           0 :         ereport(ERROR,
     464             :                 (errcode(ERRCODE_INTERNAL_ERROR),
     465             :                  errmsg("could not generate random values")));
     466             : 
     467             :     /*
     468             :      * Set magic numbers for a "version 4" (pseudorandom) UUID and variant,
     469             :      * see https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4
     470             :      */
     471          42 :     uuid_set_version(uuid, 4);
     472             : 
     473          42 :     PG_RETURN_UUID_P(uuid);
     474             : }
     475             : 
     476             : /*
     477             :  * Get the current timestamp with nanosecond precision for UUID generation.
     478             :  * The returned timestamp is ensured to be at least SUBMS_MINIMAL_STEP greater
     479             :  * than the previous returned timestamp (on this backend).
     480             :  */
     481             : static inline int64
     482       53598 : get_real_time_ns_ascending()
     483             : {
     484             :     static int64 previous_ns = 0;
     485             :     int64       ns;
     486             : 
     487             :     /* Get the current real timestamp */
     488             : 
     489             : #ifdef  _MSC_VER
     490             :     struct timeval tmp;
     491             : 
     492             :     gettimeofday(&tmp, NULL);
     493             :     ns = tmp.tv_sec * NS_PER_S + tmp.tv_usec * NS_PER_US;
     494             : #else
     495             :     struct timespec tmp;
     496             : 
     497             :     /*
     498             :      * We don't use gettimeofday(), instead use clock_gettime() with
     499             :      * CLOCK_REALTIME where available in order to get a high-precision
     500             :      * (nanoseconds) real timestamp.
     501             :      *
     502             :      * Note while a timestamp returned by clock_gettime() with CLOCK_REALTIME
     503             :      * is nanosecond-precision on most Unix-like platforms, on some platforms
     504             :      * such as macOS it's restricted to microsecond-precision.
     505             :      */
     506       53598 :     clock_gettime(CLOCK_REALTIME, &tmp);
     507       53598 :     ns = tmp.tv_sec * NS_PER_S + tmp.tv_nsec;
     508             : #endif
     509             : 
     510             :     /* Guarantee the minimal step advancement of the timestamp */
     511       53598 :     if (previous_ns + SUBMS_MINIMAL_STEP_NS >= ns)
     512           0 :         ns = previous_ns + SUBMS_MINIMAL_STEP_NS;
     513       53598 :     previous_ns = ns;
     514             : 
     515       53598 :     return ns;
     516             : }
     517             : 
     518             : /*
     519             :  * Generate UUID version 7 per RFC 9562, with the given timestamp.
     520             :  *
     521             :  * UUID version 7 consists of a Unix timestamp in milliseconds (48 bits) and
     522             :  * 74 random bits, excluding the required version and variant bits. To ensure
     523             :  * monotonicity in scenarios of high-frequency UUID generation, we employ the
     524             :  * method "Replace Leftmost Random Bits with Increased Clock Precision (Method 3)",
     525             :  * described in the RFC. This method utilizes 12 bits from the "rand_a" bits
     526             :  * to store a 1/4096 (or 2^12) fraction of sub-millisecond precision.
     527             :  *
     528             :  * unix_ts_ms is a number of milliseconds since start of the UNIX epoch,
     529             :  * and sub_ms is a number of nanoseconds within millisecond. These values are
     530             :  * used for time-dependent bits of UUID.
     531             :  *
     532             :  * NB: all numbers here are unsigned, unix_ts_ms cannot be negative per RFC.
     533             :  */
     534             : static pg_uuid_t *
     535       53598 : generate_uuidv7(uint64 unix_ts_ms, uint32 sub_ms)
     536             : {
     537       53598 :     pg_uuid_t  *uuid = palloc(UUID_LEN);
     538             :     uint32      increased_clock_precision;
     539             : 
     540             :     /* Fill in time part */
     541       53598 :     uuid->data[0] = (unsigned char) (unix_ts_ms >> 40);
     542       53598 :     uuid->data[1] = (unsigned char) (unix_ts_ms >> 32);
     543       53598 :     uuid->data[2] = (unsigned char) (unix_ts_ms >> 24);
     544       53598 :     uuid->data[3] = (unsigned char) (unix_ts_ms >> 16);
     545       53598 :     uuid->data[4] = (unsigned char) (unix_ts_ms >> 8);
     546       53598 :     uuid->data[5] = (unsigned char) unix_ts_ms;
     547             : 
     548             :     /*
     549             :      * sub-millisecond timestamp fraction (SUBMS_BITS bits, not
     550             :      * SUBMS_MINIMAL_STEP_BITS)
     551             :      */
     552       53598 :     increased_clock_precision = (sub_ms * (1 << SUBMS_BITS)) / NS_PER_MS;
     553             : 
     554             :     /* Fill the increased clock precision to "rand_a" bits */
     555       53598 :     uuid->data[6] = (unsigned char) (increased_clock_precision >> 8);
     556       53598 :     uuid->data[7] = (unsigned char) (increased_clock_precision);
     557             : 
     558             :     /* fill everything after the increased clock precision with random bytes */
     559       53598 :     if (!pg_strong_random(&uuid->data[8], UUID_LEN - 8))
     560           0 :         ereport(ERROR,
     561             :                 (errcode(ERRCODE_INTERNAL_ERROR),
     562             :                  errmsg("could not generate random values")));
     563             : 
     564             : #if SUBMS_MINIMAL_STEP_BITS == 10
     565             : 
     566             :     /*
     567             :      * On systems that have only 10 bits of sub-ms precision,  2 least
     568             :      * significant are dependent on other time-specific bits, and they do not
     569             :      * contribute to uniqueness. To make these bit random we mix in two bits
     570             :      * from CSPRNG. SUBMS_MINIMAL_STEP is chosen so that we still guarantee
     571             :      * monotonicity despite altering these bits.
     572             :      */
     573             :     uuid->data[7] = uuid->data[7] ^ (uuid->data[8] >> 6);
     574             : #endif
     575             : 
     576             :     /*
     577             :      * Set magic numbers for a "version 7" (pseudorandom) UUID and variant,
     578             :      * see https://www.rfc-editor.org/rfc/rfc9562#name-version-field
     579             :      */
     580       53598 :     uuid_set_version(uuid, 7);
     581             : 
     582       53598 :     return uuid;
     583             : }
     584             : 
     585             : /*
     586             :  * Generate UUID version 7 with the current timestamp.
     587             :  */
     588             : Datum
     589          78 : uuidv7(PG_FUNCTION_ARGS)
     590             : {
     591          78 :     int64       ns = get_real_time_ns_ascending();
     592          78 :     pg_uuid_t  *uuid = generate_uuidv7(ns / NS_PER_MS, ns % NS_PER_MS);
     593             : 
     594          78 :     PG_RETURN_UUID_P(uuid);
     595             : }
     596             : 
     597             : /*
     598             :  * Similar to uuidv7() but with the timestamp adjusted by the given interval.
     599             :  */
     600             : Datum
     601       53520 : uuidv7_interval(PG_FUNCTION_ARGS)
     602             : {
     603       53520 :     Interval   *shift = PG_GETARG_INTERVAL_P(0);
     604             :     TimestampTz ts;
     605             :     pg_uuid_t  *uuid;
     606       53520 :     int64       ns = get_real_time_ns_ascending();
     607             :     int64       us;
     608             : 
     609             :     /*
     610             :      * Shift the current timestamp by the given interval. To calculate time
     611             :      * shift correctly, we convert the UNIX epoch to TimestampTz and use
     612             :      * timestamptz_pl_interval(). This calculation is done with microsecond
     613             :      * precision.
     614             :      */
     615             : 
     616       53520 :     ts = (TimestampTz) (ns / NS_PER_US) -
     617             :         (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
     618             : 
     619             :     /* Compute time shift */
     620       53520 :     ts = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,
     621             :                                                  TimestampTzGetDatum(ts),
     622             :                                                  IntervalPGetDatum(shift)));
     623             : 
     624             :     /* Convert a TimestampTz value back to an UNIX epoch timestamp */
     625       53520 :     us = ts + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
     626             : 
     627             :     /* Generate an UUIDv7 */
     628       53520 :     uuid = generate_uuidv7(us / US_PER_MS, (us % US_PER_MS) * NS_PER_US + ns % NS_PER_US);
     629             : 
     630       53520 :     PG_RETURN_UUID_P(uuid);
     631             : }
     632             : 
     633             : /*
     634             :  * Start of a Gregorian epoch == date2j(1582,10,15)
     635             :  * We cast it to 64-bit because it's used in overflow-prone computations
     636             :  */
     637             : #define GREGORIAN_EPOCH_JDATE  INT64CONST(2299161)
     638             : 
     639             : /*
     640             :  * Extract timestamp from UUID.
     641             :  *
     642             :  * Returns null if not RFC 9562 variant or not a version that has a timestamp.
     643             :  */
     644             : Datum
     645       53538 : uuid_extract_timestamp(PG_FUNCTION_ARGS)
     646             : {
     647       53538 :     pg_uuid_t  *uuid = PG_GETARG_UUID_P(0);
     648             :     int         version;
     649             :     uint64      tms;
     650             :     TimestampTz ts;
     651             : 
     652             :     /* check if RFC 9562 variant */
     653       53538 :     if ((uuid->data[8] & 0xc0) != 0x80)
     654           6 :         PG_RETURN_NULL();
     655             : 
     656       53532 :     version = uuid->data[6] >> 4;
     657             : 
     658       53532 :     if (version == 1)
     659             :     {
     660           6 :         tms = ((uint64) uuid->data[0] << 24)
     661           6 :             + ((uint64) uuid->data[1] << 16)
     662           6 :             + ((uint64) uuid->data[2] << 8)
     663           6 :             + ((uint64) uuid->data[3])
     664           6 :             + ((uint64) uuid->data[4] << 40)
     665           6 :             + ((uint64) uuid->data[5] << 32)
     666           6 :             + (((uint64) uuid->data[6] & 0xf) << 56)
     667           6 :             + ((uint64) uuid->data[7] << 48);
     668             : 
     669             :         /* convert 100-ns intervals to us, then adjust */
     670           6 :         ts = (TimestampTz) (tms / 10) -
     671             :             ((uint64) POSTGRES_EPOCH_JDATE - GREGORIAN_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
     672           6 :         PG_RETURN_TIMESTAMPTZ(ts);
     673             :     }
     674             : 
     675       53526 :     if (version == 7)
     676             :     {
     677       53520 :         tms = (uuid->data[5])
     678       53520 :             + (((uint64) uuid->data[4]) << 8)
     679       53520 :             + (((uint64) uuid->data[3]) << 16)
     680       53520 :             + (((uint64) uuid->data[2]) << 24)
     681       53520 :             + (((uint64) uuid->data[1]) << 32)
     682       53520 :             + (((uint64) uuid->data[0]) << 40);
     683             : 
     684             :         /* convert ms to us, then adjust */
     685       53520 :         ts = (TimestampTz) (tms * NS_PER_US) -
     686             :             (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
     687             : 
     688       53520 :         PG_RETURN_TIMESTAMPTZ(ts);
     689             :     }
     690             : 
     691             :     /* not a timestamp-containing UUID version */
     692           6 :     PG_RETURN_NULL();
     693             : }
     694             : 
     695             : /*
     696             :  * Extract UUID version.
     697             :  *
     698             :  * Returns null if not RFC 9562 variant.
     699             :  */
     700             : Datum
     701          30 : uuid_extract_version(PG_FUNCTION_ARGS)
     702             : {
     703          30 :     pg_uuid_t  *uuid = PG_GETARG_UUID_P(0);
     704             :     uint16      version;
     705             : 
     706             :     /* check if RFC 9562 variant */
     707          30 :     if ((uuid->data[8] & 0xc0) != 0x80)
     708           6 :         PG_RETURN_NULL();
     709             : 
     710          24 :     version = uuid->data[6] >> 4;
     711             : 
     712          24 :     PG_RETURN_UINT16(version);
     713             : }

Generated by: LCOV version 1.14