1
0
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:
benthompson15
2021-08-19 17:47:10 -05:00
committed by GitHub
parent 0e11a6c294
commit 923bbf4033
9 changed files with 1334 additions and 2 deletions

View File

@ -27,6 +27,7 @@ SET ( libcalmysql_SRCS
ha_window_function.cpp ha_window_function.cpp
ha_mcs_partition.cpp ha_mcs_partition.cpp
ha_pseudocolumn.cpp ha_pseudocolumn.cpp
ha_tzinfo.cpp
is_columnstore_tables.cpp is_columnstore_tables.cpp
is_columnstore_columns.cpp is_columnstore_columns.cpp
is_columnstore_files.cpp is_columnstore_files.cpp

View File

@ -61,6 +61,7 @@ using namespace logging;
#include "ha_mcs_sysvars.h" #include "ha_mcs_sysvars.h"
#include "ha_subquery.h" #include "ha_subquery.h"
#include "ha_mcs_pushdown.h" #include "ha_mcs_pushdown.h"
#include "ha_tzinfo.h"
using namespace cal_impl_if; using namespace cal_impl_if;
#include "calpontselectexecutionplan.h" #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") if (funcName == "sysdate")
{ {
gwi.no_parm_func_list.push_back(fc); gwi.no_parm_func_list.push_back(fc);

600
dbcon/mysql/ha_tzinfo.cpp Normal file
View 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
View 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

View File

@ -53,6 +53,9 @@
#include "common/branchpred.h" #include "common/branchpred.h"
#include "bytestream.h"
#include "errorids.h"
// remove this block if the htonll is defined in library // remove this block if the htonll is defined in library
#ifdef __linux__ #ifdef __linux__
#include <endian.h> #include <endian.h>
@ -80,6 +83,9 @@ inline uint64_t htonll(uint64_t n)
#endif //_MSC_VER #endif //_MSC_VER
#endif //__linux__ #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 // this method evalutes the uint64 that stores a char[] to expected value
inline uint64_t uint64ToStr(uint64_t n) 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 * This function converts the timezone represented as a string
* in the format "+HH:MM" or "-HH:MM" to a signed offset in seconds * 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)) if (timeZoneToOffset(timeZone.c_str(), timeZone.size(), &offset))
{ {
isValid = false; isValid = false;
return 0; return -1;
} }
seconds = secSinceEpoch(time.year, time.month, time.day, seconds = secSinceEpoch(time.year, time.month, time.day,
time.hour, time.minute, time.second) - offset; time.hour, time.minute, time.second) - offset;
} }
/* make sure we have legit timestamps (i.e. we didn't over/underflow anywhere above) */ /* make sure we have legit timestamps (i.e. we didn't over/underflow anywhere above) */
if (seconds >= MIN_TIMESTAMP_VALUE && seconds <= MAX_TIMESTAMP_VALUE) if (seconds >= MIN_TIMESTAMP_VALUE && seconds <= MAX_TIMESTAMP_VALUE)
return seconds; 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 /** @brief a structure to hold a date
*/ */
struct Date struct Date

View File

@ -24,6 +24,7 @@ set(funcexp_LIB_SRCS
func_concat_oracle.cpp func_concat_oracle.cpp
func_concat_ws.cpp func_concat_ws.cpp
func_conv.cpp func_conv.cpp
func_convert_tz.cpp
func_crc32.cpp func_crc32.cpp
func_date.cpp func_date.cpp
func_date_add.cpp func_date_add.cpp

View 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:

View File

@ -104,6 +104,7 @@ FuncExp::FuncExp()
fFuncMap["cos"] = new Func_cos(); fFuncMap["cos"] = new Func_cos();
fFuncMap["cot"] = new Func_cot(); fFuncMap["cot"] = new Func_cot();
fFuncMap["convert"] = new Func_cast_char(); //dlh fFuncMap["convert"] = new Func_cast_char(); //dlh
fFuncMap["convert_tz"] = new Func_convert_tz(); //BT
fFuncMap["crc32"] = new Func_crc32(); fFuncMap["crc32"] = new Func_crc32();
fFuncMap["date_add_interval"] = new Func_date_add(); //dlh fFuncMap["date_add_interval"] = new Func_date_add(); //dlh
fFuncMap["date_format"] = new Func_date_format(); fFuncMap["date_format"] = new Func_date_format();

View File

@ -551,7 +551,32 @@ public:
execplan::CalpontSystemCatalog::ColType& op_ct); 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);
};
} }