From fdac4b8152ba2d16c29ed4c3e9ee46e9b3d2af4f Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Sat, 22 Jun 2024 14:16:01 -0300 Subject: [PATCH] Feature #7980 - Option for GEN_UUID to generate v7 UUID. --- builds/win32/msvc15/common.vcxproj | 1 + builds/win32/msvc15/common.vcxproj.filters | 3 + .../README.builtin_functions.txt | 11 +- src/common/classes/Uuid.h | 116 ++++++++++++++++++ src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + src/jrd/SysFunction.cpp | 35 +++++- 7 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 src/common/classes/Uuid.h diff --git a/builds/win32/msvc15/common.vcxproj b/builds/win32/msvc15/common.vcxproj index 01e5602492..18c9bbba9d 100644 --- a/builds/win32/msvc15/common.vcxproj +++ b/builds/win32/msvc15/common.vcxproj @@ -161,6 +161,7 @@ + diff --git a/builds/win32/msvc15/common.vcxproj.filters b/builds/win32/msvc15/common.vcxproj.filters index f17d16ce2a..b0e5eebb49 100644 --- a/builds/win32/msvc15/common.vcxproj.filters +++ b/builds/win32/msvc15/common.vcxproj.filters @@ -491,6 +491,9 @@ headers + + headers + headers diff --git a/doc/sql.extensions/README.builtin_functions.txt b/doc/sql.extensions/README.builtin_functions.txt index e387f8f52d..3e67eb9c31 100644 --- a/doc/sql.extensions/README.builtin_functions.txt +++ b/doc/sql.extensions/README.builtin_functions.txt @@ -550,17 +550,20 @@ Function: Returns an universal unique number in CHAR(16) OCTETS type. Format: - GEN_UUID() + GEN_UUID([]) Important: Before Firebird 2.5.2, GEN_UUID was returning completely random strings. This is not compliant with the RFC-4122 (UUID specification). - This was fixed in Firebird 2.5.2 and 3.0. Now GEN_UUID returns a compliant UUID version 4 - string, where some bits are reserved and the others are random. The string format of a compliant - UUID is XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX, where 4 is fixed (version) and Y is 8, 9, A or B. + This was fixed in Firebird 2.5.2 and 3.0. Now GEN_UUID returns a compliant UUID accordingly to the specified + version (4 or 7, with default being 4) string, where some bits are reserved and the others are random. + The string format of a compliant UUID v4/v7 is XXXXXXXX-XXXX-YXXX-ZXXX-XXXXXXXXXXXX, where Y is the version (4 or 7) + and Z is 8, 9, A or B. + Before Firebird 6, this function does not accept the version argument. Example: insert into records (id) value (gen_uuid()); + insert into records (id) value (gen_uuid(7)); See also: CHAR_TO_UUID and UUID_TO_CHAR diff --git a/src/common/classes/Uuid.h b/src/common/classes/Uuid.h new file mode 100644 index 0000000000..d407b24307 --- /dev/null +++ b/src/common/classes/Uuid.h @@ -0,0 +1,116 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2024 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef CLASSES_UUID_H +#define CLASSES_UUID_H + +#include "firebird.h" +#include "../common/gdsassert.h" +#include "../common/os/guid.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Firebird +{ + +class Uuid +{ +private: + explicit Uuid(unsigned version) + { + switch (version) + { + case 7: + generateV7(); + break; + + default: + fb_assert(false); + } + } + +public: + static Uuid generate(unsigned version) + { + return Uuid(version); + } + +public: + std::size_t extractBytes(std::uint8_t* buffer, std::size_t bufferSize) const + { + fb_assert(bufferSize >= bytes.size()); + std::copy(bytes.begin(), bytes.end(), buffer); + return bytes.size(); + } + + std::size_t toString(char* buffer, std::size_t bufferSize) const + { + fb_assert(bufferSize >= STR_LEN); + + return snprintf(buffer, bufferSize, STR_FORMAT, + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], + bytes[6], bytes[7], + bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]); + } + +private: + void generateV7() + { + GenerateRandomBytes(bytes.data() + 6, bytes.size() - 6); + + // current timestamp in ms + const auto now = std::chrono::system_clock::now(); + const auto millis = std::chrono::duration_cast(now.time_since_epoch()).count(); + + // timestamp + bytes[0] = (millis >> 40) & 0xFF; + bytes[1] = (millis >> 32) & 0xFF; + bytes[2] = (millis >> 24) & 0xFF; + bytes[3] = (millis >> 16) & 0xFF; + bytes[4] = (millis >> 8) & 0xFF; + bytes[5] = millis & 0xFF; + + // version and variant + bytes[6] = (bytes[6] & 0x0F) | 0x70; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + } + +public: + static constexpr std::size_t BYTE_LEN = 16; + static constexpr std::size_t STR_LEN = 36; + static constexpr const char* STR_FORMAT = + "%02hhX%02hhX%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX"; + +private: + std::array bytes; +}; + +} // namespace Firebird + +#endif // CLASSES_UUID_H diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 1b959de164..84055568c2 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -986,3 +986,4 @@ FB_IMPL_MSG(JRD, 983, pattern_cant_be_used_without_other_pattern_and_vice_versa, FB_IMPL_MSG(JRD, 984, incompatible_format_patterns, -901, "HY", "000", "@1 incompatible with @2") FB_IMPL_MSG(JRD, 985, only_one_pattern_can_be_used, -901, "HY", "000", "Can use only one of these patterns @1") FB_IMPL_MSG(JRD, 986, can_not_use_same_pattern_twice, -901, "HY", "000", "Cannot use the same pattern twice: @1") +FB_IMPL_MSG(JRD, 987, sysf_invalid_gen_uuid_version, -833, "42", "000", "Invalid GEN_UUID version (@1). Must be 4 or 7") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 80cb1c66c2..5b4e3385b8 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5729,6 +5729,7 @@ const isc_incompatible_format_patterns = 335545304; isc_only_one_pattern_can_be_used = 335545305; isc_can_not_use_same_pattern_twice = 335545306; + isc_sysf_invalid_gen_uuid_version = 335545307; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index b32c116cc5..f59e9eb808 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -33,6 +33,7 @@ #include "../common/TimeZoneUtil.h" #include "../common/classes/VaryStr.h" #include "../common/classes/Hash.h" +#include "../common/classes/Uuid.h" #include "../jrd/SysFunction.h" #include "../jrd/DataTypeUtil.h" #include "../include/fb_blk.h" @@ -1920,7 +1921,7 @@ void makeUnicodeChar(DataTypeUtilBase*, const SysFunction* function, dsc* result void makeUuid(DataTypeUtilBase*, const SysFunction* function, dsc* result, int argsCount, const dsc** args) { - fb_assert(argsCount == function->minArgCount); + fb_assert(argsCount >= function->minArgCount); if (argsCount > 0 && args[0]->isNull()) result->makeNullString(); @@ -4519,12 +4520,38 @@ dsc* evlFloor(thread_db* tdbb, const SysFunction*, const NestValueArray& args, dsc* evlGenUuid(thread_db* tdbb, const SysFunction*, const NestValueArray& args, impure_value* impure) { - fb_assert(args.isEmpty()); + const auto request = tdbb->getRequest(); + + fb_assert(args.getCount() <= 1); // Generate UUID and convert it into platform-independent format UCHAR data[Guid::SIZE]; + SLONG version = 4; - Guid::generate().convert(data); + if (args.getCount() > 0) + { + const auto* const versionDsc = EVL_expr(tdbb, request, args[0]); + + if (request->req_flags & req_null) + return nullptr; + + version = MOV_get_long(tdbb, versionDsc, 0); + } + + switch (version) + { + case 4: + Guid::generate().convert(data); + break; + + case 7: + Uuid::generate(version).extractBytes(data, sizeof(data)); + break; + + default: + status_exception::raise(Arg::Gds(isc_sysf_invalid_gen_uuid_version) << Arg::Num(version)); + break; + } dsc result; result.makeText(Guid::SIZE, ttype_binary, data); @@ -6885,7 +6912,7 @@ const SysFunction SysFunction::functions[] = {"EXP", 1, 1, setParamsDblDec, makeDblDecResult, evlExp, NULL}, {"FIRST_DAY", 2, 2, setParamsFirstLastDay, makeFirstLastDayResult, evlFirstLastDay, (void*) funFirstDay}, {"FLOOR", 1, 1, setParamsDblDec, makeCeilFloor, evlFloor, NULL}, - {"GEN_UUID", 0, 0, NULL, makeUuid, evlGenUuid, NULL}, + {"GEN_UUID", 0, 1, NULL, makeUuid, evlGenUuid, NULL}, {"HASH", 1, 2, setParamsHash, makeHash, evlHash, NULL}, {"HEX_DECODE", 1, 1, NULL, makeDecodeHex, evlDecodeHex, NULL}, {"HEX_ENCODE", 1, 1, NULL, makeEncodeHex, evlEncodeHex, NULL},