1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-04-18 21:44:02 +03:00
2022-01-21 16:43:49 +00:00

574 lines
18 KiB
C++

/* 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);
}
} // namespace cal_impl_if