From 72ee7d26130ff619972d62d88a6a5330e18dc4d8 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Tue, 17 Oct 2023 13:01:14 +0300 Subject: [PATCH 01/31] This should fix regression introduced for #6674 (aka CORE-6440) --- src/dsql/ExprNodes.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index dd46b6560d..57a7733413 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -108,6 +108,10 @@ namespace return castCoalesceNode->sameAs(coalesceNode, ignoreStreams); } } + else if (const auto castValueIfNode = nodeAs(castNode->source)) + { + return sameNodes(castValueIfNode, coalesceNode, ignoreStreams); + } } return false; From e3de2679a6eecdf89364d804590fd4d92d02adf1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 17 Oct 2023 20:14:08 +0000 Subject: [PATCH 02/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 2f39cf910a..1e7b646e91 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:86 + FORMAL BUILD NUMBER:87 */ -#define PRODUCT_VER_STRING "6.0.0.86" -#define FILE_VER_STRING "WI-T6.0.0.86" -#define LICENSE_VER_STRING "WI-T6.0.0.86" -#define FILE_VER_NUMBER 6, 0, 0, 86 +#define PRODUCT_VER_STRING "6.0.0.87" +#define FILE_VER_STRING "WI-T6.0.0.87" +#define LICENSE_VER_STRING "WI-T6.0.0.87" +#define FILE_VER_NUMBER 6, 0, 0, 87 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "86" +#define FB_BUILD_NO "87" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index caab45c2fb..11b8b74a9d 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=86 +BuildNum=87 NowAt=`pwd` cd `dirname $0` From 5453f7c4d61de4b92ccab231c750447498c1c6b0 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Wed, 18 Oct 2023 11:45:53 +0300 Subject: [PATCH 03/31] Restored constness propagation in AutoPtr on Adriano's request --- src/common/classes/auto.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/common/classes/auto.h b/src/common/classes/auto.h index 18005aed69..0bb70fc0e3 100644 --- a/src/common/classes/auto.h +++ b/src/common/classes/auto.h @@ -136,12 +136,22 @@ public: return *this; } - Where* get() const + const Where* get() const { return ptr; } - operator Where*() const + operator const Where*() const + { + return ptr; + } + + Where* get() + { + return ptr; + } + + operator Where*() { return ptr; } From 359d4b89b367371c02623afb1b8917d1333d550c Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Wed, 18 Oct 2023 11:46:57 +0300 Subject: [PATCH 04/31] Fixed build after changes in AutoPtr --- src/jrd/ExtEngineManager.cpp | 12 +++++++++--- src/jrd/ExtEngineManager.h | 12 +++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/jrd/ExtEngineManager.cpp b/src/jrd/ExtEngineManager.cpp index 459c37fddf..e56553fa52 100644 --- a/src/jrd/ExtEngineManager.cpp +++ b/src/jrd/ExtEngineManager.cpp @@ -747,6 +747,12 @@ ExtEngineManager::ExtRoutine::ExtRoutine(thread_db* tdbb, ExtEngineManager* aExt engine->addRef(); } +void ExtEngineManager::ExtRoutine::PluginDeleter::operator()(IPluginBase* ptr) +{ + if (ptr) + PluginManagerInterfacePtr()->releasePlugin(ptr); +} + //--------------------- @@ -770,7 +776,7 @@ ExtEngineManager::Function::~Function() void ExtEngineManager::Function::execute(thread_db* tdbb, UCHAR* inMsg, UCHAR* outMsg) const { - EngineAttachmentInfo* attInfo = extManager->getEngineAttachment(tdbb, engine); + EngineAttachmentInfo* attInfo = extManager->getEngineAttachment(tdbb, engine.get()); const MetaString& userName = udf->invoker ? udf->invoker->getUserName() : ""; ContextManager ctxManager(tdbb, attInfo, function, (udf->getName().package.isEmpty() ? @@ -821,7 +827,7 @@ ExtEngineManager::ResultSet::ResultSet(thread_db* tdbb, UCHAR* inMsg, UCHAR* out attachment(tdbb->getAttachment()), firstFetch(true) { - attInfo = procedure->extManager->getEngineAttachment(tdbb, procedure->engine); + attInfo = procedure->extManager->getEngineAttachment(tdbb, procedure->engine.get()); const MetaString& userName = procedure->prc->invoker ? procedure->prc->invoker->getUserName() : ""; ContextManager ctxManager(tdbb, attInfo, procedure->procedure, (procedure->prc->getName().package.isEmpty() ? @@ -931,7 +937,7 @@ ExtEngineManager::Trigger::~Trigger() void ExtEngineManager::Trigger::execute(thread_db* tdbb, Request* request, unsigned action, record_param* oldRpb, record_param* newRpb) const { - EngineAttachmentInfo* attInfo = extManager->getEngineAttachment(tdbb, engine); + EngineAttachmentInfo* attInfo = extManager->getEngineAttachment(tdbb, engine.get()); const TriState& ssDefiner = trg->ssDefiner.isAssigned() ? trg->ssDefiner : (trg->relation && trg->relation->rel_ss_definer.isAssigned() ? trg->relation->rel_ss_definer : TriState()); const MetaString& userName = ssDefiner.asBool() ? diff --git a/src/jrd/ExtEngineManager.h b/src/jrd/ExtEngineManager.h index 23672b951d..24106148ea 100644 --- a/src/jrd/ExtEngineManager.h +++ b/src/jrd/ExtEngineManager.h @@ -24,6 +24,9 @@ #define JRD_EXT_ENGINE_MANAGER_H #include "firebird/Interface.h" + +#include + #include "../common/classes/array.h" #include "../common/classes/fb_string.h" #include "../common/classes/GenericMap.h" @@ -216,9 +219,16 @@ public: ExtRoutine(thread_db* tdbb, ExtEngineManager* aExtManager, Firebird::IExternalEngine* aEngine, RoutineMetadata* aMetadata); + private: + class PluginDeleter + { + public: + void operator()(Firebird::IPluginBase* ptr); + }; + protected: ExtEngineManager* extManager; - Firebird::AutoPlugin engine; + std::unique_ptr engine; Firebird::AutoPtr metadata; Database* database; }; From 8124b14e7c956752aae597284a19ea037a8cd4a7 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 18 Oct 2023 20:14:16 +0000 Subject: [PATCH 05/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 1e7b646e91..9656f4d2eb 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:87 + FORMAL BUILD NUMBER:89 */ -#define PRODUCT_VER_STRING "6.0.0.87" -#define FILE_VER_STRING "WI-T6.0.0.87" -#define LICENSE_VER_STRING "WI-T6.0.0.87" -#define FILE_VER_NUMBER 6, 0, 0, 87 +#define PRODUCT_VER_STRING "6.0.0.89" +#define FILE_VER_STRING "WI-T6.0.0.89" +#define LICENSE_VER_STRING "WI-T6.0.0.89" +#define FILE_VER_NUMBER 6, 0, 0, 89 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "87" +#define FB_BUILD_NO "89" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 11b8b74a9d..f4af98eb63 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=87 +BuildNum=89 NowAt=`pwd` cd `dirname $0` From f3f231a0a34178f0428037bd4435b63bf6117aec Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Mon, 23 Oct 2023 20:15:33 +0300 Subject: [PATCH 06/31] Improvement #7814 : Don't update database-level statistics on every page cache operation. --- src/jrd/Attachment.cpp | 10 +++++++--- src/jrd/Attachment.h | 2 +- src/jrd/RuntimeStatistics.h | 14 ++++++++++++++ src/jrd/jrd.cpp | 3 +++ src/jrd/jrd.h | 2 +- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp index 79c87931b6..a0ea8190e9 100644 --- a/src/jrd/Attachment.cpp +++ b/src/jrd/Attachment.cpp @@ -637,11 +637,15 @@ void Jrd::Attachment::signalShutdown(ISC_STATUS code) } -void Jrd::Attachment::mergeStats() +void Jrd::Attachment::mergeStats(bool pageStatsOnly) { MutexLockGuard guard(att_database->dbb_stats_mutex, FB_FUNCTION); - att_database->dbb_stats.adjust(att_base_stats, att_stats, true); - att_base_stats.assign(att_stats); + att_database->dbb_stats.adjustPageStats(att_base_stats, att_stats); + if (!pageStatsOnly) + { + att_database->dbb_stats.adjust(att_base_stats, att_stats, true); + att_base_stats.assign(att_stats); + } } diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index 0a858ff28d..29f3752f4f 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -736,7 +736,7 @@ public: void signalCancel(); void signalShutdown(ISC_STATUS code); - void mergeStats(); + void mergeStats(bool pageStatsOnly = false); bool hasActiveRequests() const; bool backupStateWriteLock(thread_db* tdbb, SSHORT wait); diff --git a/src/jrd/RuntimeStatistics.h b/src/jrd/RuntimeStatistics.h index 8871aee017..79df6d7bca 100644 --- a/src/jrd/RuntimeStatistics.h +++ b/src/jrd/RuntimeStatistics.h @@ -260,7 +260,21 @@ public: addRelCounts(baseStats.rel_counts, false); } } + } + void adjustPageStats(RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) + { + if (baseStats.allChgNumber != newStats.allChgNumber) + { + allChgNumber++; + for (size_t i = 0; i < REL_BASE_OFFSET; ++i) + { + const SINT64 delta = newStats.values[i] - baseStats.values[i]; + + values[i] += delta; + baseStats.values[i] += delta; + } + } } // copy counters values from other instance diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 2ea490e162..9280e82050 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -830,6 +830,9 @@ AttachmentHolder::~AttachmentHolder() { Jrd::Attachment* attachment = sAtt->getHandle(); + if (attachment) + attachment->mergeStats(true); + if (attachment && !async) { attachment->att_use_count--; diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index 438505e9e5..1f08e9b784 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -624,7 +624,7 @@ public: reqStat->bumpValue(index, delta); traStat->bumpValue(index, delta); attStat->bumpValue(index, delta); - dbbStat->bumpValue(index, delta); + // dbbStat adjusted from attStat, see Attachment::mergeAsyncStats() } void bumpRelStats(const RuntimeStatistics::StatType index, SLONG relation_id, SINT64 delta = 1) From 1fe1abd366146092fdfb6d428a02f1e89e7815aa Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 20:12:50 +0000 Subject: [PATCH 07/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 9656f4d2eb..a560075e19 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:89 + FORMAL BUILD NUMBER:90 */ -#define PRODUCT_VER_STRING "6.0.0.89" -#define FILE_VER_STRING "WI-T6.0.0.89" -#define LICENSE_VER_STRING "WI-T6.0.0.89" -#define FILE_VER_NUMBER 6, 0, 0, 89 +#define PRODUCT_VER_STRING "6.0.0.90" +#define FILE_VER_STRING "WI-T6.0.0.90" +#define LICENSE_VER_STRING "WI-T6.0.0.90" +#define FILE_VER_NUMBER 6, 0, 0, 90 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "89" +#define FB_BUILD_NO "90" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index f4af98eb63..fe7a6de5cc 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=89 +BuildNum=90 NowAt=`pwd` cd `dirname $0` From 897ac0c6501cf85aadb7eeb6936894d27dcdade9 Mon Sep 17 00:00:00 2001 From: TreeHunter <60896014+TreeHunter9@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:01:58 +0300 Subject: [PATCH 08/31] Add FORMAT clause to convert datetime types to string and vice versa #2388 (#7629) * Add FORMAT clause to convert datetime types to string and vice versa * Add tests for FORMAT clause * Fixes after review * Change TZD to TZR * Change inline variables back to static * Add README documentation * Add ability to use " in raw string and ... Use session timezone if timezone is not specified. Add ability to use + sign in timezone offset. Add truncating string exception. * Move util methods from BOOST_AUTO_TEST_SUITE * Switch back to inline variables * Consider charset in the format string * Add ability to write patterns without separators * Use printf to add extra zeros Also add extra zeros to the year patterns. * Replace template exception with a plain function * Clean code after review * Fix bug with TZH:TZM when TZH is 0 * Add TZR to STRING to DATE --------- Co-authored-by: Artyom Ivanov --- doc/README.cast.format.md | 95 ++ src/common/ParserTokens.h | 1 + src/common/TimeZoneUtil.cpp | 28 +- src/common/TimeZoneUtil.h | 2 + src/common/classes/NoThrowTimeStamp.cpp | 74 ++ src/common/classes/NoThrowTimeStamp.h | 4 + src/common/common.h | 30 +- src/common/cvt.cpp | 1107 ++++++++++++++++++++++- src/common/cvt.h | 2 + src/common/tests/CvtTest.cpp | 321 +++++++ src/common/tests/CvtTestUtils.h | 126 +++ src/dsql/ExprNodes.cpp | 136 +-- src/dsql/ExprNodes.h | 6 +- src/dsql/parse.y | 128 +-- src/include/firebird/impl/blr.h | 2 + src/include/firebird/impl/msg/jrd.h | 9 + src/include/gen/Firebird.pas | 9 + 17 files changed, 1947 insertions(+), 133 deletions(-) create mode 100644 doc/README.cast.format.md create mode 100644 src/common/tests/CvtTest.cpp create mode 100644 src/common/tests/CvtTestUtils.h diff --git a/doc/README.cast.format.md b/doc/README.cast.format.md new file mode 100644 index 0000000000..9efcfee4d3 --- /dev/null +++ b/doc/README.cast.format.md @@ -0,0 +1,95 @@ +## 1. DATETIME TO STRING + +The following flags are currently implemented for datetime to string conversion: +| Format Pattern | Description | +| -------------- | ----------- | +| YEAR | Year (1 - 9999) | +| YYYY | Last 4 digits of Year (0001 - 9999) | +| YYY | Last 3 digits of Year (000 - 999) | +| YY | Last 2 digits of Year (00 - 99) | +| Y | Last 1 digits of Year (0 - 9) | +| Q | Quarter of the Year (1 - 4) | +| MM | Month (01 - 12) | +| MON | Short Month name (Apr) | +| MONTH | Full Month name (APRIL) | +| RM | Roman representation of the Month (I - XII) | +| WW | Week of the Year (01 - 53) | +| W | Week of the Month (1 - 5) | +| D | Day of the Week (1 - 7) | +| DAY | Full name of the Day (MONDAY) | +| DD | Day of the Month (01 - 31) | +| DDD | Day of the Year (001 - 366) | +| DY | Short name of the Day (Mon) | +| J | Julian Day (number of days since January 1, 4712 BC) | +| HH / HH12 | Hour of the Day (01 - 12) with period (AM, PM) | +| HH24 | Hour of the Day (00 - 23) | +| MI | Minutes (00 - 59) | +| SS | Seconds (00 - 59) | +| SSSSS | Seconds after midnight (0 - 86399) | +| FF1 - FF9 | Fractional seconds with the specified accuracy | +| TZH | Time zone in Hours (-14 - 14) | +| TZM | Time zone in Minutes (00 - 59) | +| TZR | Time zone Name | + +The dividers are: +| Dividers | +| ------------- | +| . | +| / | +| , | +| ; | +| : | +| 'space' | +| - | + +Patterns can be used without any dividers: +``` +SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR(50) FORMAT 'YEARMMDD HH24MISS') FROM RDB$DATABASE; +========================= +20230719 161757 +``` +However, be careful with patterns like `DDDDD`, it will be interpreted as `DDD` + `DD`. + +It is possible to insert raw text into a format string with `""`: `... FORMAT '"Today is" DAY'` - Today is MONDAY. To add `"` in output raw string use `\"` (to print `\` use `\\`). +Also the format is case-insensitive, so `YYYY-MM` == `yyyy-mm`. +Example: +``` +SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR(45) FORMAT 'DD.MM.YEAR HH24:MI:SS "is" J "Julian day"') FROM RDB$DATABASE; +========================= +14.6.2023 15:41:29 is 2460110 Julian day +``` + +## 2. STRING TO DATETIME + +The following flags are currently implemented for string to datetime conversion: +| Format Pattern | Description | +| ------------- | ------------- | +| YEAR | Year | +| YYYY | Last 4 digits of Year | +| YYY | Last 3 digits of Year | +| YY | Last 2 digits of Year | +| Y | Last 1 digits of Year | +| MM | Month (1 - 12) | +| MON | Short Month name (Apr) | +| MONTH | Full Month name (APRIL) | +| RM | Roman representation of the Month (I - XII) | +| DD | Day of the Month (1 - 31) | +| J | Julian Day (number of days since January 1, 4712 BC) | +| HH / HH12 | Hour of the Day (1 - 12) with period (AM, PM) | +| HH24 | Hour of the Day (0 - 23) | +| MI | Minutes (0 - 59) | +| SS | Seconds (0 - 59) | +| SSSSS | Seconds after midnight (0 - 86399) | +| FF1 - FF4 | Fractional seconds with the specified accuracy | +| TZH | Time zone in Hours (-14 - 14) | +| TZM | Time zone in Minutes (0 - 59) | +| TZR | Time zone Name | + +Dividers are the same as for datetime to string conversion and can also be omitted. + +Example: +``` +SELECT CAST('2000.12.08 12:35:30.5000' AS TIMESTAMP FORMAT 'YEAR.MM.DD HH24:MI:SS.FF4') FROM RDB$DATABASE; +===================== +2000-12-08 12:35:30.5000 +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 92f769f7fe..2bff7a923d 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -222,6 +222,7 @@ PARSER_TOKEN(TOK_FLOOR, "FLOOR", true) PARSER_TOKEN(TOK_FOLLOWING, "FOLLOWING", true) PARSER_TOKEN(TOK_FOR, "FOR", false) PARSER_TOKEN(TOK_FOREIGN, "FOREIGN", false) +PARSER_TOKEN(TOK_FORMAT, "FORMAT", true) PARSER_TOKEN(TOK_FREE_IT, "FREE_IT", true) PARSER_TOKEN(TOK_FROM, "FROM", false) PARSER_TOKEN(TOK_FULL, "FULL", false) diff --git a/src/common/TimeZoneUtil.cpp b/src/common/TimeZoneUtil.cpp index 712f3e8d5f..d0984db0ee 100644 --- a/src/common/TimeZoneUtil.cpp +++ b/src/common/TimeZoneUtil.cpp @@ -80,7 +80,6 @@ namespace static const TimeZoneDesc* getDesc(USHORT timeZone); static inline bool isOffset(USHORT timeZone); -static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm); static inline SSHORT offsetZoneToDisplacement(USHORT timeZone); static inline USHORT displacementToOffsetZone(SSHORT displacement); static int parseNumber(const char*& p, const char* end); @@ -634,6 +633,20 @@ void TimeZoneUtil::extractOffset(const ISC_TIME_TZ& timeTz, SSHORT* offset) extractOffset(tsTz, offset); } +// Makes a time zone id from offsets. +USHORT TimeZoneUtil::makeFromOffset(int sign, unsigned tzh, unsigned tzm) +{ + if (!TimeZoneUtil::isValidOffset(sign, tzh, tzm)) + { + string str; + str.printf("%s%02u:%02u", (sign == -1 ? "-" : "+"), tzh, tzm); + status_exception::raise(Arg::Gds(isc_invalid_timezone_offset) << str); + } + + return (USHORT)displacementToOffsetZone((tzh * 60 + tzm) * sign); +} + + // Converts a time from local to UTC. void TimeZoneUtil::localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone) { @@ -1156,19 +1169,6 @@ static inline bool isOffset(USHORT timeZone) return timeZone <= ONE_DAY * 2; } -// Makes a time zone id from offsets. -static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm) -{ - if (!TimeZoneUtil::isValidOffset(sign, tzh, tzm)) - { - string str; - str.printf("%s%02u:%02u", (sign == -1 ? "-" : "+"), tzh, tzm); - status_exception::raise(Arg::Gds(isc_invalid_timezone_offset) << str); - } - - return (USHORT)displacementToOffsetZone((tzh * 60 + tzm) * sign); -} - // Gets the displacement from a offset-based time zone id. static inline SSHORT offsetZoneToDisplacement(USHORT timeZone) { diff --git a/src/common/TimeZoneUtil.h b/src/common/TimeZoneUtil.h index 6768bc288d..ad4ab30569 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -97,6 +97,8 @@ public: static void extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, SSHORT* offset); static void extractOffset(const ISC_TIME_TZ& timeTz, SSHORT* offset); + static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm); + static void localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone); static void localTimeToUtc(ISC_TIME_TZ& timeTz); diff --git a/src/common/classes/NoThrowTimeStamp.cpp b/src/common/classes/NoThrowTimeStamp.cpp index 8542e0ee69..0384cfada2 100644 --- a/src/common/classes/NoThrowTimeStamp.cpp +++ b/src/common/classes/NoThrowTimeStamp.cpp @@ -337,6 +337,80 @@ void NoThrowTimeStamp::round_time(ISC_TIME &ntime, const int precision) ntime -= (ntime % period); } +int NoThrowTimeStamp::convertGregorianDateToWeekDate(const struct tm& times) +{ + // Algorithm for Converting Gregorian Dates to ISO 8601 Week Date by Rick McCarty, 1999 + // http://personal.ecu.edu/mccartyr/ISOwdALG.txt + + const int y = times.tm_year + 1900; + const int dayOfYearNumber = times.tm_yday + 1; + + // Find the jan1Weekday for y (Monday=1, Sunday=7) + const int yy = (y - 1) % 100; + const int c = (y - 1) - yy; + const int g = yy + yy / 4; + const int jan1Weekday = 1 + (((((c / 100) % 4) * 5) + g) % 7); + + // Find the weekday for y m d + const int h = dayOfYearNumber + (jan1Weekday - 1); + const int weekday = 1 + ((h - 1) % 7); + + // Find if y m d falls in yearNumber y-1, weekNumber 52 or 53 + int yearNumber, weekNumber; + + if ((dayOfYearNumber <= (8 - jan1Weekday)) && (jan1Weekday > 4)) + { + yearNumber = y - 1; + weekNumber = ((jan1Weekday == 5) || ((jan1Weekday == 6) && + isLeapYear(yearNumber))) ? 53 : 52; + } + else + { + yearNumber = y; + + // Find if y m d falls in yearNumber y+1, weekNumber 1 + int i = isLeapYear(y) ? 366 : 365; + + if ((i - dayOfYearNumber) < (4 - weekday)) + { + yearNumber = y + 1; + weekNumber = 1; + } + } + + // Find if y m d falls in yearNumber y, weekNumber 1 through 53 + if (yearNumber == y) + { + int j = dayOfYearNumber + (7 - weekday) + (jan1Weekday - 1); + weekNumber = j / 7; + if (jan1Weekday > 4) + weekNumber--; + } + + return weekNumber; +} + +int NoThrowTimeStamp::convertGregorianDateToJulianDate(int year, int month, int day) +{ + int jdn = (1461 * (year + 4800 + (month - 14)/12))/4 + (367 * (month - 2 - 12 * ((month - 14)/12))) + / 12 - (3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075; + return jdn; +} + +void NoThrowTimeStamp::convertJulianDateToGregorianDate(int jdn, int& outYear, int& outMonth, int& outDay) +{ + int a = jdn + 32044; + int b = (4 * a +3 ) / 146097; + int c = a - (146097 * b) / 4; + int d = (4 * c + 3) / 1461; + int e = c - (1461 * d) / 4; + int m = (5 * e + 2) / 153; + + outDay = e - (153 * m + 2) / 5 + 1; + outMonth = m + 3 - 12 * (m / 10); + outYear = 100 * b + d - 4800 + (m / 10); +} + // Encode timestamp from UNIX datetime structure void NoThrowTimeStamp::encode(const struct tm* times, int fractions) { diff --git a/src/common/classes/NoThrowTimeStamp.h b/src/common/classes/NoThrowTimeStamp.h index 3ea51f6e30..58c126af5d 100644 --- a/src/common/classes/NoThrowTimeStamp.h +++ b/src/common/classes/NoThrowTimeStamp.h @@ -168,6 +168,10 @@ public: static void add10msec(ISC_TIMESTAMP* v, SINT64 msec, SINT64 multiplier); static void round_time(ISC_TIME& ntime, const int precision); + static int convertGregorianDateToWeekDate(const struct tm& times); + static int convertGregorianDateToJulianDate(int year, int month, int day); + static void convertJulianDateToGregorianDate(int jdn, int& outYear, int& outMonth, int& outDay); + static inline bool isLeapYear(const int year) noexcept { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); diff --git a/src/common/common.h b/src/common/common.h index 2eac429923..2162205a3b 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -971,7 +971,7 @@ const int HIGH_WORD = 0; #endif #endif -static const TEXT FB_SHORT_MONTHS[][4] = +inline const TEXT FB_SHORT_MONTHS[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -980,7 +980,7 @@ static const TEXT FB_SHORT_MONTHS[][4] = "\0" }; -static const TEXT* const FB_LONG_MONTHS_UPPER[] = +inline const TEXT* const FB_LONG_MONTHS_UPPER[] = { "JANUARY", "FEBRUARY", @@ -997,6 +997,32 @@ static const TEXT* const FB_LONG_MONTHS_UPPER[] = 0 }; +// Starts with SUNDAY cuz tm.tm_wday starts with it +inline const TEXT FB_SHORT_DAYS[][4] = +{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "\0" +}; + +// Starts with SUNDAY cuz tm.tm_wday starts with it +inline const TEXT* const FB_LONG_DAYS_UPPER[] = +{ + "SUNDAY", + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "\0" +}; + const FB_SIZE_T FB_MAX_SIZEOF = ~FB_SIZE_T(0); // Assume FB_SIZE_T is unsigned inline FB_SIZE_T fb_strlen(const char* str) diff --git a/src/common/cvt.cpp b/src/common/cvt.cpp index 7f7efabf03..e2cc65d8b7 100644 --- a/src/common/cvt.cpp +++ b/src/common/cvt.cpp @@ -58,6 +58,7 @@ #include "../common/utils_proto.h" #include "../common/StatusArg.h" #include "../common/status.h" +#include "../common/TimeZones.h" #ifdef HAVE_SYS_TYPES_H @@ -105,7 +106,8 @@ using namespace Firebird; #define FLOAT_MAX 3.402823466E+38F // max float (32 bit) value #endif -#define LETTER7(c) ((c) >= 'A' && (c) <= 'Z') +#define LETTER7_UPPER(c) ((c) >= 'A' && (c) <= 'Z') +#define LETTER(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) #define DIGIT(c) ((c) >= '0' && (c) <= '9') #define ABSOLUT(x) ((x) < 0 ? -(x) : (x)) @@ -145,6 +147,126 @@ static void make_null_string(const dsc*, USHORT, const char**, vary*, USHORT, Fi class DummyException {}; +namespace +{ + class TimeZoneTrie + { + public: + static constexpr int EnglishAlphabet = 26; + static constexpr int Digits = 10; + static constexpr int OtherSymbols = 4; // '/' + '-' + '+' + '_' + + static constexpr int MaxPatternDiversity = EnglishAlphabet + Digits + OtherSymbols; + + static constexpr USHORT UninitializedTimezoneId = 0; + + struct TrieNode + { + AutoPtr childrens[MaxPatternDiversity]; + USHORT timezoneId = UninitializedTimezoneId; + }; + + TimeZoneTrie(MemoryPool& pool) + : m_root(FB_NEW_POOL(pool) TrieNode()), m_pool(pool) + { + USHORT id = 0; + for (const char* p : BUILTIN_TIME_ZONE_LIST) + { + string timezoneName(p); + timezoneName.upper(); + insertValue(timezoneName.c_str(), MAX_USHORT - id++); + } + } + + bool contains(const char* value, USHORT& outTimezoneId, int& outParsedTimezoneLength) + { + const TrieNode* currentNode = m_root; + FB_SIZE_T valueLength = fb_strlen(value); + + for (outParsedTimezoneLength = 0; outParsedTimezoneLength < valueLength; outParsedTimezoneLength++) + { + int index = calculateIndex(value[outParsedTimezoneLength]); + + if (index < 0 || currentNode->childrens[index] == nullptr) + break; + currentNode = currentNode->childrens[index]; + } + + if (currentNode->timezoneId == UninitializedTimezoneId) + return false; + + outTimezoneId = currentNode->timezoneId; + return true; + } + + private: + void insertValue(const char* value, USHORT timezoneId) + { + TrieNode* currentNode = m_root; + FB_SIZE_T valueLength = fb_strlen(value); + + for (int i = 0; i < valueLength; i++) + { + int index = calculateIndex(value[i]); + + if (currentNode->childrens[index] == nullptr) + currentNode->childrens[index] = FB_NEW_POOL(m_pool) TrieNode(); + currentNode = currentNode->childrens[index]; + } + + currentNode->timezoneId = timezoneId; + } + + int calculateIndex(char symbol) const + { + int index = -1; + symbol = UPPER(symbol); + + if (symbol >= '0' && symbol <= '9') + index = symbol - '0'; + else if (symbol >= 'A' && symbol <= 'Z') + index = symbol - 'A' + Digits; + else + { + switch (symbol) + { + case '/': index = Digits + EnglishAlphabet; break; + case '-': index = Digits + EnglishAlphabet + 1; break; + case '+': index = Digits + EnglishAlphabet + 2; break; + case '_': index = Digits + EnglishAlphabet + 3; break; + } + } + + fb_assert(index < MaxPatternDiversity); + return index; + } + + private: + AutoPtr m_root; + MemoryPool& m_pool; + }; +} + +enum class ExpectedDateType +{ + TIME, + DATE, + TIMEZONE +}; + +static const char* const TO_DATETIME_PATTERNS[] = { + "YEAR", "YYYY", "YYY", "YY", "Y", "Q", "MM", "MON", "MONTH", "RM", "WW", "W", + "D", "DAY", "DD", "DDD", "DY", "J", "HH", "HH12", "HH24", "MI", "SS", "SSSSS", + "FF1", "FF2", "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9", "TZH", "TZM", "TZR" +}; + +static const char* const TO_STRING_PATTERNS[] = { + "YEAR", "YYYY", "YYY", "YY", "Y", "MM", "MON", "MONTH", "RM", "DD", "J", "HH", "HH12", + "HH24", "MI", "SS", "SSSSS", "FF1", "FF2", "FF3", "FF4", "TZH", "TZM", "TZR" +}; + +static InitInstance timeZoneTrie; + //#ifndef WORDS_BIGENDIAN //static const SQUAD quad_min_int = { 0, SLONG_MIN }; @@ -158,6 +280,12 @@ class DummyException {}; static const double eps_double = 1e-14; static const double eps_float = 1e-5; +template +static constexpr int sign(T value) +{ + return (value >= T(0)) ? 1 : -1; +} + static void float_to_text(const dsc* from, dsc* to, Callbacks* cb) { @@ -630,7 +758,7 @@ void CVT_string_to_datetime(const dsc* desc, } description[i] = precision; } - else if (LETTER7(c) && !have_english_month && i - start_component < 2) + else if (LETTER7_UPPER(c) && !have_english_month && i - start_component < 2) { TEXT temp[sizeof(YESTERDAY) + 1]; @@ -638,7 +766,7 @@ void CVT_string_to_datetime(const dsc* desc, while ((p < end) && (t < &temp[sizeof(temp) - 1])) { c = UPPER7(*p); - if (!LETTER7(c)) + if (!LETTER7_UPPER(c)) break; *t++ = c; p++; @@ -1033,6 +1161,979 @@ void CVT_string_to_datetime(const dsc* desc, } +static void date_type_check(ExpectedDateType expected, const dsc* desc, std::string_view pattern, Firebird::Callbacks* cb) +{ + switch (expected) + { + case ExpectedDateType::TIME: + if (!desc->isDate()) + return; + break; + + case ExpectedDateType::DATE: + if (!desc->isTime()) + return; + break; + + case ExpectedDateType::TIMEZONE: + if (desc->isDateTimeTz()) + return; + break; + + default: + break; + } + + cb->err(Arg::Gds(isc_incompatible_date_format_with_current_date_type) << string(pattern.data(), pattern.length())); +} + + +static string int_to_roman(int num) +{ + static const char* const symbols[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + static const int values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + + string roman; + for (int i = 0; i < 13; i++) + { + while (num >= values[i]) + { + roman += symbols[i]; + num -= values[i]; + } + } + return roman; +} + + +static SSHORT extract_timezone_offset(const dsc* desc) +{ + SSHORT timezoneOffset = 0; + + switch (desc->dsc_dtype) + { + case dtype_sql_time_tz: + case dtype_ex_time_tz: + TimeZoneUtil::extractOffset(*(ISC_TIME_TZ*) desc->dsc_address, &timezoneOffset); + break; + + case dtype_timestamp_tz: + case dtype_ex_timestamp_tz: + TimeZoneUtil::extractOffset(*(ISC_TIMESTAMP_TZ*) desc->dsc_address, &timezoneOffset); + break; + } + + return timezoneOffset; +} + + +static string extract_timezone_name(const dsc* desc) +{ + char timezoneBuffer[TimeZoneUtil::MAX_SIZE]; + unsigned int length = 0; + + switch (desc->dsc_dtype) + { + case dtype_sql_time_tz: + case dtype_ex_time_tz: + length = TimeZoneUtil::format(timezoneBuffer, sizeof(timezoneBuffer), + ((ISC_TIME_TZ*) desc->dsc_address)->time_zone); + break; + + case dtype_timestamp_tz: + case dtype_ex_timestamp_tz: + length = TimeZoneUtil::format(timezoneBuffer, sizeof(timezoneBuffer), + ((ISC_TIMESTAMP_TZ*) desc->dsc_address)->time_zone); + break; + } + + return string(timezoneBuffer, length); +} + + +static bool is_separator(char symbol) +{ + switch (symbol) + { + case '.': + case '/': + case ',': + case ';': + case ':': + case ' ': + case '-': + case '\"': + return true; + + default: + return false; + } +} + + +static void invalid_pattern_exception(std::string_view pattern, Callbacks* cb) +{ + cb->err(Arg::Gds(isc_invalid_date_format) << string(pattern.data(), pattern.length())); +} + + +static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::string_view pattern, + std::string_view previousPattern, const struct tm& times, int fractions, + Firebird::Callbacks* cb) +{ + string patternResult; + + switch (pattern[0]) + { + case 'Y': + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + int year = times.tm_year + 1900; + if (pattern == "Y") + patternResult.printf("%d", year % 10); + else if (pattern == "YY") + patternResult.printf("%02d", year % 100); + else if (pattern == "YYY") + patternResult.printf("%03d", year % 1000); + else if (pattern == "YYYY") + patternResult.printf("%04d", year % 10000); + else if (pattern == "YEAR") + patternResult.printf("%d", year); + break; + } + + case 'Q': + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "Q") + { + int quarter = times.tm_mon / 3 + 1; + + patternResult.printf("%d", quarter); + } + break; + + case 'M': + if (pattern == "MI") + { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + + patternResult.printf("%02d", times.tm_min); + break; + } + + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "MM") + patternResult.printf("%02d", (times.tm_mon + 1)); + else if (pattern == "MON") + patternResult.printf("%s", FB_SHORT_MONTHS[times.tm_mon]); + else if (pattern == "MONTH") + patternResult.printf("%s", FB_LONG_MONTHS_UPPER[times.tm_mon]); + break; + + case 'R': + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "RM") + { + string roman = int_to_roman(times.tm_mon + 1); + patternResult.printf("%s", roman.c_str()); + } + break; + + case 'W': + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "W") + { + int week = (times.tm_mday - 1) / 7; + patternResult.printf("%d", week + 1); + } + else if (pattern == "WW") + { + int week = NoThrowTimeStamp::convertGregorianDateToWeekDate(times); + patternResult.printf("%02d", week); + } + break; + + case 'D': + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "D") + patternResult.printf("%d", times.tm_wday + 1); + else if (pattern == "DAY") + patternResult.printf("%s", FB_LONG_DAYS_UPPER[times.tm_wday]); + else if (pattern == "DD") + patternResult.printf("%02d", times.tm_mday); + else if (pattern == "DDD") + { + int daysInYear = times.tm_yday + 1; + patternResult.printf("%03d", daysInYear); + } + else if (pattern == "DY") + patternResult.printf("%s", FB_SHORT_DAYS[times.tm_wday]); + break; + + case 'J': + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + + if (pattern == "J") + { + int JulianDay = NoThrowTimeStamp::convertGregorianDateToJulianDate(times.tm_year + 1900, times.tm_mon + 1, + times.tm_mday); + + patternResult.printf("%d", JulianDay); + } + break; + + case 'H': + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + + if (pattern == "HH"|| + pattern == "HH12") + { + const char* period; + int hours = times.tm_hour; + + if (hours >= 12) + { + period = "PM"; + if (hours > 12) + hours -= 12; + } + else + { + period = "AM"; + if (hours == 0) + hours = 12; + } + + patternResult.printf("%02d %s", hours, period); + } + else if (pattern == "HH24") + patternResult.printf("%02d", times.tm_hour); + break; + + case 'S': + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + + if (pattern == "SS") + patternResult.printf("%02d", times.tm_sec); + else if (pattern == "SSSSS") + { + int secondsInDay = times.tm_hour * 60 * 60 + times.tm_min * 60 + times.tm_sec; + patternResult.printf("%d", secondsInDay); + } + break; + + case 'F': + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + + if (!strncmp(pattern.data(), "FF", pattern.length() - 1)) + { + int number = pattern.back() - '0'; + if (number < 1 || number > 9) + { + invalid_pattern_exception(pattern, cb); + } + + const int fractionsPrecision = fractions != 0 ? std::log10(fractions) + 1 : 1; + int additionalZerosCount = 0; + if (number > fractionsPrecision) + { + additionalZerosCount = number - fractionsPrecision; + number = fractionsPrecision; + } + + patternResult.printf("%d%.*s", fractions / (int) powf(10, fractionsPrecision - number), + additionalZerosCount, "00000000"); + } + break; + + case 'T': + date_type_check(ExpectedDateType::TIMEZONE, desc, pattern, cb); + + if (pattern == "TZH") + { + SSHORT timezoneOffset = extract_timezone_offset(desc); + int timezoneSign = sign(timezoneOffset); + SSHORT offsetInHours = abs(timezoneOffset / 60); + + string printfFormat = "%02d"; + + if (previousPattern != "TZM") + { + if (timezoneSign < 0) + printfFormat = "-" + printfFormat; + else + printfFormat = "+" + printfFormat; + } + + patternResult.printf(printfFormat.c_str(), offsetInHours); + } + else if (pattern == "TZM") + { + SSHORT timezoneOffset = extract_timezone_offset(desc); + int timezoneSign = sign(timezoneOffset); + SSHORT offsetInMinutes = abs(timezoneOffset % 60); + + string printfFormat = "%02d"; + + if (previousPattern != "TZH") + { + if (timezoneSign < 0) + printfFormat = "-" + printfFormat; + else if (timezoneOffset >= 0) + printfFormat = "+" + printfFormat; + } + + patternResult.printf(printfFormat.c_str(), offsetInMinutes); + } + else if (pattern == "TZR") + patternResult = extract_timezone_name(desc); + break; + + default: + invalid_pattern_exception(pattern, cb); + } + + if (patternResult.isEmpty()) + invalid_pattern_exception(pattern, cb); + + return patternResult; +} + + +string CVT_datetime_to_format_string(const dsc* desc, const string& format, Callbacks* cb) +{ + if (format.isEmpty()) + cb->err(Arg::Gds(isc_sysf_invalid_null_empty) << Arg::Str(STRINGIZE(format))); + + struct tm times; + memset(×, 0, sizeof(struct tm)); + + int fractions = 0; + + switch (desc->dsc_dtype) + { + case dtype_sql_time: + Firebird::TimeStamp::decode_time(*(GDS_TIME*) desc->dsc_address, + ×.tm_hour, ×.tm_min, ×.tm_sec, &fractions); + break; + + case dtype_sql_time_tz: + case dtype_ex_time_tz: + TimeZoneUtil::decodeTime(*(ISC_TIME_TZ*) desc->dsc_address, + true, TimeZoneUtil::NO_OFFSET, ×, &fractions); + break; + + case dtype_sql_date: + Firebird::TimeStamp::decode_date(*(GDS_DATE*) desc->dsc_address, ×); + break; + + case dtype_timestamp: + Firebird::TimeStamp::decode_timestamp(*(GDS_TIMESTAMP*) desc->dsc_address, ×, &fractions); + break; + + case dtype_timestamp_tz: + case dtype_ex_timestamp_tz: + TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) desc->dsc_address, + true, TimeZoneUtil::NO_OFFSET, ×, &fractions); + break; + + default: + cb->err(Arg::Gds(isc_invalid_data_type_for_date_format)); + } + + string formatUpper(format); + for (int i = 0; i < formatUpper.length(); i++) + { + const char symbol = formatUpper[i]; + if (symbol != '\"') + { + formatUpper[i] = toupper(symbol); + continue; + } + + while(true) + { + int pos = formatUpper.find('\"', i + 1); + if (pos == string::npos) + cb->err(Arg::Gds(isc_invalid_raw_string_in_date_format)); + int tempPos = pos; + if (formatUpper[--tempPos] == '\\') + { + int backslashCount = 1; + while(formatUpper[--tempPos] == '\\') + backslashCount++; + if (backslashCount % 2 == 1) + { + i = pos; + continue; + } + } + i = pos; + break; + } + } + + string result; + int formatOffset = 0; + std::string_view pattern; + std::string_view previousPattern; + + for (int i = 0; i < formatUpper.length(); i++) + { + const char symbol = formatUpper[i]; + + if (is_separator(symbol)) + { + if (formatOffset != i) + { + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb); + previousPattern = pattern; + } + if (symbol == '\"') + { + i++; + int rawStringLength = formatUpper.length() - i; + string rawString(rawStringLength, '\0'); + for (int j = 0; j < rawStringLength; j++, i++) + { + if (formatUpper[i] == '\"') + break; + else if (formatUpper[i] == '\\') + rawString[j] = formatUpper[++i]; + else + rawString[j] = formatUpper[i]; + } + rawString.recalculate_length(); + result += rawString; + } + else + result += symbol; + + formatOffset = i + 1; + continue; + } + + pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); + bool isFound = false; + for (int j = 0; j < FB_NELEM(TO_DATETIME_PATTERNS); j++) + { + if (!strncmp(TO_DATETIME_PATTERNS[j], pattern.data(), pattern.length())) + { + isFound = true; + if (i == formatUpper.length() - 1) + { + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb); + } + break; + } + } + if (isFound) + continue; + + if (pattern.length() <= 1) + invalid_pattern_exception(pattern, cb); + + pattern = pattern.substr(0, pattern.length() - 1); + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb); + previousPattern = pattern; + formatOffset = i; + i--; + } + + return result; +} + + +static int roman_to_int(const char* str, int length, int& offset) +{ + int result = 0; + int temp = 0; + + for (; offset < length; offset++) + { + int value = 0; + + switch (str[offset]) + { + case 'I': value = 1; break; + case 'V': value = 5; break; + case 'X': value = 10; break; + case 'L': value = 50; break; + case 'C': value = 100; break; + case 'D': value = 500; break; + case 'M': value = 1000; break; + default: return 0; + } + + result += value; + if (temp < value) + result -= temp * 2; + temp = value; + } + + return result; +} + + +static int parse_string_to_get_int(const char* str, int length, int& offset, int parseLength, bool withSign = false) +{ + int result = 0; + int sign = 1; + + if (withSign) + { + // To check '-' sign we need to move back, cuz '-' is also used as separator, + // so it will be skipped when we trying to remove "empty" space between values + if (str[offset] == '+') + offset++; + else if (offset != 0 && str[offset - 1] == '-') + sign = -1; + } + + const int parseLengthWithOffset = offset + parseLength; + for (; offset < parseLengthWithOffset && offset < length; offset++) + { + if (!DIGIT(str[offset])) + return result * sign; + + result = result * 10 + (str[offset] - '0'); + } + + return result * sign; +} + + +static std::string_view parse_string_to_get_first_word(const char* str, int length, int& offset, int parseLength) +{ + int wordLen = 0; + int startPoint = offset; + + const int parseLengthWithOffset = offset + parseLength; + for (; offset < parseLengthWithOffset && offset < length; offset++) + { + if (!LETTER(str[offset])) + break; + + ++wordLen; + } + + return std::string_view(str + startPoint, wordLen); +} + + +static void string_to_format_datetime_pattern_matcher(std::string_view pattern, std::string_view previousPattern, + const char* str, int strLength, int& strOffset, struct tm& outTimes, int& outFractions, + SSHORT& outTimezoneInMinutes, USHORT& outTimezoneId, Firebird::Callbacks* cb) +{ + switch (pattern[0]) + { + case 'Y': + if (pattern == "Y") + { + int year = parse_string_to_get_int(str, strLength, strOffset, 1); + outTimes.tm_year = 2000 + year - 1900; + return; + } + else if (pattern == "YY") + { + int year = parse_string_to_get_int(str, strLength, strOffset, 2); + outTimes.tm_year = (year > 45 ? 1900 + year : 2000 + year) - 1900; + return; + } + else if (pattern == "YYY") + { + int year = parse_string_to_get_int(str, strLength, strOffset, 3); + outTimes.tm_year = (year > 450 ? 1000 + year : 2000 + year) - 1900; + return; + } + else if (pattern == "YYYY") + { + int year = parse_string_to_get_int(str, strLength, strOffset, 4); + outTimes.tm_year = year - 1900; + return; + } + else if (pattern == "YEAR") + { + int year = parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset); + if (year > 9999) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(9999)); + } + outTimes.tm_year = year - 1900; + return; + } + break; + + case 'M': + if (pattern == "MI") + { + int minutes = parse_string_to_get_int(str, strLength, strOffset, 2); + if (minutes > 59) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(59)); + } + + outTimes.tm_min = minutes; + return; + } + else if (pattern == "MM") + { + int month = parse_string_to_get_int(str, strLength, strOffset, 2); + if (month < 1 || month > 12) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(1) << Arg::Num(12)); + } + + outTimes.tm_mon = month - 1; + return; + } + else if (pattern == "MON") + { + std::string_view monthShortName = parse_string_to_get_first_word(str, strLength, strOffset, 3); + int month = -1; + for (int i = 0; i < FB_NELEM(FB_SHORT_MONTHS) - 1; i++) + { + if (std::equal(monthShortName.begin(), monthShortName.end(), + FB_SHORT_MONTHS[i], FB_SHORT_MONTHS[i] + strlen(FB_SHORT_MONTHS[i]), + [](char a, char b) { return a == UPPER(b); })) + { + outTimes.tm_mon = i; + return; + } + } + + cb->err(Arg::Gds(isc_month_name_mismatch) << string(monthShortName.data(), monthShortName.length())); + } + else if (pattern == "MONTH") + { + std::string_view monthFullName = parse_string_to_get_first_word(str, strLength, strOffset, strLength - strOffset); + for (int i = 0; i < FB_NELEM(FB_LONG_MONTHS_UPPER) - 1; i++) + { + if (std::equal(monthFullName.begin(), monthFullName.end(), + FB_LONG_MONTHS_UPPER[i], FB_LONG_MONTHS_UPPER[i] + strlen(FB_LONG_MONTHS_UPPER[i]), + [](char a, char b) { return a == UPPER(b); })) + { + outTimes.tm_mon = i; + return; + } + } + + cb->err(Arg::Gds(isc_month_name_mismatch) << string(monthFullName.data(), monthFullName.length())); + } + break; + + case 'R': + if (pattern == "RM") + { + int month = roman_to_int(str, strLength, strOffset); + if (month == 0 || month > 12) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(1) << Arg::Num(12)); + } + + outTimes.tm_mon = month - 1; + return; + } + break; + + case 'D': + if (pattern == "DD") + { + int day = parse_string_to_get_int(str, strLength, strOffset, 2); + if (day == 0 || day > 31) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(1) << Arg::Num(31)); + } + + outTimes.tm_mday = day; + return; + } + break; + + case 'J': + if (pattern == "J") + { + int JDN = parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset); + + constexpr int minJDN = 1721426; // 0.0.0 + constexpr int maxJDN = 5373484; // 31.12.9999 + if (JDN < minJDN || JDN > maxJDN) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(minJDN) << Arg::Num(maxJDN)); + } + + int year, month, day; + NoThrowTimeStamp::convertJulianDateToGregorianDate(JDN, year, month, day); + outTimes.tm_year = year - 1900; + outTimes.tm_mon = month - 1; + outTimes.tm_mday = day; + return; + } + break; + + case 'H': + if (pattern == "HH"|| + pattern == "HH12") + { + int hours = parse_string_to_get_int(str, strLength, strOffset, 2); + if (hours > 12) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(12)); + } + + if (str[strOffset] == ' ') + strOffset++; + + std::string_view period = parse_string_to_get_first_word(str, strLength, strOffset, 2); + if (period == "AM") + { + outTimes.tm_hour = hours == 12 ? 0 : hours; + return; + } + else if (period == "PM") + { + outTimes.tm_hour = hours == 12 ? hours : 12 + hours; + return; + } + + cb->err(Arg::Gds(isc_incorrect_hours_period) << string(period.data(), period.length())); + } + else if (pattern == "HH24") + { + int hours = parse_string_to_get_int(str, strLength, strOffset, 2); + if (hours > 23) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(23)); + } + + outTimes.tm_hour = hours; + return; + } + break; + + case 'S': + if (pattern == "SS") + { + int seconds = parse_string_to_get_int(str, strLength, strOffset, 2); + if (seconds > 59) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(59)); + } + + outTimes.tm_sec = seconds; + return; + } + else if (pattern == "SSSSS") + { + constexpr int maximumSecondsInDay = 24 * 60 * 60 - 1; + + int secondsInDay = parse_string_to_get_int(str, strLength, strOffset, 5); + if (secondsInDay > maximumSecondsInDay) + { + cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << + string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(maximumSecondsInDay)); + } + + int hours = secondsInDay / 24; + int minutes = secondsInDay / 60 - hours * 60; + int seconds = secondsInDay - minutes * 60 - hours * 60 * 60; + + outTimes.tm_hour = hours; + outTimes.tm_min = minutes; + outTimes.tm_sec = seconds; + return; + } + break; + + case 'F': + if (!strncmp(pattern.data(), "FF", pattern.length() - 1)) + { + int number = pattern.back() - '0'; + if (number < 1 || number > -ISC_TIME_SECONDS_PRECISION_SCALE) + { + invalid_pattern_exception(pattern, cb); + } + + const int fractions = parse_string_to_get_int(str, strLength, strOffset, number); + outFractions = fractions * pow(10, -ISC_TIME_SECONDS_PRECISION_SCALE - number); + return; + } + break; + + case 'T': + if (pattern == "TZH") + { + if (previousPattern == "TZM") + { + outTimezoneInMinutes += sign(outTimezoneInMinutes) * + parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset) * 60; + } + else + outTimezoneInMinutes = parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset, true) * 60; + return; + } + else if (pattern == "TZM") + { + if (previousPattern == "TZH") + { + outTimezoneInMinutes += sign(outTimezoneInMinutes) * + parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset); + } + else + outTimezoneInMinutes = parse_string_to_get_int(str, strLength, strOffset, strLength - strOffset, true); + return; + } + else if (pattern == "TZR") + { + USHORT timezoneId; + + int parsedTimezoneNameLength; + bool timezoneNameIsCorrect = timeZoneTrie().contains(str + strOffset, outTimezoneId, parsedTimezoneNameLength); + if (!timezoneNameIsCorrect) + status_exception::raise(Arg::Gds(isc_invalid_timezone_region) << string(str + strOffset, parsedTimezoneNameLength)); + + strOffset += parsedTimezoneNameLength; + return; + } + break; + } + + invalid_pattern_exception(pattern, cb); +} + + +ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb) +{ + if (!DTYPE_IS_TEXT(desc->dsc_dtype)) + cb->err(Arg::Gds(isc_invalid_data_type_for_date_format)); + + if (format.isEmpty()) + cb->err(Arg::Gds(isc_sysf_invalid_null_empty) << Arg::Str(STRINGIZE(format))); + + USHORT dtype; + UCHAR* sourceString; + USHORT stringLength = CVT_get_string_ptr_common(desc, &dtype, &sourceString, nullptr, 0, 0, cb); + + string stringUpper(stringLength, '\0'); + for (int i = 0; i < stringLength; i++) + stringUpper[i] = toupper(sourceString[i]); + + string formatUpper(format.length(), '\0'); + for (int i = 0; i < format.length(); i++) + formatUpper[i] = toupper(format[i]); + + struct tm times; + memset(×, 0, sizeof(struct tm)); + times.tm_year = 1 - 1900; + times.tm_mday = 1; + + int fractions = 0; + + constexpr SSHORT uninitializedTimezoneOffsetValue = INT16_MIN; + SSHORT timezoneOffsetInMinutes = uninitializedTimezoneOffsetValue; + USHORT timezoneId = TimeZoneTrie::UninitializedTimezoneId; + + int formatOffset = 0; + int stringOffset = 0; + + std::string_view pattern; + std::string_view previousPattern; + + for (int i = 0; i < formatUpper.length(); i++) + { + const char symbol = formatUpper[i]; + + if (is_separator(symbol)) + { + if (formatOffset != i) + { + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); + previousPattern = pattern; + } + + formatOffset = i + 1; + continue; + } + + for (; stringOffset < stringLength; stringOffset++) + { + if (!is_separator(stringUpper[stringOffset])) + break; + } + + pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); + bool isFound = false; + for (int j = 0; j < FB_NELEM(TO_STRING_PATTERNS); j++) + { + if (!strncmp(TO_STRING_PATTERNS[j], pattern.data(), pattern.length())) + { + isFound = true; + if (i == formatUpper.length() - 1) + { + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); + } + break; + } + } + if (isFound) + continue; + + if (pattern.length() <= 1) + invalid_pattern_exception(pattern, cb); + + pattern = pattern.substr(0, pattern.length() - 1); + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); + previousPattern = pattern; + formatOffset = i; + i--; + } + + for (; stringOffset < stringLength; stringOffset++) + { + if (!is_separator(stringUpper[stringOffset])) + break; + } + + if (stringOffset < stringLength) + cb->err(Arg::Gds(isc_trailing_part_of_string) << string(stringUpper.c_str() + stringOffset)); + + ISC_TIMESTAMP_TZ timestampTZ; + if (timezoneOffsetInMinutes == uninitializedTimezoneOffsetValue && timezoneId == TimeZoneTrie::UninitializedTimezoneId) + timestampTZ.time_zone = cb->getSessionTimeZone(); + else if (timezoneId != TimeZoneTrie::UninitializedTimezoneId) + timestampTZ.time_zone = timezoneId; + else + { + timestampTZ.time_zone = TimeZoneUtil::makeFromOffset(sign(timezoneOffsetInMinutes), + abs(timezoneOffsetInMinutes) / 60, abs(timezoneOffsetInMinutes) % 60); + } + timestampTZ.utc_timestamp = NoThrowTimeStamp::encode_timestamp(×, fractions); + + return timestampTZ; +} + + template void adjustForScale(V& val, SSHORT scale, const V limit, ErrorFunction err) { diff --git a/src/common/cvt.h b/src/common/cvt.h index a2e9dd3665..107a64c957 100644 --- a/src/common/cvt.h +++ b/src/common/cvt.h @@ -108,5 +108,7 @@ SQUAD CVT_get_quad(const dsc*, SSHORT, Firebird::DecimalStatus, ErrorFunction); void CVT_string_to_datetime(const dsc*, ISC_TIMESTAMP_TZ*, bool*, const Firebird::EXPECT_DATETIME, bool, Firebird::Callbacks*); const UCHAR* CVT_get_bytes(const dsc*, unsigned&); +Firebird::string CVT_datetime_to_format_string(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb); +ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb); #endif //COMMON_CVT_H diff --git a/src/common/tests/CvtTest.cpp b/src/common/tests/CvtTest.cpp new file mode 100644 index 0000000000..6b8d689364 --- /dev/null +++ b/src/common/tests/CvtTest.cpp @@ -0,0 +1,321 @@ +#include "boost/test/unit_test.hpp" +#include "../common/tests/CvtTestUtils.h" +#include "../jrd/cvt_proto.h" + +using namespace Firebird; +using namespace Jrd; +using namespace CvtTestUtils; + +BOOST_AUTO_TEST_SUITE(CVTSuite) +BOOST_AUTO_TEST_SUITE(CVTDatetimeFormat) + +static void errFunc(const Firebird::Arg::StatusVector& v) +{ + v.raise(); +} + +CVTCallback cb(errFunc); + +BOOST_AUTO_TEST_SUITE(CVTDatetimeToFormatString) + +template +static void testCVTDatetimeToFormatString(T date, const string& format, const string& expected, Callbacks& cb) +{ + dsc desc; + desc.dsc_dtype = getDSCTypeFromDateType(); + desc.dsc_length = sizeof(T); + desc.dsc_address = (UCHAR*) &date; + desc.dsc_scale = 0; + + string result = CVT_datetime_to_format_string(&desc, format, &cb); + BOOST_TEST(result == expected, "\nRESULT: " << result.c_str() << "\nEXPECTED: " << expected.c_str()); +} + +BOOST_AUTO_TEST_SUITE(FunctionalTest) + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_DATE) +{ + testCVTDatetimeToFormatString(createDate(1, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1.0001.001.01.1", cb); + testCVTDatetimeToFormatString(createDate(1234, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1234.1234.234.34.4", cb); + testCVTDatetimeToFormatString(createDate(9999, 1, 1), "YEAR.YYYY.YYY.YY.Y", "9999.9999.999.99.9", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1.0001.001.01.1", cb); + testCVTDatetimeToFormatString(createDate(1234, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1234.1234.234.34.4", cb); + testCVTDatetimeToFormatString(createDate(9999, 1, 1), "YEAR.YYYY.YYY.YY.Y", "9999.9999.999.99.9", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "Q", "1", cb); + testCVTDatetimeToFormatString(createDate(1, 4, 1), "Q", "2", cb); + testCVTDatetimeToFormatString(createDate(1, 7, 1), "Q", "3", cb); + testCVTDatetimeToFormatString(createDate(1, 10, 1), "Q", "4", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "MM:RM-MON/MONTH", "01:I-Jan/JANUARY", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 1), "MM-RM.MON;MONTH", "06-VI.Jun;JUNE", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 1), "MM,RM.MON:MONTH", "12,XII.Dec:DECEMBER", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "WW/W", "01/1", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 15), "WW-W", "24-3", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 30), "WW.W", "52.5", cb); + + testCVTDatetimeToFormatString(createDate(2023, 6, 4), "D;DAY-DY", "1;SUNDAY-Sun", cb); + testCVTDatetimeToFormatString(createDate(2023, 6, 7), "D.DAY,DY", "4.WEDNESDAY,Wed", cb); + testCVTDatetimeToFormatString(createDate(2023, 6, 10), "D DAY DY", "7 SATURDAY Sat", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "DDD", "001", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 12), "DDD", "012", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 15), "DDD", "166", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 31), "DDD", "365", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "J", "1721426", cb); + testCVTDatetimeToFormatString(createDate(2000, 12, 8), "J", "2451887", cb); + testCVTDatetimeToFormatString(createDate(9999, 12, 31), "J", "5373484", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIME) +{ + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH-HH12.HH24,MI/SS SSSSS", "12 AM-12 AM.00,00/00 0", cb); + testCVTDatetimeToFormatString(createTime(12, 35, 15), "HH.HH12:HH24;MI-SS/SSSSS", "12 PM.12 PM:12;35-15/45315", cb); + testCVTDatetimeToFormatString(createTime(23, 59, 59), " HH - HH12 . HH24 , MI / SS SSSSS ", " 11 PM - 11 PM . 23 , 59 / 59 86399 ", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0, 1), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "1.10/100;1000:10000-100000,1000000-10000000 100000000", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 0, 1000), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "1.10/100;1000:10000-100000,1000000-10000000 100000000", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 0, 9999), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "9.99/999;9999:99990-999900,9999000-99990000 999900000", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP) +{ + ISC_TIMESTAMP timestamp = createTimeStamp(1982, 4, 21, 1, 34, 15, 2500); + + testCVTDatetimeToFormatString(timestamp, "YEAR.YYYY.YYY.YY.Y/J", "1982.1982.982.82.2/2445081", cb); + testCVTDatetimeToFormatString(timestamp, "Q-MM-RM-MON-MONTH", "2-04-IV-Apr-APRIL", cb); + testCVTDatetimeToFormatString(timestamp, "WW,W-D;DAY:DD DDD.DY", "16,3-4;WEDNESDAY:21 111.Wed", cb); + testCVTDatetimeToFormatString(timestamp, "HH-HH12-HH24-MI-SS-SSSSS.FF2", "01 AM-01 AM-01-34-15-5655.25", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIME_TZ) +{ + testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, 0, 900), "HH-HH12-HH24-MI-SS-SSSSS.FF1/TZH/TZM", "03 PM-03 PM-15-35-59-56159.9/+00/00", cb); + testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, 160), "HH24:MI-TZH:TZM", "18:15-+02:40", cb); + testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, -160), "HH24:MI TZH:TZM", "12:55 -02:40", cb); + + testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, 160), "TZM:TZH", "+40:02", cb); + testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, 160), "TZH MI TZM", "+02 40 +40", cb); + testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, -160), "TZH MI TZM", "-02 20 -40", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP_TZ) +{ + ISC_TIMESTAMP_TZ timestampTZ = createTimeStampTZ(1982, 4, 21, 1, 34, 15, 0, 500); + + testCVTDatetimeToFormatString(timestampTZ, "YEAR.YYYY.YYY.YY.Y/J", "1982.1982.982.82.2/2445081", cb); + testCVTDatetimeToFormatString(timestampTZ, "Q-MM-RM-MON-MONTH", "2-04-IV-Apr-APRIL", cb); + testCVTDatetimeToFormatString(timestampTZ, "WW,W-D;DAY:DD DDD.DY", "16,3-4;WEDNESDAY:21 111.Wed", cb); + testCVTDatetimeToFormatString(timestampTZ, "HH-HH12-HH24-MI-SS-SSSSS.FF2/TZH/TZM", "01 AM-01 AM-01-34-15-5655.50/+00/00", cb); + + testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 1, 34, 15, 70), "HH24:MI-TZH:TZM", "02:44-+01:10", cb); + testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 1, 34, 15, -70), "HH24:MI TZH:TZM", "00:24 -01:10", cb); + + testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, 160), "TZM:TZH", "+40:02", cb); + testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, 160), "TZH MI TZM", "+02 40 +40", cb); + testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, -160), "TZH MI TZM", "-02 20 -40", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_SOLID_PATTERNS) +{ + ISC_TIMESTAMP_TZ timestampTZ = createTimeStampTZ(1982, 4, 21, 1, 34, 15, 0, 500); + + testCVTDatetimeToFormatString(timestampTZ, "YEARYYYYYYYYYYJ", "198219821982822445081", cb); + testCVTDatetimeToFormatString(timestampTZ, "QMMRMMONMONTH", "204IVAprAPRIL", cb); + testCVTDatetimeToFormatString(timestampTZ, "WWWD/DAYDDDDDDY", "1634/WEDNESDAY1111112", cb); + testCVTDatetimeToFormatString(timestampTZ, "HHHH12HH24MISSSSSSSFF2TZHTZM", "01 AM01 AM013456551550+0000", cb); +} + +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_RAW_TEXT) +{ + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"RaW TeXt\"-MON", "1981-RaW TeXt-Jul", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"Raw Text with \\\"Quotes\\\"\"-MON", "1981-Raw Text with \"Quotes\"-Jul", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"\\\\\\\"\\\\BS\\\\\\\"\\\\\"-YYYY", "1981-\\\"\\BS\\\"\\-1981", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"Test1\"-Y\"Test2\"", "Test1-1Test2", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"-Y\"Test2\"", "-1Test2", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"Test1\"-Y\"\"", "Test1-1", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"-Y\"\"", "-1", cb); + testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"\"\"", "", cb); +} + +BOOST_AUTO_TEST_SUITE_END() // FunctionalTest +BOOST_AUTO_TEST_SUITE_END() // CVTDatetimeToFormatString + +BOOST_AUTO_TEST_SUITE(CVTStringToFormatDateTime) + +static void testCVTStringToFormatDateTime(const string& date, const string& format, + const ISC_TIMESTAMP_TZ& expected, Callbacks& cb) +{ + string varyingString = "xx"; + varyingString += date; + *(USHORT*) varyingString.data() = varyingString.size() - sizeof(USHORT); + + dsc desc; + desc.dsc_dtype = dtype_varying; + desc.dsc_length = varyingString.size() + sizeof(USHORT); + desc.dsc_address = (UCHAR*) varyingString.data(); + desc.dsc_scale = 0; + + const ISC_TIMESTAMP_TZ result = CVT_string_to_format_datetime(&desc, format, &cb); + + struct tm resultTimes; + memset(&resultTimes, 0, sizeof(resultTimes)); + int resultFractions; + NoThrowTimeStamp::decode_timestamp(result.utc_timestamp, &resultTimes, &resultFractions); + SSHORT resultOffset; + TimeZoneUtil::extractOffset(result, &resultOffset); + + struct tm expectedTimes; + memset(&expectedTimes, 0, sizeof(expectedTimes)); + int expectedFractions; + NoThrowTimeStamp::decode_timestamp(expected.utc_timestamp, &expectedTimes, &expectedFractions); + SSHORT expectedOffset; + TimeZoneUtil::extractOffset(expected, &expectedOffset); + + bool isEqual = !((bool) memcmp(&resultTimes, &expectedTimes, sizeof(struct tm))) + && resultFractions == expectedFractions && resultOffset == expectedOffset; + + BOOST_TEST(isEqual, "\nRESULT: " << DECOMPOSE_TM_STRUCT(resultTimes, resultFractions, resultOffset) + << "\nEXPECTED: " << DECOMPOSE_TM_STRUCT(expectedTimes, expectedFractions, expectedOffset)); +} + +BOOST_AUTO_TEST_SUITE(FunctionalTest) + +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_DATE) +{ + testCVTStringToFormatDateTime("1", "YEAR", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1234", "YEAR", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("9999", "YEAR", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "YYYY", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1234", "YYYY", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("9999", "YYYY", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "YYY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("450", "YYY", createTimeStampTZ(2450, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("451", "YYY", createTimeStampTZ(1451, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("999", "YYY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "YY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("45", "YY", createTimeStampTZ(2045, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("46", "YY", createTimeStampTZ(1946, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("99", "YY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "Y", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("9", "Y", createTimeStampTZ(2009, 1, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "MM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("6", "MM", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("12", "MM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("Jan", "MON", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("Jun", "MON", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("Dec", "MON", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("January", "MONTH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("June", "MONTH", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("December", "MONTH", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("I", "RM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("IV", "RM", createTimeStampTZ(1, 4, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("XII", "RM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1", "DD", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("15", "DD", createTimeStampTZ(1, 1, 15, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("31", "DD", createTimeStampTZ(1, 1, 31, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("2451887", "J", createTimeStampTZ(2000, 12, 8, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1721426", "J", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("5373484", "J", createTimeStampTZ(9999, 12, 31, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("1:1,1", "YEAR.MM.DD", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1981-8/13", "YEAR.MM.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("9999 12;31", "YEAR.MM.DD", createTimeStampTZ(9999, 12, 31, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("25.Jan.25", "YY;MON;DD", createTimeStampTZ(2025, 1, 25, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("./.1981./-8--/13--", " YEAR. -.MM.,,-.DD//", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); +} + +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TIME) +{ + testCVTStringToFormatDateTime("12 AM", "HH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1 AM", "HH", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTime("11 AM", "HH", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("12 PM", "HH", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1 PM", "HH", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTime("11 PM", "HH", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("12 AM", "HH12", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1 AM", "HH12", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTime("11 AM", "HH12", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("12 PM", "HH12", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTime("1 PM", "HH12", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTime("11 PM", "HH12", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("0", "HH24", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("12", "HH24", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTime("23", "HH24", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + + testCVTStringToFormatDateTime("0", "MI", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("30", "MI", createTimeStampTZ(1, 1, 1, 0, 30, 0, 0), cb); + testCVTStringToFormatDateTime("59", "MI", createTimeStampTZ(1, 1, 1, 0, 59, 0, 0), cb); + + testCVTStringToFormatDateTime("0", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("30", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 30, 0), cb); + testCVTStringToFormatDateTime("59", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 59, 0), cb); + + testCVTStringToFormatDateTime("0", "SSSSS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTime("45315", "SSSSS", createTimeStampTZ(1, 1, 1, 12, 35, 15, 0), cb); + testCVTStringToFormatDateTime("86399", "SSSSS", createTimeStampTZ(1, 1, 1, 23, 59, 59, 0), cb); + + testCVTStringToFormatDateTime("1", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTime("5", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTime("9", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9000), cb); + + testCVTStringToFormatDateTime("1", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTime("10", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTime("50", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTime("99", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9900), cb); + + testCVTStringToFormatDateTime("1", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb); + testCVTStringToFormatDateTime("10", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTime("100", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTime("500", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTime("999", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9990), cb); + + testCVTStringToFormatDateTime("1", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1), cb); + testCVTStringToFormatDateTime("10", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb); + testCVTStringToFormatDateTime("100", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTime("1000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTime("5000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTime("9999", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9999), cb); + + testCVTStringToFormatDateTime("1 PM - 25 - 45 - 200", "HH.MI.SS.FF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb); + testCVTStringToFormatDateTime("15:0:15:2", "HH24.MI.SS.FF1", createTimeStampTZ(1, 1, 1, 15, 0, 15, 0, 2000), cb); +} + +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TZ) +{ + testCVTStringToFormatDateTime("12:00 2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb); + testCVTStringToFormatDateTime("12:00 +2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb); + testCVTStringToFormatDateTime("12:00 -2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, -150, 0), cb); + testCVTStringToFormatDateTime("12:00 +0:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 30, 0), cb); + testCVTStringToFormatDateTime("12:00 +0:00", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0, 0), cb); +} + +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_SOLID_PATTERNS) +{ + testCVTStringToFormatDateTime("1 PM - 25 - 45 - 200", "HHMISSFF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb); + testCVTStringToFormatDateTime("1981-8/13", "YEARMMDD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); +} + +BOOST_AUTO_TEST_SUITE_END() // FunctionalTest +BOOST_AUTO_TEST_SUITE_END() // CVTStringToFormatDateTime + +BOOST_AUTO_TEST_SUITE_END() // CVTDatetimeFormat +BOOST_AUTO_TEST_SUITE_END() // CVTSuite diff --git a/src/common/tests/CvtTestUtils.h b/src/common/tests/CvtTestUtils.h new file mode 100644 index 0000000000..dae4e53ecb --- /dev/null +++ b/src/common/tests/CvtTestUtils.h @@ -0,0 +1,126 @@ +#ifndef CVT_TEST_UTILS_H +#define CVT_TEST_UTILS_H + +#include "firebird.h" +#include "../common/dsc.h" +#include "../common/TimeZoneUtil.h" +#include "../common/TimeZones.h" + +using Firebird::NoThrowTimeStamp; +using Firebird::TimeZoneUtil; + +namespace CvtTestUtils { + +#define DECOMPOSE_TM_STRUCT(times, fractions, timezone) "Year:" << times.tm_year + 1900 << \ + " Month:" << times.tm_mon << \ + " Day:" << times.tm_mday << \ + " Hour:" << times.tm_hour << \ + " Min:" << times.tm_min << \ + " Sec:" << times.tm_sec << \ + " Fract: " << fractions << \ + " IsDST:" << times.tm_isdst << \ + " WDay:" << times.tm_wday << \ + " YDay:" << times.tm_yday << \ + " TZ Offset:" << timezone \ + +template +static constexpr int sign(T value) +{ + return (T(0) < value) - (value < T(0)); +} + +static struct tm initTMStruct(int year, int month, int day) +{ + struct tm times; + memset(×, 0, sizeof(struct tm)); + + times.tm_year = year - 1900; + times.tm_mon = month - 1; + times.tm_mday = day; + mktime(×); + + return times; +} + +static ISC_DATE createDate(int year, int month, int day) +{ + struct tm times = initTMStruct(year, month, day); + return NoThrowTimeStamp::encode_date(×); +} + +static ISC_TIME createTime(int hours, int minutes, int seconds, int fractions = 0) +{ + return NoThrowTimeStamp::encode_time(hours, minutes, seconds, fractions); +} + +static ISC_TIMESTAMP createTimeStamp(int year, int month, int day, int hours, int minutes, int seconds, int fractions = 0) +{ + struct tm times = initTMStruct(year, month, day); + times.tm_hour = hours; + times.tm_min = minutes; + times.tm_sec = seconds; + + return NoThrowTimeStamp::encode_timestamp(×, fractions); +} + +static ISC_TIME_TZ createTimeTZ(int hours, int minutes, int seconds, int offsetInMinutes, int fractions = 0) +{ + ISC_TIME_TZ timeTZ; + timeTZ.time_zone = TimeZoneUtil::makeFromOffset(sign(offsetInMinutes), abs(offsetInMinutes / 60), + abs(offsetInMinutes % 60)); + timeTZ.utc_time = createTime(hours, minutes, seconds, fractions); + + return timeTZ; +} + +static ISC_TIMESTAMP_TZ createTimeStampTZ(int year, int month, int day, int hours, int minutes, int seconds, + int offsetInMinutes, int fractions = 0) +{ + ISC_TIMESTAMP_TZ timestampTZ; + timestampTZ.time_zone = TimeZoneUtil::makeFromOffset(sign(offsetInMinutes), abs(offsetInMinutes / 60), + abs(offsetInMinutes % 60)); + timestampTZ.utc_timestamp = createTimeStamp(year, month, day, hours, minutes, seconds, fractions); + + return timestampTZ; +} + +class CVTCallback : public Firebird::Callbacks +{ +public: + explicit CVTCallback(ErrorFunction aErr) : Callbacks(aErr) + {} + +public: + bool transliterate(const dsc* from, dsc* to, CHARSET_ID&) override { return true; } + CHARSET_ID getChid(const dsc* d) override { return 0; } + Jrd::CharSet* getToCharset(CHARSET_ID charset2) override { return nullptr; } + void validateData(Jrd::CharSet* toCharset, SLONG length, const UCHAR* q) override { } + ULONG validateLength(Jrd::CharSet* charSet, CHARSET_ID charSetId, ULONG length, const UCHAR* start, + const USHORT size) override { return 0; } + SLONG getLocalDate() override { return 0; } + ISC_TIMESTAMP getCurrentGmtTimeStamp() override { ISC_TIMESTAMP ts; return ts; } + USHORT getSessionTimeZone() override { return 1439; } + void isVersion4(bool& v4) override { } +}; + +template +static UCHAR getDSCTypeFromDateType() { return 0; } + +template<> +UCHAR getDSCTypeFromDateType() { return dtype_sql_date; } + +template<> +UCHAR getDSCTypeFromDateType() { return dtype_sql_time; } + +template<> +UCHAR getDSCTypeFromDateType() { return dtype_timestamp; } + +template<> +UCHAR getDSCTypeFromDateType() { return dtype_sql_time_tz; } + +template<> +UCHAR getDSCTypeFromDateType() { return dtype_timestamp_tz; } + +} // namespace CvtTestUtils + +#endif // CVT_TEST_UTILS_H diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 57a7733413..21a438879d 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -3494,24 +3494,30 @@ dsc* BoolAsValueNode::execute(thread_db* tdbb, Request* request) const //-------------------- -static RegisterNode regCastNode({blr_cast}); +static RegisterNode regCastNode({blr_cast, blr_cast_format}); -CastNode::CastNode(MemoryPool& pool, ValueExprNode* aSource, dsql_fld* aDsqlField) +CastNode::CastNode(MemoryPool& pool, ValueExprNode* aSource, dsql_fld* aDsqlField, const string& aFormat) : TypedNode(pool), dsqlAlias("CAST"), dsqlField(aDsqlField), source(aSource), itemInfo(NULL), + format(pool, aFormat), artificial(false) { castDesc.clear(); } // Parse a datatype cast. -DmlNode* CastNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/) +DmlNode* CastNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { + fb_assert(blrOp == blr_cast || blrOp == blr_cast_format); + CastNode* node = FB_NEW_POOL(pool) CastNode(pool); + if (blrOp == blr_cast_format) + csb->csb_blr_reader.getString(node->format); + ItemInfo itemInfo; PAR_desc(tdbb, csb, &node->castDesc, &itemInfo); @@ -3539,6 +3545,7 @@ string CastNode::internalPrint(NodePrinter& printer) const NODE_PRINT(printer, castDesc); NODE_PRINT(printer, source); NODE_PRINT(printer, itemInfo); + NODE_PRINT(printer, format); return "CastNode"; } @@ -3549,6 +3556,7 @@ ValueExprNode* CastNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) node->dsqlAlias = dsqlAlias; node->source = doDsqlPass(dsqlScratch, source); node->dsqlField = dsqlField; + node->format = format; DDL_resolve_intl_type(dsqlScratch, node->dsqlField, NULL); node->setParameterType(dsqlScratch, NULL, false); @@ -3593,8 +3601,16 @@ bool CastNode::setParameterType(DsqlCompilerScratch* /*dsqlScratch*/, // Generate BLR for a data-type cast operation. void CastNode::genBlr(DsqlCompilerScratch* dsqlScratch) { - dsqlScratch->appendUChar(blr_cast); + if (format.hasData()) + { + dsqlScratch->appendUChar(blr_cast_format); + dsqlScratch->appendString(0, format); + } + else + dsqlScratch->appendUChar(blr_cast); + dsqlScratch->putDtype(dsqlField, true); + GEN_expr(dsqlScratch, source); } @@ -3640,6 +3656,7 @@ ValueExprNode* CastNode::copy(thread_db* tdbb, NodeCopier& copier) const node->source = copier.copy(tdbb, source); node->castDesc = castDesc; node->itemInfo = itemInfo; + node->format = format; return node; } @@ -3652,7 +3669,7 @@ bool CastNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other const CastNode* o = nodeAs(other); fb_assert(o); - return dsqlField == o->dsqlField; + return dsqlField == o->dsqlField && format == o->format; } bool CastNode::sameAs(const ExprNode* other, bool ignoreStreams) const @@ -3666,7 +3683,7 @@ bool CastNode::sameAs(const ExprNode* other, bool ignoreStreams) const const CastNode* const otherNode = nodeAs(other); fb_assert(otherNode); - return DSC_EQUIV(&castDesc, &otherNode->castDesc, true); + return DSC_EQUIV(&castDesc, &otherNode->castDesc, true) && format == otherNode->format; } ValueExprNode* CastNode::pass1(thread_db* tdbb, CompilerScratch* csb) @@ -3706,12 +3723,12 @@ dsc* CastNode::execute(thread_db* tdbb, Request* request) const const auto impure = request->getImpure(impureOffset); - return perform(tdbb, impure, value, &castDesc, itemInfo); + return perform(tdbb, impure, value, &castDesc, itemInfo, format); } // Cast from one datatype to another. dsc* CastNode::perform(thread_db* tdbb, impure_value* impure, dsc* value, - const dsc* castDesc, const ItemInfo* itemInfo) + const dsc* castDesc, const ItemInfo* itemInfo, const Firebird::string& format) { // If validation is not required and the source value is either NULL // or already in the desired data type, simply return it "as is" @@ -3767,7 +3784,58 @@ dsc* CastNode::perform(thread_db* tdbb, impure_value* impure, dsc* value, if (!value) return nullptr; - MOV_move(tdbb, value, &impure->vlu_desc); + + if (format.hasData()) + { + if (DTYPE_IS_TEXT(impure->vlu_desc.dsc_dtype)) + { + string result = CVT_datetime_to_format_string(value, format, &EngineCallbacks::instance); + USHORT dscLength = DSC_string_length(&impure->vlu_desc); + string::size_type resultLength = result.length(); + if (resultLength > dscLength) + { + ERR_post(Arg::Gds(isc_arith_except) << Arg::Gds(isc_string_truncation) << + Arg::Gds(isc_trunc_limits) << Arg::Num(dscLength) << Arg::Num(resultLength)); + } + + USHORT dscOffset = 0; + if (impure->vlu_desc.dsc_dtype == dtype_cstring) + dscOffset = 1; + else if (impure->vlu_desc.dsc_dtype == dtype_varying) + { + dscOffset = sizeof(USHORT); + ((vary*) impure->vlu_desc.dsc_address)->vary_length = resultLength; + } + + memcpy(impure->vlu_desc.dsc_address + dscOffset, result.c_str(), resultLength); + } + else + { + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance); + switch (impure->vlu_desc.dsc_dtype) + { + case dtype_sql_time: + *(ISC_TIME*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_time; + break; + case dtype_sql_date: + *(ISC_DATE*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_date; + break; + case dtype_timestamp: + *(ISC_TIMESTAMP*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp; + break; + case dtype_sql_time_tz: + case dtype_ex_time_tz: + *(ISC_TIME_TZ*) impure->vlu_desc.dsc_address = TimeZoneUtil::timeStampTzToTimeTz(timestampTZ); + break; + case dtype_timestamp_tz: + case dtype_ex_timestamp_tz: + *(ISC_TIMESTAMP_TZ*) impure->vlu_desc.dsc_address = timestampTZ; + break; + } + } + } + else + MOV_move(tdbb, value, &impure->vlu_desc); if (impure->vlu_desc.dsc_dtype == dtype_text) INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc); @@ -5781,55 +5849,7 @@ dsc* ExtractNode::execute(thread_db* tdbb, Request* request) const case blr_extract_week: { - // Algorithm for Converting Gregorian Dates to ISO 8601 Week Date by Rick McCarty, 1999 - // http://personal.ecu.edu/mccartyr/ISOwdALG.txt - - const int y = times.tm_year + 1900; - const int dayOfYearNumber = times.tm_yday + 1; - - // Find the jan1Weekday for y (Monday=1, Sunday=7) - const int yy = (y - 1) % 100; - const int c = (y - 1) - yy; - const int g = yy + yy / 4; - const int jan1Weekday = 1 + (((((c / 100) % 4) * 5) + g) % 7); - - // Find the weekday for y m d - const int h = dayOfYearNumber + (jan1Weekday - 1); - const int weekday = 1 + ((h - 1) % 7); - - // Find if y m d falls in yearNumber y-1, weekNumber 52 or 53 - int yearNumber, weekNumber; - - if ((dayOfYearNumber <= (8 - jan1Weekday)) && (jan1Weekday > 4)) - { - yearNumber = y - 1; - weekNumber = ((jan1Weekday == 5) || ((jan1Weekday == 6) && - TimeStamp::isLeapYear(yearNumber))) ? 53 : 52; - } - else - { - yearNumber = y; - - // Find if y m d falls in yearNumber y+1, weekNumber 1 - int i = TimeStamp::isLeapYear(y) ? 366 : 365; - - if ((i - dayOfYearNumber) < (4 - weekday)) - { - yearNumber = y + 1; - weekNumber = 1; - } - } - - // Find if y m d falls in yearNumber y, weekNumber 1 through 53 - if (yearNumber == y) - { - int j = dayOfYearNumber + (7 - weekday) + (jan1Weekday - 1); - weekNumber = j / 7; - if (jan1Weekday > 4) - weekNumber--; - } - - part = weekNumber; + part = NoThrowTimeStamp::convertGregorianDateToWeekDate(times); break; } diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index a7ae73afc1..786a18e141 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -242,7 +242,8 @@ public: class CastNode final : public TypedNode { public: - explicit CastNode(MemoryPool& pool, ValueExprNode* aSource = NULL, dsql_fld* aDsqlField = NULL); + explicit CastNode(MemoryPool& pool, ValueExprNode* aSource = NULL, dsql_fld* aDsqlField = NULL, + const Firebird::string& aFormat = NULL); static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); @@ -274,13 +275,14 @@ public: virtual dsc* execute(thread_db* tdbb, Request* request) const; static dsc* perform(thread_db* tdbb, impure_value* impure, dsc* value, - const dsc* castDesc, const ItemInfo* itemInfo); + const dsc* castDesc, const ItemInfo* itemInfo, const Firebird::string& format = nullptr); public: MetaName dsqlAlias; dsql_fld* dsqlField; NestConst source; NestConst itemInfo; + Firebird::string format; dsc castDesc; bool artificial; }; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index ab093aa102..d8c6344d27 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -700,6 +700,7 @@ using namespace Firebird; %token ANY_VALUE %token CALL +%token FORMAT %token NAMED_ARG_ASSIGN // precedence declarations for expression evaluation @@ -4855,6 +4856,7 @@ non_charset_simple_type | numeric_type | float_type | decfloat_type + | date_time_type | BIGINT { $$ = newNode(); @@ -4898,60 +4900,6 @@ non_charset_simple_type $$->length = sizeof(SSHORT); $$->flags |= FLD_has_prec; } - | DATE - { - $$ = newNode(); - stmt_ambiguous = true; - - if (client_dialect <= SQL_DIALECT_V5) - { - // Post warning saying that DATE is equivalent to TIMESTAMP - ERRD_post_warning(Arg::Warning(isc_sqlwarn) << Arg::Num(301) << - Arg::Warning(isc_dtype_renamed)); - $$->dtype = dtype_timestamp; - $$->length = sizeof(GDS_TIMESTAMP); - } - else if (client_dialect == SQL_DIALECT_V6_TRANSITION) - yyabandon(YYPOSNARG(1), -104, isc_transitional_date); - else - { - $$->dtype = dtype_sql_date; - $$->length = sizeof(ULONG); - } - $$->flags |= FLD_has_prec; - } - | TIME without_time_zone_opt - { - $$ = newNode(); - - checkTimeDialect(); - $$->dtype = dtype_sql_time; - $$->length = sizeof(SLONG); - $$->flags |= FLD_has_prec; - } - | TIME WITH TIME ZONE - { - $$ = newNode(); - - checkTimeDialect(); - $$->dtype = dtype_sql_time_tz; - $$->length = sizeof(ISC_TIME_TZ); - $$->flags |= FLD_has_prec; - } - | TIMESTAMP without_time_zone_opt - { - $$ = newNode(); - $$->dtype = dtype_timestamp; - $$->length = sizeof(GDS_TIMESTAMP); - $$->flags |= FLD_has_prec; - } - | TIMESTAMP WITH TIME ZONE - { - $$ = newNode(); - $$->dtype = dtype_timestamp_tz; - $$->length = sizeof(ISC_TIMESTAMP_TZ); - $$->flags |= FLD_has_prec; - } | BOOLEAN { $$ = newNode(); @@ -8720,6 +8668,77 @@ named_argument cast_specification : CAST '(' value AS data_type_descriptor ')' { $$ = newNode($3, $5); } + | CAST '(' value AS cast_format_type cast_format_clause utf_string ')' + { $$ = newNode($3, $5, *$7); } + ; + +%type cast_format_clause +cast_format_clause + : FORMAT + ; + +%type date_time_type +date_time_type + : DATE + { + $$ = newNode(); + stmt_ambiguous = true; + + if (client_dialect <= SQL_DIALECT_V5) + { + // Post warning saying that DATE is equivalent to TIMESTAMP + ERRD_post_warning(Arg::Warning(isc_sqlwarn) << Arg::Num(301) << + Arg::Warning(isc_dtype_renamed)); + $$->dtype = dtype_timestamp; + $$->length = sizeof(GDS_TIMESTAMP); + } + else if (client_dialect == SQL_DIALECT_V6_TRANSITION) + yyabandon(YYPOSNARG(1), -104, isc_transitional_date); + else + { + $$->dtype = dtype_sql_date; + $$->length = sizeof(ULONG); + } + $$->flags |= FLD_has_prec; + } + | TIME without_time_zone_opt + { + $$ = newNode(); + + checkTimeDialect(); + $$->dtype = dtype_sql_time; + $$->length = sizeof(SLONG); + $$->flags |= FLD_has_prec; + } + | TIME WITH TIME ZONE + { + $$ = newNode(); + + checkTimeDialect(); + $$->dtype = dtype_sql_time_tz; + $$->length = sizeof(ISC_TIME_TZ); + $$->flags |= FLD_has_prec; + } + | TIMESTAMP without_time_zone_opt + { + $$ = newNode(); + $$->dtype = dtype_timestamp; + $$->length = sizeof(GDS_TIMESTAMP); + $$->flags |= FLD_has_prec; + } + | TIMESTAMP WITH TIME ZONE + { + $$ = newNode(); + $$->dtype = dtype_timestamp_tz; + $$->length = sizeof(ISC_TIMESTAMP_TZ); + $$->flags |= FLD_has_prec; + } + ; + +%type cast_format_type +cast_format_type + : character_type + | date_time_type ; // case expressions @@ -9383,6 +9402,7 @@ non_reserved_word | UNICODE_VAL // added in FB 6.0 | ANY_VALUE + | FORMAT ; %% diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index 644c4ea165..348037de2c 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -496,4 +496,6 @@ #define blr_default_arg (unsigned char) 227 +#define blr_cast_format (unsigned char) 228 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 76901784dc..0c12476bf8 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -972,3 +972,12 @@ FB_IMPL_MSG(JRD, 969, uninitialized_var, -625, "42", "000", "Variable @1 is not FB_IMPL_MSG(JRD, 970, param_not_exist, -170, "07", "001", "Parameter @1 does not exist") FB_IMPL_MSG(JRD, 971, param_no_default_not_specified, -170, "07", "001", "Parameter @1 has no default value and was not specified or was specified with DEFAULT") FB_IMPL_MSG(JRD, 972, param_multiple_assignments, -170, "07", "001", "Parameter @1 has multiple assignments") +FB_IMPL_MSG(JRD, 973, invalid_date_format, -901, "HY", "000", "Cannot recognize \"@1\" part of date format") +FB_IMPL_MSG(JRD, 974, invalid_raw_string_in_date_format, -901, "HY", "000", "Cannot find closing \" for raw text in date format") +FB_IMPL_MSG(JRD, 975, invalid_data_type_for_date_format, -901, "HY", "000", "It is not possible to use this data type for date formatting") +FB_IMPL_MSG(JRD, 976, incompatible_date_format_with_current_date_type, -901, "HY", "000", "Cannot use \"@1\" format with current date type") +FB_IMPL_MSG(JRD, 977, value_for_pattern_is_out_of_range, -901, "HY", "000", "Value for @1 pattern is out of range [@2, @3]") +FB_IMPL_MSG(JRD, 978, month_name_mismatch, -901, "HY", "000", "@1 is not MONTH") +FB_IMPL_MSG(JRD, 979, incorrect_hours_period, -901, "HY", "000", "@1 is incorrect period for 12H, it should be AM or PM") +FB_IMPL_MSG(JRD, 980, data_for_format_is_exhausted, -901, "HY", "000", "All data has been read, but format pattern wants more. Unfilled patterns: \"@1\"") +FB_IMPL_MSG(JRD, 981, trailing_part_of_string, -901, "HY", "000", "There is a trailing part of input string that does not fit into FORMAT: \"@1\"") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 0496fc4693..fec52a3055 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5704,6 +5704,15 @@ const isc_param_not_exist = 335545290; isc_param_no_default_not_specified = 335545291; isc_param_multiple_assignments = 335545292; + isc_invalid_date_format = 335545293; + isc_invalid_raw_string_in_date_format = 335545294; + isc_invalid_data_type_for_date_format = 335545295; + isc_incompatible_date_format_with_current_date_type = 335545296; + isc_value_for_pattern_is_out_of_range = 335545297; + isc_month_name_mismatch = 335545298; + isc_incorrect_hours_period = 335545299; + isc_data_for_format_is_exhausted = 335545300; + isc_trailing_part_of_string = 335545301; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From 81d2d8ed366e9a48f45b659559fb8fe68ea41672 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Tue, 24 Oct 2023 18:25:42 +0300 Subject: [PATCH 09/31] Fixed bug #7779 : Firebird 4.0.3 is constantly crashing with the same symptoms (fbclient.dll) --- src/jrd/extds/IscDS.cpp | 6 ++++++ src/yvalve/why.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jrd/extds/IscDS.cpp b/src/jrd/extds/IscDS.cpp index 3a3c8654c4..2c9ef72daa 100644 --- a/src/jrd/extds/IscDS.cpp +++ b/src/jrd/extds/IscDS.cpp @@ -113,6 +113,12 @@ void IscConnection::attach(thread_db* tdbb) validatePassword(tdbb, m_dbName, newDpb); newDpb.insertInt(isc_dpb_ext_call_depth, attachment->att_ext_call_depth + 1); + if (newDpb.getBufferLength() > MAX_USHORT) + { + ERR_post(Arg::Gds(isc_imp_exc) << + Arg::Gds(isc_random) << Arg::Str("DPB size greater than 64KB")); + } + FbLocalStatus status; { EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); diff --git a/src/yvalve/why.cpp b/src/yvalve/why.cpp index 0af0f9d017..71b1dc24f9 100644 --- a/src/yvalve/why.cpp +++ b/src/yvalve/why.cpp @@ -1652,7 +1652,7 @@ ISC_STATUS API_ROUTINE isc_attach_database(ISC_STATUS* userStatus, SSHORT fileLe return status[1]; YAttachment* attachment = dispatcher->attachDatabase(&statusWrapper, pathName.c_str(), - dpbLength, reinterpret_cast(dpb)); + static_cast(dpbLength), reinterpret_cast(dpb)); if (status.getState() & IStatus::STATE_ERRORS) return status[1]; From 858d5b0c0d4682113abe38045c913f2101aa65b0 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 24 Oct 2023 20:14:07 +0000 Subject: [PATCH 10/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index a560075e19..7ef1da8958 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:90 + FORMAL BUILD NUMBER:92 */ -#define PRODUCT_VER_STRING "6.0.0.90" -#define FILE_VER_STRING "WI-T6.0.0.90" -#define LICENSE_VER_STRING "WI-T6.0.0.90" -#define FILE_VER_NUMBER 6, 0, 0, 90 +#define PRODUCT_VER_STRING "6.0.0.92" +#define FILE_VER_STRING "WI-T6.0.0.92" +#define LICENSE_VER_STRING "WI-T6.0.0.92" +#define FILE_VER_NUMBER 6, 0, 0, 92 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "90" +#define FB_BUILD_NO "92" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index fe7a6de5cc..c078472b27 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=90 +BuildNum=92 NowAt=`pwd` cd `dirname $0` From a6d322f1b2d9469c64a50b8d51ba840729ddabf6 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Fri, 27 Oct 2023 21:17:26 +0300 Subject: [PATCH 11/31] Fixed #7800: Default publication status is not preserved after backup/restore --- src/burp/backup.epp | 19 +++++++++++++++ src/burp/burp.h | 4 +++ src/burp/restore.epp | 58 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/burp/backup.epp b/src/burp/backup.epp index 010ead1484..7881ccabd7 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -2517,6 +2517,7 @@ void write_database( const TEXT* dbb_file) FbLocalStatus status_vector; UCHAR buffer[256]; Firebird::IRequest* req_handle1 = nullptr; + Firebird::IRequest* req_handle2 = nullptr; BurpGlobals* tdgbl = BurpGlobals::getSpecific(); @@ -2619,6 +2620,23 @@ void write_database( const TEXT* dbb_file) ON_ERROR general_on_error(); END_ERROR; + + FOR (REQUEST_HANDLE req_handle2) + PUB IN RDB$PUBLICATIONS + WITH PUB.RDB$PUBLICATION_NAME EQ DEFAULT_PUBLICATION + { + fb_assert(PUB.RDB$SYSTEM_FLAG != 0); + + if (!PUB.RDB$ACTIVE_FLAG.NULL) + put_boolean(att_default_pub_active, PUB.RDB$ACTIVE_FLAG); + + if (!PUB.RDB$AUTO_ENABLE.NULL) + put_boolean(att_default_pub_auto_enable, PUB.RDB$AUTO_ENABLE); + } + END_FOR + ON_ERROR + general_on_error(); + END_ERROR } else { @@ -2636,6 +2654,7 @@ void write_database( const TEXT* dbb_file) } MISC_release_request_silent(req_handle1); + MISC_release_request_silent(req_handle2); put(tdgbl, att_end); } diff --git a/src/burp/burp.h b/src/burp/burp.h index 2c5220b5de..438aa86815 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -257,6 +257,8 @@ enum att_type { att_database_sql_security_deprecated, // can be removed later att_replica_mode, // replica mode att_database_sql_security, // default sql security value + att_default_pub_active, // default publication status + att_default_pub_auto_enable, // Relation attributes @@ -1065,6 +1067,8 @@ public: UCHAR* gbl_crypt_buffer; ULONG gbl_crypt_left; UCHAR* gbl_decompress; + bool gbl_default_pub_active = false; + bool gbl_default_pub_auto_enable = false; burp_rel* relations; burp_pkg* packages; diff --git a/src/burp/restore.epp b/src/burp/restore.epp index ab682e8617..105e45af3f 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -198,7 +198,7 @@ static inline UCHAR get(BurpGlobals* tdgbl) return tdgbl->get(); } -static inline FB_BOOLEAN get_boolean(BurpGlobals* tdgbl, bool deprecated) +static inline FB_BOOLEAN get_boolean(BurpGlobals* tdgbl, bool deprecated = false) { if (!deprecated) { @@ -531,6 +531,40 @@ int RESTORE_restore (const TEXT* file_name, const TEXT* database_name) MISC_release_request_silent(req_handle1); } + // If the default publication was backed up with non-default values, + // update the table accorgingly. + // NOTE: This change should be performed in the last transaction + // of the restore process, to avoid generating a replication stream + // before the database is restored successfully. + + if ((tdgbl->gbl_default_pub_active || tdgbl->gbl_default_pub_auto_enable) && + tdgbl->runtimeODS >= DB_VERSION_DDL12) + { + FOR (REQUEST_HANDLE req_handle1) + PUB IN RDB$PUBLICATIONS + WITH PUB.RDB$PUBLICATION_NAME EQ DEFAULT_PUBLICATION + { + fb_assert(PUB.RDB$SYSTEM_FLAG != 0); + + MODIFY PUB USING + PUB.RDB$ACTIVE_FLAG.NULL = FALSE; + PUB.RDB$ACTIVE_FLAG = tdgbl->gbl_default_pub_active ? 1 : 0; + + PUB.RDB$AUTO_ENABLE.NULL = FALSE; + PUB.RDB$AUTO_ENABLE = tdgbl->gbl_default_pub_auto_enable ? 1 : 0; + END_MODIFY; + ON_ERROR + general_on_error(); + END_ERROR; + } + END_FOR; + ON_ERROR + general_on_error(); + END_ERROR; + + MISC_release_request_silent(req_handle1); + } + // Add missing privileges fix_missing_privileges(tdgbl); @@ -10496,6 +10530,28 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file } break; + case att_default_pub_active: + if (tdgbl->RESTORE_format >= 11) + tdgbl->gbl_default_pub_active = get_boolean(tdgbl); + else + { + // Functions that use scan_next_attr initialize it to NO_SKIP using skip_init(). + // Here we don't use that logic, hence the first param to bad_attribute is hardcoded. + bad_attribute(NO_SKIP, attribute, 352); + } + break; + + case att_default_pub_auto_enable: + if (tdgbl->RESTORE_format >= 11) + tdgbl->gbl_default_pub_auto_enable = get_boolean(tdgbl); + else + { + // Functions that use scan_next_attr initialize it to NO_SKIP using skip_init(). + // Here we don't use that logic, hence the first param to bad_attribute is hardcoded. + bad_attribute(NO_SKIP, attribute, 352); + } + break; + default: { SSHORT l = get(tdgbl); From 09ae711a4ec486046bf9dcb764d3f9babff124d9 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Tue, 24 Oct 2023 10:07:41 +0300 Subject: [PATCH 12/31] Fixed #7804: The partial index is not involved when filtering conditions through OR --- src/jrd/optimizer/Optimizer.h | 20 +++++----- src/jrd/optimizer/Retrieval.cpp | 71 ++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/jrd/optimizer/Optimizer.h b/src/jrd/optimizer/Optimizer.h index ffd050c5ed..6ee2b75e70 100644 --- a/src/jrd/optimizer/Optimizer.h +++ b/src/jrd/optimizer/Optimizer.h @@ -356,6 +356,16 @@ public: static double getSelectivity(const BoolExprNode* node) { + if (const auto listNode = nodeAs(node)) + { + const auto selectivity = REDUCE_SELECTIVITY_FACTOR_EQUALITY * + listNode->list->items.getCount(); + return MIN(selectivity, MAXIMUM_SELECTIVITY); + } + + if (nodeIs(node)) + return REDUCE_SELECTIVITY_FACTOR_EQUALITY; + if (const auto cmpNode = nodeAs(node)) { switch (cmpNode->blrOp) @@ -382,16 +392,6 @@ public: break; } } - else if (const auto listNode = nodeAs(node)) - { - const auto selectivity = REDUCE_SELECTIVITY_FACTOR_EQUALITY * - listNode->list->items.getCount(); - return MIN(selectivity, MAXIMUM_SELECTIVITY); - } - else if (nodeIs(node)) - { - return REDUCE_SELECTIVITY_FACTOR_EQUALITY; - } return REDUCE_SELECTIVITY_FACTOR_OTHER; } diff --git a/src/jrd/optimizer/Retrieval.cpp b/src/jrd/optimizer/Retrieval.cpp index 09e8d8d9cd..fe55636541 100644 --- a/src/jrd/optimizer/Retrieval.cpp +++ b/src/jrd/optimizer/Retrieval.cpp @@ -104,6 +104,34 @@ namespace return newValue; } + bool matchSubset(const BoolExprNode* boolean, const BoolExprNode* sub) + { + if (boolean->sameAs(sub, true)) + return true; + + auto binaryNode = nodeAs(boolean); + if (binaryNode && binaryNode->blrOp == blr_or) + { + if (matchSubset(binaryNode->arg1, sub) || + matchSubset(binaryNode->arg2, sub)) + { + return true; + } + + binaryNode = nodeAs(sub); + if (binaryNode && binaryNode->blrOp == blr_or) + { + if (matchSubset(boolean, binaryNode->arg1) && + matchSubset(boolean, binaryNode->arg2)) + { + return true; + } + } + } + + return false; + }; + } // namespace @@ -723,24 +751,22 @@ bool Retrieval::checkIndexCondition(index_desc& idx, MatchedBooleanList& matches const auto boolean = idxIter.object(); // If the index condition is (A OR B) and any of the {A, B} is present - // among the available booleans, then the index is possibly usable - const auto binaryNode = nodeAs(boolean); - if (binaryNode && binaryNode->blrOp == blr_or) + // among the available booleans, then the index is possibly usable. + // Note: this check also includes the exact match. + + for (iter.rewind(); iter.hasData(); ++iter) { - for (iter.rewind(); iter.hasData(); ++iter) + if (matchSubset(boolean, *iter)) { - if (binaryNode->arg1->sameAs(*iter, true) || - binaryNode->arg2->sameAs(*iter, true)) - { - matches.add(*iter); - break; - } + matches.add(*iter); + break; } } // If the index condition is (A IS NOT NULL) and the available booleans // includes any comparative predicate that explicitly mentions A, // then the index is possibly usable + const auto notNode = nodeAs(boolean); const auto missingNode = notNode ? nodeAs(notNode->arg) : nullptr; if (missingNode) @@ -767,17 +793,6 @@ bool Retrieval::checkIndexCondition(index_desc& idx, MatchedBooleanList& matches } } - // If conjunct of the index condition matches any available boolean, - // then the index is possibly usable - for (iter.rewind(); iter.hasData(); ++iter) - { - if (idxIter.object()->sameAs(*iter, true)) - { - matches.add(*iter); - break; - } - } - idx.idx_fraction *= optimizer->getSelectivity(boolean); } @@ -833,7 +848,7 @@ void Retrieval::getInversionCandidates(InversionCandidateList& inversions, if (scratch.candidate) { matches.assign(scratch.matches); - scratch.selectivity = idx->idx_fraction; + scratch.selectivity = MAXIMUM_SELECTIVITY; bool unique = false; unsigned listCount = 0; @@ -1027,7 +1042,7 @@ void Retrieval::getInversionCandidates(InversionCandidateList& inversions, const auto invCandidate = FB_NEW_POOL(getPool()) InversionCandidate(getPool()); invCandidate->unique = unique; - invCandidate->selectivity = selectivity; + invCandidate->selectivity = idx->idx_fraction * selectivity; invCandidate->cost = cost; invCandidate->nonFullMatchedSegments = scratch.nonFullMatchedSegments; invCandidate->matchedSegments = MAX(scratch.lowerCount, scratch.upperCount); @@ -1054,6 +1069,13 @@ void Retrieval::getInversionCandidates(InversionCandidateList& inversions, invCandidate->scratch = &scratch; invCandidate->matches.assign(scratch.matches); + for (auto match : invCandidate->matches) + { + match->findDependentFromStreams(csb, stream, + &invCandidate->dependentFromStreams); + } + + invCandidate->dependencies = invCandidate->dependentFromStreams.getCount(); inversions.add(invCandidate); } } @@ -1377,6 +1399,9 @@ InversionCandidate* Retrieval::makeInversion(InversionCandidateList& inversions) } } + if (currentInv->boolean && matches.exist(currentInv->boolean)) + anyMatchAlreadyUsed = true; + if (anyMatchAlreadyUsed && !customPlan) { currentInv->used = true; From efeaa73a02e2f5031818f68292f4c99fee518581 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sun, 15 Oct 2023 20:51:37 +0300 Subject: [PATCH 13/31] Undo some of my prior changes (still WIP). Slightly nicer debug output. --- src/jrd/optimizer/InnerJoin.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/jrd/optimizer/InnerJoin.cpp b/src/jrd/optimizer/InnerJoin.cpp index edcdb3d06f..4da3e9ebb6 100644 --- a/src/jrd/optimizer/InnerJoin.cpp +++ b/src/jrd/optimizer/InnerJoin.cpp @@ -189,21 +189,19 @@ void InnerJoin::estimateCost(unsigned position, joinedStreams[position].selectivity = candidate->selectivity; // Get the stream cardinality - const auto tail = &csb->csb_rpt[stream->number]; - const auto streamCardinality = tail->csb_cardinality; + const auto streamCardinality = csb->csb_rpt[stream->number].csb_cardinality; - auto currentCardinality = streamCardinality * candidate->selectivity; + auto currentCardinality = candidate->unique ? + MINIMUM_CARDINALITY : streamCardinality * candidate->selectivity; auto currentCost = candidate->cost; - // Unless an external sort is to be applied, adjust estimated cost and cardinality - // accordingly to the "first-rows" retrieval (if specified) + // Given the "first-rows" mode specified (or implied) + // and unless an external sort is to be applied afterwards, + // fake the expected cardinality to look as low as possible + // to estimate the cost just for a single row being produced + if ((!sort || candidate->navigated) && optimizer->favorFirstRows()) - { - currentCost -= DEFAULT_INDEX_COST; - currentCost /= MAX(currentCardinality, MINIMUM_CARDINALITY); - currentCost += DEFAULT_INDEX_COST; currentCardinality = MINIMUM_CARDINALITY; - } // Calculate the nested loop cost, it's our default option const auto loopCost = currentCost * cardinality; @@ -687,7 +685,7 @@ InnerJoin::StreamInfo* InnerJoin::getStreamInfo(StreamType stream) // Dump finally selected stream order void InnerJoin::printBestOrder() const { - if (bestStreams.isEmpty()) + if (bestStreams.getCount() < 2) return; optimizer->printf(" best order, streams:"); @@ -742,7 +740,7 @@ void InnerJoin::printFoundOrder(StreamType position, // Dump finally selected stream order void InnerJoin::printStartOrder() const { - optimizer->printf("Start join order, streams:"); + bool found = false; const auto end = innerStreams.end(); for (auto iter = innerStreams.begin(); iter != end; iter++) @@ -750,6 +748,12 @@ void InnerJoin::printStartOrder() const const auto innerStream = *iter; if (!innerStream->used) { + if (!found) + { + optimizer->printf("Start join order, streams:"); + found = true; + } + const auto name = optimizer->getStreamName(innerStream->number); optimizer->printf(" %u (%s) base cost (%1.2f)", innerStream->number, name.c_str(), innerStream->baseCost); From d9274e1bbded560e3886091e156df1bbf8b33f04 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Thu, 26 Oct 2023 00:35:16 +0300 Subject: [PATCH 14/31] Fixed regression #7770 : restore takes 25% more time vs 4.0.0 --- src/burp/burp.h | 5 +++-- src/burp/restore.epp | 52 ++++++++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/burp/burp.h b/src/burp/burp.h index 438aa86815..6f7bb02a08 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -737,8 +737,7 @@ enum fld_flags_vals { FLD_update_missing = 8, FLD_null_flag = 16, FLD_charset_flag = 32, // column has global charset - FLD_collate_flag = 64, // local column has specific collation - FLD_system_domain = 128 // field uses a system domain (on restore) + FLD_collate_flag = 64 // local column has specific collation }; // relation definition - holds useful relation type stuff @@ -963,6 +962,7 @@ public: GblPool(us->isService()), gbl_sw_par_workers(1), defaultCollations(getPool()), + systemFields(getPool()), gbl_dpb_data(*getDefaultMemoryPool()), uSvc(us), master(true), @@ -1200,6 +1200,7 @@ public: Firebird::Array > > defaultCollations; + Firebird::SortedArray systemFields; Firebird::Array gbl_dpb_data; Firebird::UtilSvc* uSvc; bool master; // set for master thread only diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 105e45af3f..c158d02eab 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -3844,7 +3844,6 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation) case att_field_system_flag: X.RDB$SYSTEM_FLAG = (USHORT) get_int32(tdgbl); X.RDB$SYSTEM_FLAG.NULL = FALSE; - field->fld_flags |= FLD_system_domain; break; case att_view_context: @@ -4049,7 +4048,6 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation) case att_field_system_flag: X.RDB$SYSTEM_FLAG = (USHORT) get_int32(tdgbl); X.RDB$SYSTEM_FLAG.NULL = FALSE; - field->fld_flags |= FLD_system_domain; break; case att_view_context: @@ -10425,6 +10423,28 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file Firebird::IRequest* req_handle4 = nullptr; Firebird::IRequest* req_handle5 = nullptr; + // Collect system fields + { + Firebird::IRequest* req_handle = nullptr; + tdgbl->systemFields.setSortMode(Firebird::FB_ARRAY_SORT_MANUAL); + + FOR(REQUEST_HANDLE req_handle) + X IN RDB$FIELDS WITH + X.RDB$SYSTEM_FLAG EQ 1 + + const auto len = MISC_symbol_length(X.RDB$FIELD_NAME, sizeof(X.RDB$FIELD_NAME)); + Firebird::MetaString name(X.RDB$FIELD_NAME, len); + tdgbl->systemFields.add(name); + + END_FOR; + ON_ERROR + general_on_error(); + END_ERROR; + + MISC_release_request_silent(req_handle); + tdgbl->systemFields.sort(); + } + while (get_attribute(&attribute, tdgbl) != att_end) { switch (attribute) @@ -11641,6 +11661,12 @@ void WriteRelationMeta::setRelation(BurpGlobals* tdgbl, const burp_rel* relation if (tdgbl->gbl_network_protocol == 0) { + // If user relation uses system field there is a chance that definition of such + // system field was changed in target database. Old, BLR-based code, didn't + // coerce data types thus error could happen. To avoid it, let use SQL-based + // approach, while it is a bit slower in embedded mode. + // Here we are mostly interested in legacy UNICODE_FSS fields - see #7611. + bool sysDomFlag = false; burp_fld* field; @@ -11649,27 +11675,7 @@ void WriteRelationMeta::setRelation(BurpGlobals* tdgbl, const burp_rel* relation if (field->fld_flags & FLD_computed) continue; - const char* dom = field->fld_source; - - if (strncmp(dom, "RDB$", 4) == 0) - { - for (dom += 4; *dom; ++dom) - { -#ifdef HAVE_CTYPE_H - if (!isdigit(*dom)) -#else - if (*dom < '0' || *dom > '9') -#endif - { - sysDomFlag = true; - break; - } - } - } - - // Not all system domains starts with RDB$ - // There is also SEC$ fields. Here we are mostly interested in legacy UNICODE_FSS fields - see #7611. - if (field->fld_flags & FLD_system_domain) + if (tdgbl->systemFields.exist(field->fld_source)) { sysDomFlag = true; break; From 25da3089b3601216407de03d610088ed4cd3b167 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Fri, 27 Oct 2023 22:17:30 +0300 Subject: [PATCH 15/31] Warnings. --- src/burp/restore.epp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/burp/restore.epp b/src/burp/restore.epp index c158d02eab..498774e739 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -7504,8 +7504,6 @@ bool get_publication(BurpGlobals* tdgbl) * **************************************/ att_type attribute; - TEXT temp[GDS_NAME_LEN]; - SSHORT len; scan_attr_t scan_next_attr; if (tdgbl->runtimeODS >= DB_VERSION_DDL13) @@ -7603,8 +7601,6 @@ bool get_pub_table(BurpGlobals* tdgbl) * **************************************/ att_type attribute; - TEXT temp[GDS_NAME_LEN]; - SSHORT len; scan_attr_t scan_next_attr; if (tdgbl->runtimeODS >= DB_VERSION_DDL13) From 96235f842c8c2728dea5bfb3a7af21ef9e1ec47a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 27 Oct 2023 20:13:40 +0000 Subject: [PATCH 16/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 7ef1da8958..59ae196974 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:92 + FORMAL BUILD NUMBER:97 */ -#define PRODUCT_VER_STRING "6.0.0.92" -#define FILE_VER_STRING "WI-T6.0.0.92" -#define LICENSE_VER_STRING "WI-T6.0.0.92" -#define FILE_VER_NUMBER 6, 0, 0, 92 +#define PRODUCT_VER_STRING "6.0.0.97" +#define FILE_VER_STRING "WI-T6.0.0.97" +#define LICENSE_VER_STRING "WI-T6.0.0.97" +#define FILE_VER_NUMBER 6, 0, 0, 97 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "92" +#define FB_BUILD_NO "97" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index c078472b27..6aa5e219cf 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=92 +BuildNum=97 NowAt=`pwd` cd `dirname $0` From a946247ffee46fb380f1f9bd5ffd0cb38d04903b Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:50:54 -0300 Subject: [PATCH 17/31] Fix indentation. --- src/burp/restore.epp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 498774e739..0a43da379d 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -10429,8 +10429,8 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file X.RDB$SYSTEM_FLAG EQ 1 const auto len = MISC_symbol_length(X.RDB$FIELD_NAME, sizeof(X.RDB$FIELD_NAME)); - Firebird::MetaString name(X.RDB$FIELD_NAME, len); - tdgbl->systemFields.add(name); + MetaString name(X.RDB$FIELD_NAME, len); + tdgbl->systemFields.add(name); END_FOR; ON_ERROR From 9591891e906df94d66589063fcab3b0333445607 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:52:16 -0300 Subject: [PATCH 18/31] Fix typo. --- src/burp/restore.epp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 0a43da379d..26fc615c87 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -532,7 +532,7 @@ int RESTORE_restore (const TEXT* file_name, const TEXT* database_name) } // If the default publication was backed up with non-default values, - // update the table accorgingly. + // update the table accordingly. // NOTE: This change should be performed in the last transaction // of the restore process, to avoid generating a replication stream // before the database is restored successfully. From 570e59db38f7db52a4a1a3354b549bca708444d2 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Fri, 20 Oct 2023 12:55:48 +0300 Subject: [PATCH 19/31] Fixed #7812: Service backup does not work in multiple engines configuration (cherry picked from commit 28c386cb4978c27efe90cc9b1bcc7f158eb84e4a) --- src/burp/burp.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/burp/burp.cpp b/src/burp/burp.cpp index 73346bceff..749e57203a 100644 --- a/src/burp/burp.cpp +++ b/src/burp/burp.cpp @@ -572,8 +572,6 @@ int gbak(Firebird::UtilSvc* uSvc) if (switches.exists(IN_SW_BURP_SE, argv.begin(), 1, argc)) return svc_api_gbak(uSvc, switches); - uSvc->started(); - if (argc <= 1) { burp_usage(switches); @@ -1396,13 +1394,12 @@ int gbak(Firebird::UtilSvc* uSvc) tdgbl->action->act_action = ACT_unknown; action = open_files(file1, &file2, sw_replace, dpb); - MVOL_init(tdgbl->io_buffer_size); + uSvc->started(); int result; tdgbl->gbl_dpb_data.add(dpb.getBuffer(), dpb.getBufferLength()); - tdgbl->uSvc->started(); switch (action) { case RESTORE: From 6919bd67c5d7c9db8829de82eb61dbf24395a6eb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 28 Oct 2023 20:12:34 +0000 Subject: [PATCH 20/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 59ae196974..9ae3a2bc3b 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:97 + FORMAL BUILD NUMBER:100 */ -#define PRODUCT_VER_STRING "6.0.0.97" -#define FILE_VER_STRING "WI-T6.0.0.97" -#define LICENSE_VER_STRING "WI-T6.0.0.97" -#define FILE_VER_NUMBER 6, 0, 0, 97 +#define PRODUCT_VER_STRING "6.0.0.100" +#define FILE_VER_STRING "WI-T6.0.0.100" +#define LICENSE_VER_STRING "WI-T6.0.0.100" +#define FILE_VER_NUMBER 6, 0, 0, 100 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "97" +#define FB_BUILD_NO "100" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 6aa5e219cf..3711c8e3ce 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=97 +BuildNum=100 NowAt=`pwd` cd `dirname $0` From dcb7692890efd355c727b46aa9ddcc02acdd553e Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Sat, 28 Oct 2023 19:14:22 +0300 Subject: [PATCH 21/31] Post-fix for #7814, fixed regression found by QA test for CORE-3625 (#3977) --- src/jrd/jrd.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index 1f08e9b784..d5a17f3558 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -498,6 +498,7 @@ const ULONG TDBB_reset_stack = 2048; // stack should be reset after stack ove const ULONG TDBB_dfw_cleanup = 4096; // DFW cleanup phase is active const ULONG TDBB_repl_in_progress = 8192; // Prevent recursion in replication const ULONG TDBB_replicator = 16384; // Replicator +const ULONG TDBB_async = 32768; // Async context (set in AST) class thread_db : public Firebird::ThreadData { @@ -624,7 +625,11 @@ public: reqStat->bumpValue(index, delta); traStat->bumpValue(index, delta); attStat->bumpValue(index, delta); - // dbbStat adjusted from attStat, see Attachment::mergeAsyncStats() + + if ((tdbb_flags & TDBB_async) && !attachment) + dbbStat->bumpValue(index, delta); + + // else dbbStat is adjusted from attStat, see Attachment::mergeAsyncStats() } void bumpRelStats(const RuntimeStatistics::StatType index, SLONG relation_id, SINT64 delta = 1) @@ -1109,6 +1114,8 @@ namespace Jrd { fb_assert((operator thread_db*())->getAttachment()); } + + (*this)->tdbb_flags |= TDBB_async; } private: From d3ff3c7dd5b4287bba0ed8ed52a61a4c7d16c899 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 29 Oct 2023 20:12:43 +0000 Subject: [PATCH 22/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 9ae3a2bc3b..d34c4e15f5 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:100 + FORMAL BUILD NUMBER:101 */ -#define PRODUCT_VER_STRING "6.0.0.100" -#define FILE_VER_STRING "WI-T6.0.0.100" -#define LICENSE_VER_STRING "WI-T6.0.0.100" -#define FILE_VER_NUMBER 6, 0, 0, 100 +#define PRODUCT_VER_STRING "6.0.0.101" +#define FILE_VER_STRING "WI-T6.0.0.101" +#define LICENSE_VER_STRING "WI-T6.0.0.101" +#define FILE_VER_NUMBER 6, 0, 0, 101 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "100" +#define FB_BUILD_NO "101" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 3711c8e3ce..965ad0080a 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=100 +BuildNum=101 NowAt=`pwd` cd `dirname $0` From 7a858e240e74ba9dcd4291a72e9169e23794b45f Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Sun, 29 Oct 2023 23:22:38 +0200 Subject: [PATCH 23/31] Implement #7818 : Extend rdb$get_context('SYSTEM', '***') with other info from MON$ATTACHMENT --- doc/sql.extensions/README.context_variables2 | 4 ++++ src/jrd/SysFunction.cpp | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/doc/sql.extensions/README.context_variables2 b/doc/sql.extensions/README.context_variables2 index 4dca3805ff..9d5b8ddb2b 100644 --- a/doc/sql.extensions/README.context_variables2 +++ b/doc/sql.extensions/README.context_variables2 @@ -70,10 +70,14 @@ Usage: CLIENT_HOST | The wire protocol host name of remote client. Value is | returned for all supported protocols. | + CLIENT_OS_USER | Remote OS user name + | CLIENT_PID | Process ID of remote client application | CLIENT_PROCESS | Process name of remote client application | + CLIENT_VERSION | Version of the client library used by client application + | DB_NAME | Canonical name of current database. It is either alias | name if connectivity via file names is not allowed or | fully expanded database file name otherwise. diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index 2cd6ee977b..0b19b45601 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -388,8 +388,10 @@ const char WIRE_CRYPT_PLUGIN_NAME[] = "WIRE_CRYPT_PLUGIN", CLIENT_ADDRESS_NAME[] = "CLIENT_ADDRESS", CLIENT_HOST_NAME[] = "CLIENT_HOST", + CLIENT_OS_USER_NAME[] = "CLIENT_OS_USER", CLIENT_PID_NAME[] = "CLIENT_PID", CLIENT_PROCESS_NAME[] = "CLIENT_PROCESS", + CLIENT_VERSION_NAME[] = "CLIENT_VERSION", CURRENT_USER_NAME[] = "CURRENT_USER", CURRENT_ROLE_NAME[] = "CURRENT_ROLE", SESSION_IDLE_TIMEOUT[] = "SESSION_IDLE_TIMEOUT", @@ -4641,6 +4643,13 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar resultStr = attachment->att_remote_host; } + else if (nameStr == CLIENT_OS_USER_NAME) + { + if (attachment->att_remote_os_user.isEmpty()) + return NULL; + + resultStr = attachment->att_remote_os_user; + } else if (nameStr == CLIENT_PID_NAME) { if (!attachment->att_remote_pid) @@ -4655,6 +4664,13 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar resultStr = attachment->att_remote_process.ToString(); } + else if (nameStr == CLIENT_VERSION_NAME) + { + if (attachment->att_client_version.isEmpty()) + return NULL; + + resultStr = attachment->att_client_version.ToString(); + } else if (nameStr == CURRENT_USER_NAME) { const MetaString& user = attachment->getUserName(); From 044fbcb703bc534d16fcab3b3e63082199b4aa23 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Mon, 30 Oct 2023 14:14:26 +0200 Subject: [PATCH 24/31] Removed not needed conversion, as noted by @aafemt --- src/jrd/SysFunction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index 0b19b45601..cafa2bd72e 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -4669,7 +4669,7 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar if (attachment->att_client_version.isEmpty()) return NULL; - resultStr = attachment->att_client_version.ToString(); + resultStr = attachment->att_client_version; } else if (nameStr == CURRENT_USER_NAME) { From 5e0cfe3985646e5a8a89e447f9780e474702970b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 30 Oct 2023 20:13:39 +0000 Subject: [PATCH 25/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index d34c4e15f5..fd30871623 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:101 + FORMAL BUILD NUMBER:103 */ -#define PRODUCT_VER_STRING "6.0.0.101" -#define FILE_VER_STRING "WI-T6.0.0.101" -#define LICENSE_VER_STRING "WI-T6.0.0.101" -#define FILE_VER_NUMBER 6, 0, 0, 101 +#define PRODUCT_VER_STRING "6.0.0.103" +#define FILE_VER_STRING "WI-T6.0.0.103" +#define LICENSE_VER_STRING "WI-T6.0.0.103" +#define FILE_VER_NUMBER 6, 0, 0, 103 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "101" +#define FB_BUILD_NO "103" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 965ad0080a..76eaeb374b 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=101 +BuildNum=103 NowAt=`pwd` cd `dirname $0` From 749bcc05cca9b2e859cbd21a3a272be92d12a766 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Tue, 31 Oct 2023 08:53:19 +0300 Subject: [PATCH 26/31] This should fix #7817: Memory leak is possible for UDF array arguments --- src/jrd/fun.epp | 64 ++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/jrd/fun.epp b/src/jrd/fun.epp index 44541207d2..729f862a20 100644 --- a/src/jrd/fun.epp +++ b/src/jrd/fun.epp @@ -292,7 +292,8 @@ enum UdfError static SSHORT blob_get_segment(blb*, UCHAR*, USHORT, USHORT*); static void blob_put_segment(blb*, const UCHAR*, USHORT); static SLONG blob_lseek(blb*, USHORT, SLONG); -static SLONG get_scalar_array(const Parameter*, DSC*, scalar_array_desc*, UCharStack&); +static ULONG get_scalar_array(thread_db* tdbb, const Parameter*, DSC*, + scalar_array_desc*, UCharStack&); static void invoke(thread_db* tdbb, const Function* function, const Parameter* return_ptr, @@ -436,8 +437,7 @@ void FUN_evaluate(thread_db* tdbb, const Function* function, const NestValueArra temp_desc = parameter->prm_desc; temp_desc.dsc_address = temp_ptr; - // CVC: There's a theoretical possibility of overflowing "length" here. - USHORT length = FB_ALIGN(temp_desc.dsc_length, FB_DOUBLE_ALIGN); + ULONG length = FB_ALIGN(temp_desc.dsc_length, FB_DOUBLE_ALIGN); // If we've got a null argument, just pass zeros (got any better ideas?) @@ -446,7 +446,7 @@ void FUN_evaluate(thread_db* tdbb, const Function* function, const NestValueArra if (parameter->prm_fun_mechanism == FUN_value) { UCHAR* p = (UCHAR *) arg_ptr; - MOVE_CLEAR(p, (SLONG) length); + MOVE_CLEAR(p, length); p += length; arg_ptr = reinterpret_cast(p); continue; @@ -458,7 +458,7 @@ void FUN_evaluate(thread_db* tdbb, const Function* function, const NestValueArra } if (parameter->prm_fun_mechanism != FUN_ref_with_null) - MOVE_CLEAR(temp_ptr, (SLONG) length); + MOVE_CLEAR(temp_ptr, length); else { // Probably for arrays and blobs it's better to preserve the @@ -468,7 +468,7 @@ void FUN_evaluate(thread_db* tdbb, const Function* function, const NestValueArra case dtype_quad: case dtype_array: case dtype_blob: - MOVE_CLEAR(temp_ptr, (SLONG) length); + MOVE_CLEAR(temp_ptr, length); break; default: // FUN_ref_with_null, non-blob, non-array: we send null pointer. *arg_ptr++ = 0; @@ -478,7 +478,8 @@ void FUN_evaluate(thread_db* tdbb, const Function* function, const NestValueArra } else if (parameter->prm_fun_mechanism == FUN_scalar_array) { - length = get_scalar_array(parameter, input, (scalar_array_desc*)temp_ptr, array_stack); + length = get_scalar_array(tdbb, parameter, input, (scalar_array_desc*) temp_ptr, + array_stack); } else { @@ -975,10 +976,11 @@ static SSHORT blob_get_segment(blb* blob, UCHAR* buffer, USHORT length, USHORT* } -static SLONG get_scalar_array(const Parameter* arg, - DSC* value, - scalar_array_desc* scalar_desc, - UCharStack& stack) +static ULONG get_scalar_array(thread_db* tdbb, + const Parameter* arg, + DSC* value, + scalar_array_desc* scalar_desc, + UCharStack& stack) { /************************************** * @@ -992,17 +994,16 @@ static SLONG get_scalar_array(const Parameter* arg, * Return length of array desc. * **************************************/ - thread_db* tdbb = JRD_get_thread_data(); + MemoryPool& pool = *tdbb->getDefaultPool(); // Get first the array descriptor, then the array SLONG stuff[IAD_LEN(16) / 4]; Ods::InternalArrayDesc* array_desc = (Ods::InternalArrayDesc*) stuff; - blb* blob = blb::get_array(tdbb, tdbb->getRequest()->req_transaction, (bid*)value->dsc_address, - array_desc); + blb* blob = blb::get_array(tdbb, tdbb->getRequest()->req_transaction, + (bid*) value->dsc_address, array_desc); - fb_assert(array_desc->iad_total_length >= 0); // check before upcasting to size_t - UCHAR* data = FB_NEW_POOL(*getDefaultMemoryPool()) UCHAR[static_cast(array_desc->iad_total_length)]; + AutoPtr data(FB_NEW_POOL(pool) UCHAR[array_desc->iad_total_length]); blob->BLB_get_data(tdbb, data, array_desc->iad_total_length); const USHORT dimensions = array_desc->iad_dimensions; @@ -1012,39 +1013,29 @@ static SLONG get_scalar_array(const Parameter* arg, dsc from = array_desc->iad_rpt[0].iad_desc; if (to.dsc_dtype != from.dsc_dtype || - to.dsc_scale != from.dsc_scale || to.dsc_length != from.dsc_length) + to.dsc_scale != from.dsc_scale || + to.dsc_length != from.dsc_length) { - SLONG n = array_desc->iad_count; - UCHAR* const temp = FB_NEW_POOL(*getDefaultMemoryPool()) UCHAR[static_cast(to.dsc_length * n)]; + ULONG n = array_desc->iad_count; + AutoPtr temp(FB_NEW_POOL(pool) UCHAR[to.dsc_length * n]); to.dsc_address = temp; from.dsc_address = data; - // This loop may call ERR_post indirectly. - try + for (; n; --n, to.dsc_address += to.dsc_length, + from.dsc_address += array_desc->iad_element_length) { - for (; n; --n, to.dsc_address += to.dsc_length, - from.dsc_address += array_desc->iad_element_length) - { - MOV_move(tdbb, &from, &to); - } - } - catch (const Exception&) - { - delete[] data; - delete[] temp; - throw; + MOV_move(tdbb, &from, &to); } - delete[] data; - data = temp; + data = temp.release(); } // Fill out the scalar array descriptor - stack.push(data); scalar_desc->sad_desc = arg->prm_desc; scalar_desc->sad_desc.dsc_address = data; scalar_desc->sad_dimensions = dimensions; + stack.push(data.release()); const Ods::InternalArrayDesc::iad_repeat* tail1 = array_desc->iad_rpt; scalar_array_desc::sad_repeat* tail2 = scalar_desc->sad_rpt; @@ -1055,7 +1046,8 @@ static SLONG get_scalar_array(const Parameter* arg, tail2->sad_lower = tail1->iad_lower; } - return static_cast(sizeof(scalar_array_desc) + (dimensions - 1u) * sizeof(scalar_array_desc::sad_repeat)); + return static_cast(sizeof(scalar_array_desc) + + (dimensions - 1u) * sizeof(scalar_array_desc::sad_repeat)); } From a2762554352b2102f3641f312717ca7c800286d3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 31 Oct 2023 20:13:38 +0000 Subject: [PATCH 27/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index fd30871623..e60f989d68 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:103 + FORMAL BUILD NUMBER:104 */ -#define PRODUCT_VER_STRING "6.0.0.103" -#define FILE_VER_STRING "WI-T6.0.0.103" -#define LICENSE_VER_STRING "WI-T6.0.0.103" -#define FILE_VER_NUMBER 6, 0, 0, 103 +#define PRODUCT_VER_STRING "6.0.0.104" +#define FILE_VER_STRING "WI-T6.0.0.104" +#define LICENSE_VER_STRING "WI-T6.0.0.104" +#define FILE_VER_NUMBER 6, 0, 0, 104 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "103" +#define FB_BUILD_NO "104" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 76eaeb374b..63a6b94739 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=103 +BuildNum=104 NowAt=`pwd` cd `dirname $0` From 32f7dfaf7130c3f78542ea8137d909a7a7526053 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Wed, 8 Nov 2023 22:03:43 -0300 Subject: [PATCH 28/31] Fix #7832 - Crash on "... RETURNING *" without INTO in PSQL. --- src/dsql/StmtNodes.cpp | 2 +- src/dsql/parse.y | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 84008b4c3c..75f7b8b125 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -10971,7 +10971,7 @@ static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, d auto inputFirst = input->first; - if (!inputFirst) + if (inputFirst->items.isEmpty()) { // Process RETURNING * inputFirst = FB_NEW_POOL(pool) ValueListNode(pool, 0u); diff --git a/src/dsql/parse.y b/src/dsql/parse.y index d8c6344d27..58df2a5cf0 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -6042,7 +6042,7 @@ query_spec rse->dsqlFirst = $2 ? $2->items[1] : NULL; rse->dsqlSkip = $2 ? $2->items[0] : NULL; rse->dsqlDistinct = $3; - rse->dsqlSelectList = $4; + rse->dsqlSelectList = $4->items.hasData() ? $4 : nullptr; rse->dsqlFrom = $5; rse->dsqlWhere = $6; rse->dsqlGroup = $7; @@ -6077,14 +6077,14 @@ skip_clause %type distinct_clause distinct_clause - : DISTINCT { $$ = newNode(0); } + : DISTINCT { $$ = newNode(0u); } | all_noise { $$ = NULL; } ; %type select_list select_list : select_items { $$ = $1; } - | '*' { $$ = NULL; } + | '*' { $$ = newNode(0u); } ; %type select_items From a30cb9566ddf86e41597b7c235bcc3560f572fc4 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Wed, 8 Nov 2023 21:44:42 +0300 Subject: [PATCH 29/31] Reworked PR #7426. This fixes the crash during second invocation of external trigger. Using a temporary vector is a bad idea because it owns the trigger object but external triggers store a back pointer to Jrd::Trigger, thus implying it being persistent. --- src/jrd/exe.cpp | 72 +++++++++++++++------------------------------ src/jrd/exe_proto.h | 2 +- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index 457a578bf1..a4c222c32c 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -590,58 +590,19 @@ void EXE_execute_db_triggers(thread_db* tdbb, jrd_tra* transaction, TriggerActio // Execute DDL triggers. void EXE_execute_ddl_triggers(thread_db* tdbb, jrd_tra* transaction, bool preTriggers, int action) { - Jrd::Attachment* attachment = tdbb->getAttachment(); + const auto attachment = tdbb->getAttachment(); - // Our caller verifies (ATT_no_db_triggers) if DDL triggers should not run. + // Our caller verifies (ATT_no_db_triggers) if DDL triggers should not run if (attachment->att_ddl_triggers) { - TrigVector triggers; - TrigVector* triggersPtr = &triggers; - HalfStaticArray cachedTriggers; + AutoSetRestore2 tempTrans(tdbb, + &thread_db::getTransaction, + &thread_db::setTransaction, + transaction); - for (auto& trigger : *attachment->att_ddl_triggers) - { - const auto type = trigger.type & ~TRIGGER_TYPE_MASK; - const bool preTrigger = ((type & 1) == 0); - - if ((type & (1LL << action)) && (preTriggers == preTrigger)) - { - triggers.add() = trigger; - cachedTriggers.add(&trigger); - } - } - - if (triggers.hasData()) - { - FbLocalStatus tempStatus; - - jrd_tra* const oldTransaction = tdbb->getTransaction(); - tdbb->setTransaction(transaction); - - try - { - EXE_execute_triggers(tdbb, &triggersPtr, NULL, NULL, TRIGGER_DDL, - preTriggers ? StmtNode::PRE_TRIG : StmtNode::POST_TRIG); - } - catch (const Exception& ex) - { - ex.stuffException(&tempStatus); - } - - tdbb->setTransaction(oldTransaction); - - // Triggers could be compiled inside EXE_execute_triggers(), - // so ensure the new pointers are copied back to the cache - fb_assert(triggers.getCount() == cachedTriggers.getCount()); - for (unsigned i = 0; i < triggers.getCount(); i++) - { - *cachedTriggers[i] = triggers[i]; - triggers[i].extTrigger = nullptr; // avoid deletion inside d'tor - } - - tempStatus.check(); - } + EXE_execute_triggers(tdbb, &attachment->att_ddl_triggers, NULL, NULL, TRIGGER_DDL, + preTriggers ? StmtNode::PRE_TRIG : StmtNode::POST_TRIG, action); } } @@ -1166,7 +1127,8 @@ void EXE_execute_triggers(thread_db* tdbb, record_param* old_rpb, record_param* new_rpb, TriggerAction trigger_action, - StmtNode::WhichTrigger which_trig) + StmtNode::WhichTrigger which_trig, + int ddl_action) { /************************************** * @@ -1219,6 +1181,20 @@ void EXE_execute_triggers(thread_db* tdbb, { for (TrigVector::iterator ptr = vector->begin(); ptr != vector->end(); ++ptr) { + if (trigger_action == TRIGGER_DDL && ddl_action) + { + // Skip triggers not matching our action + + fb_assert(which_trig == StmtNode::PRE_TRIG || which_trig == StmtNode::POST_TRIG); + const bool preTriggers = (which_trig == StmtNode::PRE_TRIG); + + const auto type = ptr->type & ~TRIGGER_TYPE_MASK; + const bool preTrigger = ((type & 1) == 0); + + if (!(type & (1LL << ddl_action)) || preTriggers != preTrigger) + continue; + } + ptr->compile(tdbb); trigger = ptr->statement->findRequest(tdbb); diff --git a/src/jrd/exe_proto.h b/src/jrd/exe_proto.h index 4a1b4d8332..39365aec0e 100644 --- a/src/jrd/exe_proto.h +++ b/src/jrd/exe_proto.h @@ -46,7 +46,7 @@ const Jrd::StmtNode* EXE_looper(Jrd::thread_db* tdbb, Jrd::Request* request, const Jrd::StmtNode* in_node); void EXE_execute_triggers(Jrd::thread_db*, Jrd::TrigVector**, Jrd::record_param*, Jrd::record_param*, - enum TriggerAction, Jrd::StmtNode::WhichTrigger); + enum TriggerAction, Jrd::StmtNode::WhichTrigger, int = 0); void EXE_receive(Jrd::thread_db*, Jrd::Request*, USHORT, ULONG, void*, bool = false); void EXE_release(Jrd::thread_db*, Jrd::Request*); From ca6f63261cb949ce476ae65b7e834f028332c022 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Thu, 9 Nov 2023 09:15:37 +0300 Subject: [PATCH 30/31] Add missing namespace --- src/common/classes/TriState.h | 2 ++ src/dsql/DdlNodes.h | 26 +++++++++++++------------- src/dsql/NodePrinter.h | 2 +- src/dsql/PackageNodes.h | 2 +- src/dsql/Parser.h | 6 +++--- src/dsql/StmtNodes.h | 16 ++++++++-------- src/dsql/parse.y | 2 +- src/jrd/Attachment.h | 2 +- src/jrd/Database.h | 2 +- src/jrd/RecordSourceNodes.h | 2 +- src/jrd/Relation.h | 4 ++-- src/jrd/jrd.h | 2 +- src/jrd/met_proto.h | 2 +- src/jrd/val.h | 1 - 14 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/common/classes/TriState.h b/src/common/classes/TriState.h index 08347d0aea..6c481dfc6e 100644 --- a/src/common/classes/TriState.h +++ b/src/common/classes/TriState.h @@ -29,6 +29,7 @@ #ifndef CLASSES_TRISTATE_H #define CLASSES_TRISTATE_H +namespace Firebird { class TriState { @@ -129,5 +130,6 @@ inline bool TriState::toggle() return true; } +} // namespace Firebird #endif // CLASSES_TRISTATE_H diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 2cdc276c25..aa8ca427c4 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -482,7 +482,7 @@ public: bool privateScope; bool preserveDefaults; SLONG udfReturnPos; - TriState ssDefiner; + Firebird::TriState ssDefiner; }; @@ -616,7 +616,7 @@ public: MetaName packageOwner; bool privateScope; bool preserveDefaults; - TriState ssDefiner; + Firebird::TriState ssDefiner; }; @@ -700,7 +700,7 @@ public: MetaName name; MetaName relationName; std::optional type; - TriState active; + Firebird::TriState active; std::optional position; NestConst external; Firebird::string source; @@ -990,7 +990,7 @@ public: NestConst setDefault; MetaName renameTo; Firebird::AutoPtr type; - TriState notNullFlag; // true = NOT NULL / false = NULL + Firebird::TriState notNullFlag; // true = NOT NULL / false = NULL }; @@ -1214,7 +1214,7 @@ public: MetaName identitySequence; std::optional identityType; std::optional collationId; - TriState notNullFlag; // true = NOT NULL / false = NULL + Firebird::TriState notNullFlag; // true = NOT NULL / false = NULL std::optional position; Firebird::string defaultSource; Firebird::ByteChunk defaultValue; @@ -1553,8 +1553,8 @@ public: NestConst dsqlNode; MetaName name; Firebird::Array > clauses; - TriState ssDefiner; - TriState replicationState; + Firebird::TriState ssDefiner; + Firebird::TriState replicationState; }; @@ -1726,9 +1726,9 @@ public: MetaName relation; Firebird::ObjectsArray columns; - TriState unique; - TriState descending; - TriState inactive; + Firebird::TriState unique; + Firebird::TriState descending; + Firebird::TriState inactive; SSHORT type; bid expressionBlr; bid expressionSource; @@ -2219,8 +2219,8 @@ public: Firebird::string* lastName; MetaName* plugin; Firebird::string* comment; - TriState adminRole; - TriState active; + Firebird::TriState adminRole; + Firebird::TriState active; Mode mode; void addProperty(MetaName* pr, Firebird::string* val = NULL) @@ -2461,7 +2461,7 @@ public: Firebird::Array > files; MetaName cryptPlugin; MetaName keyName; - TriState ssDefiner; + Firebird::TriState ssDefiner; Firebird::Array pubTables; }; diff --git a/src/dsql/NodePrinter.h b/src/dsql/NodePrinter.h index 271993d17e..4caa8fedc9 100644 --- a/src/dsql/NodePrinter.h +++ b/src/dsql/NodePrinter.h @@ -267,7 +267,7 @@ public: print(s, *array); } - void print(const Firebird::string& s, const TriState& triState) + void print(const Firebird::string& s, const Firebird::TriState& triState) { if (triState.isAssigned()) print(s, triState.asBool()); diff --git a/src/dsql/PackageNodes.h b/src/dsql/PackageNodes.h index 0e1ad83d2b..7c68ec011c 100644 --- a/src/dsql/PackageNodes.h +++ b/src/dsql/PackageNodes.h @@ -110,7 +110,7 @@ public: Firebird::Array* items; Firebird::SortedArray functionNames; Firebird::SortedArray procedureNames; - TriState ssDefiner; + Firebird::TriState ssDefiner; private: MetaName owner; diff --git a/src/dsql/Parser.h b/src/dsql/Parser.h index 229b8132f5..77e0b2ac04 100644 --- a/src/dsql/Parser.h +++ b/src/dsql/Parser.h @@ -261,13 +261,13 @@ private: } } - void setClause(TriState& clause, const char* duplicateMsg, bool value) + void setClause(Firebird::TriState& clause, const char* duplicateMsg, bool value) { checkDuplicateClause(clause, duplicateMsg); clause = value; } - void setClause(TriState& clause, const char* duplicateMsg, const TriState& value) + void setClause(Firebird::TriState& clause, const char* duplicateMsg, const Firebird::TriState& value) { if (value.isAssigned()) { @@ -322,7 +322,7 @@ private: return clause.hasData(); } - bool isDuplicateClause(const TriState& clause) + bool isDuplicateClause(const Firebird::TriState& clause) { return clause.isAssigned(); } diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 6976fd8103..fee771dfc8 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -1340,7 +1340,7 @@ public: public: NestConst selectExpr; NestConst rse; - TriState optimizeForFirstRows; + Firebird::TriState optimizeForFirstRows; bool forUpdate = false; bool withLock = false; bool skipLocked = false; @@ -1626,12 +1626,12 @@ public: std::optional atSnapshotNumber; std::optional isoLevel; std::optional lockTimeout; - TriState readOnly; - TriState wait; - TriState noAutoUndo; - TriState ignoreLimbo; - TriState restartRequests; - TriState autoCommit; + Firebird::TriState readOnly; + Firebird::TriState wait; + Firebird::TriState noAutoUndo; + Firebird::TriState ignoreLimbo; + Firebird::TriState restartRequests; + Firebird::TriState autoCommit; }; @@ -1901,7 +1901,7 @@ public: virtual void execute(thread_db* tdbb, DsqlRequest* request, jrd_tra** traHandle) const; public: - TriState optimizeMode; + Firebird::TriState optimizeMode; }; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 58df2a5cf0..a40f513fe3 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -736,7 +736,7 @@ using namespace Firebird; {} std::optional nullableIntVal; - TriState triState; + Firebird::TriState triState; std::optional nullableSqlSecurityVal; std::optional nullableOverrideClause; struct { bool first; bool second; } boolPair; diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index 29f3752f4f..1df3287a15 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -640,7 +640,7 @@ public: USHORT att_original_timezone; USHORT att_current_timezone; int att_parallel_workers; - TriState att_opt_first_rows; + Firebird::TriState att_opt_first_rows; PageToBufferMap* att_bdb_cache; // managed in CCH, created in att_pool, freed with it diff --git a/src/jrd/Database.h b/src/jrd/Database.h index 1f7d5073dc..ae780fa56b 100644 --- a/src/jrd/Database.h +++ b/src/jrd/Database.h @@ -532,7 +532,7 @@ public: time_t dbb_linger_end; Firebird::RefPtr dbb_plugin_config; - TriState dbb_repl_state; // replication state + Firebird::TriState dbb_repl_state; // replication state Lock* dbb_repl_lock; // replication state lock Firebird::SyncObject dbb_repl_sync; FB_UINT64 dbb_repl_sequence; // replication sequence diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index b64b3afcbb..65ee4ed7e0 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -878,7 +878,7 @@ public: Firebird::Array > rse_relations; USHORT flags = 0; USHORT rse_jointype = blr_inner; // inner, left, full - TriState firstRows; // optimize for first rows + Firebird::TriState firstRows; // optimize for first rows }; class SelectExprNode final : public TypedNode diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index ab721d5bc2..5445b60c4e 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -264,9 +264,9 @@ public: TrigVector* rel_post_store; // Post-operation store trigger prim rel_primary_dpnds; // foreign dependencies on this relation's primary key frgn rel_foreign_refs; // foreign references to other relations' primary keys - TriState rel_ss_definer; - TriState rel_repl_state; // replication state + Firebird::TriState rel_ss_definer; + Firebird::TriState rel_repl_state; // replication state Firebird::Mutex rel_drop_mutex; diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index d5a17f3558..250239e0cf 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -148,7 +148,7 @@ public: Firebird::string entryPoint; // External trigger entrypoint Firebird::string extBody; // External trigger body ExtEngineManager::Trigger* extTrigger; // External trigger - TriState ssDefiner; + Firebird::TriState ssDefiner; MetaName owner; // Owner for SQL SECURITY bool isActive() const; diff --git a/src/jrd/met_proto.h b/src/jrd/met_proto.h index 6e4b984912..35534208d2 100644 --- a/src/jrd/met_proto.h +++ b/src/jrd/met_proto.h @@ -148,6 +148,6 @@ void MET_store_dependencies(Jrd::thread_db*, Firebird::Array Date: Thu, 9 Nov 2023 20:14:23 +0000 Subject: [PATCH 31/31] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index e60f989d68..f63debacca 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:104 + FORMAL BUILD NUMBER:107 */ -#define PRODUCT_VER_STRING "6.0.0.104" -#define FILE_VER_STRING "WI-T6.0.0.104" -#define LICENSE_VER_STRING "WI-T6.0.0.104" -#define FILE_VER_NUMBER 6, 0, 0, 104 +#define PRODUCT_VER_STRING "6.0.0.107" +#define FILE_VER_STRING "WI-T6.0.0.107" +#define LICENSE_VER_STRING "WI-T6.0.0.107" +#define FILE_VER_NUMBER 6, 0, 0, 107 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "104" +#define FB_BUILD_NO "107" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 63a6b94739..e2459026b2 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=104 +BuildNum=107 NowAt=`pwd` cd `dirname $0`