1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-05-31 11:41:14 +03:00
Andrew Hutchings 5f70568d5c MCOL-325 Fix WEEK() and YEARWEEK()
The flags mask logic for the week function was incorrect. This patch
fixes it so that the behaviour is the same as MariaDB's
2016-09-27 14:32:26 +01:00

732 lines
16 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. */
// $Id: funchelpers.h 3921 2013-06-19 18:59:56Z bwilkinson $
/** @file */
#ifndef FUNCHELPERS_H__
#define FUNCHELPERS_H__
#include <string>
#ifndef _MSC_VER
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#endif
#include <inttypes.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/regex.hpp>
#include <boost/tokenizer.hpp>
#include "dataconvert.h"
#include "operator.h"
#include "intervalcolumn.h"
#include "treenode.h"
#define ULONGLONG_MAX ulonglong_max
namespace funcexp
{
namespace helpers
{
// 10 ** i
const int64_t powerOf10_c[] = {
1ll,
10ll,
100ll,
1000ll,
10000ll,
100000ll,
1000000ll,
10000000ll,
100000000ll,
1000000000ll,
10000000000ll,
100000000000ll,
1000000000000ll,
10000000000000ll,
100000000000000ll,
1000000000000000ll,
10000000000000000ll,
100000000000000000ll,
1000000000000000000ll };
// max integer number of i digits
const int64_t maxNumber_c[] = {
0ll,
9ll,
99ll,
999ll,
9999ll,
99999ll,
999999ll,
9999999ll,
99999999ll,
999999999ll,
9999999999ll,
99999999999ll,
999999999999ll,
9999999999999ll,
99999999999999ll,
999999999999999ll,
9999999999999999ll,
99999999999999999ll,
999999999999999999ll };
const uint32_t TIMESTAMP_MAX_YEAR = 2038;
const uint32_t TIMESTAMP_MIN_YEAR = (1970 - 1);
const int TIMESTAMP_MIN_VALUE = 1;
const int64_t TIMESTAMP_MAX_VALUE = 0x7FFFFFFFL;
const unsigned long long MAX_NEGATIVE_NUMBER = 0x8000000000000000ULL;
const long long LONGLONG_MIN = 0x8000000000000000LL;
const int INIT_CNT = 9;
const unsigned long LFACTOR = 1000000000;
const unsigned long long LFACTOR1 = 10000000000ULL;
const unsigned long long LFACTOR2 = 100000000000ULL;
const unsigned long long ulonglong_max= ~(unsigned long long) 0;
static std::string monthFullNames[13] =
{
"NON_VALID",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
static std::string monthAbNames[13] =
{
"NON_VALID",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
static std::string weekdayFullNames[8] =
{
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
static std::string weekdayAbNames[8] =
{
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
static std::string dayOfMonth[32] =
{"0th", "1st", "2nd", "3rd", "4th",
"5th", "6th", "7th", "8th", "9th",
"10th", "11th", "12th", "13th", "14th",
"15th", "16th", "17th", "18th", "19th", "20th",
"21st", "22nd", "23rd", "24th", "25th",
"26th", "27th", "28th", "29th", "30th",
"31st"
};
static uint8_t days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
// Given a date, calculate the number of days since year 0
// This is a mirror of calc_daynr, at a later date we should use my_time.h
inline uint32_t calc_mysql_daynr( uint32_t year, uint32_t month, uint32_t day )
{
int temp;
int y = year;
long delsum;
if( !dataconvert::isDateValid( day, month, year ) )
return 0;
delsum= (long) (365 * y + 31 *((int) month - 1) + (int) day);
if (month <= 2)
y--;
else
delsum-= (long) ((int) month * 4 + 23) / 10;
temp=(int) ((y/100+1)*3)/4;
return delsum+(int) y/4-temp;
}
// used by get_date_from_mysql_daynr() and calc_mysql_week()
inline uint32_t calc_mysql_days_in_year(uint32_t year)
{
return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
366 : 365);
}
// convert from a MySQL day number (offset from year 0) to a date
// This is a mirror of get_date_from_daynr, at a later date we should use sql_time.h
inline void get_date_from_mysql_daynr(long daynr,dataconvert::DateTime & dateTime)
{
uint32_t year, temp, leap_day, day_of_year, days_in_year;
uint8_t *month_pos;
uint32_t ret_year, ret_month, ret_day;
const int MAX_DAY_NUMBER= 3652424;
if (daynr < 366 || daynr > MAX_DAY_NUMBER)
{
dateTime.year= dateTime.month = dateTime.day =0;
return;
}
year= (uint32_t) (daynr*100 / 36525L);
temp=(((year-1)/100+1)*3)/4;
day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp;
while (day_of_year > (days_in_year= calc_mysql_days_in_year(year)))
{
day_of_year-=days_in_year;
(year)++;
}
leap_day=0;
if (days_in_year == 366)
{
if (day_of_year > 31+28)
{
day_of_year--;
if (day_of_year == 31+28)
leap_day=1; /* Handle leapyears leapday */
}
}
ret_month=1;
for (month_pos= days_in_month ;
day_of_year > (uint32_t) *month_pos ;
day_of_year-= *(month_pos++), (ret_month)++)
;
ret_year=year;
ret_day=day_of_year+leap_day;
dateTime.year = ret_year;
dateTime.month = ret_month;
dateTime.day = ret_day;
}
// Returns the weekday index for a given date:
// if sundayFirst:
// 0 = Sunday, 1 = Monday, ..., 6 = Saturday
// else:
// 0 = Monday, 1 = Tuesday, ..., 6 = Sunday
// This is a mirror of calc_weekday, at a later date we should use sql_time.h
inline uint32_t calc_mysql_weekday( uint32_t year, uint32_t month, uint32_t day, bool sundayFirst )
{
if( !dataconvert::isDateValid( day, month, year ) )
return 0;
uint32_t daynr = calc_mysql_daynr(year, month, day);
return ((int) ((daynr + 5L + (sundayFirst ? 1L : 0L)) % 7));
}
// Flags for calc_mysql_week
const uint32_t WEEK_MONDAY_FIRST = 1;
const uint32_t WEEK_NO_ZERO = 2;
const uint32_t WEEK_GT_THREE_DAYS = 4;
// Takes a MySQL WEEK() function mode setting and converts to a bitmask
// used by calc_mysql_week() mirror of MariaDB's week_mode()
inline int16_t convert_mysql_mode_to_modeflags( int16_t mode )
{
if(!(mode & WEEK_MONDAY_FIRST))
mode ^= WEEK_GT_THREE_DAYS;
return mode;
}
// Returns a week index conforming to the MySQL WEEK() function. Note
// that modeflags is not the MySQL mode - it is a bitmask of the abvoe
// 3 flags. The utility function convert_mysql_mode_to_modeflags should
// be applied to the MySQL mode before calling this function (or the
// flags may be used directly). The optional argument is for callers
// that need to know the year that the week actually corresponds to -
// see MySQL documentation for how the year returned can be different
// than the year of the input date
//
// This is a mirror of calc_week, at a later date we should use sql_time.h
inline uint32_t calc_mysql_week( uint32_t year, uint32_t month, uint32_t day,
int16_t modeflags,
uint32_t* weekyear = 0 )
{
// need to make sure that the date is valid
if( !dataconvert::isDateValid( day, month, year ) )
return 0;
uint32_t days;
uint32_t daynr=calc_mysql_daynr(year,month,day);
uint32_t first_daynr=calc_mysql_daynr(year,1,1);
bool monday_first= modeflags & WEEK_MONDAY_FIRST;
bool week_year= modeflags & WEEK_NO_ZERO;
bool first_weekday= modeflags & WEEK_GT_THREE_DAYS;
uint32_t weekday=calc_mysql_weekday(year, 1, 1, !monday_first);
if (weekyear)
{
*weekyear=year;
}
if (month == 1 && day <= 7-weekday)
{
if (!week_year &&
((first_weekday && weekday != 0) ||
(!first_weekday && weekday >= 4)))
return 0;
week_year= 1;
if (weekyear)
{
(*weekyear)--;
}
year--;
first_daynr-= (days=calc_mysql_days_in_year(year));
weekday= (weekday + 53*7- days) % 7;
}
if ((first_weekday && weekday != 0) ||
(!first_weekday && weekday >= 4))
days= daynr - (first_daynr+ (7-weekday));
else
days= daynr - (first_daynr - weekday);
if (week_year && days >= 52*7)
{
weekday= (weekday + calc_mysql_days_in_year(year)) % 7;
if ((!first_weekday && weekday < 4) ||
(first_weekday && weekday == 0))
{
if (weekyear)
{
(*weekyear)++;
}
return 1;
}
}
return days/7+1;
}
inline bool calc_time_diff(int64_t time1, int64_t time2, int l_sign, long long *seconds_out, long long *microseconds_out)
{
int64_t days;
bool neg;
int64_t microseconds;
uint64_t year1 = 0,
month1 = 0,
day1 = 0,
hour1 = 0,
min1 = 0,
sec1 = 0,
msec1 = 0;
uint64_t year2 = 0,
month2 = 0,
day2 = 0,
hour2 = 0,
min2 = 0,
sec2 = 0,
msec2 = 0;
year1 = (uint32_t)((time1 >> 48) & 0xffff);
month1 = (uint32_t)((time1 >> 44) & 0xf);
day1 = (uint32_t)((time1 >> 38) & 0x3f);
hour1 = (uint32_t)((time1 >> 32) & 0x3f);
min1 = (uint32_t)((time1 >> 26) & 0x3f);
sec1 = (uint32_t)((time1 >> 20) & 0x3f);
msec1 = (uint32_t)((time1 & 0xfffff));
year2 = (uint32_t)((time2 >> 48) & 0xffff);
month2 = (uint32_t)((time2 >> 44) & 0xf);
day2 = (uint32_t)((time2 >> 38) & 0x3f);
hour2 = (uint32_t)((time2 >> 32) & 0x3f);
min2 = (uint32_t)((time2 >> 26) & 0x3f);
sec2 = (uint32_t)((time2 >> 20) & 0x3f);
msec2 = (uint32_t)((time2 & 0xfffff));
days= calc_mysql_daynr(year1, month1, day1);
days-= l_sign*calc_mysql_daynr(year2, month2, day2);
microseconds= ((long long)days*(long)(86400) +
(long long)(hour1*3600L +
min1*60L +
sec1) -
l_sign*(long long)(hour2*3600L +
min2*60L +
sec2)) * (long long)(1000000) +
(long long)msec1 -
l_sign*(long long)msec2;
neg= 0;
if (microseconds < 0)
{
microseconds= -microseconds;
neg= 1;
}
*seconds_out= microseconds/1000000L;
*microseconds_out= (long long) (microseconds%1000000L);
return neg;
}
inline int power ( int16_t a ) {
int b = 1;
for ( int i=0 ; i < a ; i++ )
{
b = b * 10;
}
return b;
}
inline void decimalPlaceDec(int64_t& d, int64_t& p, int8_t& s)
{
// find new scale if D < s
if (d < s)
{
int64_t t = s;
s = d; // the new scale
d -= t;
int64_t i = (d >= 0) ? d : (-d);
while (i--)
p *= 10;
}
else
{
d = s;
}
}
inline uint32_t convertMonth (std::string month)
{
uint32_t value = 0;
boost::algorithm::to_lower(month);
if ( month == "jan" || month == "january" )
{
value = 1;
}
else if ( month == "feb" || month == "february" )
{
value = 2;
}
else if ( month == "mar" || month == "march" )
{
value = 3;
}
else if ( month == "apr" || month == "april" )
{
value = 4;
}
else if ( month == "may" )
{
value = 5;
}
else if ( month == "jun" || month == "june" )
{
value = 6;
}
else if ( month == "jul" || month == "july" )
{
value = 7;
}
else if ( month == "aug" || month == "august" )
{
value = 8;
}
else if ( month == "sep" || month == "september" )
{
value = 9;
}
else if ( month == "oct" || month == "october" )
{
value = 10;
}
else if ( month == "nov" || month == "november" )
{
value = 11;
}
else if ( month == "dec" || month == "december" )
{
value = 12;
}
else
{
value = 0;
}
return value;
}
class to_lower
{
public:
char operator() (char c) const // notice the return type
{
return tolower(c);
}
};
inline int getNumbers( const std::string& expr, int64_t *array, execplan::OpType funcType)
{
int index = 0;
int funcNeg = 1;
if ( funcType == execplan::OP_SUB )
funcNeg = -1;
if ( expr.size() == 0 )
return 0;
// @bug 4703 reworked this code to avoid use of incrementally
// built string to hold temporary values while
// scanning expr for numbers. This function is now
// covered by a unit test in tdriver.cpp
bool foundNumber = false;
int64_t number = 0;
int neg = 1;
for ( unsigned int i=0 ; i < expr.size() ; i++ )
{
char value = expr[i];
if ( (value >= '0' && value <= '9') )
{
foundNumber = true;
number = ( number * 10 ) + ( value - '0' );
}
else if ( value == '-' && !foundNumber )
{
neg = -1;
}
else if ( value == '-')
{
// this is actually an error condition - it means that
// input came in with something like NN-NN (i.e. a dash
// between two numbers. To match prior code we will
// return the number up to the dash and just return
array[index] = number * funcNeg * neg;
index++;
return index;
}
else
{
if ( foundNumber )
{
array[index] = number * funcNeg * neg;
number = 0;
neg = 1;
index++;
if ( index > 9 )
return index;
}
}
}
if ( foundNumber )
{
array[index] = number * funcNeg * neg;
index++;
}
return index;
}
inline int getNumbers( const std::string& expr, int *array, execplan::OpType funcType)
{
int index = 0;
int funcNeg = 1;
if ( funcType == execplan::OP_SUB )
funcNeg = -1;
if ( expr.size() == 0 )
return 0;
// @bug 4703 reworked this code to avoid use of incrementally
// built string to hold temporary values while
// scanning expr for numbers. This function is now
// covered by a unit test in tdriver.cpp
bool foundNumber = false;
int number = 0;
int neg = 1;
for ( unsigned int i=0 ; i < expr.size() ; i++ )
{
char value = expr[i];
if ( (value >= '0' && value <= '9') )
{
foundNumber = true;
number = ( number * 10 ) + ( value - '0' );
}
else if ( value == '-' && !foundNumber )
{
neg = -1;
}
else if ( value == '-')
{
// this is actually an error condition - it means that
// input came in with something like NN-NN (i.e. a dash
// between two numbers. To match prior code we will
// return the number up to the dash and just return
array[index] = number * funcNeg * neg;
index++;
return index;
}
else
{
if ( foundNumber )
{
array[index] = number * funcNeg * neg;
number = 0;
neg = 1;
index++;
if ( index > 9 )
return index;
}
}
}
if ( foundNumber )
{
array[index] = number * funcNeg * neg;
index++;
}
return index;
}
inline int dayOfWeek(std::string day) //Sunday = 0
{
int value = -1;
boost::to_lower(day);
if ( day == "sunday" || day == "sun" )
{
value = 0;
}
else if ( day == "monday" || day == "mon" )
{
value = 1;
}
else if ( day == "tuesday" || day == "tue" )
{
value = 2;
}
else if ( day == "wednesday" || day == "wed" )
{
value = 3;
}
else if ( day == "thursday" || day == "thu" )
{
value = 4;
}
else if ( day == "friday" || day == "fri" )
{
value = 5;
}
else if ( day == "saturday" || day == "sat" )
{
value = 6;
}
else
{
value = -1;
}
return value;
}
inline
string intToString(int64_t i)
{
char buf[32];
#ifndef _MSC_VER
snprintf(buf, 32, "%" PRId64 "", i);
#else
snprintf(buf, 32, "%lld", i);
#endif
return buf;
}
inline
string uintToString(uint64_t i)
{
char buf[32];
#ifndef _MSC_VER
snprintf(buf, 32, "%" PRIu64 "", i);
#else
snprintf(buf, 32, "%llu", i);
#endif
return buf;
}
inline
string doubleToString(double d)
{
// double's can be *really* long to print out. Max mysql
// is e308 so allow for 308 + 36 decimal places minimum.
char buf[384];
snprintf(buf, 384, "%f", d);
return buf;
}
//@bug6146, remove duplicate function with incorrect impl. Use the DataConvert::decimalToString()
//string decimalToString( execplan::IDB_Decimal x, int p )
uint64_t dateAdd( uint64_t time, const std::string& expr, execplan::IntervalColumn::interval_type unit, bool dateType, execplan::OpType funcType );
const std::string IDB_date_format(const dataconvert::DateTime&, const std::string&);
const std::string timediff(int64_t, int64_t);
const char *convNumToStr(int64_t, char*, int);
} //namespace funcexp::helpers
} //namespace funcexp
#endif