Skip to content

Commit d4a1d77

Browse files
committed
Add support for partitioning on UUIDv7
Add initial support for partitioning a hypertable on a UUID column. Currently, only version 7 UUIDs are supported in a UUID partitioning column. Inserting a non-v7 UUID generates an error. This might be relaxed in the future. Chunk exclusion and filtering on a UUID column works as expected. However, native UUID support for query functions, such as time_bucket and gapfill, is not added as part of this change.
1 parent 3779fae commit d4a1d77

File tree

12 files changed

+594
-12
lines changed

12 files changed

+594
-12
lines changed

.unreleased/pr_8505

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #8505 Add support for partitioning on UUIDv7

src/dimension.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1041,9 +1041,11 @@ get_default_interval(Oid dimtype, bool adaptive_chunking)
10411041
case TIMESTAMPOID:
10421042
case TIMESTAMPTZOID:
10431043
case DATEOID:
1044+
case UUIDOID:
10441045
interval = adaptive_chunking ? DEFAULT_CHUNK_TIME_INTERVAL_ADAPTIVE :
10451046
DEFAULT_CHUNK_TIME_INTERVAL;
10461047
break;
1048+
break;
10471049
default:
10481050
ereport(ERROR,
10491051
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1085,7 +1087,7 @@ dimension_interval_to_internal(const char *colname, Oid dimtype, Oid valuetype,
10851087
interval = get_validated_integer_interval(dimtype, DatumGetInt64(value));
10861088
break;
10871089
case INTERVALOID:
1088-
if (!IS_TIMESTAMP_TYPE(dimtype))
1090+
if (!IS_TIMESTAMP_TYPE(dimtype) && !IS_UUID_TYPE(dimtype))
10891091
ereport(ERROR,
10901092
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
10911093
errmsg("invalid interval type for %s dimension", format_type_be(dimtype)),

src/dimension.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ typedef struct Dimension
3939
#define IS_OPEN_DIMENSION(d) ((d)->type == DIMENSION_TYPE_OPEN)
4040
#define IS_CLOSED_DIMENSION(d) ((d)->type == DIMENSION_TYPE_CLOSED)
4141
#define IS_VALID_OPEN_DIM_TYPE(type) \
42-
(IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type) || ts_type_is_int8_binary_compatible(type))
42+
(IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type) || IS_UUID_TYPE(type) || \
43+
ts_type_is_int8_binary_compatible(type))
4344

4445
/*
4546
* A hyperspace defines how to partition in a N-dimensional space.

src/time_utils.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ ts_time_datum_get_min(Oid timetype)
199199
return Int32GetDatum(PG_INT32_MIN);
200200
case INT8OID:
201201
return Int64GetDatum(PG_INT64_MIN);
202+
case UUIDOID:
203+
return Int64GetDatum(TS_TIME_UUID_MIN);
202204
default:
203205
break;
204206
}
@@ -229,6 +231,7 @@ ts_time_datum_get_end(Oid timetype)
229231
case INT2OID:
230232
case INT4OID:
231233
case INT8OID:
234+
case UUIDOID:
232235
elog(ERROR, "END is not defined for \"%s\"", format_type_be(timetype));
233236
break;
234237
default:
@@ -255,7 +258,8 @@ ts_time_datum_get_max(Oid timetype)
255258
return Int32GetDatum(PG_INT32_MAX);
256259
case INT8OID:
257260
return Int64GetDatum(PG_INT64_MAX);
258-
break;
261+
case UUIDOID:
262+
return Int64GetDatum(TS_TIME_UUID_MAX);
259263
default:
260264
break;
261265
}
@@ -277,6 +281,7 @@ ts_time_datum_get_nobegin(Oid timetype)
277281
case INT2OID:
278282
case INT4OID:
279283
case INT8OID:
284+
case UUIDOID:
280285
elog(ERROR, "NOBEGIN is not defined for \"%s\"", format_type_be(timetype));
281286
break;
282287
default:
@@ -309,6 +314,7 @@ ts_time_datum_get_noend(Oid timetype)
309314
case INT2OID:
310315
case INT4OID:
311316
case INT8OID:
317+
case UUIDOID:
312318
elog(ERROR, "NOEND is not defined for \"%s\"", format_type_be(timetype));
313319
break;
314320
default:
@@ -338,6 +344,8 @@ ts_time_get_min(Oid timetype)
338344
return PG_INT32_MIN;
339345
case INT8OID:
340346
return PG_INT64_MIN;
347+
case UUIDOID:
348+
return TS_TIME_UUID_MIN;
341349
default:
342350
break;
343351
}
@@ -365,6 +373,8 @@ ts_time_get_max(Oid timetype)
365373
return PG_INT32_MAX;
366374
case INT8OID:
367375
return PG_INT64_MAX;
376+
case UUIDOID:
377+
return TS_TIME_UUID_MAX;
368378
default:
369379
break;
370380
}
@@ -391,6 +401,7 @@ ts_time_get_end(Oid timetype)
391401
case INT2OID:
392402
case INT4OID:
393403
case INT8OID:
404+
case UUIDOID:
394405
elog(ERROR, "END is not defined for \"%s\"", format_type_be(timetype));
395406
break;
396407
default:
@@ -426,6 +437,7 @@ ts_time_get_nobegin(Oid timetype)
426437
case INT2OID:
427438
case INT4OID:
428439
case INT8OID:
440+
case UUIDOID:
429441
elog(ERROR, "-Infinity not defined for \"%s\"", format_type_be(timetype));
430442
break;
431443
default:
@@ -456,6 +468,7 @@ ts_time_get_noend(Oid timetype)
456468
case INT2OID:
457469
case INT4OID:
458470
case INT8OID:
471+
case UUIDOID:
459472
elog(ERROR, "+Infinity not defined for \"%s\"", format_type_be(timetype));
460473
break;
461474
default:

src/time_utils.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,24 @@
3939
#define TS_TIME_NOBEGIN (PG_INT64_MIN)
4040
#define TS_TIME_NOEND (PG_INT64_MAX)
4141

42+
/*
43+
* A UUIDv7 timestamp is 6 bytes milliseconds in Unix epoch (unsigned).
44+
*
45+
* Since RFC9562 specifies the timestamp as unsigned, the minimum value is
46+
* 0. Further, the sub-millisecond part cannot be used as time since the bits
47+
* are optional and it is not possible to know if they are random or represent
48+
* a time fraction. Therefore, the max value is limited to the milliseconds.
49+
*/
50+
#define TS_TIME_UUID_MS_MIN (0x000000000000)
51+
#define TS_TIME_UUID_MIN (0x000000000000 * 1000) /* microseconds */
52+
#define TS_TIME_UUID_MS_MAX (0xFFFFFFFFFFFF)
53+
#define TS_TIME_UUID_MAX (TS_TIME_UUID_MS_MAX * 1000) /* microseconds */
54+
4255
#define IS_INTEGER_TYPE(type) (type == INT2OID || type == INT4OID || type == INT8OID)
4356
#define IS_TIMESTAMP_TYPE(type) (type == TIMESTAMPOID || type == TIMESTAMPTZOID || type == DATEOID)
44-
#define IS_VALID_TIME_TYPE(type) (IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type))
57+
#define IS_UUID_TYPE(type) (type == UUIDOID)
58+
#define IS_VALID_TIME_TYPE(type) \
59+
(IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type) || IS_UUID_TYPE(type))
4560

4661
#define TS_TIME_DATUM_IS_MIN(timeval, type) (timeval == ts_time_datum_get_min(type))
4762
#define TS_TIME_DATUM_IS_MAX(timeval, type) (timeval == ts_time_datum_get_max(type))

src/utils.c

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <utils/builtins.h>
3232
#include <utils/catcache.h>
3333
#include <utils/date.h>
34+
#include <utils/elog.h>
3435
#include <utils/fmgroids.h>
3536
#include <utils/fmgrprotos.h>
3637
#include <utils/lsyscache.h>
@@ -47,6 +48,9 @@
4748
#include "jsonb_utils.h"
4849
#include "time_utils.h"
4950
#include "utils.h"
51+
#include "uuid.h"
52+
#include <utils/timestamp.h>
53+
#include <utils/uuid.h>
5054

5155
typedef struct
5256
{
@@ -148,7 +152,7 @@ ts_time_value_to_internal(Datum time_val, Oid type_oid)
148152
elog(ERROR, "unknown time type \"%s\"", format_type_be(type_oid));
149153
}
150154

151-
if (IS_INTEGER_TYPE(type_oid))
155+
if (IS_INTEGER_TYPE(type_oid) || IS_UUID_TYPE(type_oid))
152156
{
153157
/* Integer time types have no distinction between min, max and
154158
* infinity. We don't want min and max to be turned into infinity for
@@ -189,6 +193,30 @@ ts_time_value_to_internal(Datum time_val, Oid type_oid)
189193
res = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, tz);
190194

191195
return DatumGetInt64(res);
196+
case UUIDOID:
197+
{
198+
uint64 unixtime_ms = 0;
199+
200+
/*
201+
* Extract the unix timestamp from the UUUID. Note that we cannot
202+
* use the (optional) sub-milliseconds part because there is no
203+
* way to know whether it represents time or is random.
204+
*
205+
* If the UUID is not v7, error out.
206+
*/
207+
if (!ts_uuid_v7_extract_unixtime(DatumGetUUIDP(time_val), &unixtime_ms, NULL))
208+
{
209+
ereport(ERROR,
210+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
211+
errmsg("%s is not version 7 UUID",
212+
DatumGetCString(DirectFunctionCall1(uuid_out, time_val))),
213+
errdetail(
214+
"UUID \"time\" partitioning columns only support version 7 UUIDs."));
215+
}
216+
217+
/* Convert to microseconds */
218+
return unixtime_ms * 1000;
219+
}
192220
default:
193221
elog(ERROR, "unknown time type \"%s\"", format_type_be(type_oid));
194222
return -1;
@@ -294,6 +322,30 @@ ts_time_value_to_internal_or_infinite(Datum time_val, Oid type_oid)
294322

295323
return ts_time_value_to_internal(time_val, type_oid);
296324
}
325+
case UUIDOID:
326+
{
327+
uint64 unixtime_ms = 0;
328+
329+
/*
330+
* Extract the unix timestamp from the UUUID. Note that we cannot
331+
* use the (optional) sub-milliseconds part because there is no
332+
* way to know whether it represents time or is random.
333+
*
334+
* If the UUID is not v7, error out.
335+
*/
336+
if (!ts_uuid_v7_extract_unixtime(DatumGetUUIDP(time_val), &unixtime_ms, NULL))
337+
{
338+
ereport(ERROR,
339+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
340+
errmsg("%s is not version 7 UUID",
341+
DatumGetCString(DirectFunctionCall1(uuid_out, time_val))),
342+
errdetail(
343+
"UUID \"time\" partitioning columns only support version 7 UUIDs."));
344+
}
345+
346+
/* Convert to microseconds */
347+
return unixtime_ms * 1000;
348+
}
297349
default:
298350
return ts_time_value_to_internal(time_val, type_oid);
299351
}
@@ -338,6 +390,17 @@ ts_internal_to_time_value(int64 value, Oid type)
338390
return DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(value));
339391
case DATEOID:
340392
return DirectFunctionCall1(ts_pg_unix_microseconds_to_date, Int64GetDatum(value));
393+
case UUIDOID:
394+
{
395+
/*
396+
* Convert the internal unixtime in ms to a UUID with the
397+
* non-timestamp bits set to zero. We do not set the version
398+
* either, because for ranges we only care about the prefix in
399+
* order to divide the whole UUID space into a set of slices.
400+
*/
401+
const pg_uuid_t *uuid = ts_create_uuid_v7_from_unixtime_us(value, true, false);
402+
return UUIDPGetDatum(uuid);
403+
}
341404
default:
342405
if (ts_type_is_int8_binary_compatible(type))
343406
return Int64GetDatum(value);
@@ -365,6 +428,7 @@ ts_internal_to_time_int64(int64 value, Oid type)
365428
return value;
366429
case TIMESTAMPOID:
367430
case TIMESTAMPTZOID:
431+
case UUIDOID:
368432
/* we continue ts_time_value_to_internal's incorrect handling of TIMESTAMPs for
369433
* compatibility */
370434
return DatumGetInt64(

src/uuid.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ ts_uuid_generate(PG_FUNCTION_ARGS)
5858
}
5959

6060
pg_uuid_t *
61-
ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool zeroed)
61+
ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool zeroed, bool set_version)
6262
{
6363
pg_uuid_t *uuid;
6464
uint64_t timestamp_be = pg_hton64((unixtime_us / 1000) << 16);
@@ -87,11 +87,14 @@ ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool zeroed)
8787
uuid->data[6] = (unsigned char) (ts_micros >> 8);
8888
uuid->data[7] = (unsigned char) ts_micros;
8989

90-
/* Set version 7 (0111) in bits 6-7 of byte 6, keep random bits 0-5 */
91-
uuid->data[6] = (uuid->data[6] & 0x0F) | 0x70;
90+
if (set_version)
91+
{
92+
/* Set version 7 (0111) in bits 6-7 of byte 6, keep random bits 0-5 */
93+
uuid->data[6] = (uuid->data[6] & 0x0F) | 0x70;
9294

93-
/* Set variant (10) in bits 4-5 of byte 8, keep random bits 0-3 and 6-7 */
94-
uuid->data[8] = (uuid->data[8] & 0x3F) | 0x80;
95+
/* Set variant (10) in bits 4-5 of byte 8, keep random bits 0-3 and 6-7 */
96+
uuid->data[8] = (uuid->data[8] & 0x3F) | 0x80;
97+
}
9598

9699
return uuid;
97100
}
@@ -102,7 +105,7 @@ ts_create_uuid_v7_from_timestamptz(TimestampTz ts, bool zeroed)
102105
int64 epoch_diff_us = ((int64) (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * USECS_PER_DAY);
103106
int64 unixtime_us = ts + epoch_diff_us;
104107

105-
return ts_create_uuid_v7_from_unixtime_us(unixtime_us, zeroed);
108+
return ts_create_uuid_v7_from_unixtime_us(unixtime_us, zeroed, true);
106109
}
107110

108111
TS_FUNCTION_INFO_V1(ts_uuid_generate_v7);

src/uuid.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <utils/uuid.h>
1111

1212
extern pg_uuid_t *ts_uuid_create(void);
13-
extern pg_uuid_t *ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool zeroed);
13+
extern pg_uuid_t *ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool zeroed,
14+
bool set_version);
1415
extern TSDLLEXPORT pg_uuid_t *ts_create_uuid_v7_from_timestamptz(TimestampTz ts, bool zero);
1516
extern bool ts_uuid_v7_extract_unixtime(const pg_uuid_t *uuid, uint64 *unixtime_ms, uint16 *sub_us);

0 commit comments

Comments
 (0)