8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 20:03:02 +01:00

Support for system packages and RDB$TIME_ZONE_UTIL system package implementation.

This commit is contained in:
Adriano dos Santos Fernandes 2018-05-25 13:06:30 -03:00
parent 4ea34933f0
commit 3c4c8e781d
19 changed files with 985 additions and 67 deletions

View File

@ -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

View File

@ -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,6 +247,19 @@ USHORT TimeZoneUtil::getSystemTimeZone()
return timeZoneStartup().systemTimeZone;
}
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<void (USHORT, const char*)> func)
{
for (USHORT i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i)
@ -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)
{

View File

@ -29,7 +29,9 @@
#include <functional>
#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 getDatabaseVersion(Firebird::string& str);
static void iterateRegions(std::function<void (USHORT, const char*)> 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

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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<IExternalEngineImpl<SystemEngine, ThrowStatusExceptionWrapper> >
{
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);
}

View File

@ -294,13 +294,7 @@ public:
};
public:
explicit ExtEngineManager(MemoryPool& p)
: PermanentStorage(p),
engines(p),
enginesAttachments(p)
{
}
explicit ExtEngineManager(MemoryPool& p);
~ExtEngineManager();
public:

View File

@ -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())

View File

@ -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())

View File

@ -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 <adrianosf@gmail.com>
* 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> 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}
}
}
}
};

95
src/jrd/SystemPackages.h Normal file
View File

@ -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 <adrianosf@gmail.com>
* 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 <initializer_list>
#include <functional>
namespace Jrd
{
struct SystemProcedureParameter
{
const char* name;
USHORT fieldId;
bool nullable;
};
struct SystemProcedure
{
const char* name;
std::function<Firebird::IExternalProcedure* (
Firebird::ThrowStatusExceptionWrapper*,
Firebird::IExternalContext*,
Firebird::IRoutineMetadata*,
Firebird::IMetadataBuilder*,
Firebird::IMetadataBuilder*
)> factory;
prc_t type;
std::initializer_list<SystemProcedureParameter> inputParameters;
std::initializer_list<SystemProcedureParameter> outputParameters;
};
struct SystemFunctionParameter
{
const char* name;
USHORT fieldId;
bool nullable;
};
struct SystemFunctionReturnType
{
USHORT fieldId;
bool nullable;
};
struct SystemFunction
{
const char* name;
std::function<Firebird::IExternalFunction* (
Firebird::ThrowStatusExceptionWrapper*,
Firebird::IExternalContext*,
Firebird::IRoutineMetadata*,
Firebird::IMetadataBuilder*,
Firebird::IMetadataBuilder*
)> factory;
std::initializer_list<SystemFunctionParameter> parameters;
SystemFunctionReturnType returnType;
};
struct SystemPackage
{
const char* name;
USHORT odsVersion;
std::initializer_list<SystemProcedure> procedures;
std::initializer_list<SystemFunction> functions;
static std::initializer_list<SystemPackage> LIST;
};
} // namespace Jrd
#endif // JRD_SYSTEM_PACKAGES_H

View File

@ -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<USHORT>(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<USHORT>(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<TimeZoneTransitionsOutput::Type*>(outMsg))
{
TimeZoneTransitionsInput::Type* in = static_cast<TimeZoneTransitionsInput::Type*>(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<TimeZoneDatabaseVersionOutput::Type*>(outMsg);
string str;
TimeZoneUtil::getDatabaseVersion(str);
out->versionNull = FB_FALSE;
out->version.set(str.c_str());
}

View File

@ -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<TimeZoneTransitionsResultSet, Firebird::ThrowStatusExceptionWrapper> >
{
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<Firebird::TimeZoneRuleIterator> iterator;
};
class TimeZoneTransitionsProcedure :
public Firebird::DisposeIface<
Firebird::IExternalProcedureImpl<TimeZoneTransitionsProcedure, Firebird::ThrowStatusExceptionWrapper> >
{
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<TimeZoneDatabaseVersionFunction, Firebird::ThrowStatusExceptionWrapper> >
{
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

View File

@ -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)

View File

@ -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)
{
/**************************************

View File

@ -67,6 +67,7 @@ static const TEXT* const names[] =
//******************************
const USHORT BLOB_SIZE = 8;
const USHORT TIMESTAMP_SIZE = 8;
const USHORT TIMESTAMP_TZ_SIZE = 12;
// Pick up global ids

View File

@ -428,6 +428,10 @@ public:
static Static<EngineFactory> engineFactory;
//-------------------------------
void registerEngine(IPluginManager* iPlugin)
{
UnloadDetectorHelper* module = getUnloadDetector();

View File

@ -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)