Skip to content
/ server Public
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion include/my_sys.h
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ int my_msync(int, void *, size_t, int);
void my_uuid_init(ulong seed1, ulong seed2);
void my_uuid(uchar *guid);
void my_uuid_end(void);

int my_uuid_extract_ts(const char *uuid, my_time_t *seconds, ulong *usec);
static inline void my_uuid2str(const uchar *guid, char *s, int with_separators)
{
int i;
Expand Down
63 changes: 63 additions & 0 deletions mysys/my_uuid.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,66 @@ void my_uuid_end()
mysql_mutex_destroy(&LOCK_uuid_generator);
}
}


/**
Extract Unix timestamp from a UUIDv1 or UUIDv7

@param[in] uuid UUID bytes (16 bytes, big-endian)
@param[out] seconds Unix timestamp seconds
@param[out] usec Microseconds part

@return
@retval 1 Success
@retval 0 UUID version doesn't contain timestamp or timestamp invalid

UUIDv1 format (RFC 4122, big-endian):
Bytes 0-3: time_low (32 bits, low part of timestamp)
Bytes 4-5: time_mid (16 bits, middle part of timestamp)
Bytes 6-7: version (4 bits) + time_hi (12 bits, high part of timestamp)
Timestamp is 100-nanosecond intervals since 1582-10-15

UUIDv7 format (RFC 9562, big-endian):
Bytes 0-5: Unix timestamp in milliseconds (48 bits)
Bytes 6-7: version (4 bits) + sub-millisecond precision (12 bits)
*/
int my_uuid_extract_ts(const char *uuid, my_time_t *seconds, ulong *usec)
{
uint version= (uchar) uuid[6] >> 4;
ulonglong ts;

if (version == 7)
{
/* UUIDv7: bytes 0-5 are Unix timestamp in milliseconds (big-endian) */
ts= mi_uint6korr(uuid);
*seconds= ts / 1000;
*usec= (ts % 1000) * 1000;
return 1;
}

if (version == 1)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beginner question: Would a switch block work here?

/*
UUIDv1: reconstruct 60-bit timestamp from three fields:
- time_low (bytes 0-3): bits 0-31 of timestamp
- time_mid (bytes 4-5): bits 32-47 of timestamp
- time_hi (bytes 6-7): bits 48-59 of timestamp (masked, 4 bits are version)
Formula: (time_hi << 48) | (time_mid << 32) | time_low
*/
ts= ((ulonglong)(mi_uint2korr(uuid + 6) & 0x0FFF) << 48) |
((ulonglong) mi_uint2korr(uuid + 4) << 32) |
(ulonglong) mi_uint4korr(uuid);

/* Timestamp before Unix epoch (1970-01-01) */
if (ts < UUID_TIME_OFFSET)
return 0;

ts= (ts - UUID_TIME_OFFSET) / 10; /* Convert to microseconds */
*seconds= ts / 1000000;
*usec= ts % 1000000;
return 1;
}

/* Other versions (e.g., v4) don't contain timestamps */
return 0;
}
29 changes: 29 additions & 0 deletions plugin/type_uuid/item_uuidfunc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,32 @@ String *Item_func_sys_guid::val_str(String *str)
my_uuid2str(buf, const_cast<char*>(str->ptr()), 0);
return str;
}


bool Item_func_uuid_timestamp::fix_length_and_dec(THD *thd)
{
Type_std_attributes::set(
Type_temporal_attributes_not_fixed_dec(MAX_DATETIME_WIDTH,
TIME_SECOND_PART_DIGITS, false),
DTCollation_numeric());
set_maybe_null();
return false;
}


bool Item_func_uuid_timestamp::get_timestamp(my_time_t *sec, ulong *usec)
{
Type_handler_uuid_new::Fbt_null uuid(args[0]);
if (uuid.is_null())
return true;
return !my_uuid_extract_ts(uuid.to_lex_cstring().str, sec, usec);
}


bool Item_func_uuid_timestamp::val_native(THD *thd, Native *to)
{
my_time_t seconds;
ulong usec;
return (null_value= get_timestamp(&seconds, &usec)) ||
(null_value= Timestamp(seconds, usec).to_native(to, TIME_SECOND_PART_DIGITS));
}
24 changes: 24 additions & 0 deletions plugin/type_uuid/item_uuidfunc.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@


#include "item.h"
#include "item_timefunc.h"
#include "sql_type_uuid_v1.h"
#include "sql_type_uuid_v4.h"
#include "sql_type_uuid_v7.h"
Expand Down Expand Up @@ -115,4 +116,27 @@ class Item_func_uuid_v7: public Item_func_uuid_vx<UUIDv7>
Item *do_get_copy(THD *thd) const override
{ return get_item_copy<Item_func_uuid_v7>(thd, this); }
};


class Item_func_uuid_timestamp: public Item_timestampfunc
{
bool check_arguments() const override
{
return args[0]->check_type_can_return_str(func_name_cstring());
}
bool get_timestamp(my_time_t *sec, ulong *usec);
public:
Item_func_uuid_timestamp(THD *thd, Item *arg1)
: Item_timestampfunc(thd, arg1) {}

LEX_CSTRING func_name_cstring() const override
{ return {STRING_WITH_LEN("uuid_timestamp")}; }

bool fix_length_and_dec(THD *thd) override;
bool val_native(THD *thd, Native *to) override;

Item *do_get_copy(THD *thd) const override
{ return get_item_copy<Item_func_uuid_timestamp>(thd, this); }
};

#endif // ITEM_UUIDFUNC_INCLUDED
77 changes: 77 additions & 0 deletions plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
SET time_zone='+00:00';
#
# UUIDv1 with known timestamps
#
SELECT UUID_TIMESTAMP('63b00000-bfde-11d3-80fe-9f59977b836e');
UUID_TIMESTAMP('63b00000-bfde-11d3-80fe-9f59977b836e')
2000-01-01 00:00:00.000000
SELECT UUID_TIMESTAMP('16488880-2b13-11ef-808b-603c7ba5e656');
UUID_TIMESTAMP('16488880-2b13-11ef-808b-603c7ba5e656')
2024-06-15 12:30:45.000000
SELECT UUID_TIMESTAMP('d02b2980-e6a4-11f0-8072-39f36896a88f');
UUID_TIMESTAMP('d02b2980-e6a4-11f0-8072-39f36896a88f')
2025-12-31 23:59:59.000000
#
# UUIDv7 with known timestamps (ms precision)
#
SELECT UUID_TIMESTAMP('00dc6acf-ac00-737d-9f12-3229a718bf21');
UUID_TIMESTAMP('00dc6acf-ac00-737d-9f12-3229a718bf21')
2000-01-01 00:00:00.000000
SELECT UUID_TIMESTAMP('01901be0-f183-7e6c-9601-62a5e4f6e7b8');
UUID_TIMESTAMP('01901be0-f183-7e6c-9601-62a5e4f6e7b8')
2024-06-15 12:30:45.123000
SELECT UUID_TIMESTAMP('019b76da-a7ff-7ece-a98f-44624a8d3b0f');
UUID_TIMESTAMP('019b76da-a7ff-7ece-a98f-44624a8d3b0f')
2025-12-31 23:59:59.999000
#
# UUIDv4 returns NULL (no timestamp)
#
SELECT UUID_TIMESTAMP(UUID_V4()) IS NULL AS v4_returns_null;
v4_returns_null
1
SELECT UUID_TIMESTAMP('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11') IS NULL AS v4_string_returns_null;
v4_string_returns_null
1
#
# NULL and invalid input
#
SELECT UUID_TIMESTAMP(NULL) IS NULL AS null_input;
null_input
1
SELECT UUID_TIMESTAMP('not-a-valid-uuid');
UUID_TIMESTAMP('not-a-valid-uuid')
NULL
Warnings:
Warning 1292 Incorrect uuid value: 'not-a-valid-uuid'
#
# Native UUID type
#
SELECT UUID_TIMESTAMP(CAST('01901be0-f183-7e6c-9601-62a5e4f6e7b8' AS UUID));
UUID_TIMESTAMP(CAST('01901be0-f183-7e6c-9601-62a5e4f6e7b8' AS UUID))
2024-06-15 12:30:45.123000
#
# Return type is TIMESTAMP(6)
#
CREATE TABLE t1 AS SELECT UUID_TIMESTAMP('01901be0-f183-7e6c-9601-62a5e4f6e7b8') AS ts;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`ts` timestamp(6) NULL DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
DROP TABLE t1;
#
# Edge cases
#
SELECT UUID_TIMESTAMP('00000000-0000-1000-8000-000000000000') IS NULL AS before_unix_epoch;
before_unix_epoch
1
SELECT UUID_TIMESTAMP('00000000-0000-7000-8000-000000000000');
UUID_TIMESTAMP('00000000-0000-7000-8000-000000000000')
0000-00-00 00:00:00.000000
SELECT UUID_TIMESTAMP('ffffffff-ffff-7fff-8000-000000000000');
UUID_TIMESTAMP('ffffffff-ffff-7fff-8000-000000000000')
2042-12-13 16:54:30.655000
SELECT UUID_TIMESTAMP('ffffffff-ffff-1fff-8000-000000000000');
UUID_TIMESTAMP('ffffffff-ffff-1fff-8000-000000000000')
2105-11-25 16:30:52.684697
SET time_zone=DEFAULT;
61 changes: 61 additions & 0 deletions plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# MDEV-33710: UUID_TIMESTAMP() extracts timestamp from UUIDv1 and UUIDv7

SET time_zone='+00:00';

--echo #
--echo # UUIDv1 with known timestamps
--echo #
# 2000-01-01 00:00:00.000000
SELECT UUID_TIMESTAMP('63b00000-bfde-11d3-80fe-9f59977b836e');
# 2024-06-15 12:30:45.000000
SELECT UUID_TIMESTAMP('16488880-2b13-11ef-808b-603c7ba5e656');
# 2025-12-31 23:59:59.000000
SELECT UUID_TIMESTAMP('d02b2980-e6a4-11f0-8072-39f36896a88f');

--echo #
--echo # UUIDv7 with known timestamps (ms precision)
--echo #
# 2000-01-01 00:00:00.000000
SELECT UUID_TIMESTAMP('00dc6acf-ac00-737d-9f12-3229a718bf21');
# 2024-06-15 12:30:45.123000
SELECT UUID_TIMESTAMP('01901be0-f183-7e6c-9601-62a5e4f6e7b8');
# 2025-12-31 23:59:59.999000
SELECT UUID_TIMESTAMP('019b76da-a7ff-7ece-a98f-44624a8d3b0f');

--echo #
--echo # UUIDv4 returns NULL (no timestamp)
--echo #
SELECT UUID_TIMESTAMP(UUID_V4()) IS NULL AS v4_returns_null;
SELECT UUID_TIMESTAMP('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11') IS NULL AS v4_string_returns_null;

--echo #
--echo # NULL and invalid input
--echo #
SELECT UUID_TIMESTAMP(NULL) IS NULL AS null_input;
SELECT UUID_TIMESTAMP('not-a-valid-uuid');

--echo #
--echo # Native UUID type
--echo #
SELECT UUID_TIMESTAMP(CAST('01901be0-f183-7e6c-9601-62a5e4f6e7b8' AS UUID));

--echo #
--echo # Return type is TIMESTAMP(6)
--echo #
CREATE TABLE t1 AS SELECT UUID_TIMESTAMP('01901be0-f183-7e6c-9601-62a5e4f6e7b8') AS ts;
SHOW CREATE TABLE t1;
DROP TABLE t1;

--echo #
--echo # Edge cases
--echo #
# UUIDv1 before Unix epoch
SELECT UUID_TIMESTAMP('00000000-0000-1000-8000-000000000000') IS NULL AS before_unix_epoch;
# UUIDv7 at Unix epoch (ts=0)
SELECT UUID_TIMESTAMP('00000000-0000-7000-8000-000000000000');
# UUIDv7 max 48-bit timestamp
SELECT UUID_TIMESTAMP('ffffffff-ffff-7fff-8000-000000000000');
# UUIDv1 max timestamp
SELECT UUID_TIMESTAMP('ffffffff-ffff-1fff-8000-000000000000');

SET time_zone=DEFAULT;
34 changes: 33 additions & 1 deletion plugin/type_uuid/plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,33 @@ class Create_func_uuid_v7 : public Create_func_arg0
virtual ~Create_func_uuid_v7() {}
};

class Create_func_uuid_timestamp : public Create_func_arg1
{
public:
Item *create_1_arg(THD *thd, Item *arg1) override
{
DBUG_ENTER("Create_func_uuid_timestamp::create_1_arg");
DBUG_RETURN(new (thd->mem_root) Item_func_uuid_timestamp(thd, arg1));
}
static Create_func_uuid_timestamp s_singleton;

protected:
Create_func_uuid_timestamp() {}
virtual ~Create_func_uuid_timestamp() {}
};

Create_func_uuid Create_func_uuid::s_singleton;
Create_func_sys_guid Create_func_sys_guid::s_singleton;
Create_func_uuid_v4 Create_func_uuid_v4::s_singleton;
Create_func_uuid_v7 Create_func_uuid_v7::s_singleton;
Create_func_uuid_timestamp Create_func_uuid_timestamp::s_singleton;

static Plugin_function
plugin_descriptor_function_uuid(&Create_func_uuid::s_singleton),
plugin_descriptor_function_sys_guid(&Create_func_sys_guid::s_singleton),
plugin_descriptor_function_uuid_v4(&Create_func_uuid_v4::s_singleton),
plugin_descriptor_function_uuid_v7(&Create_func_uuid_v7::s_singleton);
plugin_descriptor_function_uuid_v7(&Create_func_uuid_v7::s_singleton),
plugin_descriptor_function_uuid_timestamp(&Create_func_uuid_timestamp::s_singleton);

static constexpr Name type_name={STRING_WITH_LEN("uuid")};

Expand Down Expand Up @@ -301,5 +318,20 @@ maria_declare_plugin(type_uuid)
NULL, // System variables
"1.0.1", // String version representation
MariaDB_PLUGIN_MATURITY_STABLE// Maturity(see include/mysql/plugin.h)*/
},
{
MariaDB_FUNCTION_PLUGIN, // the plugin type (see include/mysql/plugin.h)
&plugin_descriptor_function_uuid_timestamp, // pointer to type-specific plugin descriptor
"uuid_timestamp", // plugin name
"Varun Deep Saini", // plugin author
"Function UUID_TIMESTAMP()", // the plugin description
PLUGIN_LICENSE_GPL, // the plugin license (see include/mysql/plugin.h)
0, // Pointer to plugin initialization function
0, // Pointer to plugin deinitialization function
0x0100, // Numeric version 0xAABB means AA.BB version
NULL, // Status variables
NULL, // System variables
"1.0", // String version representation
MariaDB_PLUGIN_MATURITY_STABLE// Maturity(see include/mysql/plugin.h)*/
}
maria_declare_plugin_end;