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

520 lines
13 KiB
C++

/* Copyright (C) 2014 InfiniDB, Inc.
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 <string>
#include "funchelpers.h"
#include "dataconvert.h"
namespace funcexp
{
class TimeExtractor
{
private:
int32_t dayOfWeek;
int32_t dayOfYear;
int32_t weekOfYear;
bool sundayFirst;
public:
TimeExtractor() : dayOfWeek(-1), dayOfYear(-1), weekOfYear(-1), sundayFirst(false)
{
;
}
/**
* extractTime is an implementation that matches MySQL behavior for the
* STR_TO_DATE() function. See MySQL documentation for details.
*
* Returns 0 on success, -1 on failure. On failure the DateTime is
* reset to 0s in all fields.
*/
int extractTime(const std::string& valStr, const std::string& formatStr, dataconvert::DateTime& dateTime)
{
uint32_t fcur = 0;
uint32_t vcur = 0;
while (fcur != formatStr.length())
{
if (!handleNextToken(valStr, vcur, formatStr, fcur, dateTime))
return returnError(dateTime);
}
if (dayOfYear > 0)
{
// they set day of year - we also need to make sure there is a year to work with
if (!dataconvert::isDateValid(1, 1, dateTime.year))
return returnError(dateTime);
helpers::get_date_from_mysql_daynr(helpers::calc_mysql_daynr(dateTime.year, 1, 1) + dayOfYear - 1,
dateTime);
}
else if (weekOfYear > 0)
{
if (dayOfWeek < 0 || !dataconvert::isDateValid(1, 1, dateTime.year))
return returnError(dateTime);
uint32_t yearfirst = helpers::calc_mysql_daynr(dateTime.year, 1, 1);
// figure out which day of week Jan-01 is
uint32_t firstweekday = helpers::calc_mysql_weekday(dateTime.year, 1, 1, sundayFirst);
// calculate the offset to the first week starting day
uint32_t firstoffset = firstweekday ? (7 - firstweekday) : 0;
firstoffset += ((weekOfYear - 1) * 7) + dayOfWeek - (sundayFirst ? 0 : 1);
yearfirst += firstoffset;
helpers::get_date_from_mysql_daynr(yearfirst, dateTime);
}
if (!dataconvert::isDateTimeValid(dateTime.hour, dateTime.minute, dateTime.second, dateTime.msecond))
return returnError(dateTime);
return 0;
}
private:
int returnError(dataconvert::DateTime& dateTime)
{
(*(reinterpret_cast<uint64_t*>(&dateTime))) = (uint64_t)0;
return -1;
}
bool scanDecimalVal(const char* nptr, const char** endptr, int32_t& value)
{
value = 0;
const char* p = nptr;
while (p < *endptr && isdigit(*p))
{
value = value * 10 + ((*p) - '0');
++p;
}
*endptr = p;
return (*endptr != nptr);
}
bool handleNextToken(const std::string& valStr, uint32_t& vptr, const std::string& formatStr,
uint32_t& fptr, dataconvert::DateTime& dateTime)
{
// advance both strings to the first non-whitespace character
while (valStr[vptr] == ' ' && vptr < valStr.length())
++vptr;
bool vend = (vptr == valStr.length());
while (formatStr[fptr] == ' ' && fptr < formatStr.length())
++fptr;
bool fend = (fptr == formatStr.length());
if (vend && fend)
{
// apparent trailing whitespace
return true;
}
else if (!vend && !fend)
{
if (formatStr[fptr] == '%')
{
// has to be at least one more character in format string
if (fptr >= formatStr.length() - 1)
return false;
fptr++; // skip over %
// check for special case of %%
if (formatStr[fptr] == '%')
{
bool ret = formatStr[fptr] == valStr[vptr];
++fptr;
++vptr;
return ret;
}
else
{
char field = formatStr[fptr];
++fptr; // also skip the format code
bool ret = handleField(field, valStr, vptr, dateTime);
return ret;
}
}
else
{
bool ret = formatStr[fptr] == valStr[vptr];
++fptr;
++vptr;
return ret;
}
}
else
{
// one string finish before the other one - not good
return false;
}
}
bool handleField(char field, const std::string& valStr, uint32_t& vptr, dataconvert::DateTime& dateTime)
{
int32_t value;
const char* valptr = valStr.c_str() + vptr;
switch (field)
{
case 'a':
{
// weekday abbreviations are always exactly 3 characters
std::string weekday_str(valStr, vptr, 3);
vptr += 3;
dayOfWeek = helpers::dayOfWeek(weekday_str);
if (dayOfWeek < 0)
{
return false;
}
break;
}
case 'b':
{
// month abbreviations are always exactly 3 characters
std::string month_str(valStr, vptr, 3);
vptr += 3;
value = helpers::convertMonth(month_str);
if (value < 0)
{
return false;
}
else
{
dateTime.month = value;
}
break;
}
case 'c':
case 'm':
{
// Month, numeric (0..12)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
// we have to do range checking on the month value here because
// dateTime will arbitrarily truncate to 4 bits and may turn a
// bad value into a good one
if (value < 0 || value > 12)
return false;
dateTime.month = value;
break;
}
case 'D':
case 'd':
case 'e':
{
// %D - Day of the month with English suffix (0th, 1st, 2nd, 3rd, …)
// %d - Day of the month, numeric (00..31)
// %e - Day of the month, numeric (0..31)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
// now also skip suffix if required - always 2 characters
if (field == 'D')
vptr += 2;
// we have to do range checking on the month value here because
// dateTime will arbitrarily truncate to 6 bits and may turn a
// bad value into a good one
if (value < 0 || value > 31)
return false;
dateTime.day = value;
break;
}
case 'f':
{
// Microseconds (000000..999999)
const char* vend = valptr + min(6, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
for (int i = (vend - valptr); i < 6; ++i)
value = value * 10;
dateTime.msecond = value;
break;
}
case 'H':
case 'k':
{
// Hour (00..23)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
dateTime.hour = value;
break;
}
case 'h':
case 'I':
case 'l':
{
// Hour (01..12)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
dateTime.hour = value;
break;
}
case 'i':
{
// Minutes, numeric (00..59)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
dateTime.minute = value;
break;
}
case 'j':
{
// Day of year (001..366)
const char* vend = valptr + min(3, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
if (value < 1 || value > 366)
return false;
dayOfYear = value;
break;
}
case 'M':
{
// Month name (January..December)
// look for the first non-alphabetic character
size_t endpos = vptr;
while (tolower(valStr[endpos]) >= 'a' && tolower(valStr[endpos]) <= 'z' && endpos < valStr.length())
++endpos;
std::string month_str(valStr, vptr, endpos);
vptr += month_str.length();
value = helpers::convertMonth(month_str);
if (value < 0)
{
return false;
}
else
{
dateTime.month = value;
}
break;
}
case 'p':
{
// AM or PM
if (tolower(valStr[vptr]) == 'p' && tolower(valStr[vptr + 1]) == 'm')
{
if (dateTime.hour < 12)
dateTime.hour += 12;
}
else if (tolower(valStr[vptr]) == 'a' && tolower(valStr[vptr + 1]) == 'm')
{
if (dateTime.hour == 12)
dateTime.hour = 0;
}
else
{
vptr += 2;
return false;
}
vptr += 2;
break;
}
case 'r':
case 'T':
{
// Time, 12-hour (hh:mm:ss followed by AM or PM)
// Time, 24-hour (hh:mm:ss)
int32_t hour = -1;
int32_t min = -1;
int32_t sec = -1;
int32_t numread;
int32_t sscanf_ck;
if ((sscanf_ck = sscanf(valptr, "%2d:%2d:%2d%n", &hour, &min, &sec, &numread)) != 3)
{
return false;
}
valptr += numread;
vptr += numread;
dateTime.hour = hour;
dateTime.minute = min;
dateTime.second = sec;
break;
}
case 'S':
case 's':
{
// Seconds (00..59)
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
dateTime.second = value;
break;
}
case 'U':
case 'u':
case 'V':
case 'v':
{
// %U - Week (00..53), where Sunday is the first day of the week
// %u - Week (00..53), where Monday is the first day of the week
// %V - Week (01..53), where Sunday is the first day of the week; used with %X
// %v - Week (01..53), where Monday is the first day of the week; used with %x
sundayFirst = (isupper(field) != 0);
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
weekOfYear = value;
break;
}
case 'X':
case 'x':
case 'Y':
case 'y':
{
// %X - Year for the week where Sunday is the first day of the week, numeric, four digits; used with
// %V %x - Year for the week, where Monday is the first day of the week, numeric, four digits; used
// with %v %Y - Year, numeric, four digits %y - Year, numeric (two digits)
sundayFirst = (field == 'X');
int minFieldWidth = (field == 'y' ? 2 : 4);
const char* vend = valptr + min(minFieldWidth, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
if ((vend - valptr) <= 2)
{
// regardless of whether field was supposed to be 4 characters.
// If we read two then apply year 2000 handling
value += 2000;
if (value > 2069)
value -= 100;
}
dateTime.year = value;
break;
}
case 'W':
{
// Weekday name (Sunday..Saturday)
// look for the first non-alphabetic character
size_t endpos = vptr;
while (tolower(valStr[endpos]) >= 'a' && tolower(valStr[endpos]) <= 'z' && endpos < valStr.length())
++endpos;
std::string weekday_str(valStr, vptr, endpos);
vptr += weekday_str.length();
value = helpers::dayOfWeek(weekday_str);
if (value < 0)
{
return false;
}
else
{
dayOfWeek = value;
}
break;
}
case 'w':
{
// Day of the week (0=Sunday..6=Saturday)
const char* vend = valptr + min(1, (int)(valStr.length() - vptr));
if (!scanDecimalVal(valptr, &vend, value))
return false;
vptr += (vend - valptr);
dayOfWeek = value;
break;
}
default: return false;
}
return true;
}
};
} // namespace funcexp