You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-07-29 08:21:15 +03:00
MCOL-1356: Add convert_tz (#2099)
This commit is contained in:
@ -27,6 +27,7 @@ SET ( libcalmysql_SRCS
|
||||
ha_window_function.cpp
|
||||
ha_mcs_partition.cpp
|
||||
ha_pseudocolumn.cpp
|
||||
ha_tzinfo.cpp
|
||||
is_columnstore_tables.cpp
|
||||
is_columnstore_columns.cpp
|
||||
is_columnstore_files.cpp
|
||||
|
@ -61,6 +61,7 @@ using namespace logging;
|
||||
#include "ha_mcs_sysvars.h"
|
||||
#include "ha_subquery.h"
|
||||
#include "ha_mcs_pushdown.h"
|
||||
#include "ha_tzinfo.h"
|
||||
using namespace cal_impl_if;
|
||||
|
||||
#include "calpontselectexecutionplan.h"
|
||||
@ -4177,6 +4178,63 @@ ReturnedColumn* buildFunctionColumn(
|
||||
}
|
||||
}
|
||||
|
||||
// Take the value of to/from TZ data and check if its a description or offset via
|
||||
// my_tzinfo_find. Offset value will leave the corresponding tzinfo funcParms empty.
|
||||
// while descriptions will lookup the time_zone_info structure and serialize for use
|
||||
// in primproc func_convert_tz
|
||||
if (funcName == "convert_tz")
|
||||
{
|
||||
messageqcpp::ByteStream bs;
|
||||
string tzinfo;
|
||||
SimpleColumn* scCheck;
|
||||
// MCOL-XXXX There is no way currently to perform this lookup when the timezone description
|
||||
// comes from another table of timezone descriptions.
|
||||
// 1. Move proc code into plugin where it will have access to this table data
|
||||
// 2. Create a library that primproc can use to access the time zone data tables.
|
||||
// for now throw a message that this is not supported
|
||||
scCheck = dynamic_cast<SimpleColumn*>(funcParms[1]->data());
|
||||
if (scCheck)
|
||||
{
|
||||
gwi.fatalParseError = true;
|
||||
gwi.parseErrorText = "convert_tz with parameter column_name in a Columnstore table";
|
||||
setError(gwi.thd, ER_NOT_SUPPORTED_YET, gwi.parseErrorText, gwi);
|
||||
return NULL;
|
||||
}
|
||||
scCheck = dynamic_cast<SimpleColumn*>(funcParms[2]->data());
|
||||
if (scCheck)
|
||||
{
|
||||
gwi.fatalParseError = true;
|
||||
gwi.parseErrorText = "convert_tz with parameter column_name in a Columnstore table";
|
||||
setError(gwi.thd, ER_NOT_SUPPORTED_YET, gwi.parseErrorText, gwi);
|
||||
return NULL;
|
||||
}
|
||||
dataconvert::TIME_ZONE_INFO* from_tzinfo = my_tzinfo_find(gwi.thd, ifp->arguments()[1]->val_str());
|
||||
dataconvert::TIME_ZONE_INFO* to_tzinfo = my_tzinfo_find(gwi.thd, ifp->arguments()[2]->val_str());
|
||||
if (from_tzinfo)
|
||||
{
|
||||
serializeTimezoneInfo(bs,from_tzinfo);
|
||||
uint32_t length = bs.length();
|
||||
uint8_t *buf = new uint8_t[length];
|
||||
bs >> buf;
|
||||
tzinfo = string((char*)buf, length);
|
||||
}
|
||||
sptp.reset(new ParseTree(new ConstantColumn(tzinfo)));
|
||||
(dynamic_cast<ConstantColumn*>(sptp->data()))->timeZone(gwi.thd->variables.time_zone->get_name()->ptr());
|
||||
funcParms.push_back(sptp);
|
||||
tzinfo.clear();
|
||||
if (to_tzinfo)
|
||||
{
|
||||
serializeTimezoneInfo(bs,to_tzinfo);
|
||||
uint32_t length = bs.length();
|
||||
uint8_t *buf = new uint8_t[length];
|
||||
bs >> buf;
|
||||
tzinfo = string((char*)buf, length);
|
||||
}
|
||||
sptp.reset(new ParseTree(new ConstantColumn(tzinfo)));
|
||||
(dynamic_cast<ConstantColumn*>(sptp->data()))->timeZone(gwi.thd->variables.time_zone->get_name()->ptr());
|
||||
funcParms.push_back(sptp);
|
||||
tzinfo.clear();
|
||||
}
|
||||
if (funcName == "sysdate")
|
||||
{
|
||||
gwi.no_parm_func_list.push_back(fc);
|
||||
|
600
dbcon/mysql/ha_tzinfo.cpp
Normal file
600
dbcon/mysql/ha_tzinfo.cpp
Normal file
@ -0,0 +1,600 @@
|
||||
/* Copyright (C) 2021 MariaDB Corporation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; version 2 of
|
||||
the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301, USA. */
|
||||
|
||||
#include "ha_tzinfo.h"
|
||||
#include "tzfile.h"
|
||||
#include <my_sys.h>
|
||||
|
||||
/* needed for sql_base.h which contains open_system_tables_for_read*/
|
||||
#ifndef TRUE
|
||||
#define TRUE (1) /* Logical true */
|
||||
#define FALSE (0) /* Logical false */
|
||||
#endif
|
||||
#include <sql_base.h>
|
||||
|
||||
//static HASH tz_names;
|
||||
//static HASH offset_tzs;
|
||||
static MEM_ROOT tz_storage;
|
||||
static uint tz_leapcnt= 0;
|
||||
static LS_INFO *tz_lsis= 0;
|
||||
static bool time_zone_tables_exist= 1;
|
||||
|
||||
namespace cal_impl_if
|
||||
{
|
||||
|
||||
/*
|
||||
Names of tables (with their lengths) that are needed
|
||||
for dynamical loading of time zone descriptions.
|
||||
*/
|
||||
|
||||
static const LEX_CSTRING tz_tables_names[MY_TZ_TABLES_COUNT]=
|
||||
{
|
||||
{ STRING_WITH_LEN("time_zone_name")},
|
||||
{ STRING_WITH_LEN("time_zone")},
|
||||
{ STRING_WITH_LEN("time_zone_transition_type")},
|
||||
{ STRING_WITH_LEN("time_zone_transition")}
|
||||
};
|
||||
|
||||
static void
|
||||
tz_init_table_list(TABLE_LIST *tz_tabs)
|
||||
{
|
||||
for (int i= 0; i < MY_TZ_TABLES_COUNT; i++)
|
||||
{
|
||||
tz_tabs[i].init_one_table(&MYSQL_SCHEMA_NAME, tz_tables_names + i,
|
||||
NULL, TL_READ);
|
||||
if (i != MY_TZ_TABLES_COUNT - 1)
|
||||
tz_tabs[i].next_global= tz_tabs[i].next_local= &tz_tabs[i+1];
|
||||
if (i != 0)
|
||||
tz_tabs[i].prev_global= &tz_tabs[i-1].next_global;
|
||||
}
|
||||
}
|
||||
|
||||
static my_bool
|
||||
prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage)
|
||||
{
|
||||
my_time_t cur_t= MY_TIME_T_MIN;
|
||||
my_time_t cur_l, end_t;
|
||||
my_time_t end_l = 0;
|
||||
my_time_t cur_max_seen_l= MY_TIME_T_MIN;
|
||||
long cur_offset, cur_corr, cur_off_and_corr;
|
||||
uint next_trans_idx, next_leap_idx;
|
||||
uint i;
|
||||
/*
|
||||
Temporary arrays where we will store tables. Needed because
|
||||
we don't know table sizes ahead. (Well we can estimate their
|
||||
upper bound but this will take extra space.)
|
||||
*/
|
||||
my_time_t revts[TZ_MAX_REV_RANGES];
|
||||
REVT_INFO revtis[TZ_MAX_REV_RANGES];
|
||||
|
||||
/*
|
||||
Let us setup fallback time type which will be used if we have not any
|
||||
transitions or if we have moment of time before first transition.
|
||||
We will find first non-DST local time type and use it (or use first
|
||||
local time type if all of them are DST types).
|
||||
*/
|
||||
for (i= 0; i < sp->typecnt && sp->ttis[i].tt_isdst; i++)
|
||||
/* no-op */ ;
|
||||
if (i == sp->typecnt)
|
||||
i= 0;
|
||||
sp->fallback_tti= &(sp->ttis[i]);
|
||||
|
||||
|
||||
/*
|
||||
Let us build shifted my_time_t -> my_time_t map.
|
||||
*/
|
||||
sp->revcnt= 0;
|
||||
|
||||
/* Let us find initial offset */
|
||||
if (sp->timecnt == 0 || cur_t < sp->ats[0])
|
||||
{
|
||||
/*
|
||||
If we have not any transitions or t is before first transition we are using
|
||||
already found fallback time type which index is already in i.
|
||||
*/
|
||||
next_trans_idx= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* cur_t == sp->ats[0] so we found transition */
|
||||
i= sp->types[0];
|
||||
next_trans_idx= 1;
|
||||
}
|
||||
|
||||
cur_offset= sp->ttis[i].tt_gmtoff;
|
||||
|
||||
|
||||
/* let us find leap correction... unprobable, but... */
|
||||
for (next_leap_idx= 0; next_leap_idx < sp->leapcnt &&
|
||||
cur_t >= sp->lsis[next_leap_idx].ls_trans;
|
||||
++next_leap_idx)
|
||||
continue;
|
||||
|
||||
if (next_leap_idx > 0)
|
||||
cur_corr= sp->lsis[next_leap_idx - 1].ls_corr;
|
||||
else
|
||||
cur_corr= 0;
|
||||
|
||||
/* Iterate trough t space */
|
||||
while (sp->revcnt < TZ_MAX_REV_RANGES - 1)
|
||||
{
|
||||
cur_off_and_corr= cur_offset - cur_corr;
|
||||
|
||||
/*
|
||||
We assuming that cur_t could be only overflowed downwards,
|
||||
we also assume that end_t won't be overflowed in this case.
|
||||
*/
|
||||
if (cur_off_and_corr < 0 &&
|
||||
cur_t < MY_TIME_T_MIN - cur_off_and_corr)
|
||||
cur_t= MY_TIME_T_MIN - cur_off_and_corr;
|
||||
|
||||
cur_l= cur_t + cur_off_and_corr;
|
||||
|
||||
/*
|
||||
Let us choose end_t as point before next time type change or leap
|
||||
second correction.
|
||||
*/
|
||||
end_t= MY_MIN((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1:
|
||||
MY_TIME_T_MAX,
|
||||
(next_leap_idx < sp->leapcnt) ?
|
||||
sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX);
|
||||
/*
|
||||
again assuming that end_t can be overlowed only in positive side
|
||||
we also assume that end_t won't be overflowed in this case.
|
||||
*/
|
||||
if (cur_off_and_corr > 0 &&
|
||||
end_t > MY_TIME_T_MAX - cur_off_and_corr)
|
||||
end_t= MY_TIME_T_MAX - cur_off_and_corr;
|
||||
|
||||
end_l= end_t + cur_off_and_corr;
|
||||
|
||||
|
||||
if (end_l > cur_max_seen_l)
|
||||
{
|
||||
/* We want special handling in the case of first range */
|
||||
if (cur_max_seen_l == MY_TIME_T_MIN)
|
||||
{
|
||||
revts[sp->revcnt]= cur_l;
|
||||
revtis[sp->revcnt].rt_offset= cur_off_and_corr;
|
||||
revtis[sp->revcnt].rt_type= 0;
|
||||
sp->revcnt++;
|
||||
cur_max_seen_l= end_l;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cur_l > cur_max_seen_l + 1)
|
||||
{
|
||||
/* We have a spring time-gap and we are not at the first range */
|
||||
revts[sp->revcnt]= cur_max_seen_l + 1;
|
||||
revtis[sp->revcnt].rt_offset= revtis[sp->revcnt-1].rt_offset;
|
||||
revtis[sp->revcnt].rt_type= 1;
|
||||
sp->revcnt++;
|
||||
if (sp->revcnt == TZ_MAX_TIMES + TZ_MAX_LEAPS + 1)
|
||||
break; /* That was too much */
|
||||
cur_max_seen_l= cur_l - 1;
|
||||
}
|
||||
|
||||
/* Assume here end_l > cur_max_seen_l (because end_l>=cur_l) */
|
||||
|
||||
revts[sp->revcnt]= cur_max_seen_l + 1;
|
||||
revtis[sp->revcnt].rt_offset= cur_off_and_corr;
|
||||
revtis[sp->revcnt].rt_type= 0;
|
||||
sp->revcnt++;
|
||||
cur_max_seen_l= end_l;
|
||||
}
|
||||
}
|
||||
|
||||
if (end_t == MY_TIME_T_MAX ||
|
||||
((cur_off_and_corr > 0) &&
|
||||
(end_t >= MY_TIME_T_MAX - cur_off_and_corr)))
|
||||
/* end of t space */
|
||||
break;
|
||||
|
||||
cur_t= end_t + 1;
|
||||
|
||||
/*
|
||||
Let us find new offset and correction. Because of our choice of end_t
|
||||
cur_t can only be point where new time type starts or/and leap
|
||||
correction is performed.
|
||||
*/
|
||||
if (sp->timecnt != 0 && cur_t >= sp->ats[0]) /* else reuse old offset */
|
||||
if (next_trans_idx < sp->timecnt &&
|
||||
cur_t == sp->ats[next_trans_idx])
|
||||
{
|
||||
/* We are at offset point */
|
||||
cur_offset= sp->ttis[sp->types[next_trans_idx]].tt_gmtoff;
|
||||
++next_trans_idx;
|
||||
}
|
||||
|
||||
if (next_leap_idx < sp->leapcnt &&
|
||||
cur_t == sp->lsis[next_leap_idx].ls_trans)
|
||||
{
|
||||
/* we are at leap point */
|
||||
cur_corr= sp->lsis[next_leap_idx].ls_corr;
|
||||
++next_leap_idx;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if we have had enough space */
|
||||
if (sp->revcnt == TZ_MAX_REV_RANGES - 1)
|
||||
return 1;
|
||||
|
||||
/* set maximum end_l as finisher */
|
||||
revts[sp->revcnt]= end_l;
|
||||
|
||||
/* Allocate arrays of proper size in sp and copy result there */
|
||||
if (!(sp->revts= (my_time_t *)alloc_root(storage,
|
||||
sizeof(my_time_t) * (sp->revcnt + 1))) ||
|
||||
!(sp->revtis= (REVT_INFO *)alloc_root(storage,
|
||||
sizeof(REVT_INFO) * sp->revcnt)))
|
||||
return 1;
|
||||
|
||||
memcpy(sp->revts, revts, sizeof(my_time_t) * (sp->revcnt + 1));
|
||||
memcpy(sp->revtis, revtis, sizeof(REVT_INFO) * sp->revcnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static TIME_ZONE_INFO* tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables)
|
||||
{
|
||||
TABLE *table= 0;
|
||||
TIME_ZONE_INFO *tz_info= NULL;
|
||||
//Tz_names_entry *tmp_tzname;
|
||||
TIME_ZONE_INFO *return_val= 0;
|
||||
int res;
|
||||
uint tzid, ttid;
|
||||
my_time_t ttime;
|
||||
char buff[MAX_FIELD_WIDTH];
|
||||
uchar keybuff[32];
|
||||
Field *field;
|
||||
String abbr(buff, sizeof(buff), &my_charset_latin1);
|
||||
char *alloc_buff= NULL;
|
||||
char *tz_name_buff= NULL;
|
||||
/*
|
||||
Temporary arrays that are used for loading of data for filling
|
||||
TIME_ZONE_INFO structure
|
||||
*/
|
||||
my_time_t ats[TZ_MAX_TIMES];
|
||||
uchar types[TZ_MAX_TIMES];
|
||||
TRAN_TYPE_INFO ttis[TZ_MAX_TYPES];
|
||||
#ifdef ABBR_ARE_USED
|
||||
char chars[MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))];
|
||||
#endif
|
||||
/*
|
||||
Used as a temporary tz_info until we decide that we actually want to
|
||||
allocate and keep the tz info and tz name in tz_storage.
|
||||
*/
|
||||
TIME_ZONE_INFO tmp_tz_info;
|
||||
memset(&tmp_tz_info, 0, sizeof(TIME_ZONE_INFO));
|
||||
|
||||
DBUG_ENTER("tz_load_from_open_tables");
|
||||
|
||||
/*
|
||||
Let us find out time zone id by its name (there is only one index
|
||||
and it is specifically for this purpose).
|
||||
*/
|
||||
table= tz_tables->table;
|
||||
tz_tables= tz_tables->next_local;
|
||||
table->field[0]->store(tz_name->ptr(), tz_name->length(),
|
||||
&my_charset_latin1);
|
||||
if (table->file->ha_index_init(0, 1))
|
||||
goto end;
|
||||
|
||||
if (table->file->ha_index_read_map(table->record[0], table->field[0]->ptr,
|
||||
HA_WHOLE_KEY, HA_READ_KEY_EXACT))
|
||||
{
|
||||
#ifdef EXTRA_DEBUG
|
||||
/*
|
||||
Most probably user has mistyped time zone name, so no need to bark here
|
||||
unless we need it for debugging.
|
||||
*/
|
||||
sql_print_error("Can't find description of time zone '%.*b'",
|
||||
tz_name->length(), tz_name->ptr());
|
||||
#endif
|
||||
goto end;
|
||||
}
|
||||
|
||||
tzid= (uint)table->field[1]->val_int();
|
||||
|
||||
(void)table->file->ha_index_end();
|
||||
|
||||
/*
|
||||
Now we need to lookup record in mysql.time_zone table in order to
|
||||
understand whenever this timezone uses leap seconds (again we are
|
||||
using the only index in this table).
|
||||
*/
|
||||
table= tz_tables->table;
|
||||
tz_tables= tz_tables->next_local;
|
||||
field= table->field[0];
|
||||
field->store((longlong) tzid, true);
|
||||
DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
|
||||
field->get_key_image(keybuff,
|
||||
MY_MIN(field->key_length(), sizeof(keybuff)),
|
||||
Field::itRAW);
|
||||
if (table->file->ha_index_init(0, 1))
|
||||
goto end;
|
||||
|
||||
if (table->file->ha_index_read_map(table->record[0], keybuff,
|
||||
HA_WHOLE_KEY, HA_READ_KEY_EXACT))
|
||||
{
|
||||
sql_print_error("Can't find description of time zone '%u'", tzid);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* If Uses_leap_seconds == 'Y' */
|
||||
if (table->field[1]->val_int() == 1)
|
||||
{
|
||||
tmp_tz_info.leapcnt= tz_leapcnt;
|
||||
tmp_tz_info.lsis= tz_lsis;
|
||||
}
|
||||
|
||||
(void)table->file->ha_index_end();
|
||||
|
||||
/*
|
||||
Now we will iterate through records for out time zone in
|
||||
mysql.time_zone_transition_type table. Because we want records
|
||||
only for our time zone guess what are we doing?
|
||||
Right - using special index.
|
||||
*/
|
||||
table= tz_tables->table;
|
||||
tz_tables= tz_tables->next_local;
|
||||
field= table->field[0];
|
||||
field->store((longlong) tzid, true);
|
||||
DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
|
||||
field->get_key_image(keybuff,
|
||||
MY_MIN(field->key_length(), sizeof(keybuff)),
|
||||
Field::itRAW);
|
||||
if (table->file->ha_index_init(0, 1))
|
||||
goto end;
|
||||
|
||||
res= table->file->ha_index_read_map(table->record[0], keybuff,
|
||||
(key_part_map)1, HA_READ_KEY_EXACT);
|
||||
while (!res)
|
||||
{
|
||||
ttid= (uint)table->field[1]->val_int();
|
||||
|
||||
if (ttid >= TZ_MAX_TYPES)
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition_type table: too big "
|
||||
"transition type id");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ttis[ttid].tt_gmtoff= (long)table->field[2]->val_int();
|
||||
ttis[ttid].tt_isdst= (table->field[3]->val_int() > 0);
|
||||
|
||||
#ifdef ABBR_ARE_USED
|
||||
// FIXME should we do something with duplicates here ?
|
||||
table->field[4]->val_str(&abbr, &abbr);
|
||||
if (tmp_tz_info.charcnt + abbr.length() + 1 > sizeof(chars))
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition_type table: not enough "
|
||||
"room for abbreviations");
|
||||
goto end;
|
||||
}
|
||||
ttis[ttid].tt_abbrind= tmp_tz_info.charcnt;
|
||||
memcpy(chars + tmp_tz_info.charcnt, abbr.ptr(), abbr.length());
|
||||
tmp_tz_info.charcnt+= abbr.length();
|
||||
chars[tmp_tz_info.charcnt]= 0;
|
||||
tmp_tz_info.charcnt++;
|
||||
|
||||
DBUG_PRINT("info",("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
|
||||
"abbr='%s' tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff,
|
||||
chars + ttis[ttid].tt_abbrind, ttis[ttid].tt_isdst));
|
||||
#else
|
||||
DBUG_PRINT("info",("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
|
||||
"tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, ttis[ttid].tt_isdst));
|
||||
#endif
|
||||
|
||||
/* ttid is increasing because we are reading using index */
|
||||
DBUG_ASSERT(ttid >= tmp_tz_info.typecnt);
|
||||
|
||||
tmp_tz_info.typecnt= ttid + 1;
|
||||
|
||||
res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
|
||||
}
|
||||
|
||||
if (res != HA_ERR_END_OF_FILE)
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition_type table");
|
||||
goto end;
|
||||
}
|
||||
|
||||
(void)table->file->ha_index_end();
|
||||
|
||||
|
||||
/*
|
||||
At last we are doing the same thing for records in
|
||||
mysql.time_zone_transition table. Here we additionally need records
|
||||
in ascending order by index scan also satisfies us.
|
||||
*/
|
||||
table= tz_tables->table;
|
||||
table->field[0]->store((longlong) tzid, true);
|
||||
if (table->file->ha_index_init(0, 1))
|
||||
goto end;
|
||||
|
||||
res= table->file->ha_index_read_map(table->record[0], keybuff,
|
||||
(key_part_map)1, HA_READ_KEY_EXACT);
|
||||
while (!res)
|
||||
{
|
||||
ttime= (my_time_t)table->field[1]->val_int();
|
||||
ttid= (uint)table->field[2]->val_int();
|
||||
|
||||
if (tmp_tz_info.timecnt + 1 > TZ_MAX_TIMES)
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition table: "
|
||||
"too much transitions");
|
||||
goto end;
|
||||
}
|
||||
if (ttid + 1 > tmp_tz_info.typecnt)
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition table: "
|
||||
"bad transition type id");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ats[tmp_tz_info.timecnt]= ttime;
|
||||
types[tmp_tz_info.timecnt]= ttid;
|
||||
tmp_tz_info.timecnt++;
|
||||
|
||||
DBUG_PRINT("info", ("time_zone_transition table: tz_id: %u tt_time: %lu tt_id: %u",
|
||||
tzid, (ulong) ttime, ttid));
|
||||
|
||||
res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
|
||||
}
|
||||
|
||||
/*
|
||||
We have to allow HA_ERR_KEY_NOT_FOUND because some time zones
|
||||
for example UTC have no transitons.
|
||||
*/
|
||||
if (res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND)
|
||||
{
|
||||
sql_print_error("Error while loading time zone description from "
|
||||
"mysql.time_zone_transition table");
|
||||
goto end;
|
||||
}
|
||||
|
||||
(void)table->file->ha_index_end();
|
||||
table= 0;
|
||||
|
||||
/*
|
||||
Let us check how correct our time zone description is. We don't check for
|
||||
tz->timecnt < 1 since it is ok for GMT.
|
||||
*/
|
||||
if (tmp_tz_info.typecnt < 1)
|
||||
{
|
||||
sql_print_error("loading time zone without transition types");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Allocate memory for the timezone info and timezone name in tz_storage. */
|
||||
if (!(alloc_buff= (char*) alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) + tz_name->length() + 1)))
|
||||
{
|
||||
sql_print_error("Out of memory while loading time zone description");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Move the temporary tz_info into the allocated area */
|
||||
tz_info= (TIME_ZONE_INFO *)alloc_buff;
|
||||
memcpy(tz_info, &tmp_tz_info, sizeof(TIME_ZONE_INFO));
|
||||
tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO);
|
||||
/*
|
||||
By writing zero to the end we guarantee that we can call ptr()
|
||||
instead of c_ptr() for time zone name.
|
||||
*/
|
||||
strmake(tz_name_buff, tz_name->ptr(), tz_name->length());
|
||||
|
||||
/*
|
||||
Now we will allocate memory and init TIME_ZONE_INFO structure.
|
||||
*/
|
||||
if (!(alloc_buff= (char*) alloc_root(&tz_storage,
|
||||
ALIGN_SIZE(sizeof(my_time_t) *
|
||||
tz_info->timecnt) +
|
||||
ALIGN_SIZE(tz_info->timecnt) +
|
||||
#ifdef ABBR_ARE_USED
|
||||
ALIGN_SIZE(tz_info->charcnt) +
|
||||
#endif
|
||||
sizeof(TRAN_TYPE_INFO) *
|
||||
tz_info->typecnt)))
|
||||
{
|
||||
sql_print_error("Out of memory while loading time zone description");
|
||||
goto end;
|
||||
}
|
||||
|
||||
tz_info->ats= (my_time_t *) alloc_buff;
|
||||
memcpy(tz_info->ats, ats, tz_info->timecnt * sizeof(my_time_t));
|
||||
alloc_buff+= ALIGN_SIZE(sizeof(my_time_t) * tz_info->timecnt);
|
||||
tz_info->types= (uchar *)alloc_buff;
|
||||
memcpy(tz_info->types, types, tz_info->timecnt);
|
||||
alloc_buff+= ALIGN_SIZE(tz_info->timecnt);
|
||||
#ifdef ABBR_ARE_USED
|
||||
tz_info->chars= alloc_buff;
|
||||
memcpy(tz_info->chars, chars, tz_info->charcnt);
|
||||
alloc_buff+= ALIGN_SIZE(tz_info->charcnt);
|
||||
#endif
|
||||
tz_info->ttis= (TRAN_TYPE_INFO *)alloc_buff;
|
||||
memcpy(tz_info->ttis, ttis, tz_info->typecnt * sizeof(TRAN_TYPE_INFO));
|
||||
|
||||
/* Build reversed map. */
|
||||
if (prepare_tz_info(tz_info, &tz_storage))
|
||||
{
|
||||
sql_print_error("Unable to build mktime map for time zone");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/*
|
||||
Loading of time zone succeeded
|
||||
*/
|
||||
return_val= tz_info;
|
||||
|
||||
end:
|
||||
|
||||
if (table && table->file->inited)
|
||||
(void) table->file->ha_index_end();
|
||||
|
||||
DBUG_RETURN(return_val);
|
||||
}
|
||||
|
||||
TIME_ZONE_INFO* my_tzinfo_find(THD *thd, const String *name)
|
||||
{
|
||||
TIME_ZONE_INFO* result_tzinfo= 0;
|
||||
long offset;
|
||||
DBUG_ENTER("my_tz_find");
|
||||
DBUG_PRINT("enter", ("time zone name='%s'\n",
|
||||
name ? ((String *)name)->c_ptr_safe() : "NULL"));
|
||||
|
||||
if (!name || name->is_empty())
|
||||
DBUG_RETURN(0);
|
||||
|
||||
if (!timeZoneToOffset(name->ptr(), name->length(), &offset))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
result_tzinfo= 0;
|
||||
my_tz_init(thd,NULL, 0);
|
||||
if (time_zone_tables_exist)
|
||||
{
|
||||
TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT];
|
||||
|
||||
/*
|
||||
Allocate start_new_trans with malloc as it's > 4000 bytes and this
|
||||
function can be called deep inside a stored procedure
|
||||
*/
|
||||
start_new_trans *new_trans= new start_new_trans(thd);
|
||||
tz_init_table_list(tz_tables);
|
||||
init_mdl_requests(tz_tables);
|
||||
if (!open_system_tables_for_read(thd, tz_tables))
|
||||
{
|
||||
result_tzinfo= tz_load_from_open_tables(name, tz_tables);
|
||||
thd->commit_whole_transaction_and_close_tables();
|
||||
}
|
||||
new_trans->restore_old_transaction();
|
||||
delete new_trans;
|
||||
}
|
||||
}
|
||||
|
||||
DBUG_RETURN(result_tzinfo);
|
||||
}
|
||||
|
||||
}
|
34
dbcon/mysql/ha_tzinfo.h
Normal file
34
dbcon/mysql/ha_tzinfo.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* Copyright (C) 2021 MariaDB Corporation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; version 2 of
|
||||
the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301, USA. */
|
||||
|
||||
#ifndef HA_TZINFO
|
||||
#define HA_TZINFO
|
||||
|
||||
//#undef LOG_INFO
|
||||
#include <my_config.h>
|
||||
#include "idb_mysql.h"
|
||||
#include "dataconvert.h"
|
||||
using namespace dataconvert;
|
||||
|
||||
namespace cal_impl_if
|
||||
{
|
||||
|
||||
TIME_ZONE_INFO* my_tzinfo_find(THD *thd, const String *name);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -53,6 +53,9 @@
|
||||
#include "common/branchpred.h"
|
||||
|
||||
|
||||
#include "bytestream.h"
|
||||
#include "errorids.h"
|
||||
|
||||
// remove this block if the htonll is defined in library
|
||||
#ifdef __linux__
|
||||
#include <endian.h>
|
||||
@ -80,6 +83,9 @@ inline uint64_t htonll(uint64_t n)
|
||||
#endif //_MSC_VER
|
||||
#endif //__linux__
|
||||
|
||||
#define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400)
|
||||
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
|
||||
|
||||
// this method evalutes the uint64 that stores a char[] to expected value
|
||||
inline uint64_t uint64ToStr(uint64_t n)
|
||||
{
|
||||
@ -183,6 +189,116 @@ struct MySQLTime
|
||||
}
|
||||
};
|
||||
|
||||
/* Structure describing local time type (e.g. Moscow summer time (MSD)) */
|
||||
typedef struct ttinfo
|
||||
{
|
||||
long tt_gmtoff; // Offset from UTC in seconds
|
||||
uint tt_isdst; // Is daylight saving time or not. Used to set tm_isdst
|
||||
#ifdef ABBR_ARE_USED
|
||||
uint tt_abbrind; // Index of start of abbreviation for this time type.
|
||||
#endif
|
||||
/*
|
||||
We don't use tt_ttisstd and tt_ttisgmt members of original elsie-code
|
||||
struct since we don't support POSIX-style TZ descriptions in variables.
|
||||
*/
|
||||
} TRAN_TYPE_INFO;
|
||||
|
||||
/* Structure describing leap-second corrections. */
|
||||
typedef struct lsinfo
|
||||
{
|
||||
int64_t ls_trans; // Transition time
|
||||
long ls_corr; // Correction to apply
|
||||
} LS_INFO;
|
||||
|
||||
/*
|
||||
Structure with information describing ranges of my_time_t shifted to local
|
||||
time (my_time_t + offset). Used for local MYSQL_TIME -> my_time_t conversion.
|
||||
See comments for TIME_to_gmt_sec() for more info.
|
||||
*/
|
||||
typedef struct revtinfo
|
||||
{
|
||||
long rt_offset; // Offset of local time from UTC in seconds
|
||||
uint rt_type; // Type of period 0 - Normal period. 1 - Spring time-gap
|
||||
} REVT_INFO;
|
||||
|
||||
/*
|
||||
Structure which fully describes time zone which is
|
||||
described in our db or in zoneinfo files.
|
||||
*/
|
||||
typedef struct st_time_zone_info
|
||||
{
|
||||
uint leapcnt; // Number of leap-second corrections
|
||||
uint timecnt; // Number of transitions between time types
|
||||
uint typecnt; // Number of local time types
|
||||
uint charcnt; // Number of characters used for abbreviations
|
||||
uint revcnt; // Number of transition descr. for TIME->my_time_t conversion
|
||||
/* The following are dynamical arrays are allocated in MEM_ROOT */
|
||||
int64_t *ats; // Times of transitions between time types
|
||||
unsigned char *types; // Local time types for transitions
|
||||
TRAN_TYPE_INFO *ttis; // Local time types descriptions
|
||||
#ifdef ABBR_ARE_USED
|
||||
/* Storage for local time types abbreviations. They are stored as ASCIIZ */
|
||||
char *chars;
|
||||
#endif
|
||||
/*
|
||||
Leap seconds corrections descriptions, this array is shared by
|
||||
all time zones who use leap seconds.
|
||||
*/
|
||||
LS_INFO *lsis;
|
||||
/*
|
||||
Starting points and descriptions of shifted my_time_t (my_time_t + offset)
|
||||
ranges on which shifted my_time_t -> my_time_t mapping is linear or undefined.
|
||||
Used for tm -> my_time_t conversion.
|
||||
*/
|
||||
int64_t *revts;
|
||||
REVT_INFO *revtis;
|
||||
/*
|
||||
Time type which is used for times smaller than first transition or if
|
||||
there are no transitions at all.
|
||||
*/
|
||||
TRAN_TYPE_INFO *fallback_tti;
|
||||
|
||||
} TIME_ZONE_INFO;
|
||||
|
||||
inline void serializeTimezoneInfo(messageqcpp::ByteStream& bs, TIME_ZONE_INFO* tz)
|
||||
{
|
||||
bs << (uint) tz->leapcnt;
|
||||
bs << (uint) tz->timecnt;
|
||||
bs << (uint) tz->typecnt;
|
||||
bs << (uint) tz->charcnt;
|
||||
bs << (uint) tz->revcnt;
|
||||
|
||||
// going to put size in front of these dynamically sized arrays
|
||||
// and use deserializeInlineVector on the other side
|
||||
bs << (uint64_t) tz->timecnt;
|
||||
bs.append((uint8_t*)tz->ats,(tz->timecnt*sizeof(int64_t)));
|
||||
bs << (uint64_t) tz->timecnt;
|
||||
bs.append((uint8_t*)tz->types,tz->timecnt);
|
||||
bs << (uint64_t) tz->typecnt;
|
||||
bs.append((uint8_t*)tz->ttis,(tz->typecnt*sizeof(TRAN_TYPE_INFO)));
|
||||
#ifdef ABBR_ARE_USED
|
||||
bs << (uint) tz->charcnt;
|
||||
bs.append((uint8_t*)tz->chars,tz->charcnt);
|
||||
#endif
|
||||
bs << (uint64_t) tz->leapcnt;
|
||||
bs.append((uint8_t*)tz->lsis,(tz->leapcnt*sizeof(LS_INFO)));
|
||||
bs << (uint64_t) (tz->revcnt + 1);
|
||||
bs.append((uint8_t*)tz->revts,((tz->revcnt + 1)*sizeof(int64_t)));
|
||||
bs << (uint64_t) tz->revcnt;
|
||||
bs.append((uint8_t*)tz->revtis,(tz->revcnt*sizeof(REVT_INFO)));
|
||||
bs << (uint64_t) tz->typecnt;
|
||||
bs.append((uint8_t*)tz->fallback_tti,(tz->typecnt*sizeof(TRAN_TYPE_INFO)));
|
||||
};
|
||||
|
||||
inline void unserializeTimezoneInfo(messageqcpp::ByteStream& bs, TIME_ZONE_INFO* tz)
|
||||
{
|
||||
bs >> (uint&)tz->leapcnt;
|
||||
bs >> (uint&)tz->timecnt;
|
||||
bs >> (uint&)tz->typecnt;
|
||||
bs >> (uint&)tz->charcnt;
|
||||
bs >> (uint&)tz->revcnt;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function converts the timezone represented as a string
|
||||
* in the format "+HH:MM" or "-HH:MM" to a signed offset in seconds
|
||||
@ -631,12 +747,11 @@ inline int64_t mySQLTimeToGmtSec(const MySQLTime& time,
|
||||
if (timeZoneToOffset(timeZone.c_str(), timeZone.size(), &offset))
|
||||
{
|
||||
isValid = false;
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
seconds = secSinceEpoch(time.year, time.month, time.day,
|
||||
time.hour, time.minute, time.second) - offset;
|
||||
}
|
||||
|
||||
/* make sure we have legit timestamps (i.e. we didn't over/underflow anywhere above) */
|
||||
if (seconds >= MIN_TIMESTAMP_VALUE && seconds <= MAX_TIMESTAMP_VALUE)
|
||||
return seconds;
|
||||
@ -647,6 +762,271 @@ inline int64_t mySQLTimeToGmtSec(const MySQLTime& time,
|
||||
}
|
||||
|
||||
|
||||
inline uint find_time_range(int64_t t, const int64_t *range_boundaries, uint higher_bound)
|
||||
{
|
||||
uint i, lower_bound= 0;
|
||||
|
||||
/*
|
||||
Function will work without this assertion but result would be meaningless.
|
||||
*/
|
||||
idbassert(higher_bound > 0 && t >= range_boundaries[0]);
|
||||
|
||||
/*
|
||||
Do binary search for minimal interval which contain t. We preserve:
|
||||
range_boundaries[lower_bound] <= t < range_boundaries[higher_bound]
|
||||
invariant and decrease this higher_bound - lower_bound gap twice
|
||||
times on each step.
|
||||
*/
|
||||
|
||||
while (higher_bound - lower_bound > 1)
|
||||
{
|
||||
i= (lower_bound + higher_bound) >> 1;
|
||||
if (range_boundaries[i] <= t)
|
||||
lower_bound= i;
|
||||
else
|
||||
higher_bound= i;
|
||||
}
|
||||
return lower_bound;
|
||||
}
|
||||
|
||||
inline int64_t TIME_to_gmt_sec(const MySQLTime& time, const TIME_ZONE_INFO *sp, uint32_t* error_code)
|
||||
{
|
||||
int64_t local_t;
|
||||
uint saved_seconds;
|
||||
uint i;
|
||||
int shift= 0;
|
||||
//DBUG_ENTER("TIME_to_gmt_sec");
|
||||
|
||||
if (!validateTimestampRange(time))
|
||||
{
|
||||
//*error_code= ER_WARN_DATA_OUT_OF_RANGE;
|
||||
//DBUG_RETURN(0);
|
||||
*error_code = logging::ERR_FUNC_OUT_OF_RANGE_RESULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need this for correct leap seconds handling */
|
||||
if (time.second < SECS_PER_MIN)
|
||||
saved_seconds= 0;
|
||||
else
|
||||
saved_seconds= time.second;
|
||||
|
||||
/*
|
||||
NOTE: to convert full my_time_t range we do a shift of the
|
||||
boundary dates here to avoid overflow of my_time_t.
|
||||
We use alike approach in my_system_gmt_sec().
|
||||
|
||||
However in that function we also have to take into account
|
||||
overflow near 0 on some platforms. That's because my_system_gmt_sec
|
||||
uses localtime_r(), which doesn't work with negative values correctly
|
||||
on platforms with unsigned time_t (QNX). Here we don't use localtime()
|
||||
=> we negative values of local_t are ok.
|
||||
*/
|
||||
|
||||
if ((time.year == MAX_TIMESTAMP_YEAR) && (time.month == 1) && time.day > 4)
|
||||
{
|
||||
/*
|
||||
We will pass (time.day - shift) to sec_since_epoch(), and
|
||||
want this value to be a positive number, so we shift
|
||||
only dates > 4.01.2038 (to avoid owerflow).
|
||||
*/
|
||||
shift= 2;
|
||||
}
|
||||
|
||||
|
||||
local_t= secSinceEpoch(time.year, time.month, (time.day - shift),
|
||||
time.hour, time.minute,
|
||||
saved_seconds ? 0 : time.second);
|
||||
|
||||
/* We have at least one range */
|
||||
idbassert(sp->revcnt >= 1);
|
||||
|
||||
if (local_t < sp->revts[0] || local_t > sp->revts[sp->revcnt])
|
||||
{
|
||||
/*
|
||||
This means that source time can't be represented as my_time_t due to
|
||||
limited my_time_t range.
|
||||
*/
|
||||
*error_code = logging::ERR_FUNC_OUT_OF_RANGE_RESULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* binary search for our range */
|
||||
i= find_time_range(local_t, sp->revts, sp->revcnt);
|
||||
|
||||
/*
|
||||
As there are no offset switches at the end of TIMESTAMP range,
|
||||
we could simply check for overflow here (and don't need to bother
|
||||
about DST gaps etc)
|
||||
*/
|
||||
if (shift)
|
||||
{
|
||||
if (local_t > (int64_t) (MAX_TIMESTAMP_VALUE - shift * SECS_PER_DAY +
|
||||
sp->revtis[i].rt_offset - saved_seconds))
|
||||
{
|
||||
*error_code = logging::ERR_FUNC_OUT_OF_RANGE_RESULT;
|
||||
return 0; /* my_time_t overflow */
|
||||
}
|
||||
local_t+= shift * SECS_PER_DAY;
|
||||
}
|
||||
|
||||
if (sp->revtis[i].rt_type)
|
||||
{
|
||||
/*
|
||||
Oops! We are in spring time gap.
|
||||
May be we should return error here?
|
||||
Now we are returning my_time_t value corresponding to the
|
||||
beginning of the gap.
|
||||
*/
|
||||
//*error_code= ER_WARN_INVALID_TIMESTAMP;
|
||||
local_t= sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds;
|
||||
}
|
||||
else
|
||||
local_t= local_t - sp->revtis[i].rt_offset + saved_seconds;
|
||||
|
||||
/* check for TIMESTAMP_MAX_VALUE was already done above */
|
||||
if (local_t < MIN_TIMESTAMP_VALUE)
|
||||
{
|
||||
*error_code= logging::ERR_FUNC_OUT_OF_RANGE_RESULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return local_t;
|
||||
}
|
||||
|
||||
static const TRAN_TYPE_INFO* find_transition_type(int64_t t, const TIME_ZONE_INFO *sp)
|
||||
{
|
||||
if ((sp->timecnt == 0 || t < sp->ats[0]))
|
||||
{
|
||||
/*
|
||||
If we have not any transitions or t is before first transition let
|
||||
us use fallback time type.
|
||||
*/
|
||||
return sp->fallback_tti;
|
||||
}
|
||||
|
||||
/*
|
||||
Do binary search for minimal interval between transitions which
|
||||
contain t. With this localtime_r on real data may takes less
|
||||
time than with linear search (I've seen 30% speed up).
|
||||
*/
|
||||
return &(sp->ttis[sp->types[find_time_range(t, sp->ats, sp->timecnt)]]);
|
||||
}
|
||||
|
||||
static void sec_to_TIME(MySQLTime * tmp, int64_t t, long offset)
|
||||
{
|
||||
long days;
|
||||
long rem;
|
||||
int y;
|
||||
int yleap;
|
||||
const uint *ip;
|
||||
|
||||
days= (long) (t / SECS_PER_DAY);
|
||||
rem= (long) (t % SECS_PER_DAY);
|
||||
|
||||
/*
|
||||
We do this as separate step after dividing t, because this
|
||||
allows us handle times near my_time_t bounds without overflows.
|
||||
*/
|
||||
rem+= offset;
|
||||
while (rem < 0)
|
||||
{
|
||||
rem+= SECS_PER_DAY;
|
||||
days--;
|
||||
}
|
||||
while (rem >= SECS_PER_DAY)
|
||||
{
|
||||
rem -= SECS_PER_DAY;
|
||||
days++;
|
||||
}
|
||||
tmp->hour= (uint)(rem / SECS_PER_HOUR);
|
||||
rem= rem % SECS_PER_HOUR;
|
||||
tmp->minute= (uint)(rem / SECS_PER_MIN);
|
||||
/*
|
||||
A positive leap second requires a special
|
||||
representation. This uses "... ??:59:60" et seq.
|
||||
*/
|
||||
tmp->second= (uint)(rem % SECS_PER_MIN);
|
||||
|
||||
y= EPOCH_YEAR;
|
||||
while (days < 0 || days >= (long)year_lengths[yleap= isleap(y)])
|
||||
{
|
||||
int newy;
|
||||
|
||||
newy= y + days / DAYS_PER_NYEAR;
|
||||
if (days < 0)
|
||||
newy--;
|
||||
days-= (newy - y) * DAYS_PER_NYEAR +
|
||||
LEAPS_THRU_END_OF(newy - 1) -
|
||||
LEAPS_THRU_END_OF(y - 1);
|
||||
y= newy;
|
||||
}
|
||||
tmp->year= y;
|
||||
|
||||
ip= mon_lengths[yleap];
|
||||
for (tmp->month= 0; days >= (long) ip[tmp->month]; tmp->month++)
|
||||
days= days - (long) ip[tmp->month];
|
||||
tmp->month++;
|
||||
tmp->day= (uint)(days + 1);
|
||||
|
||||
/* filling MySQL specific MYSQL_TIME members */
|
||||
tmp->second_part= 0;
|
||||
tmp->time_type= CALPONTDATETIME_ENUM;
|
||||
}
|
||||
|
||||
inline void gmt_sec_to_TIME(MySQLTime *tmp, int64_t sec_in_utc, const TIME_ZONE_INFO *sp)
|
||||
{
|
||||
const TRAN_TYPE_INFO *ttisp;
|
||||
const LS_INFO *lp;
|
||||
long corr= 0;
|
||||
int hit= 0;
|
||||
int i;
|
||||
|
||||
/*
|
||||
Find proper transition (and its local time type) for our sec_in_utc value.
|
||||
Funny but again by separating this step in function we receive code
|
||||
which very close to glibc's code. No wonder since they obviously use
|
||||
the same base and all steps are sensible.
|
||||
*/
|
||||
ttisp= find_transition_type(sec_in_utc, sp);
|
||||
|
||||
/*
|
||||
Let us find leap correction for our sec_in_utc value and number of extra
|
||||
secs to add to this minute.
|
||||
This loop is rarely used because most users will use time zones without
|
||||
leap seconds, and even in case when we have such time zone there won't
|
||||
be many iterations (we have about 22 corrections at this moment (2004)).
|
||||
*/
|
||||
for ( i= sp->leapcnt; i-- > 0; )
|
||||
{
|
||||
lp= &sp->lsis[i];
|
||||
if (sec_in_utc >= lp->ls_trans)
|
||||
{
|
||||
if (sec_in_utc == lp->ls_trans)
|
||||
{
|
||||
hit= ((i == 0 && lp->ls_corr > 0) ||
|
||||
lp->ls_corr > sp->lsis[i - 1].ls_corr);
|
||||
if (hit)
|
||||
{
|
||||
while (i > 0 &&
|
||||
sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 &&
|
||||
sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1)
|
||||
{
|
||||
hit++;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
corr= lp->ls_corr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sec_to_TIME(tmp, sec_in_utc, ttisp->tt_gmtoff - corr);
|
||||
|
||||
tmp->second+= hit;
|
||||
}
|
||||
|
||||
/** @brief a structure to hold a date
|
||||
*/
|
||||
struct Date
|
||||
|
@ -24,6 +24,7 @@ set(funcexp_LIB_SRCS
|
||||
func_concat_oracle.cpp
|
||||
func_concat_ws.cpp
|
||||
func_conv.cpp
|
||||
func_convert_tz.cpp
|
||||
func_crc32.cpp
|
||||
func_date.cpp
|
||||
func_date_add.cpp
|
||||
|
232
utils/funcexp/func_convert_tz.cpp
Normal file
232
utils/funcexp/func_convert_tz.cpp
Normal file
@ -0,0 +1,232 @@
|
||||
/* Copyright (C) 2021 MariaDB Corporation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; version 2 of
|
||||
the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301, USA. */
|
||||
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
#include "functor_dtm.h"
|
||||
#include "funchelpers.h"
|
||||
#include "functioncolumn.h"
|
||||
#include "intervalcolumn.h"
|
||||
#include "rowgroup.h"
|
||||
using namespace execplan;
|
||||
|
||||
#include "dataconvert.h"
|
||||
using namespace dataconvert;
|
||||
|
||||
#include "bytestream.h"
|
||||
using namespace messageqcpp;
|
||||
using namespace funcexp;
|
||||
|
||||
#include "errorcodes.h"
|
||||
#include "idberrorinfo.h"
|
||||
#include "errorids.h"
|
||||
using namespace logging;
|
||||
|
||||
namespace funcexp
|
||||
{
|
||||
|
||||
// need my_tz_find in dataconvert
|
||||
|
||||
CalpontSystemCatalog::ColType Func_convert_tz::operationType( FunctionParm& fp, CalpontSystemCatalog::ColType& resultType )
|
||||
{
|
||||
return resultType;
|
||||
}
|
||||
|
||||
int64_t Func_convert_tz::getDatetimeIntVal(rowgroup::Row& row,
|
||||
FunctionParm& parm,
|
||||
bool& isNull,
|
||||
execplan::CalpontSystemCatalog::ColType& ct)
|
||||
{
|
||||
return dataconvert::DataConvert::intToDatetime(getIntVal(row, parm, isNull, ct));
|
||||
}
|
||||
|
||||
int64_t Func_convert_tz::getIntVal(rowgroup::Row& row,
|
||||
FunctionParm& parm,
|
||||
bool& isNull,
|
||||
CalpontSystemCatalog::ColType& op_ct)
|
||||
{
|
||||
messageqcpp::ByteStream bs;
|
||||
bool bFromTz = false;
|
||||
bool bToTz = false;
|
||||
dataconvert::TIME_ZONE_INFO tzinfo;
|
||||
std::vector<int64_t> ats;
|
||||
std::vector<unsigned char> types;
|
||||
std::vector<TRAN_TYPE_INFO> ttis;
|
||||
#ifdef ABBR_ARE_USED
|
||||
std::vector<char> chars;
|
||||
#endif
|
||||
std::vector<LS_INFO> lsis;
|
||||
std::vector<int64_t> revts;
|
||||
std::vector<REVT_INFO> revtis;
|
||||
std::vector<TRAN_TYPE_INFO> fallback_tti;
|
||||
|
||||
int64_t seconds = 0;
|
||||
uint32_t err_code = 0;
|
||||
int64_t datetime_start = parm[0]->data()->getDatetimeIntVal(row, isNull);
|
||||
int64_t rtn = 0;
|
||||
|
||||
if (isNull)
|
||||
return 0;
|
||||
// Adding a zero date to a time is always NULL
|
||||
if (datetime_start == 0)
|
||||
{
|
||||
isNull = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const string& from_tz = parm[1]->data()->getStrVal(row, isNull);
|
||||
if (isNull)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const string& to_tz = parm[2]->data()->getStrVal(row, isNull);
|
||||
if (isNull)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
cout << "from " << from_tz << endl;
|
||||
cout << "to " << to_tz << endl;
|
||||
|
||||
const string& serialized_from_tzinfo = parm[3]->data()->getStrVal(row, isNull);
|
||||
const string& serialized_to_tzinfo = parm[4]->data()->getStrVal(row, isNull);
|
||||
|
||||
if (!serialized_from_tzinfo.empty())
|
||||
{
|
||||
bs.append((uint8_t*)serialized_from_tzinfo.c_str(),serialized_from_tzinfo.length());
|
||||
dataconvert::unserializeTimezoneInfo(bs,&tzinfo);
|
||||
deserializeInlineVector<int64_t>(bs, ats);
|
||||
tzinfo.ats = ats.data();
|
||||
deserializeInlineVector<unsigned char>(bs, types);
|
||||
tzinfo.types = types.data();
|
||||
deserializeInlineVector<TRAN_TYPE_INFO>(bs, ttis);
|
||||
tzinfo.ttis = ttis.data();
|
||||
#ifdef ABBR_ARE_USED
|
||||
deserializeInlineVector<char>(bs_fromTZ, chars);
|
||||
tzinfo.chars = chars.data();
|
||||
#endif
|
||||
deserializeInlineVector<LS_INFO>(bs, lsis);
|
||||
tzinfo.lsis = lsis.data();
|
||||
deserializeInlineVector<int64_t>(bs, revts);
|
||||
tzinfo.revts = revts.data();
|
||||
deserializeInlineVector<REVT_INFO>(bs, revtis);
|
||||
tzinfo.revtis = revtis.data();
|
||||
deserializeInlineVector<TRAN_TYPE_INFO>(bs, fallback_tti);
|
||||
tzinfo.fallback_tti = fallback_tti.data();
|
||||
bFromTz = true;
|
||||
}
|
||||
|
||||
dataconvert::DateTime datetime(datetime_start);
|
||||
if (!dataconvert::DataConvert::isColumnDateTimeValid( datetime_start ))
|
||||
return datetime_start;
|
||||
|
||||
//int64_t result_datetime;
|
||||
bool valid = true;
|
||||
MySQLTime my_time_tmp,my_start_time;
|
||||
my_time_tmp.reset();
|
||||
my_start_time.year = datetime.year;
|
||||
my_start_time.month = datetime.month;
|
||||
my_start_time.day = datetime.day;
|
||||
my_start_time.hour = datetime.hour;
|
||||
my_start_time.minute = datetime.minute;
|
||||
my_start_time.second = datetime.second;
|
||||
my_start_time.second_part = datetime.msecond;
|
||||
|
||||
if (bFromTz)
|
||||
{
|
||||
seconds = dataconvert::TIME_to_gmt_sec(my_start_time,&tzinfo,&err_code);
|
||||
if (err_code)
|
||||
{
|
||||
return datetime.convertToMySQLint();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
seconds = dataconvert::mySQLTimeToGmtSec(my_start_time,from_tz,valid);
|
||||
if (!valid)
|
||||
{
|
||||
if (seconds != 0)
|
||||
isNull = true;
|
||||
return datetime.convertToMySQLint();
|
||||
}
|
||||
}
|
||||
|
||||
if (!serialized_to_tzinfo.empty())
|
||||
{
|
||||
bs.reset();
|
||||
bs.append((uint8_t*)serialized_to_tzinfo.c_str(),serialized_to_tzinfo.length());
|
||||
dataconvert::unserializeTimezoneInfo(bs,&tzinfo);
|
||||
deserializeInlineVector<int64_t>(bs, ats);
|
||||
tzinfo.ats = ats.data();
|
||||
deserializeInlineVector<unsigned char>(bs, types);
|
||||
tzinfo.types = types.data();
|
||||
deserializeInlineVector<TRAN_TYPE_INFO>(bs, ttis);
|
||||
tzinfo.ttis = ttis.data();
|
||||
#ifdef ABBR_ARE_USED
|
||||
deserializeInlineVector<char>(bs_fromTZ, chars);
|
||||
tzinfo.chars = chars.data();
|
||||
#endif
|
||||
deserializeInlineVector<LS_INFO>(bs, lsis);
|
||||
tzinfo.lsis = lsis.data();
|
||||
deserializeInlineVector<int64_t>(bs, revts);
|
||||
tzinfo.revts = revts.data();
|
||||
deserializeInlineVector<REVT_INFO>(bs, revtis);
|
||||
tzinfo.revtis = revtis.data();
|
||||
deserializeInlineVector<TRAN_TYPE_INFO>(bs, fallback_tti);
|
||||
tzinfo.fallback_tti = fallback_tti.data();
|
||||
bToTz = true;
|
||||
}
|
||||
if (bToTz)
|
||||
{
|
||||
dataconvert::gmt_sec_to_TIME(&my_time_tmp, seconds, &tzinfo);
|
||||
if (my_time_tmp.second == 60 || my_time_tmp.second == 61)
|
||||
my_time_tmp.second= 59;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataconvert::gmtSecToMySQLTime(seconds, my_time_tmp, to_tz);
|
||||
}
|
||||
|
||||
dataconvert::DateTime result_datetime(my_time_tmp.year,
|
||||
my_time_tmp.month,
|
||||
my_time_tmp.day,
|
||||
my_time_tmp.hour,
|
||||
my_time_tmp.minute,
|
||||
my_time_tmp.second,
|
||||
my_time_tmp.second_part);
|
||||
|
||||
if ((rtn = result_datetime.convertToMySQLint()) == 0)
|
||||
isNull = true;
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
||||
string Func_convert_tz::getStrVal(rowgroup::Row& row,
|
||||
FunctionParm& parm,
|
||||
bool& isNull,
|
||||
CalpontSystemCatalog::ColType& ct)
|
||||
{
|
||||
return dataconvert::DataConvert::datetimeToString(getIntVal(row, parm, isNull, ct));
|
||||
}
|
||||
|
||||
|
||||
} // namespace funcexp
|
||||
// vim:ts=4 sw=4:
|
@ -104,6 +104,7 @@ FuncExp::FuncExp()
|
||||
fFuncMap["cos"] = new Func_cos();
|
||||
fFuncMap["cot"] = new Func_cot();
|
||||
fFuncMap["convert"] = new Func_cast_char(); //dlh
|
||||
fFuncMap["convert_tz"] = new Func_convert_tz(); //BT
|
||||
fFuncMap["crc32"] = new Func_crc32();
|
||||
fFuncMap["date_add_interval"] = new Func_date_add(); //dlh
|
||||
fFuncMap["date_format"] = new Func_date_format();
|
||||
|
@ -551,7 +551,32 @@ public:
|
||||
execplan::CalpontSystemCatalog::ColType& op_ct);
|
||||
};
|
||||
|
||||
class Func_convert_tz: public Func_Dtm
|
||||
{
|
||||
public:
|
||||
Func_convert_tz() : Func_Dtm("convert_tz") {}
|
||||
virtual ~Func_convert_tz() {}
|
||||
|
||||
//bool from_tz_cached, to_tz_cached;
|
||||
//Time_zone *from_tz, *to_tz;
|
||||
|
||||
execplan::CalpontSystemCatalog::ColType operationType(FunctionParm& fp, execplan::CalpontSystemCatalog::ColType& resultType);
|
||||
|
||||
int64_t getIntVal(rowgroup::Row& row,
|
||||
FunctionParm& fp,
|
||||
bool& isNull,
|
||||
execplan::CalpontSystemCatalog::ColType& op_ct);
|
||||
|
||||
int64_t getDatetimeIntVal(rowgroup::Row& row,
|
||||
FunctionParm& parm,
|
||||
bool& isNull,
|
||||
execplan::CalpontSystemCatalog::ColType& ct);
|
||||
|
||||
std::string getStrVal(rowgroup::Row& row,
|
||||
FunctionParm& fp,
|
||||
bool& isNull,
|
||||
execplan::CalpontSystemCatalog::ColType& op_ct);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user