From 923bbf4033a8b3ac1d1215359dec4f195e53df2c Mon Sep 17 00:00:00 2001 From: benthompson15 Date: Thu, 19 Aug 2021 17:47:10 -0500 Subject: [PATCH] MCOL-1356: Add convert_tz (#2099) --- dbcon/mysql/CMakeLists.txt | 1 + dbcon/mysql/ha_mcs_execplan.cpp | 58 +++ dbcon/mysql/ha_tzinfo.cpp | 600 ++++++++++++++++++++++++++++++ dbcon/mysql/ha_tzinfo.h | 34 ++ utils/dataconvert/dataconvert.h | 384 ++++++++++++++++++- utils/funcexp/CMakeLists.txt | 1 + utils/funcexp/func_convert_tz.cpp | 232 ++++++++++++ utils/funcexp/funcexp.cpp | 1 + utils/funcexp/functor_dtm.h | 25 ++ 9 files changed, 1334 insertions(+), 2 deletions(-) create mode 100644 dbcon/mysql/ha_tzinfo.cpp create mode 100644 dbcon/mysql/ha_tzinfo.h create mode 100644 utils/funcexp/func_convert_tz.cpp diff --git a/dbcon/mysql/CMakeLists.txt b/dbcon/mysql/CMakeLists.txt index ded9c6f4b..96abc5a25 100644 --- a/dbcon/mysql/CMakeLists.txt +++ b/dbcon/mysql/CMakeLists.txt @@ -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 diff --git a/dbcon/mysql/ha_mcs_execplan.cpp b/dbcon/mysql/ha_mcs_execplan.cpp index bb90a5b59..c7e77e810 100755 --- a/dbcon/mysql/ha_mcs_execplan.cpp +++ b/dbcon/mysql/ha_mcs_execplan.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(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(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(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(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); diff --git a/dbcon/mysql/ha_tzinfo.cpp b/dbcon/mysql/ha_tzinfo.cpp new file mode 100644 index 000000000..be9a70a41 --- /dev/null +++ b/dbcon/mysql/ha_tzinfo.cpp @@ -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 + +/* 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 + +//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); +} + +} diff --git a/dbcon/mysql/ha_tzinfo.h b/dbcon/mysql/ha_tzinfo.h new file mode 100644 index 000000000..4f428651a --- /dev/null +++ b/dbcon/mysql/ha_tzinfo.h @@ -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 +#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 diff --git a/utils/dataconvert/dataconvert.h b/utils/dataconvert/dataconvert.h index a1b413dbc..4ba5b5ce8 100644 --- a/utils/dataconvert/dataconvert.h +++ b/utils/dataconvert/dataconvert.h @@ -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 @@ -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 diff --git a/utils/funcexp/CMakeLists.txt b/utils/funcexp/CMakeLists.txt index 0d47d21ed..60e6483d8 100644 --- a/utils/funcexp/CMakeLists.txt +++ b/utils/funcexp/CMakeLists.txt @@ -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 diff --git a/utils/funcexp/func_convert_tz.cpp b/utils/funcexp/func_convert_tz.cpp new file mode 100644 index 000000000..1a58c0612 --- /dev/null +++ b/utils/funcexp/func_convert_tz.cpp @@ -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 +#include +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 ats; + std::vector types; + std::vector ttis; +#ifdef ABBR_ARE_USED + std::vector chars; +#endif + std::vector lsis; + std::vector revts; + std::vector revtis; + std::vector 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(bs, ats); + tzinfo.ats = ats.data(); + deserializeInlineVector(bs, types); + tzinfo.types = types.data(); + deserializeInlineVector(bs, ttis); + tzinfo.ttis = ttis.data(); +#ifdef ABBR_ARE_USED + deserializeInlineVector(bs_fromTZ, chars); + tzinfo.chars = chars.data(); +#endif + deserializeInlineVector(bs, lsis); + tzinfo.lsis = lsis.data(); + deserializeInlineVector(bs, revts); + tzinfo.revts = revts.data(); + deserializeInlineVector(bs, revtis); + tzinfo.revtis = revtis.data(); + deserializeInlineVector(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(bs, ats); + tzinfo.ats = ats.data(); + deserializeInlineVector(bs, types); + tzinfo.types = types.data(); + deserializeInlineVector(bs, ttis); + tzinfo.ttis = ttis.data(); +#ifdef ABBR_ARE_USED + deserializeInlineVector(bs_fromTZ, chars); + tzinfo.chars = chars.data(); +#endif + deserializeInlineVector(bs, lsis); + tzinfo.lsis = lsis.data(); + deserializeInlineVector(bs, revts); + tzinfo.revts = revts.data(); + deserializeInlineVector(bs, revtis); + tzinfo.revtis = revtis.data(); + deserializeInlineVector(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: diff --git a/utils/funcexp/funcexp.cpp b/utils/funcexp/funcexp.cpp index 1b80fa978..5f5881fb4 100644 --- a/utils/funcexp/funcexp.cpp +++ b/utils/funcexp/funcexp.cpp @@ -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(); diff --git a/utils/funcexp/functor_dtm.h b/utils/funcexp/functor_dtm.h index f9b61ee80..c9d144ac3 100644 --- a/utils/funcexp/functor_dtm.h +++ b/utils/funcexp/functor_dtm.h @@ -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); +}; }