diff --git a/include/my_sys.h b/include/my_sys.h index 3fd85988a7619..b7798c9f64ce3 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -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; diff --git a/mysys/my_uuid.c b/mysys/my_uuid.c index 7925f80191b77..22beb0c6449d3 100644 --- a/mysys/my_uuid.c +++ b/mysys/my_uuid.c @@ -39,6 +39,7 @@ identifier. */ +#include #include "mysys_priv.h" #include #include @@ -224,3 +225,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 0 Success + @retval 1 UUID version doesn't contain timestamp or timestamp invalid + + UUIDv1 format (RFC 9562 Section 5.1, 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 Section 5.7, 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) +{ + char version= uuid[6] >> 4; + ulonglong ts; + + switch (version) + { + case 7: + /* UUIDv7: bytes 0-5 are Unix timestamp in milliseconds (big-endian) */ + ts= mi_uint6korr(uuid); + *seconds= ts / 1000; + *usec= (ts % 1000) * 1000; + return false; + + case 1: + /* + 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 true; + + ts= (ts - UUID_TIME_OFFSET) / 10; /* Convert to microseconds */ + *seconds= ts / 1000000; + *usec= ts % 1000000; + return false; + + default: + /* Other versions (e.g., v4) don't contain timestamps */ + return true; + } +} diff --git a/plugin/type_uuid/item_uuidfunc.cc b/plugin/type_uuid/item_uuidfunc.cc index c024cb30e3e5a..efbc43f857423 100644 --- a/plugin/type_uuid/item_uuidfunc.cc +++ b/plugin/type_uuid/item_uuidfunc.cc @@ -29,3 +29,32 @@ String *Item_func_sys_guid::val_str(String *str) my_uuid2str(buf, const_cast(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)); +} diff --git a/plugin/type_uuid/item_uuidfunc.h b/plugin/type_uuid/item_uuidfunc.h index 5de03746a3ada..a21587ad57ce0 100644 --- a/plugin/type_uuid/item_uuidfunc.h +++ b/plugin/type_uuid/item_uuidfunc.h @@ -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" @@ -115,4 +116,27 @@ class Item_func_uuid_v7: public Item_func_uuid_vx Item *do_get_copy(THD *thd) const override { return get_item_copy(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(thd, this); } +}; + #endif // ITEM_UUIDFUNC_INCLUDED diff --git a/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.result b/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.result new file mode 100644 index 0000000000000..e7bc9c16d4557 --- /dev/null +++ b/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.result @@ -0,0 +1,88 @@ +SET time_zone='+00:00'; +# +# UUIDv1 with known timestamps +# +SELECT '4bd352dc-e593-11f0-8de9-0242ac120002' AS uuid, +UUID_TIMESTAMP('4bd352dc-e593-11f0-8de9-0242ac120002') AS ts; +uuid ts +4bd352dc-e593-11f0-8de9-0242ac120002 2025-12-30 15:22:04.357910 +SELECT 'bec1046a-e593-11f0-8de9-0242ac120002' AS uuid, +UUID_TIMESTAMP('bec1046a-e593-11f0-8de9-0242ac120002') AS ts; +uuid ts +bec1046a-e593-11f0-8de9-0242ac120002 2025-12-30 15:25:17.175921 +SELECT 'c28c09a0-e593-11f0-8de9-0242ac120002' AS uuid, +UUID_TIMESTAMP('c28c09a0-e593-11f0-8de9-0242ac120002') AS ts; +uuid ts +c28c09a0-e593-11f0-8de9-0242ac120002 2025-12-30 15:25:23.539600 +# +# UUIDv7 with known timestamps (ms precision) +# +SELECT '019b6fdd-4937-7bb5-95c7-53363c6df927' AS uuid, +UUID_TIMESTAMP('019b6fdd-4937-7bb5-95c7-53363c6df927') AS ts; +uuid ts +019b6fdd-4937-7bb5-95c7-53363c6df927 2025-12-30 15:25:31.831000 +SELECT '019b6fdd-5f17-7cf2-b034-248b9c207db6' AS uuid, +UUID_TIMESTAMP('019b6fdd-5f17-7cf2-b034-248b9c207db6') AS ts; +uuid ts +019b6fdd-5f17-7cf2-b034-248b9c207db6 2025-12-30 15:25:37.431000 +SELECT '019b6fdd-7327-7a5b-935c-4de00dd0e7c6' AS uuid, +UUID_TIMESTAMP('019b6fdd-7327-7a5b-935c-4de00dd0e7c6') AS ts; +uuid ts +019b6fdd-7327-7a5b-935c-4de00dd0e7c6 2025-12-30 15:25:42.567000 +# +# 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 '019b6fdd-4937-7bb5-95c7-53363c6df927' AS uuid, +UUID_TIMESTAMP(CAST('019b6fdd-4937-7bb5-95c7-53363c6df927' AS UUID)) AS ts; +uuid ts +019b6fdd-4937-7bb5-95c7-53363c6df927 2025-12-30 15:25:31.831000 +# +# Return type is TIMESTAMP(6) +# +CREATE TABLE t1 AS SELECT UUID_TIMESTAMP('019b6fdd-4937-7bb5-95c7-53363c6df927') 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 '00000000-0000-1000-8000-000000000000' AS uuid, +UUID_TIMESTAMP('00000000-0000-1000-8000-000000000000') IS NULL AS before_unix_epoch; +uuid before_unix_epoch +00000000-0000-1000-8000-000000000000 1 +SELECT '00000000-0000-7000-8000-000000000000' AS uuid, +UUID_TIMESTAMP('00000000-0000-7000-8000-000000000000') AS ts; +uuid ts +00000000-0000-7000-8000-000000000000 0000-00-00 00:00:00.000000 +SELECT 'ffffffff-ffff-7fff-8000-000000000000' AS uuid, +UUID_TIMESTAMP('ffffffff-ffff-7fff-8000-000000000000') AS ts; +uuid ts +ffffffff-ffff-7fff-8000-000000000000 2042-12-13 16:54:30.655000 +SELECT 'ffffffff-ffff-1fff-8000-000000000000' AS uuid, +UUID_TIMESTAMP('ffffffff-ffff-1fff-8000-000000000000') AS ts; +uuid ts +ffffffff-ffff-1fff-8000-000000000000 2105-11-25 16:30:52.684697 +SET time_zone=DEFAULT; diff --git a/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.test b/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.test new file mode 100644 index 0000000000000..86ae7b4b55c53 --- /dev/null +++ b/plugin/type_uuid/mysql-test/type_uuid/func_uuid_timestamp.test @@ -0,0 +1,66 @@ +# MDEV-33710: UUID_TIMESTAMP() extracts timestamp from UUIDv1 and UUIDv7 + +SET time_zone='+00:00'; + +--echo # +--echo # UUIDv1 with known timestamps +--echo # +SELECT '4bd352dc-e593-11f0-8de9-0242ac120002' AS uuid, + UUID_TIMESTAMP('4bd352dc-e593-11f0-8de9-0242ac120002') AS ts; +SELECT 'bec1046a-e593-11f0-8de9-0242ac120002' AS uuid, + UUID_TIMESTAMP('bec1046a-e593-11f0-8de9-0242ac120002') AS ts; +SELECT 'c28c09a0-e593-11f0-8de9-0242ac120002' AS uuid, + UUID_TIMESTAMP('c28c09a0-e593-11f0-8de9-0242ac120002') AS ts; + +--echo # +--echo # UUIDv7 with known timestamps (ms precision) +--echo # +SELECT '019b6fdd-4937-7bb5-95c7-53363c6df927' AS uuid, + UUID_TIMESTAMP('019b6fdd-4937-7bb5-95c7-53363c6df927') AS ts; +SELECT '019b6fdd-5f17-7cf2-b034-248b9c207db6' AS uuid, + UUID_TIMESTAMP('019b6fdd-5f17-7cf2-b034-248b9c207db6') AS ts; +SELECT '019b6fdd-7327-7a5b-935c-4de00dd0e7c6' AS uuid, + UUID_TIMESTAMP('019b6fdd-7327-7a5b-935c-4de00dd0e7c6') AS ts; + +--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 '019b6fdd-4937-7bb5-95c7-53363c6df927' AS uuid, + UUID_TIMESTAMP(CAST('019b6fdd-4937-7bb5-95c7-53363c6df927' AS UUID)) AS ts; + +--echo # +--echo # Return type is TIMESTAMP(6) +--echo # +CREATE TABLE t1 AS SELECT UUID_TIMESTAMP('019b6fdd-4937-7bb5-95c7-53363c6df927') AS ts; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +--echo # +--echo # Edge cases +--echo # +# UUIDv1 before Unix epoch +SELECT '00000000-0000-1000-8000-000000000000' AS uuid, + UUID_TIMESTAMP('00000000-0000-1000-8000-000000000000') IS NULL AS before_unix_epoch; +# UUIDv7 at Unix epoch (ts=0) +SELECT '00000000-0000-7000-8000-000000000000' AS uuid, + UUID_TIMESTAMP('00000000-0000-7000-8000-000000000000') AS ts; +# UUIDv7 max 48-bit timestamp +SELECT 'ffffffff-ffff-7fff-8000-000000000000' AS uuid, + UUID_TIMESTAMP('ffffffff-ffff-7fff-8000-000000000000') AS ts; +# UUIDv1 max timestamp +SELECT 'ffffffff-ffff-1fff-8000-000000000000' AS uuid, + UUID_TIMESTAMP('ffffffff-ffff-1fff-8000-000000000000') AS ts; + +SET time_zone=DEFAULT; diff --git a/plugin/type_uuid/plugin.cc b/plugin/type_uuid/plugin.cc index fa15d847d2356..53b598343909d 100644 --- a/plugin/type_uuid/plugin.cc +++ b/plugin/type_uuid/plugin.cc @@ -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")}; @@ -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;