diff --git a/doc/sql.extensions/README.time_zone.md b/doc/sql.extensions/README.time_zone.md index 599f5f4b49..3cb9674aff 100644 --- a/doc/sql.extensions/README.time_zone.md +++ b/doc/sql.extensions/README.time_zone.md @@ -221,11 +221,9 @@ select localtimestamp from rdb$database; ``` -# TODO: CURRENT_TIME and CURRENT_TIMESTAMP changes +## TODO: CURRENT_TIME and CURRENT_TIMESTAMP changes -## Virtual tables - -### `RDB$TIME_ZONES` table +## Virtual table `RDB$TIME_ZONES` This virtual table lists time zones supported in the engine. @@ -233,6 +231,64 @@ Columns: - `RDB$TIME_ZONE_ID` type `INTEGER` - `RDB$TIME_ZONE_NAME` type `CHAR(63)` +## Package `RDB$TIME_ZONE_UTIL` + +This package has time zone utility functions and procedures. + +### Function `DATABASE_VERSION` + +`RDB$TIME_ZONE_UTIL.DATABASE_VERSION` returns the time zone database version. + +Return type: `VARCHAR(10) CHARACTER SET ASCII` + +``` +select rdb$time_zone_util.database_version() + from rdb$database; +``` + +Returns: +``` +DATABASE_VERSION +================ +2017c +``` + +### Procedure `TRANSITIONS` + +`RDB$TIME_ZONE_UTIL.TRANSITIONS` returns the set of rules between the start and end timestamps. + +Input parameters: + - `TIME_ZONE_NAME` type `CHAR(63)` + - `FROM_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` + - `TO_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` + + Output parameters: + - `START_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition' start timestamp + - `END_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition's end timestamp + - `ZONE_OFFSET` type `SMALLINT` - number of minutes related to the zone's offset + - `DST_OFFSET` type `SMALLINT` - number of minutes related to the zone's DST offset + - `EFFECTIVE_OFFSET` type `SMALLINT` - effective offset (`ZONE_OFFSET + DST_OFFSET`) + +``` +select * + from rdb$time_zone_util.transitions( + 'America/Sao_Paulo', + timestamp '2017-01-01', + timestamp '2019-01-01'); +``` + +Returns: + +``` + START_TIMESTAMP END_TIMESTAMP ZONE_OFFSET DST_OFFSET EFFECTIVE_OFFSET +============================ ============================ =========== ========== ================ +2016-10-16 03:00:00.0000 GMT 2017-02-19 01:59:59.9999 GMT -180 60 -120 +2017-02-19 02:00:00.0000 GMT 2017-10-15 02:59:59.9999 GMT -180 0 -180 +2017-10-15 03:00:00.0000 GMT 2018-02-18 01:59:59.9999 GMT -180 60 -120 +2018-02-18 02:00:00.0000 GMT 2018-10-21 02:59:59.9999 GMT -180 0 -180 +2018-10-21 03:00:00.0000 GMT 2019-02-17 01:59:59.9999 GMT -180 60 -120 +``` + # Appendix: time zone regions diff --git a/src/common/TimeZoneUtil.cpp b/src/common/TimeZoneUtil.cpp index 767ff5524f..d5c2f1158a 100644 --- a/src/common/TimeZoneUtil.cpp +++ b/src/common/TimeZoneUtil.cpp @@ -33,7 +33,6 @@ #include "firebird.h" #include "../common/TimeZoneUtil.h" #include "../common/StatusHolder.h" -#include "../common/unicode_util.h" #include "../common/classes/timestamp.h" #include "../common/classes/GenericMap.h" #include "unicode/ucal.h" @@ -62,7 +61,6 @@ namespace static const TimeZoneDesc* getDesc(USHORT timeZone); static inline bool isOffset(USHORT timeZone); static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm); -static USHORT makeFromRegion(const char* str, unsigned strLen); static inline SSHORT offsetZoneToDisplacement(USHORT timeZone); static int parseNumber(const char*& p, const char* end); static void skipSpaces(const char*& p, const char* end); @@ -249,7 +247,20 @@ USHORT TimeZoneUtil::getSystemTimeZone() return timeZoneStartup().systemTimeZone; } -void TimeZoneUtil::iterateRegions(std::function func) +void TimeZoneUtil::getDatabaseVersion(Firebird::string& str) +{ + Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); + UErrorCode icuErrorCode = U_ZERO_ERROR; + + const char* version = icuLib.ucalGetTZDataVersion(&icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_getTZDataVersion."); + + str = version; +} + +void TimeZoneUtil::iterateRegions(std::function func) { for (USHORT i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) func(MAX_USHORT - i, TIME_ZONE_LIST[i].asciiName); @@ -295,7 +306,45 @@ USHORT TimeZoneUtil::parse(const char* str, unsigned strLen) return makeFromOffset(sign, tzh, tzm); } else - return makeFromRegion(p, str + strLen - p); + return parseRegion(p, str + strLen - p); +} + +// Parses a time zone id from a region string. +USHORT TimeZoneUtil::parseRegion(const char* str, unsigned strLen) +{ + const char* end = str + strLen; + + skipSpaces(str, end); + + const char* start = str; + + while (str < end && + ((*str >= 'a' && *str <= 'z') || + (*str >= 'A' && *str <= 'Z') || + *str == '_' || + *str == '/') || + (str != start && *str >= '0' && *str <= '9') || + (str != start && *str == '+') || + (str != start && *str == '-')) + { + ++str; + } + + unsigned len = str - start; + + skipSpaces(str, end); + + if (str == end) + { + string s(start, len); + USHORT id; + + if (timeZoneStartup().getId(s, id)) + return id; + } + + status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone region"); //// TODO: + return 0; } // Format a time zone to string, as offset or region. @@ -353,7 +402,7 @@ void TimeZoneUtil::extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, int* sign, SINT64 ticks = timeStampTz.utc_timestamp.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + timeStampTz.utc_timestamp.timestamp_time; - icuLib.ucalSetMillis(icuCalendar, (ticks - (40587 * TimeStamp::ISC_TICKS_PER_DAY)) / 10, &icuErrorCode); + icuLib.ucalSetMillis(icuCalendar, ticksToIcuDate(ticks), &icuErrorCode); if (U_FAILURE(icuErrorCode)) { @@ -521,7 +570,7 @@ void TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, struct t if (!icuCalendar) status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_open."); - icuLib.ucalSetMillis(icuCalendar, (ticks - (40587 * TimeStamp::ISC_TICKS_PER_DAY)) / 10, &icuErrorCode); + icuLib.ucalSetMillis(icuCalendar, ticksToIcuDate(ticks), &icuErrorCode); if (U_FAILURE(icuErrorCode)) { @@ -685,6 +734,103 @@ ISC_TIMESTAMP_TZ TimeZoneUtil::cvtDateToTimeStampTz(const ISC_DATE& date, Callba //------------------------------------- +TimeZoneRuleIterator::TimeZoneRuleIterator(USHORT aId, ISC_TIMESTAMP_TZ& aFrom, ISC_TIMESTAMP_TZ& aTo) + : id(aId), + icuLib(Jrd::UnicodeUtil::getConversionICU()), + toTicks(aTo.utc_timestamp.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + aTo.utc_timestamp.timestamp_time) +{ + UErrorCode icuErrorCode = U_ZERO_ERROR; + + icuCalendar = icuLib.ucalOpen(getDesc(id)->icuName, -1, NULL, UCAL_GREGORIAN, &icuErrorCode); + + if (!icuCalendar) + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_open."); + + SINT64 ticks = aFrom.utc_timestamp.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + + aFrom.utc_timestamp.timestamp_time; + + icuDate = TimeZoneUtil::ticksToIcuDate(ticks); + + icuLib.ucalSetMillis(icuCalendar, icuDate, &icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + { + fb_assert(false); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_setMillis."); + } + + UBool hasNext = icuLib.ucalGetTimeZoneTransitionDate(icuCalendar, UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE, + &icuDate, &icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + { + fb_assert(false); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_getTimeZoneTransitionDate."); + } + + if (!hasNext) + icuDate = TimeZoneUtil::ticksToIcuDate(TimeStamp::MIN_DATE * TimeStamp::ISC_TICKS_PER_DAY); + + icuLib.ucalSetMillis(icuCalendar, icuDate, &icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + { + fb_assert(false); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_setMillis."); + } + + startTicks = TimeZoneUtil::icuDateToTicks(icuDate); +} + +TimeZoneRuleIterator::~TimeZoneRuleIterator() +{ + icuLib.ucalClose(icuCalendar); +} + +bool TimeZoneRuleIterator::next() +{ + if (startTicks > toTicks) + return false; + + UErrorCode icuErrorCode = U_ZERO_ERROR; + + startTimestamp.utc_timestamp.timestamp_date = startTicks / TimeStamp::ISC_TICKS_PER_DAY; + startTimestamp.utc_timestamp.timestamp_time = startTicks % TimeStamp::ISC_TICKS_PER_DAY; + startTimestamp.time_zone = TimeZoneUtil::GMT_ZONE; + + zoneOffset = icuLib.ucalGet(icuCalendar, UCAL_ZONE_OFFSET, &icuErrorCode) / U_MILLIS_PER_MINUTE; + dstOffset = icuLib.ucalGet(icuCalendar, UCAL_DST_OFFSET, &icuErrorCode) / U_MILLIS_PER_MINUTE; + + UBool hasNext = icuLib.ucalGetTimeZoneTransitionDate(icuCalendar, UCAL_TZ_TRANSITION_NEXT, + &icuDate, &icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + { + fb_assert(false); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_getTimeZoneTransitionDate."); + } + + if (!hasNext) + { + icuDate = TimeZoneUtil::ticksToIcuDate( + TimeStamp::MAX_DATE * TimeStamp::ISC_TICKS_PER_DAY + TimeStamp::ISC_TICKS_PER_DAY); + } + + icuLib.ucalSetMillis(icuCalendar, icuDate, &icuErrorCode); + + SINT64 endTicks = TimeZoneUtil::icuDateToTicks(icuDate) - 1; + + endTimestamp.utc_timestamp.timestamp_date = endTicks / TimeStamp::ISC_TICKS_PER_DAY; + endTimestamp.utc_timestamp.timestamp_time = endTicks % TimeStamp::ISC_TICKS_PER_DAY; + endTimestamp.time_zone = TimeZoneUtil::GMT_ZONE; + + startTicks = endTicks + 1; + + return true; +} + +//------------------------------------- + static const TimeZoneDesc* getDesc(USHORT timeZone) { if (MAX_USHORT - timeZone < FB_NELEM(TIME_ZONE_LIST)) @@ -709,44 +855,6 @@ static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm) return (USHORT)((tzh * 60 + tzm) * sign + TimeZoneUtil::ONE_DAY); } -// Makes a time zone id from a region. -static USHORT makeFromRegion(const char* str, unsigned strLen) -{ - const char* end = str + strLen; - - skipSpaces(str, end); - - const char* start = str; - - while (str < end && - ((*str >= 'a' && *str <= 'z') || - (*str >= 'A' && *str <= 'Z') || - *str == '_' || - *str == '/') || - (str != start && *str >= '0' && *str <= '9') || - (str != start && *str == '+') || - (str != start && *str == '-')) - { - ++str; - } - - unsigned len = str - start; - - skipSpaces(str, end); - - if (str == end) - { - string s(start, len); - USHORT id; - - if (timeZoneStartup().getId(s, id)) - return id; - } - - status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone region"); //// TODO: - return 0; -} - // 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 56150e3564..b7b34f997c 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -29,7 +29,9 @@ #include #include "../common/classes/fb_string.h" +#include "../common/classes/timestamp.h" #include "../common/cvt.h" +#include "../common/unicode_util.h" // struct tm declaration #if defined(TIME_WITH_SYS_TIME) @@ -57,11 +59,25 @@ public: static const unsigned MAX_SIZE = MAX_LEN + 1; public: + static UDate ticksToIcuDate(SINT64 ticks) + { + return (ticks - (40587 * TimeStamp::ISC_TICKS_PER_DAY)) / 10; + } + + static SINT64 icuDateToTicks(UDate icuDate) + { + return (SINT64(icuDate) * 10) + (40587 * TimeStamp::ISC_TICKS_PER_DAY); + } + static USHORT getSystemTimeZone(); - static void iterateRegions(std::function func); + static void getDatabaseVersion(Firebird::string& str); + + static void iterateRegions(std::function func); static USHORT parse(const char* str, unsigned strLen); + static USHORT parseRegion(const char* str, unsigned strLen); + static unsigned format(char* buffer, size_t bufferSize, USHORT timeZone); static bool isValidOffset(int sign, unsigned tzh, unsigned tzm); @@ -100,6 +116,30 @@ public: static ISC_TIMESTAMP_TZ cvtDateToTimeStampTz(const ISC_DATE& date, Callbacks* cb); }; +class TimeZoneRuleIterator +{ +public: + TimeZoneRuleIterator(USHORT aId, ISC_TIMESTAMP_TZ& aFrom, ISC_TIMESTAMP_TZ& aTo); + ~TimeZoneRuleIterator(); + +public: + bool next(); + +public: + ISC_TIMESTAMP_TZ startTimestamp; + ISC_TIMESTAMP_TZ endTimestamp; + SSHORT zoneOffset; + SSHORT dstOffset; + +private: + const USHORT id; + Jrd::UnicodeUtil::ConversionICU& icuLib; + SINT64 startTicks; + SINT64 toTicks; + UCalendar* icuCalendar; + UDate icuDate; +}; + } // namespace Firebird #endif // COMMON_TIME_ZONE_UTIL_H diff --git a/src/common/unicode_util.cpp b/src/common/unicode_util.cpp index d46e7ad4cb..4b3e5b5bae 100644 --- a/src/common/unicode_util.cpp +++ b/src/common/unicode_util.cpp @@ -284,6 +284,7 @@ private: if (!inModule) return; + getEntryPoint("ucal_getTZDataVersion", inModule, ucalGetTZDataVersion); getEntryPoint("ucal_open", inModule, ucalOpen); getEntryPoint("ucal_close", inModule, ucalClose); getEntryPoint("ucal_setMillis", inModule, ucalSetMillis); @@ -291,6 +292,9 @@ private: getEntryPoint("ucal_setDateTime", inModule, ucalSetDateTime); getEntryPoint("ucal_getTimeZoneID", inModule, ucalGetTimeZoneID); + getEntryPoint("ucal_getNow", inModule, ucalGetNow); + getEntryPoint("ucal_getTimeZoneTransitionDate", inModule, ucalGetTimeZoneTransitionDate); + #ifdef DEV_BUILD getEntryPoint("ucal_openTimeZones", inModule, ucalOpenTimeZones); diff --git a/src/common/unicode_util.h b/src/common/unicode_util.h index 8e332b27a6..2bc4fd1042 100644 --- a/src/common/unicode_util.h +++ b/src/common/unicode_util.h @@ -123,6 +123,7 @@ public: int32_t (U_EXPORT2* ustrcmp) (const UChar* s1, const UChar* s2); + const char* (U_EXPORT2* ucalGetTZDataVersion) (UErrorCode* status); UCalendar* (U_EXPORT2* ucalOpen) (const UChar* zoneID, int32_t len, const char* locale, UCalendarType type, UErrorCode* err); void (U_EXPORT2* ucalClose) (UCalendar* cal); @@ -133,6 +134,10 @@ public: int32_t (U_EXPORT2* ucalGetTimeZoneID) (const UCalendar* cal, UChar* result, int32_t resultLength, UErrorCode *status); + UDate (U_EXPORT2* ucalGetNow) (); + UBool (U_EXPORT2* ucalGetTimeZoneTransitionDate) (const UCalendar* cal, UTimeZoneTransitionType type, + UDate* transition, UErrorCode* status); + #ifdef DEV_BUILD UEnumeration* (U_EXPORT2* ucalOpenTimeZones) (UErrorCode* ec); diff --git a/src/include/firebird/Message.h b/src/include/firebird/Message.h index 5b01be782d..5899663dc8 100644 --- a/src/include/firebird/Message.h +++ b/src/include/firebird/Message.h @@ -290,6 +290,23 @@ public: value = util->encodeDate(year, month, day); } +public: + FbDate& operator=(ISC_DATE& val) + { + *(this) = *(FbDate*) &val; + return *this; + } + + operator ISC_DATE&() + { + return *(ISC_DATE*) this; + } + + operator const ISC_DATE&() const + { + return *(ISC_DATE*) this; + } + public: ISC_DATE value; }; @@ -337,6 +354,23 @@ public: value = util->encodeTime(hours, minutes, seconds, fractions); } +public: + FbTime& operator=(ISC_TIME& val) + { + *(this) = *(FbTime*) &val; + return *this; + } + + operator ISC_TIME&() + { + return *(ISC_TIME*) this; + } + + operator const ISC_TIME&() const + { + return *(ISC_TIME*) this; + } + public: ISC_TIME value; }; @@ -344,6 +378,23 @@ public: // This class has memory layout identical to ISC_TIME_TZ. class FbTimeTz { +public: + FbTimeTz& operator=(ISC_TIME_TZ& val) + { + *(this) = *(FbTimeTz*) &val; + return *this; + } + + operator ISC_TIME_TZ&() + { + return *(ISC_TIME_TZ*) this; + } + + operator const ISC_TIME_TZ&() const + { + return *(ISC_TIME_TZ*) this; + } + public: FbTime utcTime; ISC_USHORT timeZone; @@ -352,6 +403,23 @@ public: // This class has memory layout identical to ISC_TIMESTAMP. class FbTimestamp { +public: + FbTimestamp& operator=(ISC_TIMESTAMP& val) + { + *(this) = *(FbTimestamp*) &val; + return *this; + } + + operator ISC_TIMESTAMP&() + { + return *(ISC_TIMESTAMP*) this; + } + + operator const ISC_TIMESTAMP&() const + { + return *(ISC_TIMESTAMP*) this; + } + public: FbDate date; FbTime time; @@ -360,6 +428,23 @@ public: // This class has memory layout identical to ISC_TIMESTAMP_TZ. class FbTimestampTz { +public: + FbTimestampTz& operator=(ISC_TIMESTAMP_TZ& val) + { + *(this) = *(FbTimestampTz*) &val; + return *this; + } + + operator ISC_TIMESTAMP_TZ&() + { + return *(ISC_TIMESTAMP_TZ*) this; + } + + operator const ISC_TIMESTAMP_TZ&() const + { + return *(ISC_TIMESTAMP_TZ*) this; + } + public: FbTimestamp utcTimestamp; ISC_USHORT timeZone; diff --git a/src/jrd/ExtEngineManager.cpp b/src/jrd/ExtEngineManager.cpp index 88cff93fb2..c17af29431 100644 --- a/src/jrd/ExtEngineManager.cpp +++ b/src/jrd/ExtEngineManager.cpp @@ -43,6 +43,8 @@ #include "../jrd/mov_proto.h" #include "../jrd/par_proto.h" #include "../jrd/Function.h" +#include "../jrd/TimeZone.h" +#include "../jrd/SystemPackages.h" #include "../common/isc_proto.h" #include "../common/classes/auto.h" #include "../common/classes/fb_string.h" @@ -1016,8 +1018,114 @@ unloaded only on program exit, causing at that moment AV if this code is active: //--------------------- +namespace +{ + class SystemEngine : public StdPlugin > + { + public: + explicit SystemEngine() + { + } + + int release() override + { + if (--refCounter == 0) + { + delete this; + return 0; + } + + return 1; + } + + public: + void open(ThrowStatusExceptionWrapper* status, IExternalContext* context, + char* name, unsigned nameSize) override + { + } + + void openAttachment(ThrowStatusExceptionWrapper* status, IExternalContext* context) override + { + } + + void closeAttachment(ThrowStatusExceptionWrapper* status, IExternalContext* context) override + { + } + + IExternalFunction* makeFunction(ThrowStatusExceptionWrapper* status, IExternalContext* context, + IRoutineMetadata* metadata, IMetadataBuilder* inBuilder, IMetadataBuilder* outBuilder) override + { + const char* packageName = metadata->getPackage(status); + const char* routineName = metadata->getName(status); + + for (auto& package : SystemPackage::LIST) + { + if (strcmp(package.name, packageName) == 0) + { + for (auto& routine : package.functions) + { + if (strcmp(routine.name, routineName) == 0) + return routine.factory(status, context, metadata, inBuilder, outBuilder); + } + } + } + + fb_assert(false); + return nullptr; + } + + IExternalProcedure* makeProcedure(ThrowStatusExceptionWrapper* status, IExternalContext* context, + IRoutineMetadata* metadata, IMetadataBuilder* inBuilder, IMetadataBuilder* outBuilder) override + { + const char* packageName = metadata->getPackage(status); + const char* routineName = metadata->getName(status); + + for (auto& package : SystemPackage::LIST) + { + if (strcmp(package.name, packageName) == 0) + { + for (auto& routine : package.procedures) + { + if (strcmp(routine.name, routineName) == 0) + return routine.factory(status, context, metadata, inBuilder, outBuilder); + } + } + } + + fb_assert(false); + return nullptr; + } + + IExternalTrigger* makeTrigger(ThrowStatusExceptionWrapper* status, IExternalContext* context, + IRoutineMetadata* metadata, IMetadataBuilder* fieldsBuilder) override + { + fb_assert(false); + return nullptr; + } + + public: + static SystemEngine* INSTANCE; + }; + + SystemEngine* SystemEngine::INSTANCE = nullptr; +} + + +//--------------------- + + void ExtEngineManager::initialize() { + SystemEngine::INSTANCE = FB_NEW SystemEngine(); +} + + +ExtEngineManager::ExtEngineManager(MemoryPool& p) + : PermanentStorage(p), + engines(p), + enginesAttachments(p) +{ + engines.put("SYSTEM", SystemEngine::INSTANCE); } diff --git a/src/jrd/ExtEngineManager.h b/src/jrd/ExtEngineManager.h index dabf651321..f181148357 100644 --- a/src/jrd/ExtEngineManager.h +++ b/src/jrd/ExtEngineManager.h @@ -294,13 +294,7 @@ public: }; public: - explicit ExtEngineManager(MemoryPool& p) - : PermanentStorage(p), - engines(p), - enginesAttachments(p) - { - } - + explicit ExtEngineManager(MemoryPool& p); ~ExtEngineManager(); public: diff --git a/src/jrd/Monitoring.cpp b/src/jrd/Monitoring.cpp index 79147c5efb..58a6641925 100644 --- a/src/jrd/Monitoring.cpp +++ b/src/jrd/Monitoring.cpp @@ -661,6 +661,16 @@ void SnapshotData::putField(thread_db* tdbb, Record* record, const DumpField& fi from_desc.makeTimestamp(&value); MOV_move(tdbb, &from_desc, &to_desc); } + else if (field.type == VALUE_TIMESTAMP_TZ) + { + fb_assert(field.length == sizeof(ISC_TIMESTAMP_TZ)); + ISC_TIMESTAMP_TZ value; + memcpy(&value, field.data, field.length); + + dsc from_desc; + from_desc.makeTimestampTz(&value); + MOV_move(tdbb, &from_desc, &to_desc); + } else if (field.type == VALUE_STRING) { if (to_desc.isBlob()) diff --git a/src/jrd/Monitoring.h b/src/jrd/Monitoring.h index 3642054fc2..1e23f85d3c 100644 --- a/src/jrd/Monitoring.h +++ b/src/jrd/Monitoring.h @@ -56,6 +56,7 @@ public: VALUE_TABLE_ID, VALUE_INTEGER, VALUE_TIMESTAMP, + VALUE_TIMESTAMP_TZ, VALUE_STRING, VALUE_BOOLEAN }; @@ -142,6 +143,11 @@ public: storeField(field_id, VALUE_TIMESTAMP, sizeof(ISC_TIMESTAMP), &value); } + void storeTimestampTz(int field_id, const ISC_TIMESTAMP_TZ& value) + { + storeField(field_id, VALUE_TIMESTAMP_TZ, sizeof(ISC_TIMESTAMP), &value); + } + void storeString(int field_id, const Firebird::string& value) { if (value.length()) diff --git a/src/jrd/SystemPackages.cpp b/src/jrd/SystemPackages.cpp new file mode 100644 index 0000000000..251607e3e7 --- /dev/null +++ b/src/jrd/SystemPackages.cpp @@ -0,0 +1,80 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2018 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/SystemPackages.h" +#include "../jrd/ods.h" +#include "../jrd/ini.h" +#include "../jrd/TimeZone.h" + +using namespace Firebird; +using namespace Jrd; + + +std::initializer_list SystemPackage::LIST = +{ + // packages + { + "RDB$TIME_ZONE_UTIL", + ODS_13_0, + { + // procedures + { + "TRANSITIONS", + [] + (ThrowStatusExceptionWrapper* status, IExternalContext* /*context*/, + IRoutineMetadata* /*metadata*/, IMetadataBuilder* inBuilder, IMetadataBuilder* outBuilder) + { + return FB_NEW TimeZoneTransitionsProcedure(status, inBuilder, outBuilder); + }, + prc_selectable, + { // input parameters + {"TIME_ZONE_NAME", fld_tz_name, false}, + {"FROM_TIMESTAMP", fld_timestamp_tz, false}, + {"TO_TIMESTAMP", fld_timestamp_tz, false} + }, + { // output parameters + {"START_TIMESTAMP", fld_timestamp_tz, false}, + {"END_TIMESTAMP", fld_timestamp_tz, false}, + {"ZONE_OFFSET", fld_tz_offset, false}, + {"DST_OFFSET", fld_tz_offset, false}, + {"EFFECTIVE_OFFSET", fld_tz_offset, false} + } + }, + }, + { + // functions + { + "DATABASE_VERSION", + [] + (ThrowStatusExceptionWrapper* status, IExternalContext* /*context*/, + IRoutineMetadata* /*metadata*/, IMetadataBuilder* inBuilder, IMetadataBuilder* outBuilder) + { + return FB_NEW TimeZoneDatabaseVersionFunction(status, inBuilder, outBuilder); + }, + { // parameters + }, + {fld_tz_db_version, false} + } + } + } +}; diff --git a/src/jrd/SystemPackages.h b/src/jrd/SystemPackages.h new file mode 100644 index 0000000000..9cdfc1b97d --- /dev/null +++ b/src/jrd/SystemPackages.h @@ -0,0 +1,95 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2018 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_SYSTEM_PACKAGES_H +#define JRD_SYSTEM_PACKAGES_H + +#include "firebird.h" +#include "../common/status.h" +#include "../jrd/constants.h" +#include "firebird/Interface.h" +#include +#include + +namespace Jrd +{ + struct SystemProcedureParameter + { + const char* name; + USHORT fieldId; + bool nullable; + }; + + struct SystemProcedure + { + const char* name; + std::function factory; + prc_t type; + std::initializer_list inputParameters; + std::initializer_list outputParameters; + }; + + struct SystemFunctionParameter + { + const char* name; + USHORT fieldId; + bool nullable; + }; + + struct SystemFunctionReturnType + { + USHORT fieldId; + bool nullable; + }; + + struct SystemFunction + { + const char* name; + std::function factory; + std::initializer_list parameters; + SystemFunctionReturnType returnType; + }; + + struct SystemPackage + { + const char* name; + USHORT odsVersion; + std::initializer_list procedures; + std::initializer_list functions; + + static std::initializer_list LIST; + }; +} // namespace Jrd + +#endif // JRD_SYSTEM_PACKAGES_H diff --git a/src/jrd/TimeZone.cpp b/src/jrd/TimeZone.cpp index 4ab46eeb38..d89c092473 100644 --- a/src/jrd/TimeZone.cpp +++ b/src/jrd/TimeZone.cpp @@ -34,27 +34,28 @@ using namespace Firebird; TimeZoneSnapshot::TimeZoneSnapshot(thread_db* tdbb, MemoryPool& pool) : SnapshotData(pool) { - RecordBuffer* buffer = allocBuffer(tdbb, pool, rel_time_zones); - - Record* record = buffer->getTempRecord(); - record->nullify(); + RecordBuffer* tzBuffer = allocBuffer(tdbb, pool, rel_time_zones); + Record* tzRecord = tzBuffer->getTempRecord(); + tzRecord->nullify(); TimeZoneUtil::iterateRegions( [=] (USHORT id, const char* name) { SINT64 idValue = id; - putField(tdbb, record, DumpField(f_tz_id, VALUE_INTEGER, sizeof(idValue), &idValue)); - putField(tdbb, record, DumpField(f_tz_name, VALUE_STRING, static_cast(strlen(name)), name)); - - buffer->store(record); + putField(tdbb, tzRecord, DumpField(f_tz_id, VALUE_INTEGER, sizeof(idValue), &idValue)); + putField(tdbb, tzRecord, DumpField(f_tz_name, VALUE_STRING, static_cast(strlen(name)), name)); + tzBuffer->store(tzRecord); } ); } -TimeZonesTableScan::TimeZonesTableScan(CompilerScratch* csb, const Firebird::string& alias, +//-------------------------------------- + + +TimeZonesTableScan::TimeZonesTableScan(CompilerScratch* csb, const string& alias, StreamType stream, jrd_rel* relation) : VirtualTableScan(csb, alias, stream, relation) { @@ -66,8 +67,59 @@ const Format* TimeZonesTableScan::getFormat(thread_db* tdbb, jrd_rel* relation) } +//-------------------------------------- + + bool TimeZonesTableScan::retrieveRecord(thread_db* tdbb, jrd_rel* relation, FB_UINT64 position, Record* record) const { return tdbb->getTransaction()->getTimeZoneSnapshot(tdbb)->getData(relation)->fetch(position, record); } + + +//-------------------------------------- + + +TimeZoneTransitionsResultSet::TimeZoneTransitionsResultSet(ThrowStatusExceptionWrapper* status, + IExternalContext* context, void* inMsg, void* outMsg) + : out(static_cast(outMsg)) +{ + TimeZoneTransitionsInput::Type* in = static_cast(inMsg); + + out->startTimestampNull = out->endTimestampNull = out->zoneOffsetNull = + out->dstOffsetNull = out->effectiveOffsetNull = FB_FALSE; + + USHORT tzId = TimeZoneUtil::parseRegion(in->timeZoneName.str, in->timeZoneName.length); + + iterator = FB_NEW TimeZoneRuleIterator(tzId, in->fromTimestamp, in->toTimestamp); +} + +FB_BOOLEAN TimeZoneTransitionsResultSet::fetch(ThrowStatusExceptionWrapper* status) +{ + if (!iterator->next()) + return false; + + out->startTimestamp = iterator->startTimestamp; + out->endTimestamp = iterator->endTimestamp; + out->zoneOffset = iterator->zoneOffset; + out->dstOffset = iterator->dstOffset; + out->effectiveOffset = iterator->zoneOffset + iterator->dstOffset; + + return true; +} + + +//-------------------------------------- + + +void TimeZoneDatabaseVersionFunction::execute(ThrowStatusExceptionWrapper* status, + IExternalContext* context, void* inMsg, void* outMsg) +{ + TimeZoneDatabaseVersionOutput::Type* out = static_cast(outMsg); + + string str; + TimeZoneUtil::getDatabaseVersion(str); + + out->versionNull = FB_FALSE; + out->version.set(str.c_str()); +} diff --git a/src/jrd/TimeZone.h b/src/jrd/TimeZone.h index ad006e0f0c..27fd904664 100644 --- a/src/jrd/TimeZone.h +++ b/src/jrd/TimeZone.h @@ -24,6 +24,7 @@ #define JRD_TIME_ZONE_H #include "firebird.h" +#include "firebird/Message.h" #include "../common/classes/fb_string.h" #include "../jrd/Monitoring.h" #include "../jrd/recsrc/RecordSource.h" @@ -52,6 +53,111 @@ protected: }; +FB_MESSAGE(TimeZoneTransitionsInput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTL_VARCHAR(MAX_SQL_IDENTIFIER_LEN, CS_METADATA), timeZoneName) + (FB_TIMESTAMP_TZ, fromTimestamp) + (FB_TIMESTAMP_TZ, toTimestamp) +); + +FB_MESSAGE(TimeZoneTransitionsOutput, Firebird::ThrowStatusExceptionWrapper, + (FB_TIMESTAMP_TZ, startTimestamp) + (FB_TIMESTAMP_TZ, endTimestamp) + (FB_SMALLINT, zoneOffset) + (FB_SMALLINT, dstOffset) + (FB_SMALLINT, effectiveOffset) +); + +class TimeZoneTransitionsResultSet : + public Firebird::DisposeIface< + Firebird::IExternalResultSetImpl > +{ +public: + TimeZoneTransitionsResultSet(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, + void* inMsg, void* outMsg); + +public: + void dispose() override + { + delete this; + } + +public: + FB_BOOLEAN fetch(Firebird::ThrowStatusExceptionWrapper* status) override; + +private: + TimeZoneTransitionsOutput::Type* out; + Firebird::AutoPtr iterator; +}; + +class TimeZoneTransitionsProcedure : + public Firebird::DisposeIface< + Firebird::IExternalProcedureImpl > +{ +public: + TimeZoneTransitionsProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IMetadataBuilder* inBuilder, Firebird::IMetadataBuilder* outBuilder) + { + TimeZoneTransitionsInput::setup(status, inBuilder); + TimeZoneTransitionsOutput::setup(status, outBuilder); + } + +public: + void dispose() override + { + delete this; + } + +public: + void getCharSet(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, + char* name, unsigned nameSize) override + { + strncpy(name, "UTF8", nameSize); + } + + Firebird::IExternalResultSet* open(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, void* inMsg, void* outMsg) override + { + return FB_NEW TimeZoneTransitionsResultSet(status, context, inMsg, outMsg); + } +}; + + +FB_MESSAGE(TimeZoneDatabaseVersionInput, Firebird::ThrowStatusExceptionWrapper, +); + +FB_MESSAGE(TimeZoneDatabaseVersionOutput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTL_VARCHAR(10, CS_ASCII), version) +); + +class TimeZoneDatabaseVersionFunction : + public Firebird::DisposeIface< + Firebird::IExternalFunctionImpl > +{ +public: + TimeZoneDatabaseVersionFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IMetadataBuilder* inBuilder, Firebird::IMetadataBuilder* outBuilder) + { + TimeZoneDatabaseVersionInput::setup(status, inBuilder); + TimeZoneDatabaseVersionOutput::setup(status, outBuilder); + } + +public: + void dispose() override + { + delete this; + } + +public: + void getCharSet(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, + char* name, unsigned nameSize) override + { + strncpy(name, "UTF8", nameSize); + } + + void execute(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, void* inMsg, void* outMsg) override; +}; + } // namespace diff --git a/src/jrd/fields.h b/src/jrd/fields.h index fdd475c3e0..3f3739de49 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -203,3 +203,7 @@ FIELD(fld_tz_id , nam_tz_id , dtype_long , sizeof(SLONG) , 0 , NULL , true) FIELD(fld_tz_name , nam_tz_name , dtype_text , MAX_SQL_IDENTIFIER_LEN , dsc_text_type_metadata , NULL , true) + FIELD(fld_tz_offset , nam_tz_offset , dtype_short , sizeof(SSHORT) , 0 , NULL , true) + FIELD(fld_timestamp_tz , nam_timestamp_tz , dtype_timestamp_tz, TIMESTAMP_TZ_SIZE , 0 , NULL , true) + + FIELD(fld_tz_db_version , nam_tz_db_version , dtype_varying , 10 , dsc_text_type_ascii , NULL , true) diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp index d84f90334e..a4cbad4a08 100644 --- a/src/jrd/ini.epp +++ b/src/jrd/ini.epp @@ -59,6 +59,7 @@ #include "../jrd/PreparedStatement.h" #include "../jrd/constants.h" #include "../jrd/grant_proto.h" +#include "../jrd/SystemPackages.h" using namespace Firebird; using namespace Jrd; @@ -80,6 +81,7 @@ static void store_global_field(thread_db*, const gfld*, AutoRequest&, const Meta static void store_intlnames(thread_db*, const MetaName&); static void store_message(thread_db*, const trigger_msg*, AutoRequest&); static void store_relation_field(thread_db*, const int*, const int*, int, AutoRequest&); +static void store_packages(thread_db* tdbb, const MetaName& owner); static void store_trigger(thread_db*, const jrd_trg*, AutoRequest&); static void store_admin_grant(thread_db*, const char* grantee, USHORT grantee_type, const char* object, USHORT object_type, const char* prvl, USHORT option = 0, bool dflt = false); @@ -533,6 +535,8 @@ void INI_format(const char* owner, const char* charset) add_security_to_sys_rel(tdbb, ownerName, names[relfld[RFLD_R_NAME]], length, buffer); } + store_packages(tdbb, ownerName); + // store system-defined triggers handle1.reset(); @@ -1376,6 +1380,10 @@ static void store_global_field(thread_db* tdbb, const gfld* gfield, AutoRequest& X.RDB$FIELD_TYPE = (int) blr_timestamp; break; + case dtype_timestamp_tz: + X.RDB$FIELD_TYPE = (int) blr_timestamp_tz; + break; + case dtype_sql_time: X.RDB$FIELD_TYPE = (int) blr_sql_time; break; @@ -1620,6 +1628,154 @@ static void store_relation_field(thread_db* tdbb, } +// Store system packages. +static void store_packages(thread_db* tdbb, const MetaName& owner) +{ + SET_TDBB(tdbb); + Jrd::Attachment* attachment = tdbb->getAttachment(); + Database* const dbb = tdbb->getDatabase(); + const USHORT majorVersion = dbb->dbb_ods_version; + const USHORT minorVersion = dbb->dbb_minor_version; + + AutoRequest packageHandle, procedureHandle, procedureParameterHandle; + AutoRequest functionHandle, functionReturnHandle, functionArgumentHandle; + //// FIXME: check negative issue + SSHORT procId = 0; + SSHORT funcId = 0; + + for (auto& systemPackage : SystemPackage::LIST) + { + if (systemPackage.odsVersion > ENCODE_ODS(majorVersion, minorVersion)) + continue; + + STORE (REQUEST_HANDLE packageHandle) PKG IN RDB$PACKAGES + { + PAD(systemPackage.name, PKG.RDB$PACKAGE_NAME); + + PAD(owner.c_str(), PKG.RDB$OWNER_NAME); + PKG.RDB$SYSTEM_FLAG = RDB_system; + PKG.RDB$VALID_BODY_FLAG = TRUE; + + //// FIXME: RDB$SECURITY_CLASS, RDB$SQL_SECURITY + } + END_STORE + + for (auto& procedure : systemPackage.procedures) + { + --procId; + + STORE (REQUEST_HANDLE procedureHandle) PRC IN RDB$PROCEDURES + { + PAD(systemPackage.name, PRC.RDB$PACKAGE_NAME); + PAD(procedure.name, PRC.RDB$PROCEDURE_NAME); + + PAD(owner.c_str(), PRC.RDB$OWNER_NAME); + PRC.RDB$SYSTEM_FLAG = RDB_system; + + PRC.RDB$PROCEDURE_ID = procId; + + PRC.RDB$PROCEDURE_INPUTS = (SSHORT) procedure.inputParameters.size(); + PRC.RDB$PROCEDURE_OUTPUTS = (SSHORT) procedure.outputParameters.size(); + PRC.RDB$PROCEDURE_TYPE = (SSHORT) procedure.type; + PRC.RDB$PRIVATE_FLAG = FALSE; + PRC.RDB$VALID_BLR = TRUE; + PAD("SYSTEM", PRC.RDB$ENGINE_NAME); + + //// FIXME: RDB$SECURITY_CLASS, RDB$SQL_SECURITY + } + END_STORE + + for (SSHORT parameterType = 0; parameterType <= 1; ++parameterType) + { + SSHORT paramNumber = -1; + + for (auto& parameter : parameterType == 0 ? procedure.inputParameters : procedure.outputParameters) + { + ++paramNumber; + + STORE (REQUEST_HANDLE procedureParameterHandle) PP IN RDB$PROCEDURE_PARAMETERS + { + PAD(systemPackage.name, PP.RDB$PACKAGE_NAME); + PAD(procedure.name, PP.RDB$PROCEDURE_NAME); + PAD(parameter.name, PP.RDB$PARAMETER_NAME); + + PP.RDB$SYSTEM_FLAG = RDB_system; + + PP.RDB$PARAMETER_NUMBER = paramNumber; + PP.RDB$PARAMETER_TYPE = parameterType; + PP.RDB$PARAMETER_MECHANISM = (SSHORT) prm_mech_normal; + PP.RDB$NULL_FLAG = !parameter.nullable; + + PAD(names[gfields[parameter.fieldId].gfld_name], PP.RDB$FIELD_SOURCE); + } + END_STORE + } + } + } + + for (auto& function : systemPackage.functions) + { + --funcId; + + STORE (REQUEST_HANDLE functionHandle) FUN IN RDB$FUNCTIONS + { + PAD(systemPackage.name, FUN.RDB$PACKAGE_NAME); + PAD(function.name, FUN.RDB$FUNCTION_NAME); + + PAD(owner.c_str(), FUN.RDB$OWNER_NAME); + FUN.RDB$SYSTEM_FLAG = RDB_system; + + FUN.RDB$FUNCTION_ID = procId; + + FUN.RDB$RETURN_ARGUMENT = 0; + FUN.RDB$PRIVATE_FLAG = FALSE; + FUN.RDB$VALID_BLR = TRUE; + PAD("SYSTEM", FUN.RDB$ENGINE_NAME); + + //// FIXME: RDB$SECURITY_CLASS, RDB$SQL_SECURITY + } + END_STORE + + SSHORT paramNumber = 0; + + STORE (REQUEST_HANDLE functionReturnHandle) ARG IN RDB$FUNCTION_ARGUMENTS + { + PAD(systemPackage.name, ARG.RDB$PACKAGE_NAME); + PAD(function.name, ARG.RDB$FUNCTION_NAME); + + ARG.RDB$SYSTEM_FLAG = RDB_system; + + ARG.RDB$ARGUMENT_POSITION = paramNumber; + ARG.RDB$NULL_FLAG = !function.returnType.nullable; + + PAD(names[gfields[function.returnType.fieldId].gfld_name], ARG.RDB$FIELD_SOURCE); + } + END_STORE + + for (auto& parameter : function.parameters) + { + ++paramNumber; + + STORE (REQUEST_HANDLE functionArgumentHandle) ARG IN RDB$FUNCTION_ARGUMENTS + { + PAD(systemPackage.name, ARG.RDB$PACKAGE_NAME); + PAD(function.name, ARG.RDB$FUNCTION_NAME); + PAD(parameter.name, ARG.RDB$ARGUMENT_NAME); + + ARG.RDB$SYSTEM_FLAG = RDB_system; + + ARG.RDB$ARGUMENT_POSITION = paramNumber; + ARG.RDB$NULL_FLAG = !parameter.nullable; + + PAD(names[gfields[parameter.fieldId].gfld_name], ARG.RDB$FIELD_SOURCE); + } + END_STORE + } + } + } +} + + static void store_trigger(thread_db* tdbb, const jrd_trg* trigger, AutoRequest& handle) { /************************************** diff --git a/src/jrd/ini.h b/src/jrd/ini.h index b10f80fbcc..051c497a88 100644 --- a/src/jrd/ini.h +++ b/src/jrd/ini.h @@ -65,8 +65,9 @@ static const TEXT* const names[] = //****************************** // fields.h //****************************** -const USHORT BLOB_SIZE = 8; -const USHORT TIMESTAMP_SIZE = 8; +const USHORT BLOB_SIZE = 8; +const USHORT TIMESTAMP_SIZE = 8; +const USHORT TIMESTAMP_TZ_SIZE = 12; // Pick up global ids diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 9e8eecedec..76cd4ca73a 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -428,6 +428,10 @@ public: static Static engineFactory; + +//------------------------------- + + void registerEngine(IPluginManager* iPlugin) { UnloadDetectorHelper* module = getUnloadDetector(); diff --git a/src/jrd/names.h b/src/jrd/names.h index f6c2c8d590..7a01f37789 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -421,3 +421,7 @@ NAME("MON$WIRE_ENCRYPTED", nam_wire_encrypted) NAME("RDB$TIME_ZONES", nam_time_zones) NAME("RDB$TIME_ZONE_ID", nam_tz_id) NAME("RDB$TIME_ZONE_NAME", nam_tz_name) + +NAME("RDB$TIME_ZONE_OFFSET", nam_tz_offset) +NAME("RDB$TIMESTAMP_TZ", nam_timestamp_tz) +NAME("RDB$DBTZ_VERSION", nam_tz_db_version)