diff --git a/doc/sql.extensions/README.time_zone.md b/doc/sql.extensions/README.time_zone.md index f6ec319dff..174beb077a 100644 --- a/doc/sql.extensions/README.time_zone.md +++ b/doc/sql.extensions/README.time_zone.md @@ -223,6 +223,16 @@ select localtimestamp # TODO: CURRENT_TIME and CURRENT_TIMESTAMP changes +## Virtual tables + +### `RDB$TIME_ZONES` table + +This virtual table lists time zones supported in the engine. + +Columns: +- `RDB$TIME_ZONE_ID` type `INTEGER` +- `RDB$TIME_ZONE_NAME` type `CHAR(63)` + # Appendix: time zone regions diff --git a/src/common/TimeZoneUtil.cpp b/src/common/TimeZoneUtil.cpp index 3a0c256515..767ff5524f 100644 --- a/src/common/TimeZoneUtil.cpp +++ b/src/common/TimeZoneUtil.cpp @@ -46,13 +46,14 @@ using namespace Firebird; //------------------------------------- -struct TimeZoneDesc +namespace { - const char* asciiName; - const UChar* icuName; -}; - -//------------------------------------- + struct TimeZoneDesc + { + const char* asciiName; + const UChar* icuName; + }; +} // namespace #include "./TimeZones.h" @@ -68,170 +69,175 @@ static void skipSpaces(const char*& p, const char* end); //------------------------------------- -struct TimeZoneStartup +namespace { - TimeZoneStartup(MemoryPool& pool) - : systemTimeZone(TimeZoneUtil::GMT_ZONE), - nameIdMap(pool) + struct TimeZoneStartup { + TimeZoneStartup(MemoryPool& pool) + : systemTimeZone(TimeZoneUtil::GMT_ZONE), + nameIdMap(pool) + { #if defined DEV_BUILD && defined TZ_UPDATE - tzUpdate(); + tzUpdate(); #endif - for (USHORT i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) - { - string s(TIME_ZONE_LIST[i].asciiName); - s.upper(); - nameIdMap.put(s, i); - } + for (USHORT i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) + { + string s(TIME_ZONE_LIST[i].asciiName); + s.upper(); + nameIdMap.put(s, i); + } - UErrorCode icuErrorCode = U_ZERO_ERROR; + UErrorCode icuErrorCode = U_ZERO_ERROR; - Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); - UCalendar* icuCalendar = icuLib.ucalOpen(NULL, -1, NULL, UCAL_GREGORIAN, &icuErrorCode); + Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); + UCalendar* icuCalendar = icuLib.ucalOpen(NULL, -1, NULL, UCAL_GREGORIAN, &icuErrorCode); - if (!icuCalendar) - { - gds__log("ICU's ucal_open error opening the default callendar."); - return; - } + if (!icuCalendar) + { + gds__log("ICU's ucal_open error opening the default callendar."); + return; + } - UChar buffer[TimeZoneUtil::MAX_SIZE]; - bool found = false; + UChar buffer[TimeZoneUtil::MAX_SIZE]; + bool found = false; - int32_t len = icuLib.ucalGetTimeZoneID(icuCalendar, buffer, FB_NELEM(buffer), &icuErrorCode); + int32_t len = icuLib.ucalGetTimeZoneID(icuCalendar, buffer, FB_NELEM(buffer), &icuErrorCode); - if (!U_FAILURE(icuErrorCode)) - { - bool error; - string bufferStrUnicode(reinterpret_cast(buffer), len * sizeof(USHORT)); - string bufferStrAscii(IntlUtil::convertUtf16ToAscii(bufferStrUnicode, &error)); - found = getId(bufferStrAscii, systemTimeZone); - } - else - icuErrorCode = U_ZERO_ERROR; + if (!U_FAILURE(icuErrorCode)) + { + bool error; + string bufferStrUnicode(reinterpret_cast(buffer), len * sizeof(USHORT)); + string bufferStrAscii(IntlUtil::convertUtf16ToAscii(bufferStrUnicode, &error)); + found = getId(bufferStrAscii, systemTimeZone); + } + else + icuErrorCode = U_ZERO_ERROR; + + if (found) + { + icuLib.ucalClose(icuCalendar); + return; + } + + gds__log("ICU error retrieving the system time zone: %d. Fallbacking to displacement.", int(icuErrorCode)); + + int32_t displacement = (icuLib.ucalGet(icuCalendar, UCAL_ZONE_OFFSET, &icuErrorCode) + + icuLib.ucalGet(icuCalendar, UCAL_DST_OFFSET, &icuErrorCode)) / U_MILLIS_PER_MINUTE; - if (found) - { icuLib.ucalClose(icuCalendar); - return; + + if (!U_FAILURE(icuErrorCode)) + { + int sign = displacement < 0 ? -1 : 1; + unsigned tzh = (unsigned) abs(int(displacement / 60)); + unsigned tzm = (unsigned) abs(int(displacement % 60)); + systemTimeZone = makeFromOffset(sign, tzh, tzm); + } + else + gds__log("Cannot retrieve the system time zone: %d.", int(icuErrorCode)); } - gds__log("ICU error retrieving the system time zone: %d. Fallbacking to displacement.", int(icuErrorCode)); - - int32_t displacement = (icuLib.ucalGet(icuCalendar, UCAL_ZONE_OFFSET, &icuErrorCode) + - icuLib.ucalGet(icuCalendar, UCAL_DST_OFFSET, &icuErrorCode)) / U_MILLIS_PER_MINUTE; - - icuLib.ucalClose(icuCalendar); - - if (!U_FAILURE(icuErrorCode)) + bool getId(string name, USHORT& id) { - int sign = displacement < 0 ? -1 : 1; - unsigned tzh = (unsigned) abs(int(displacement / 60)); - unsigned tzm = (unsigned) abs(int(displacement % 60)); - systemTimeZone = makeFromOffset(sign, tzh, tzm); - } - else - gds__log("Cannot retrieve the system time zone: %d.", int(icuErrorCode)); - } + USHORT index; + name.upper(); - bool getId(string name, USHORT& id) - { - USHORT index; - name.upper(); - - if (nameIdMap.get(name, index)) - { - id = MAX_USHORT - index; - return true; + if (nameIdMap.get(name, index)) + { + id = MAX_USHORT - index; + return true; + } + else + return false; } - else - return false; - } #if defined DEV_BUILD && defined TZ_UPDATE - void tzUpdate() - { - SortedObjectsArray currentZones, icuZones; - - for (unsigned i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) - currentZones.push(TIME_ZONE_LIST[i].asciiName); - - Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); - UErrorCode icuErrorCode = U_ZERO_ERROR; - - UEnumeration* uenum = icuLib.ucalOpenTimeZones(&icuErrorCode); - int32_t length; - - while (const UChar* str = icuLib.uenumUnext(uenum, &length, &icuErrorCode)) + void tzUpdate() { - char buffer[256]; + SortedObjectsArray currentZones, icuZones; - for (int i = 0; i <= length; ++i) - buffer[i] = (char) str[i]; + for (unsigned i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) + currentZones.push(TIME_ZONE_LIST[i].asciiName); - icuZones.push(buffer); + Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); + UErrorCode icuErrorCode = U_ZERO_ERROR; + + UEnumeration* uenum = icuLib.ucalOpenTimeZones(&icuErrorCode); + int32_t length; + + while (const UChar* str = icuLib.uenumUnext(uenum, &length, &icuErrorCode)) + { + char buffer[256]; + + for (int i = 0; i <= length; ++i) + buffer[i] = (char) str[i]; + + icuZones.push(buffer); + } + + icuLib.uenumClose(uenum); + + for (auto const& zone : currentZones) + { + FB_SIZE_T pos; + + if (icuZones.find(zone, pos)) + icuZones.remove(pos); + else + printf("--> %s does not exist in ICU.\n", zone.c_str()); + } + + ObjectsArray newZones; + + for (int i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) + newZones.push(TIME_ZONE_LIST[i].asciiName); + + for (auto const& zone : icuZones) + newZones.push(zone); + + printf("// The content of this file is generated with help of macro TZ_UPDATE.\n\n"); + + int index = 0; + + for (auto const& zone : newZones) + { + printf("static const UChar TZSTR_%d[] = {", index); + + for (int i = 0; i < zone.length(); ++i) + printf("'%c', ", zone[i]); + + printf("'\\0'};\n"); + + ++index; + } + + printf("\n"); + + printf("// Do not change order of items in this array! The index corresponds to a TimeZone ID, which must be fixed!\n"); + printf("static const TimeZoneDesc TIME_ZONE_LIST[] = {"); + + index = 0; + + for (auto const& zone : newZones) + { + printf("%s\n\t{\"%s\", TZSTR_%d}", (index == 0 ? "" : ","), zone.c_str(), index); + ++index; + } + + printf("\n"); + printf("};\n\n"); } - - icuLib.uenumClose(uenum); - - for (auto const& zone : currentZones) - { - FB_SIZE_T pos; - - if (icuZones.find(zone, pos)) - icuZones.remove(pos); - else - printf("--> %s does not exist in ICU.\n", zone.c_str()); - } - - ObjectsArray newZones; - - for (int i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i) - newZones.push(TIME_ZONE_LIST[i].asciiName); - - for (auto const& zone : icuZones) - newZones.push(zone); - - printf("// The content of this file is generated with help of macro TZ_UPDATE.\n\n"); - - int index = 0; - - for (auto const& zone : newZones) - { - printf("static const UChar TZSTR_%d[] = {", index); - - for (int i = 0; i < zone.length(); ++i) - printf("'%c', ", zone[i]); - - printf("'\\0'};\n"); - - ++index; - } - - printf("\n"); - - printf("// Do not change order of items in this array! The index corresponds to a TimeZone ID, which must be fixed!\n"); - printf("static const TimeZoneDesc TIME_ZONE_LIST[] = {"); - - index = 0; - - for (auto const& zone : newZones) - { - printf("%s\n\t{\"%s\", TZSTR_%d}", (index == 0 ? "" : ","), zone.c_str(), index); - ++index; - } - - printf("\n"); - printf("};\n\n"); - } #endif // defined DEV_BUILD && defined TZ_UPDATE - USHORT systemTimeZone; + USHORT systemTimeZone; -private: - GenericMap > > nameIdMap; -}; + private: + GenericMap > > nameIdMap; + }; +} // namespace + +//------------------------------------- static InitInstance timeZoneStartup; @@ -243,6 +249,12 @@ USHORT TimeZoneUtil::getSystemTimeZone() return timeZoneStartup().systemTimeZone; } +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); +} + // Parses a time zone, offset- or region-based. USHORT TimeZoneUtil::parse(const char* str, unsigned strLen) { diff --git a/src/common/TimeZoneUtil.h b/src/common/TimeZoneUtil.h index 86d701d0c7..56150e3564 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -27,6 +27,7 @@ #ifndef COMMON_TIME_ZONE_UTIL_H #define COMMON_TIME_ZONE_UTIL_H +#include #include "../common/classes/fb_string.h" #include "../common/cvt.h" @@ -58,6 +59,8 @@ public: public: static USHORT getSystemTimeZone(); + static void iterateRegions(std::function func); + static USHORT parse(const char* str, unsigned strLen); static unsigned format(char* buffer, size_t bufferSize, USHORT timeZone); diff --git a/src/include/gen/ids.h b/src/include/gen/ids.h index 9316bd4df2..b6f080ed06 100644 --- a/src/include/gen/ids.h +++ b/src/include/gen/ids.h @@ -698,3 +698,9 @@ const USHORT f_mon_tab_rec_stat_id = 3; +// Relation 50 (RDB$TIME_ZONES) + + const USHORT f_tz_id = 0; + const USHORT f_tz_name = 1; + + diff --git a/src/jrd/TimeZone.cpp b/src/jrd/TimeZone.cpp new file mode 100644 index 0000000000..3e5ebf3359 --- /dev/null +++ b/src/jrd/TimeZone.cpp @@ -0,0 +1,73 @@ +/* + * 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 Alex Peshkov + * 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/TimeZone.h" +#include "../jrd/Record.h" +#include "../jrd/ini.h" +#include "../jrd/tra.h" +#include "gen/ids.h" + +using namespace Jrd; +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(); + + 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); + } + ); +} + + +TimeZonesTableScan::TimeZonesTableScan(CompilerScratch* csb, const Firebird::string& alias, + StreamType stream, jrd_rel* relation) + : VirtualTableScan(csb, alias, stream, relation) +{ +} + +const Format* TimeZonesTableScan::getFormat(thread_db* tdbb, jrd_rel* relation) const +{ + return tdbb->getTransaction()->getTimeZoneSnapshot(tdbb)->getData(relation)->getFormat(); +} + + +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); +} diff --git a/src/jrd/TimeZone.h b/src/jrd/TimeZone.h new file mode 100644 index 0000000000..69ae2260bb --- /dev/null +++ b/src/jrd/TimeZone.h @@ -0,0 +1,58 @@ +/* + * 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 Alex Peshkov + * 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_TIME_ZONE_H +#define JRD_TIME_ZONE_H + +#include "firebird.h" +#include "../common/classes/fb_string.h" +#include "../jrd/Monitoring.h" +#include "../jrd/recsrc/RecordSource.h" + +namespace Jrd { + +class thread_db; +class jrd_tra; +class RecordBuffer; + + +class TimeZoneSnapshot : public SnapshotData +{ +public: + TimeZoneSnapshot(thread_db* tdbb, MemoryPool& pool); +}; + +class TimeZonesTableScan: public VirtualTableScan +{ +public: + TimeZonesTableScan(CompilerScratch* csb, const Firebird::string& alias, StreamType stream, jrd_rel* relation); + +protected: + const Format* getFormat(thread_db* tdbb, jrd_rel* relation) const override; + bool retrieveRecord(thread_db* tdbb, jrd_rel* relation, FB_UINT64 position, Record* record) const override; +}; + + + +} // namespace + +#endif // JRD_TIME_ZONE_H diff --git a/src/jrd/fields.h b/src/jrd/fields.h index ce88bdaeca..fdd475c3e0 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -200,3 +200,6 @@ FIELD(fld_idle_timer , nam_idle_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true) FIELD(fld_stmt_timeout , nam_stmt_timeout , dtype_long , sizeof(SLONG) , 0 , NULL , false) FIELD(fld_stmt_timer , nam_stmt_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true) + + 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) diff --git a/src/jrd/names.h b/src/jrd/names.h index b42e653789..f6c2c8d590 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -417,3 +417,7 @@ NAME("MON$STATEMENT_TIMER", nam_stmt_timer) NAME("MON$WIRE_COMPRESSED", nam_wire_compressed) 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) diff --git a/src/jrd/opt.cpp b/src/jrd/opt.cpp index 6ecf32b963..a091913c0d 100644 --- a/src/jrd/opt.cpp +++ b/src/jrd/opt.cpp @@ -78,6 +78,7 @@ #include "../jrd/RecordSourceNodes.h" #include "../jrd/VirtualTable.h" #include "../jrd/Monitoring.h" +#include "../jrd/TimeZone.h" #include "../jrd/UserManagement.h" #include "../common/classes/array.h" #include "../common/classes/objects_array.h" @@ -2265,6 +2266,10 @@ static RecordSource* gen_retrieval(thread_db* tdbb, rsb = FB_NEW_POOL(*tdbb->getDefaultPool()) DbCreatorsScan(csb, alias, stream, relation); break; + case rel_time_zones: + rsb = FB_NEW_POOL(*tdbb->getDefaultPool()) TimeZonesTableScan(csb, alias, stream, relation); + break; + default: rsb = FB_NEW_POOL(*tdbb->getDefaultPool()) MonitoringTableScan(csb, alias, stream, relation); break; diff --git a/src/jrd/relations.h b/src/jrd/relations.h index a1108a23b8..c880ea0587 100644 --- a/src/jrd/relations.h +++ b/src/jrd/relations.h @@ -696,3 +696,9 @@ RELATION(nam_mon_tab_stats, rel_mon_tab_stats, ODS_12_0, rel_virtual) FIELD(f_mon_tab_name, nam_mon_tab_name, fld_r_name, 0, ODS_12_0) FIELD(f_mon_tab_rec_stat_id, nam_mon_rec_stat_id, fld_stat_id, 0, ODS_12_0) END_RELATION + +// Relation 50 (RDB$TIME_ZONES) +RELATION(nam_time_zones, rel_time_zones, ODS_13_0, rel_virtual) + FIELD(f_tz_id, nam_tz_id, fld_tz_id, 0, ODS_13_0) + FIELD(f_tz_name, nam_tz_name, fld_tz_name, 0, ODS_13_0) +END_RELATION diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 28ab9d2209..cbff888ec8 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -40,6 +40,7 @@ #include "../jrd/rse.h" #include "../jrd/intl_classes.h" #include "../common/ThreadStart.h" +#include "../jrd/TimeZone.h" #include "../jrd/UserManagement.h" #include "../jrd/blb_proto.h" #include "../jrd/cch_proto.h" @@ -3495,6 +3496,7 @@ jrd_tra::~jrd_tra() delete tra_undo_space; delete tra_user_management; + delete tra_timezone_snapshot; delete tra_mapping_list; delete tra_gen_ids; @@ -3538,6 +3540,15 @@ void jrd_tra::setInterface(JTransaction* jt) } +TimeZoneSnapshot* jrd_tra::getTimeZoneSnapshot(thread_db* tdbb) +{ + if (!tra_timezone_snapshot) + tra_timezone_snapshot = FB_NEW_POOL(*tra_pool) TimeZoneSnapshot(tdbb, *tra_pool); + + return tra_timezone_snapshot; +} + + UserManagement* jrd_tra::getUserManagement() { if (!tra_user_management) diff --git a/src/jrd/tra.h b/src/jrd/tra.h index e97f689aac..cf0f7e40a0 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -62,6 +62,7 @@ class ArrayField; class Attachment; class DeferredWork; class DeferredJob; +class TimeZoneSnapshot; class UserManagement; class MappingList; class DbCreatorsList; @@ -187,6 +188,7 @@ public: tra_blob_space(NULL), tra_undo_space(NULL), tra_undo_records(*p), + tra_timezone_snapshot(NULL), tra_user_management(NULL), tra_sec_db_context(NULL), tra_mapping_list(NULL), @@ -301,6 +303,7 @@ private: TempSpace* tra_undo_space; // undo log storage UndoRecordList tra_undo_records; // temporary records used for the undo purposes + TimeZoneSnapshot* tra_timezone_snapshot; UserManagement* tra_user_management; SecDbContext* tra_sec_db_context; MappingList* tra_mapping_list; @@ -368,6 +371,7 @@ public: void linkToAttachment(Attachment* attachment); static void tra_abort(const char* reason); + TimeZoneSnapshot* getTimeZoneSnapshot(thread_db* tdbb); UserManagement* getUserManagement(); SecDbContext* getSecDbContext(); SecDbContext* setSecDbContext(Firebird::IAttachment* att, Firebird::ITransaction* tra);