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},