mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
e2da748c29
Problem: Item_func_date_format::val_str() and make_date_time() did not take into account that the format string and the result string (separately or at the same time) can be of a tricky character set like UCS2, UTF16, UTF32. As a result, DATE_FORMAT() could generate an ill-formed result which crashed on DBUG_ASSERTs testing well-formedness in other parts of the code. Fix: 1. class String changes Removing String::append_with_prefill(). It was not compatible with tricky character sets. Also it was inconvenient to use and required too much duplicate code on the caller side. Adding String::append_zerofill() instead. It's compatible with tricky character sets and is easier to use. Adding helper methods Static_binary_string::q_append_wc() and String::append_wc(), to append a single wide character (a Unicode code point in my_wc_t). 2. storage/spider changes Removing spider_string::append_with_prefill(). It used String::append_with_prefix() inside, but it was unused itself. 3. Changing tricky charset incompatible code pieces in make_date_time() to compatible replacements: - Fixing the loop scanning the format string to iterate in terms of Unicode code points (using mb_wc()) rather than in terms of "char" items. - Using append_wc(my_wc_t) instead of append(char) to append a single character to the result string. - Using append_zerofill() instead of append_with_prefill() to append date/time numeric components to the result string.
3014 lines
87 KiB
C++
3014 lines
87 KiB
C++
/*
|
|
Copyright (c) 2000, 2012, Oracle and/or its affiliates.
|
|
Copyright (c) 2009, 2020, MariaDB
|
|
|
|
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 St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
|
|
/**
|
|
@file
|
|
|
|
@brief
|
|
This file defines all time functions
|
|
|
|
@todo
|
|
Move month and days to language files
|
|
*/
|
|
|
|
#ifdef USE_PRAGMA_IMPLEMENTATION
|
|
#pragma implementation // gcc: Class implementation
|
|
#endif
|
|
|
|
#include "mariadb.h"
|
|
#include "sql_priv.h"
|
|
/*
|
|
It is necessary to include set_var.h instead of item.h because there
|
|
are dependencies on include order for set_var.h and item.h. This
|
|
will be resolved later.
|
|
*/
|
|
#include "sql_class.h" // set_var.h: THD
|
|
#include "set_var.h"
|
|
#include "sql_locale.h" // MY_LOCALE my_locale_en_US
|
|
#include "strfunc.h" // check_word
|
|
#include "sql_type_int.h" // Longlong_hybrid
|
|
#include "sql_time.h" // make_truncated_value_warning,
|
|
// get_date_from_daynr,
|
|
// calc_weekday, calc_week,
|
|
// convert_month_to_period,
|
|
// convert_period_to_month,
|
|
// TIME_to_timestamp,
|
|
// calc_time_diff,
|
|
// calc_time_from_sec,
|
|
// get_date_time_format_str
|
|
#include "tztime.h" // struct Time_zone
|
|
#include "sql_class.h" // THD
|
|
#include <m_ctype.h>
|
|
#include <time.h>
|
|
|
|
/** Day number for Dec 31st, 9999. */
|
|
#define MAX_DAY_NUMBER 3652424L
|
|
|
|
|
|
Func_handler_date_add_interval_datetime_arg0_time
|
|
func_handler_date_add_interval_datetime_arg0_time;
|
|
|
|
Func_handler_date_add_interval_datetime func_handler_date_add_interval_datetime;
|
|
Func_handler_date_add_interval_date func_handler_date_add_interval_date;
|
|
Func_handler_date_add_interval_time func_handler_date_add_interval_time;
|
|
Func_handler_date_add_interval_string func_handler_date_add_interval_string;
|
|
|
|
Func_handler_add_time_datetime func_handler_add_time_datetime_add(1);
|
|
Func_handler_add_time_datetime func_handler_add_time_datetime_sub(-1);
|
|
Func_handler_add_time_time func_handler_add_time_time_add(1);
|
|
Func_handler_add_time_time func_handler_add_time_time_sub(-1);
|
|
Func_handler_add_time_string func_handler_add_time_string_add(1);
|
|
Func_handler_add_time_string func_handler_add_time_string_sub(-1);
|
|
|
|
Func_handler_str_to_date_datetime_sec func_handler_str_to_date_datetime_sec;
|
|
Func_handler_str_to_date_datetime_usec func_handler_str_to_date_datetime_usec;
|
|
Func_handler_str_to_date_date func_handler_str_to_date_date;
|
|
Func_handler_str_to_date_time_sec func_handler_str_to_date_time_sec;
|
|
Func_handler_str_to_date_time_usec func_handler_str_to_date_time_usec;
|
|
|
|
|
|
/*
|
|
Date formats corresponding to compound %r and %T conversion specifiers
|
|
|
|
Note: We should init at least first element of "positions" array
|
|
(first member) or hpux11 compiler will die horribly.
|
|
*/
|
|
static DATE_TIME_FORMAT time_ampm_format= {{0}, '\0', 0,
|
|
{(char *)"%I:%i:%S %p", 11}};
|
|
static DATE_TIME_FORMAT time_24hrs_format= {{0}, '\0', 0,
|
|
{(char *)"%H:%i:%S", 8}};
|
|
|
|
/**
|
|
Extract datetime value to MYSQL_TIME struct from string value
|
|
according to format string.
|
|
|
|
@param format date/time format specification
|
|
@param val String to decode
|
|
@param length Length of string
|
|
@param l_time Store result here
|
|
@param cached_timestamp_type It uses to get an appropriate warning
|
|
in the case when the value is truncated.
|
|
@param sub_pattern_end if non-zero then we are parsing string which
|
|
should correspond compound specifier (like %T or
|
|
%r) and this parameter is pointer to place where
|
|
pointer to end of string matching this specifier
|
|
should be stored.
|
|
|
|
@note
|
|
Possibility to parse strings matching to patterns equivalent to compound
|
|
specifiers is mainly intended for use from inside of this function in
|
|
order to understand %T and %r conversion specifiers, so number of
|
|
conversion specifiers that can be used in such sub-patterns is limited.
|
|
Also most of checks are skipped in this case.
|
|
|
|
@note
|
|
If one adds new format specifiers to this function he should also
|
|
consider adding them to get_date_time_result_type() function.
|
|
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 error
|
|
*/
|
|
|
|
static bool extract_date_time(THD *thd, DATE_TIME_FORMAT *format,
|
|
const char *val, uint length, MYSQL_TIME *l_time,
|
|
timestamp_type cached_timestamp_type,
|
|
const char **sub_pattern_end,
|
|
const char *date_time_type,
|
|
date_conv_mode_t fuzzydate)
|
|
{
|
|
int weekday= 0, yearday= 0, daypart= 0;
|
|
int week_number= -1;
|
|
int error= 0;
|
|
int strict_week_number_year= -1;
|
|
int frac_part;
|
|
bool usa_time= 0;
|
|
bool UNINIT_VAR(sunday_first_n_first_week_non_iso);
|
|
bool UNINIT_VAR(strict_week_number);
|
|
bool UNINIT_VAR(strict_week_number_year_type);
|
|
const char *val_begin= val;
|
|
const char *val_end= val + length;
|
|
const char *ptr= format->format.str;
|
|
const char *end= ptr + format->format.length;
|
|
CHARSET_INFO *cs= &my_charset_bin;
|
|
DBUG_ENTER("extract_date_time");
|
|
|
|
if (!sub_pattern_end)
|
|
bzero((char*) l_time, sizeof(*l_time));
|
|
|
|
l_time->time_type= cached_timestamp_type;
|
|
|
|
for (; ptr != end && val != val_end; ptr++)
|
|
{
|
|
/* Skip pre-space between each argument */
|
|
if ((val+= cs->cset->scan(cs, val, val_end, MY_SEQ_SPACES)) >= val_end)
|
|
break;
|
|
|
|
if (*ptr == '%' && ptr+1 != end)
|
|
{
|
|
int val_len;
|
|
char *tmp;
|
|
|
|
error= 0;
|
|
|
|
val_len= (uint) (val_end - val);
|
|
switch (*++ptr) {
|
|
/* Year */
|
|
case 'Y':
|
|
tmp= (char*) val + MY_MIN(4, val_len);
|
|
l_time->year= (int) my_strtoll10(val, &tmp, &error);
|
|
if ((int) (tmp-val) <= 2)
|
|
l_time->year= year_2000_handling(l_time->year);
|
|
val= tmp;
|
|
break;
|
|
case 'y':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->year= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
l_time->year= year_2000_handling(l_time->year);
|
|
break;
|
|
|
|
/* Month */
|
|
case 'm':
|
|
case 'c':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->month= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
case 'M':
|
|
if ((l_time->month= check_word(my_locale_en_US.month_names,
|
|
val, val_end, &val)) <= 0)
|
|
goto err;
|
|
break;
|
|
case 'b':
|
|
if ((l_time->month= check_word(my_locale_en_US.ab_month_names,
|
|
val, val_end, &val)) <= 0)
|
|
goto err;
|
|
break;
|
|
/* Day */
|
|
case 'd':
|
|
case 'e':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->day= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
case 'D':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->day= (int) my_strtoll10(val, &tmp, &error);
|
|
/* Skip 'st, 'nd, 'th .. */
|
|
val= tmp + MY_MIN((int) (val_end-tmp), 2);
|
|
break;
|
|
|
|
/* Hour */
|
|
case 'h':
|
|
case 'I':
|
|
case 'l':
|
|
usa_time= 1;
|
|
/* fall through */
|
|
case 'k':
|
|
case 'H':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->hour= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Minute */
|
|
case 'i':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->minute= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Second */
|
|
case 's':
|
|
case 'S':
|
|
tmp= (char*) val + MY_MIN(2, val_len);
|
|
l_time->second= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Second part */
|
|
case 'f':
|
|
tmp= (char*) val_end;
|
|
if (tmp - val > 6)
|
|
tmp= (char*) val + 6;
|
|
l_time->second_part= (int) my_strtoll10(val, &tmp, &error);
|
|
frac_part= 6 - (int) (tmp - val);
|
|
if (frac_part > 0)
|
|
l_time->second_part*= (ulong) log_10_int[frac_part];
|
|
val= tmp;
|
|
break;
|
|
|
|
/* AM / PM */
|
|
case 'p':
|
|
if (val_len < 2 || ! usa_time)
|
|
goto err;
|
|
if (!my_strnncoll(&my_charset_latin1,
|
|
(const uchar *) val, 2,
|
|
(const uchar *) "PM", 2))
|
|
daypart= 12;
|
|
else if (my_strnncoll(&my_charset_latin1,
|
|
(const uchar *) val, 2,
|
|
(const uchar *) "AM", 2))
|
|
goto err;
|
|
val+= 2;
|
|
break;
|
|
|
|
/* Exotic things */
|
|
case 'W':
|
|
if ((weekday= check_word(my_locale_en_US.day_names, val, val_end, &val)) <= 0)
|
|
goto err;
|
|
break;
|
|
case 'a':
|
|
if ((weekday= check_word(my_locale_en_US.ab_day_names, val, val_end, &val)) <= 0)
|
|
goto err;
|
|
break;
|
|
case 'w':
|
|
tmp= (char*) val + 1;
|
|
if (unlikely((weekday= (int) my_strtoll10(val, &tmp, &error)) < 0 ||
|
|
weekday >= 7))
|
|
goto err;
|
|
/* We should use the same 1 - 7 scale for %w as for %W */
|
|
if (!weekday)
|
|
weekday= 7;
|
|
val= tmp;
|
|
break;
|
|
case 'j':
|
|
tmp= (char*) val + MY_MIN(val_len, 3);
|
|
yearday= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Week numbers */
|
|
case 'V':
|
|
case 'U':
|
|
case 'v':
|
|
case 'u':
|
|
sunday_first_n_first_week_non_iso= (*ptr=='U' || *ptr== 'V');
|
|
strict_week_number= (*ptr=='V' || *ptr=='v');
|
|
tmp= (char*) val + MY_MIN(val_len, 2);
|
|
if (unlikely((week_number=
|
|
(int) my_strtoll10(val, &tmp, &error)) < 0 ||
|
|
(strict_week_number && !week_number) ||
|
|
week_number > 53))
|
|
goto err;
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Year used with 'strict' %V and %v week numbers */
|
|
case 'X':
|
|
case 'x':
|
|
strict_week_number_year_type= (*ptr=='X');
|
|
tmp= (char*) val + MY_MIN(4, val_len);
|
|
strict_week_number_year= (int) my_strtoll10(val, &tmp, &error);
|
|
val= tmp;
|
|
break;
|
|
|
|
/* Time in AM/PM notation */
|
|
case 'r':
|
|
/*
|
|
We can't just set error here, as we don't want to generate two
|
|
warnings in case of errors
|
|
*/
|
|
if (extract_date_time(thd, &time_ampm_format, val,
|
|
(uint)(val_end - val), l_time,
|
|
cached_timestamp_type, &val, "time", fuzzydate))
|
|
DBUG_RETURN(1);
|
|
break;
|
|
|
|
/* Time in 24-hour notation */
|
|
case 'T':
|
|
if (extract_date_time(thd, &time_24hrs_format, val,
|
|
(uint)(val_end - val), l_time,
|
|
cached_timestamp_type, &val, "time", fuzzydate))
|
|
DBUG_RETURN(1);
|
|
break;
|
|
|
|
/* Conversion specifiers that match classes of characters */
|
|
case '.':
|
|
while (my_ispunct(cs, *val) && val != val_end)
|
|
val++;
|
|
break;
|
|
case '@':
|
|
while (my_isalpha(cs, *val) && val != val_end)
|
|
val++;
|
|
break;
|
|
case '#':
|
|
while (my_isdigit(cs, *val) && val != val_end)
|
|
val++;
|
|
break;
|
|
default:
|
|
goto err;
|
|
}
|
|
if (unlikely(error)) // Error from my_strtoll10
|
|
goto err;
|
|
}
|
|
else if (!my_isspace(cs, *ptr))
|
|
{
|
|
if (*val != *ptr)
|
|
goto err;
|
|
val++;
|
|
}
|
|
}
|
|
if (usa_time)
|
|
{
|
|
if (l_time->hour > 12 || l_time->hour < 1)
|
|
goto err;
|
|
l_time->hour= l_time->hour%12+daypart;
|
|
}
|
|
|
|
/*
|
|
If we are recursively called for parsing string matching compound
|
|
specifiers we are already done.
|
|
*/
|
|
if (sub_pattern_end)
|
|
{
|
|
*sub_pattern_end= val;
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
if (yearday > 0)
|
|
{
|
|
uint days;
|
|
days= calc_daynr(l_time->year,1,1) + yearday - 1;
|
|
if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
|
|
goto err;
|
|
}
|
|
|
|
if (week_number >= 0 && weekday)
|
|
{
|
|
int days;
|
|
uint weekday_b;
|
|
|
|
/*
|
|
%V,%v require %X,%x resprectively,
|
|
%U,%u should be used with %Y and not %X or %x
|
|
*/
|
|
if ((strict_week_number &&
|
|
(strict_week_number_year < 0 ||
|
|
strict_week_number_year_type !=
|
|
sunday_first_n_first_week_non_iso)) ||
|
|
(!strict_week_number && strict_week_number_year >= 0))
|
|
goto err;
|
|
|
|
/* Number of days since year 0 till 1st Jan of this year */
|
|
days= calc_daynr((strict_week_number ? strict_week_number_year :
|
|
l_time->year),
|
|
1, 1);
|
|
/* Which day of week is 1st Jan of this year */
|
|
weekday_b= calc_weekday(days, sunday_first_n_first_week_non_iso);
|
|
|
|
/*
|
|
Below we are going to sum:
|
|
1) number of days since year 0 till 1st day of 1st week of this year
|
|
2) number of days between 1st week and our week
|
|
3) and position of our day in the week
|
|
*/
|
|
if (sunday_first_n_first_week_non_iso)
|
|
{
|
|
days+= ((weekday_b == 0) ? 0 : 7) - weekday_b +
|
|
(week_number - 1) * 7 +
|
|
weekday % 7;
|
|
}
|
|
else
|
|
{
|
|
days+= ((weekday_b <= 3) ? 0 : 7) - weekday_b +
|
|
(week_number - 1) * 7 +
|
|
(weekday - 1);
|
|
}
|
|
|
|
if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
|
|
goto err;
|
|
}
|
|
|
|
if (l_time->month > 12 || l_time->day > 31 || l_time->hour > 23 ||
|
|
l_time->minute > 59 || l_time->second > 59)
|
|
goto err;
|
|
|
|
int was_cut;
|
|
if (check_date(l_time, fuzzydate | TIME_INVALID_DATES, &was_cut))
|
|
goto err;
|
|
|
|
if (val != val_end)
|
|
{
|
|
do
|
|
{
|
|
if (!my_isspace(&my_charset_latin1,*val))
|
|
{
|
|
ErrConvString err(val_begin, length, &my_charset_bin);
|
|
make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
&err, cached_timestamp_type,
|
|
nullptr, nullptr, nullptr);
|
|
break;
|
|
}
|
|
} while (++val != val_end);
|
|
}
|
|
DBUG_RETURN(0);
|
|
|
|
err:
|
|
{
|
|
char buff[128];
|
|
strmake(buff, val_begin, MY_MIN(length, sizeof(buff)-1));
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WRONG_VALUE_FOR_TYPE,
|
|
ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE),
|
|
date_time_type, buff, "str_to_date");
|
|
}
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
|
|
/**
|
|
Create a formatted date/time value in a string.
|
|
*/
|
|
|
|
static bool make_date_time(const LEX_CSTRING &format,
|
|
CHARSET_INFO *format_charset,
|
|
MYSQL_TIME *l_time,
|
|
timestamp_type type, const MY_LOCALE *locale,
|
|
String *str)
|
|
{
|
|
char intbuff[15];
|
|
uint hours_i;
|
|
uint weekday;
|
|
ulong length;
|
|
const uchar *ptr, *end;
|
|
|
|
str->length(0);
|
|
|
|
if (l_time->neg)
|
|
str->append_wc('-');
|
|
|
|
end= (ptr= (const uchar *) format.str) + format.length;
|
|
|
|
for ( ; ; )
|
|
{
|
|
my_wc_t wc;
|
|
int mblen= format_charset->cset->mb_wc(format_charset, &wc, ptr, end);
|
|
if (mblen < 1)
|
|
return false;
|
|
ptr+= mblen;
|
|
|
|
if (wc != '%' || ptr >= end)
|
|
str->append_wc(wc);
|
|
else
|
|
{
|
|
mblen= format_charset->cset->mb_wc(format_charset, &wc, ptr, end);
|
|
if (mblen < 1)
|
|
return false;
|
|
ptr+= mblen;
|
|
|
|
switch (wc) {
|
|
case 'M':
|
|
if (type == MYSQL_TIMESTAMP_TIME || !l_time->month)
|
|
return 1;
|
|
str->append(locale->month_names->type_names[l_time->month-1],
|
|
(uint) strlen(locale->month_names->type_names[l_time->month-1]),
|
|
system_charset_info);
|
|
break;
|
|
case 'b':
|
|
if (type == MYSQL_TIMESTAMP_TIME || !l_time->month)
|
|
return 1;
|
|
str->append(locale->ab_month_names->type_names[l_time->month-1],
|
|
(uint) strlen(locale->ab_month_names->type_names[l_time->month-1]),
|
|
system_charset_info);
|
|
break;
|
|
case 'W':
|
|
if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
|
|
return 1;
|
|
weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
|
|
l_time->day),0);
|
|
str->append(locale->day_names->type_names[weekday],
|
|
(uint) strlen(locale->day_names->type_names[weekday]),
|
|
system_charset_info);
|
|
break;
|
|
case 'a':
|
|
if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
|
|
return 1;
|
|
weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
|
|
l_time->day),0);
|
|
str->append(locale->ab_day_names->type_names[weekday],
|
|
(uint) strlen(locale->ab_day_names->type_names[weekday]),
|
|
system_charset_info);
|
|
break;
|
|
case 'D':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->day, 1);
|
|
if (l_time->day >= 10 && l_time->day <= 19)
|
|
str->append(STRING_WITH_LEN("th"));
|
|
else
|
|
{
|
|
switch (l_time->day %10) {
|
|
case 1:
|
|
str->append(STRING_WITH_LEN("st"));
|
|
break;
|
|
case 2:
|
|
str->append(STRING_WITH_LEN("nd"));
|
|
break;
|
|
case 3:
|
|
str->append(STRING_WITH_LEN("rd"));
|
|
break;
|
|
default:
|
|
str->append(STRING_WITH_LEN("th"));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'Y':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->year, 4);
|
|
break;
|
|
case 'y':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->year % 100, 2);
|
|
break;
|
|
case 'm':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->month, 2);
|
|
break;
|
|
case 'c':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->month, 1);
|
|
break;
|
|
case 'd':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->day, 2);
|
|
break;
|
|
case 'e':
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
str->append_zerofill(l_time->day, 1);
|
|
break;
|
|
case 'f':
|
|
str->append_zerofill((uint) l_time->second_part, 6);
|
|
break;
|
|
case 'H':
|
|
str->append_zerofill(l_time->hour, 2);
|
|
break;
|
|
case 'h':
|
|
case 'I':
|
|
hours_i= (l_time->hour%24 + 11)%12+1;
|
|
str->append_zerofill(hours_i, 2);
|
|
break;
|
|
case 'i': /* minutes */
|
|
str->append_zerofill(l_time->minute, 2);
|
|
break;
|
|
case 'j':
|
|
{
|
|
if (type == MYSQL_TIMESTAMP_TIME || !l_time->month || !l_time->year)
|
|
return 1;
|
|
long value= calc_daynr(l_time->year,l_time->month, l_time->day) -
|
|
calc_daynr(l_time->year,1,1) + 1;
|
|
str->append_zerofill((uint) value, 3);
|
|
break;
|
|
}
|
|
case 'k':
|
|
str->append_zerofill(l_time->hour, 1);
|
|
break;
|
|
case 'l':
|
|
hours_i= (l_time->hour%24 + 11)%12+1;
|
|
str->append_zerofill(hours_i, 1);
|
|
break;
|
|
case 'p':
|
|
hours_i= l_time->hour%24;
|
|
str->append(hours_i < 12 ? "AM" : "PM",2);
|
|
break;
|
|
case 'r':
|
|
length= sprintf(intbuff, ((l_time->hour % 24) < 12) ?
|
|
"%02d:%02d:%02d AM" : "%02d:%02d:%02d PM",
|
|
(l_time->hour+11)%12+1,
|
|
l_time->minute,
|
|
l_time->second);
|
|
str->append(intbuff, length);
|
|
break;
|
|
case 'S':
|
|
case 's':
|
|
str->append_zerofill(l_time->second, 2);
|
|
break;
|
|
case 'T':
|
|
length= sprintf(intbuff, "%02d:%02d:%02d",
|
|
l_time->hour, l_time->minute, l_time->second);
|
|
str->append(intbuff, length);
|
|
break;
|
|
case 'U':
|
|
case 'u':
|
|
{
|
|
uint year;
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
|
|
uint value= calc_week(l_time,
|
|
wc == 'U' ? WEEK_FIRST_WEEKDAY :
|
|
WEEK_MONDAY_FIRST,
|
|
&year);
|
|
str->append_zerofill(value, 2);
|
|
}
|
|
break;
|
|
case 'v':
|
|
case 'V':
|
|
{
|
|
uint year;
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
uint value= calc_week(l_time, wc == 'V' ?
|
|
(WEEK_YEAR | WEEK_FIRST_WEEKDAY) :
|
|
(WEEK_YEAR | WEEK_MONDAY_FIRST),
|
|
&year);
|
|
str->append_zerofill(value, 2);
|
|
}
|
|
break;
|
|
case 'x':
|
|
case 'X':
|
|
{
|
|
uint year;
|
|
if (type == MYSQL_TIMESTAMP_TIME)
|
|
return 1;
|
|
(void) calc_week(l_time,
|
|
(wc == 'X' ?
|
|
WEEK_YEAR | WEEK_FIRST_WEEKDAY :
|
|
WEEK_YEAR | WEEK_MONDAY_FIRST),
|
|
&year);
|
|
str->append_zerofill(year, 4);
|
|
}
|
|
break;
|
|
case 'w':
|
|
if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
|
|
return 1;
|
|
weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
|
|
l_time->day),1);
|
|
str->append_zerofill(weekday, 1);
|
|
break;
|
|
|
|
default:
|
|
str->append_wc(wc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
@details
|
|
Get a array of positive numbers from a string object.
|
|
Each number is separated by 1 non digit character
|
|
Return error if there is too many numbers.
|
|
If there is too few numbers, assume that the numbers are left out
|
|
from the high end. This allows one to give:
|
|
DAY_TO_SECOND as "D MM:HH:SS", "MM:HH:SS" "HH:SS" or as seconds.
|
|
|
|
@param length: length of str
|
|
@param cs: charset of str
|
|
@param values: array of results
|
|
@param count: count of elements in result array
|
|
@param transform_msec: if value is true we suppose
|
|
that the last part of string value is microseconds
|
|
and we should transform value to six digit value.
|
|
For example, '1.1' -> '1.100000'
|
|
*/
|
|
|
|
#define MAX_DIGITS_IN_TIME_SPEC 20
|
|
|
|
static bool get_interval_info(const char *str, size_t length,CHARSET_INFO *cs,
|
|
size_t count, ulonglong *values,
|
|
bool transform_msec)
|
|
{
|
|
const char *end=str+length;
|
|
uint i;
|
|
size_t field_length= 0;
|
|
|
|
while (str != end && !my_isdigit(cs,*str))
|
|
str++;
|
|
|
|
for (i=0 ; i < count ; i++)
|
|
{
|
|
ulonglong value;
|
|
const char *start= str;
|
|
const char *local_end= end;
|
|
|
|
/*
|
|
We limit things to 19 digits to not get an overflow. This is ok as
|
|
this function is meant to read up to microseconds
|
|
*/
|
|
if ((local_end-str) > MAX_DIGITS_IN_TIME_SPEC)
|
|
local_end= str+ MAX_DIGITS_IN_TIME_SPEC;
|
|
|
|
for (value= 0; str != local_end && my_isdigit(cs, *str) ; str++)
|
|
value= value*10 + *str - '0';
|
|
|
|
if ((field_length= (size_t)(str - start)) >= MAX_DIGITS_IN_TIME_SPEC)
|
|
return true;
|
|
values[i]= value;
|
|
while (str != end && !my_isdigit(cs,*str))
|
|
str++;
|
|
if (str == end && i != count-1)
|
|
{
|
|
i++;
|
|
/* Change values[0...i-1] -> values[0...count-1] */
|
|
bmove_upp((uchar*) (values+count), (uchar*) (values+i),
|
|
sizeof(*values)*i);
|
|
bzero((uchar*) values, sizeof(*values)*(count-i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (transform_msec && field_length > 0)
|
|
{
|
|
if (field_length < 6)
|
|
values[count - 1] *= log_10_int[6 - field_length];
|
|
else if (field_length > 6)
|
|
values[count - 1] /= log_10_int[field_length - 6];
|
|
}
|
|
|
|
return (str != end);
|
|
}
|
|
|
|
|
|
longlong Item_func_period_add::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
ulong period=(ulong) args[0]->val_int();
|
|
int months=(int) args[1]->val_int();
|
|
|
|
if ((null_value=args[0]->null_value || args[1]->null_value) ||
|
|
period == 0L)
|
|
return 0; /* purecov: inspected */
|
|
return (longlong)
|
|
convert_month_to_period((uint) ((int) convert_period_to_month(period)+
|
|
months));
|
|
}
|
|
|
|
|
|
longlong Item_func_period_diff::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
ulong period1=(ulong) args[0]->val_int();
|
|
ulong period2=(ulong) args[1]->val_int();
|
|
|
|
if ((null_value=args[0]->null_value || args[1]->null_value))
|
|
return 0; /* purecov: inspected */
|
|
return (longlong) ((long) convert_period_to_month(period1)-
|
|
(long) convert_period_to_month(period2));
|
|
}
|
|
|
|
|
|
|
|
longlong Item_func_to_days::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.daynr();
|
|
}
|
|
|
|
|
|
longlong Item_func_to_seconds::val_int_endpoint(bool left_endp,
|
|
bool *incl_endp)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
// val_int_endpoint() is called only if args[0] is a temporal Item_field
|
|
Datetime_from_temporal dt(current_thd, args[0], TIME_FUZZY_DATES);
|
|
if ((null_value= !dt.is_valid_datetime()))
|
|
{
|
|
/* got NULL, leave the incl_endp intact */
|
|
return LONGLONG_MIN;
|
|
}
|
|
/* Set to NULL if invalid date, but keep the value */
|
|
null_value= dt.check_date(TIME_NO_ZEROS);
|
|
/*
|
|
Even if the evaluation return NULL, seconds is useful for pruning
|
|
*/
|
|
return dt.to_seconds();
|
|
}
|
|
|
|
longlong Item_func_to_seconds::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
/*
|
|
Unlike val_int_endpoint(), we cannot use Datetime_from_temporal here.
|
|
The argument can be of a non-temporal data type.
|
|
*/
|
|
Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
return (null_value= !dt.is_valid_datetime()) ? 0 : dt.to_seconds();
|
|
}
|
|
|
|
/*
|
|
Get information about this Item tree monotonicity
|
|
|
|
SYNOPSIS
|
|
Item_func_to_days::get_monotonicity_info()
|
|
|
|
DESCRIPTION
|
|
Get information about monotonicity of the function represented by this item
|
|
tree.
|
|
|
|
RETURN
|
|
See enum_monotonicity_info.
|
|
*/
|
|
|
|
enum_monotonicity_info Item_func_to_days::get_monotonicity_info() const
|
|
{
|
|
if (args[0]->type() == Item::FIELD_ITEM)
|
|
{
|
|
if (args[0]->field_type() == MYSQL_TYPE_DATE)
|
|
return MONOTONIC_STRICT_INCREASING_NOT_NULL;
|
|
if (args[0]->field_type() == MYSQL_TYPE_DATETIME)
|
|
return MONOTONIC_INCREASING_NOT_NULL;
|
|
}
|
|
return NON_MONOTONIC;
|
|
}
|
|
|
|
enum_monotonicity_info Item_func_to_seconds::get_monotonicity_info() const
|
|
{
|
|
if (args[0]->type() == Item::FIELD_ITEM)
|
|
{
|
|
if (args[0]->field_type() == MYSQL_TYPE_DATE ||
|
|
args[0]->field_type() == MYSQL_TYPE_DATETIME)
|
|
return MONOTONIC_STRICT_INCREASING_NOT_NULL;
|
|
}
|
|
return NON_MONOTONIC;
|
|
}
|
|
|
|
|
|
longlong Item_func_to_days::val_int_endpoint(bool left_endp, bool *incl_endp)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
// val_int_endpoint() is only called if args[0] is a temporal Item_field
|
|
Datetime_from_temporal dt(current_thd, args[0], TIME_CONV_NONE);
|
|
longlong res;
|
|
if ((null_value= !dt.is_valid_datetime()))
|
|
{
|
|
/* got NULL, leave the incl_endp intact */
|
|
return LONGLONG_MIN;
|
|
}
|
|
res= (longlong) dt.daynr();
|
|
/* Set to NULL if invalid date, but keep the value */
|
|
null_value= dt.check_date(TIME_NO_ZEROS);
|
|
if (null_value)
|
|
{
|
|
/*
|
|
Even if the evaluation return NULL, the calc_daynr is useful for pruning
|
|
*/
|
|
if (args[0]->field_type() != MYSQL_TYPE_DATE)
|
|
*incl_endp= TRUE;
|
|
return res;
|
|
}
|
|
|
|
if (args[0]->field_type() == MYSQL_TYPE_DATE)
|
|
{
|
|
// TO_DAYS() is strictly monotonic for dates, leave incl_endp intact
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
Handle the special but practically useful case of datetime values that
|
|
point to day bound ("strictly less" comparison stays intact):
|
|
|
|
col < '2007-09-15 00:00:00' -> TO_DAYS(col) < TO_DAYS('2007-09-15')
|
|
col > '2007-09-15 23:59:59' -> TO_DAYS(col) > TO_DAYS('2007-09-15')
|
|
|
|
which is different from the general case ("strictly less" changes to
|
|
"less or equal"):
|
|
|
|
col < '2007-09-15 12:34:56' -> TO_DAYS(col) <= TO_DAYS('2007-09-15')
|
|
*/
|
|
const MYSQL_TIME <ime= dt.get_mysql_time()[0];
|
|
if ((!left_endp && dt.hhmmssff_is_zero()) ||
|
|
(left_endp && ltime.hour == 23 && ltime.minute == 59 &&
|
|
ltime.second == 59))
|
|
/* do nothing */
|
|
;
|
|
else
|
|
*incl_endp= TRUE;
|
|
return res;
|
|
}
|
|
|
|
|
|
longlong Item_func_dayofyear::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.dayofyear();
|
|
}
|
|
|
|
longlong Item_func_dayofmonth::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->day;
|
|
}
|
|
|
|
longlong Item_func_month::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->month;
|
|
}
|
|
|
|
|
|
bool Item_func_monthname::fix_length_and_dec()
|
|
{
|
|
THD* thd= current_thd;
|
|
CHARSET_INFO *cs= thd->variables.collation_connection;
|
|
locale= thd->variables.lc_time_names;
|
|
collation.set(cs, DERIVATION_COERCIBLE, locale->repertoire());
|
|
decimals=0;
|
|
max_length= locale->max_month_name_length * collation.collation->mbmaxlen;
|
|
maybe_null=1;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
String* Item_func_monthname::val_str(String* str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
const char *month_name;
|
|
uint err;
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
|
|
if ((null_value= (!d.is_valid_datetime() || !d.get_mysql_time()->month)))
|
|
return (String *) 0;
|
|
|
|
month_name= locale->month_names->type_names[d.get_mysql_time()->month - 1];
|
|
str->copy(month_name, (uint) strlen(month_name), &my_charset_utf8_bin,
|
|
collation.collation, &err);
|
|
return str;
|
|
}
|
|
|
|
|
|
/**
|
|
Returns the quarter of the year.
|
|
*/
|
|
|
|
longlong Item_func_quarter::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.quarter();
|
|
}
|
|
|
|
longlong Item_func_hour::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->hour;
|
|
}
|
|
|
|
longlong Item_func_minute::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->minute;
|
|
}
|
|
|
|
/**
|
|
Returns the second in time_exp in the range of 0 - 59.
|
|
*/
|
|
longlong Item_func_second::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->second;
|
|
}
|
|
|
|
|
|
uint week_mode(uint mode)
|
|
{
|
|
uint week_format= (mode & 7);
|
|
if (!(week_format & WEEK_MONDAY_FIRST))
|
|
week_format^= WEEK_FIRST_WEEKDAY;
|
|
return week_format;
|
|
}
|
|
|
|
/**
|
|
@verbatim
|
|
The bits in week_format(for calc_week() function) has the following meaning:
|
|
WEEK_MONDAY_FIRST (0) If not set Sunday is first day of week
|
|
If set Monday is first day of week
|
|
WEEK_YEAR (1) If not set Week is in range 0-53
|
|
|
|
Week 0 is returned for the the last week of the previous year (for
|
|
a date at start of january) In this case one can get 53 for the
|
|
first week of next year. This flag ensures that the week is
|
|
relevant for the given year. Note that this flag is only
|
|
relevant if WEEK_JANUARY is not set.
|
|
|
|
If set Week is in range 1-53.
|
|
|
|
In this case one may get week 53 for a date in January (when
|
|
the week is that last week of previous year) and week 1 for a
|
|
date in December.
|
|
|
|
WEEK_FIRST_WEEKDAY (2) If not set Weeks are numbered according
|
|
to ISO 8601:1988
|
|
If set The week that contains the first
|
|
'first-day-of-week' is week 1.
|
|
|
|
ISO 8601:1988 means that if the week containing January 1 has
|
|
four or more days in the new year, then it is week 1;
|
|
Otherwise it is the last week of the previous year, and the
|
|
next week is week 1.
|
|
@endverbatim
|
|
*/
|
|
|
|
longlong Item_func_week::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
uint week_format;
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
if ((null_value= !d.is_valid_datetime()))
|
|
return 0;
|
|
if (arg_count > 1)
|
|
week_format= (uint)args[1]->val_int();
|
|
else
|
|
week_format= thd->variables.default_week_format;
|
|
return d.week(week_mode(week_format));
|
|
}
|
|
|
|
|
|
longlong Item_func_yearweek::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 :
|
|
d.yearweek((week_mode((uint) args[1]->val_int()) | WEEK_YEAR));
|
|
}
|
|
|
|
|
|
longlong Item_func_weekday::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
if ((null_value= !dt.is_valid_datetime()))
|
|
return 0;
|
|
return dt.weekday(odbc_type) + MY_TEST(odbc_type);
|
|
}
|
|
|
|
bool Item_func_dayname::fix_length_and_dec()
|
|
{
|
|
THD* thd= current_thd;
|
|
CHARSET_INFO *cs= thd->variables.collation_connection;
|
|
locale= thd->variables.lc_time_names;
|
|
collation.set(cs, DERIVATION_COERCIBLE, locale->repertoire());
|
|
decimals=0;
|
|
max_length= locale->max_day_name_length * collation.collation->mbmaxlen;
|
|
maybe_null=1;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
String* Item_func_dayname::val_str(String* str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
const char *day_name;
|
|
uint err;
|
|
THD *thd= current_thd;
|
|
Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
|
|
|
|
if ((null_value= !dt.is_valid_datetime()))
|
|
return (String*) 0;
|
|
|
|
day_name= locale->day_names->type_names[dt.weekday(false)];
|
|
str->copy(day_name, (uint) strlen(day_name), &my_charset_utf8_bin,
|
|
collation.collation, &err);
|
|
return str;
|
|
}
|
|
|
|
|
|
longlong Item_func_year::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
|
|
return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->year;
|
|
}
|
|
|
|
|
|
/*
|
|
Get information about this Item tree monotonicity
|
|
|
|
SYNOPSIS
|
|
Item_func_year::get_monotonicity_info()
|
|
|
|
DESCRIPTION
|
|
Get information about monotonicity of the function represented by this item
|
|
tree.
|
|
|
|
RETURN
|
|
See enum_monotonicity_info.
|
|
*/
|
|
|
|
enum_monotonicity_info Item_func_year::get_monotonicity_info() const
|
|
{
|
|
if (args[0]->type() == Item::FIELD_ITEM &&
|
|
(args[0]->field_type() == MYSQL_TYPE_DATE ||
|
|
args[0]->field_type() == MYSQL_TYPE_DATETIME))
|
|
return MONOTONIC_INCREASING;
|
|
return NON_MONOTONIC;
|
|
}
|
|
|
|
|
|
longlong Item_func_year::val_int_endpoint(bool left_endp, bool *incl_endp)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
// val_int_endpoint() is cally only if args[0] is a temporal Item_field
|
|
Datetime_from_temporal dt(current_thd, args[0], TIME_CONV_NONE);
|
|
if ((null_value= !dt.is_valid_datetime()))
|
|
{
|
|
/* got NULL, leave the incl_endp intact */
|
|
return LONGLONG_MIN;
|
|
}
|
|
|
|
/*
|
|
Handle the special but practically useful case of datetime values that
|
|
point to year bound ("strictly less" comparison stays intact) :
|
|
|
|
col < '2007-01-01 00:00:00' -> YEAR(col) < 2007
|
|
|
|
which is different from the general case ("strictly less" changes to
|
|
"less or equal"):
|
|
|
|
col < '2007-09-15 23:00:00' -> YEAR(col) <= 2007
|
|
*/
|
|
const MYSQL_TIME <ime= dt.get_mysql_time()[0];
|
|
if (!left_endp && ltime.day == 1 && ltime.month == 1 &&
|
|
dt.hhmmssff_is_zero())
|
|
; /* do nothing */
|
|
else
|
|
*incl_endp= TRUE;
|
|
return ltime.year;
|
|
}
|
|
|
|
|
|
bool Item_func_unix_timestamp::get_timestamp_value(my_time_t *seconds,
|
|
ulong *second_part)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
if (args[0]->type() == FIELD_ITEM)
|
|
{ // Optimize timestamp field
|
|
Field *field=((Item_field*) args[0])->field;
|
|
if (field->type() == MYSQL_TYPE_TIMESTAMP)
|
|
{
|
|
if ((null_value= field->is_null()))
|
|
return 1;
|
|
*seconds= ((Field_timestamp*)field)->get_timestamp(second_part);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
Timestamp_or_zero_datetime_native_null native(current_thd, args[0], true);
|
|
if ((null_value= native.is_null() || native.is_zero_datetime()))
|
|
return true;
|
|
Timestamp tm(native);
|
|
*seconds= tm.tv().tv_sec;
|
|
*second_part= tm.tv().tv_usec;
|
|
return false;
|
|
}
|
|
|
|
|
|
longlong Item_func_unix_timestamp::int_op()
|
|
{
|
|
if (arg_count == 0)
|
|
return (longlong) current_thd->query_start();
|
|
|
|
ulong second_part;
|
|
my_time_t seconds;
|
|
if (get_timestamp_value(&seconds, &second_part))
|
|
return 0;
|
|
|
|
return seconds;
|
|
}
|
|
|
|
|
|
my_decimal *Item_func_unix_timestamp::decimal_op(my_decimal* buf)
|
|
{
|
|
ulong second_part;
|
|
my_time_t seconds;
|
|
if (get_timestamp_value(&seconds, &second_part))
|
|
return 0;
|
|
|
|
return seconds2my_decimal(seconds < 0, seconds < 0 ? -seconds : seconds,
|
|
second_part, buf);
|
|
}
|
|
|
|
|
|
enum_monotonicity_info Item_func_unix_timestamp::get_monotonicity_info() const
|
|
{
|
|
if (args[0]->type() == Item::FIELD_ITEM &&
|
|
(args[0]->field_type() == MYSQL_TYPE_TIMESTAMP))
|
|
return MONOTONIC_INCREASING;
|
|
return NON_MONOTONIC;
|
|
}
|
|
|
|
|
|
longlong Item_func_unix_timestamp::val_int_endpoint(bool left_endp, bool *incl_endp)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
DBUG_ASSERT(arg_count == 1 &&
|
|
args[0]->type() == Item::FIELD_ITEM &&
|
|
args[0]->field_type() == MYSQL_TYPE_TIMESTAMP);
|
|
Field_timestamp *field=(Field_timestamp *)(((Item_field*)args[0])->field);
|
|
/* Leave the incl_endp intact */
|
|
ulong unused;
|
|
my_time_t ts= field->get_timestamp(&unused);
|
|
null_value= field->is_null();
|
|
return ts;
|
|
}
|
|
|
|
|
|
longlong Item_func_time_to_sec::int_op()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
return ((null_value= !tm.is_valid_time())) ? 0 : tm.to_seconds();
|
|
}
|
|
|
|
|
|
my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
if ((null_value= !tm.is_valid_time()))
|
|
return 0;
|
|
const MYSQL_TIME *ltime= tm.get_mysql_time();
|
|
longlong seconds= tm.to_seconds_abs();
|
|
return seconds2my_decimal(ltime->neg, seconds, ltime->second_part, buf);
|
|
}
|
|
|
|
|
|
/**
|
|
Convert a string to a interval value.
|
|
|
|
To make code easy, allow interval objects without separators.
|
|
*/
|
|
|
|
bool get_interval_value(THD *thd, Item *args,
|
|
interval_type int_type, INTERVAL *interval)
|
|
{
|
|
ulonglong array[5];
|
|
longlong UNINIT_VAR(value);
|
|
const char *UNINIT_VAR(str);
|
|
size_t UNINIT_VAR(length);
|
|
CHARSET_INFO *UNINIT_VAR(cs);
|
|
char buf[100];
|
|
String str_value(buf, sizeof(buf), &my_charset_bin);
|
|
|
|
bzero((char*) interval,sizeof(*interval));
|
|
if (int_type == INTERVAL_SECOND && args->decimals)
|
|
{
|
|
VDec val(args);
|
|
if (val.is_null())
|
|
return true;
|
|
Sec6 d(val.ptr());
|
|
interval->neg= d.neg();
|
|
if (d.sec() >= LONGLONG_MAX)
|
|
{
|
|
ErrConvDecimal err(val.ptr());
|
|
thd->push_warning_truncated_wrong_value("seconds", err.ptr());
|
|
return true;
|
|
}
|
|
interval->second= d.sec();
|
|
interval->second_part= d.usec();
|
|
return false;
|
|
}
|
|
else if ((int) int_type <= INTERVAL_MICROSECOND)
|
|
{
|
|
value= args->val_int();
|
|
if (args->null_value)
|
|
return 1;
|
|
if (value < 0)
|
|
{
|
|
interval->neg=1;
|
|
value= -value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String *res;
|
|
if (!(res= args->val_str_ascii(&str_value)))
|
|
return (1);
|
|
|
|
/* record negative intervals in interval->neg */
|
|
str=res->ptr();
|
|
cs= res->charset();
|
|
const char *end=str+res->length();
|
|
while (str != end && my_isspace(cs,*str))
|
|
str++;
|
|
if (str != end && *str == '-')
|
|
{
|
|
interval->neg=1;
|
|
str++;
|
|
}
|
|
length= (size_t) (end-str); // Set up pointers to new str
|
|
}
|
|
|
|
switch (int_type) {
|
|
case INTERVAL_YEAR:
|
|
interval->year= (ulong) value;
|
|
break;
|
|
case INTERVAL_QUARTER:
|
|
interval->month= (ulong)(value*3);
|
|
break;
|
|
case INTERVAL_MONTH:
|
|
interval->month= (ulong) value;
|
|
break;
|
|
case INTERVAL_WEEK:
|
|
interval->day= (ulong)(value*7);
|
|
break;
|
|
case INTERVAL_DAY:
|
|
interval->day= (ulong) value;
|
|
break;
|
|
case INTERVAL_HOUR:
|
|
interval->hour= (ulong) value;
|
|
break;
|
|
case INTERVAL_MICROSECOND:
|
|
interval->second_part=value;
|
|
break;
|
|
case INTERVAL_MINUTE:
|
|
interval->minute=value;
|
|
break;
|
|
case INTERVAL_SECOND:
|
|
interval->second=value;
|
|
break;
|
|
case INTERVAL_YEAR_MONTH: // Allow YEAR-MONTH YYYYYMM
|
|
if (get_interval_info(str,length,cs,2,array,0))
|
|
return (1);
|
|
interval->year= (ulong) array[0];
|
|
interval->month= (ulong) array[1];
|
|
break;
|
|
case INTERVAL_DAY_HOUR:
|
|
if (get_interval_info(str,length,cs,2,array,0))
|
|
return (1);
|
|
interval->day= (ulong) array[0];
|
|
interval->hour= (ulong) array[1];
|
|
break;
|
|
case INTERVAL_DAY_MICROSECOND:
|
|
if (get_interval_info(str,length,cs,5,array,1))
|
|
return (1);
|
|
interval->day= (ulong) array[0];
|
|
interval->hour= (ulong) array[1];
|
|
interval->minute= array[2];
|
|
interval->second= array[3];
|
|
interval->second_part= array[4];
|
|
break;
|
|
case INTERVAL_DAY_MINUTE:
|
|
if (get_interval_info(str,length,cs,3,array,0))
|
|
return (1);
|
|
interval->day= (ulong) array[0];
|
|
interval->hour= (ulong) array[1];
|
|
interval->minute= array[2];
|
|
break;
|
|
case INTERVAL_DAY_SECOND:
|
|
if (get_interval_info(str,length,cs,4,array,0))
|
|
return (1);
|
|
interval->day= (ulong) array[0];
|
|
interval->hour= (ulong) array[1];
|
|
interval->minute= array[2];
|
|
interval->second= array[3];
|
|
break;
|
|
case INTERVAL_HOUR_MICROSECOND:
|
|
if (get_interval_info(str,length,cs,4,array,1))
|
|
return (1);
|
|
interval->hour= (ulong) array[0];
|
|
interval->minute= array[1];
|
|
interval->second= array[2];
|
|
interval->second_part= array[3];
|
|
break;
|
|
case INTERVAL_HOUR_MINUTE:
|
|
if (get_interval_info(str,length,cs,2,array,0))
|
|
return (1);
|
|
interval->hour= (ulong) array[0];
|
|
interval->minute= array[1];
|
|
break;
|
|
case INTERVAL_HOUR_SECOND:
|
|
if (get_interval_info(str,length,cs,3,array,0))
|
|
return (1);
|
|
interval->hour= (ulong) array[0];
|
|
interval->minute= array[1];
|
|
interval->second= array[2];
|
|
break;
|
|
case INTERVAL_MINUTE_MICROSECOND:
|
|
if (get_interval_info(str,length,cs,3,array,1))
|
|
return (1);
|
|
interval->minute= array[0];
|
|
interval->second= array[1];
|
|
interval->second_part= array[2];
|
|
break;
|
|
case INTERVAL_MINUTE_SECOND:
|
|
if (get_interval_info(str,length,cs,2,array,0))
|
|
return (1);
|
|
interval->minute= array[0];
|
|
interval->second= array[1];
|
|
break;
|
|
case INTERVAL_SECOND_MICROSECOND:
|
|
if (get_interval_info(str,length,cs,2,array,1))
|
|
return (1);
|
|
interval->second= array[0];
|
|
interval->second_part= array[1];
|
|
break;
|
|
case INTERVAL_LAST: /* purecov: begin deadcode */
|
|
DBUG_ASSERT(0);
|
|
break; /* purecov: end */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_func_from_days::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
longlong value=args[0]->val_int();
|
|
if ((null_value= (args[0]->null_value ||
|
|
((fuzzydate & TIME_NO_ZERO_DATE) && value == 0))))
|
|
return true;
|
|
bzero(ltime, sizeof(MYSQL_TIME));
|
|
if (get_date_from_daynr((long) value, <ime->year, <ime->month,
|
|
<ime->day))
|
|
return 0;
|
|
|
|
ltime->time_type= MYSQL_TIMESTAMP_DATE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for local
|
|
time zone. Defines time zone (local) used for whole CURDATE function.
|
|
*/
|
|
void Item_func_curdate_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
thd->time_zone_used= 1;
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for UTC
|
|
time zone. Defines time zone (UTC) used for whole UTC_DATE function.
|
|
*/
|
|
void Item_func_curdate_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
/*
|
|
We are not flagging this query as using time zone, since it uses fixed
|
|
UTC-SYSTEM time-zone.
|
|
*/
|
|
}
|
|
|
|
|
|
bool Item_func_curdate::get_date(THD *thd, MYSQL_TIME *res,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
query_id_t query_id= thd->query_id;
|
|
/* Cache value for this query */
|
|
if (last_query_id != query_id)
|
|
{
|
|
last_query_id= query_id;
|
|
store_now_in_TIME(thd, <ime);
|
|
/* We don't need to set second_part and neg because they already 0 */
|
|
ltime.hour= ltime.minute= ltime.second= 0;
|
|
ltime.time_type= MYSQL_TIMESTAMP_DATE;
|
|
}
|
|
*res=ltime;
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_func_curtime::fix_fields(THD *thd, Item **items)
|
|
{
|
|
if (decimals > TIME_SECOND_PART_DIGITS)
|
|
{
|
|
my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<ulonglong>(decimals),
|
|
func_name(), TIME_SECOND_PART_DIGITS);
|
|
return 1;
|
|
}
|
|
return Item_timefunc::fix_fields(thd, items);
|
|
}
|
|
|
|
bool Item_func_curtime::get_date(THD *thd, MYSQL_TIME *res,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
query_id_t query_id= thd->query_id;
|
|
/* Cache value for this query */
|
|
if (last_query_id != query_id)
|
|
{
|
|
last_query_id= query_id;
|
|
store_now_in_TIME(thd, <ime);
|
|
}
|
|
*res= ltime;
|
|
return 0;
|
|
}
|
|
|
|
void Item_func_curtime::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
str->append('(');
|
|
if (decimals)
|
|
str->append_ulonglong(decimals);
|
|
str->append(')');
|
|
}
|
|
|
|
static void set_sec_part(ulong sec_part, MYSQL_TIME *ltime, Item *item)
|
|
{
|
|
DBUG_ASSERT(item->decimals == AUTO_SEC_PART_DIGITS ||
|
|
item->decimals <= TIME_SECOND_PART_DIGITS);
|
|
if (item->decimals)
|
|
{
|
|
ltime->second_part= sec_part;
|
|
if (item->decimals < TIME_SECOND_PART_DIGITS)
|
|
my_datetime_trunc(ltime, item->decimals);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for local
|
|
time zone. Defines time zone (local) used for whole CURTIME function.
|
|
*/
|
|
void Item_func_curtime_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
now_time->year= now_time->month= now_time->day= 0;
|
|
now_time->time_type= MYSQL_TIMESTAMP_TIME;
|
|
set_sec_part(thd->query_start_sec_part(), now_time, this);
|
|
thd->time_zone_used= 1;
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for UTC
|
|
time zone. Defines time zone (UTC) used for whole UTC_TIME function.
|
|
*/
|
|
void Item_func_curtime_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
now_time->year= now_time->month= now_time->day= 0;
|
|
now_time->time_type= MYSQL_TIMESTAMP_TIME;
|
|
set_sec_part(thd->query_start_sec_part(), now_time, this);
|
|
/*
|
|
We are not flagging this query as using time zone, since it uses fixed
|
|
UTC-SYSTEM time-zone.
|
|
*/
|
|
}
|
|
|
|
bool Item_func_now::fix_fields(THD *thd, Item **items)
|
|
{
|
|
if (decimals > TIME_SECOND_PART_DIGITS)
|
|
{
|
|
my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<ulonglong>(decimals),
|
|
func_name(), TIME_SECOND_PART_DIGITS);
|
|
return 1;
|
|
}
|
|
return Item_datetimefunc::fix_fields(thd, items);
|
|
}
|
|
|
|
void Item_func_now::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
str->append('(');
|
|
if (decimals)
|
|
str->append_ulonglong(decimals);
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
int Item_func_now_local::save_in_field(Field *field, bool no_conversions)
|
|
{
|
|
if (field->type() == MYSQL_TYPE_TIMESTAMP)
|
|
{
|
|
THD *thd= field->get_thd();
|
|
my_time_t ts= thd->query_start();
|
|
ulong sec_part= decimals ? thd->query_start_sec_part() : 0;
|
|
sec_part-= my_time_fraction_remainder(sec_part, decimals);
|
|
field->set_notnull();
|
|
((Field_timestamp*)field)->store_TIME(ts, sec_part);
|
|
return 0;
|
|
}
|
|
else
|
|
return Item_datetimefunc::save_in_field(field, no_conversions);
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for local
|
|
time zone. Defines time zone (local) used for whole NOW function.
|
|
*/
|
|
void Item_func_now_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
set_sec_part(thd->query_start_sec_part(), now_time, this);
|
|
thd->time_zone_used= 1;
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for UTC
|
|
time zone. Defines time zone (UTC) used for whole UTC_TIMESTAMP function.
|
|
*/
|
|
void Item_func_now_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
|
|
set_sec_part(thd->query_start_sec_part(), now_time, this);
|
|
/*
|
|
We are not flagging this query as using time zone, since it uses fixed
|
|
UTC-SYSTEM time-zone.
|
|
*/
|
|
}
|
|
|
|
|
|
bool Item_func_now::get_date(THD *thd, MYSQL_TIME *res,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
query_id_t query_id= thd->query_id;
|
|
/* Cache value for this query */
|
|
if (last_query_id != query_id)
|
|
{
|
|
last_query_id= query_id;
|
|
store_now_in_TIME(thd, <ime);
|
|
}
|
|
*res= ltime;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Converts current time in my_time_t to MYSQL_TIME representation for local
|
|
time zone. Defines time zone (local) used for whole SYSDATE function.
|
|
*/
|
|
void Item_func_sysdate_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
|
|
{
|
|
my_hrtime_t now= my_hrtime();
|
|
thd->variables.time_zone->gmt_sec_to_TIME(now_time, hrtime_to_my_time(now));
|
|
set_sec_part(hrtime_sec_part(now), now_time, this);
|
|
thd->time_zone_used= 1;
|
|
}
|
|
|
|
|
|
bool Item_func_sysdate_local::get_date(THD *thd, MYSQL_TIME *res,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
store_now_in_TIME(thd, res);
|
|
return 0;
|
|
}
|
|
|
|
bool Item_func_sec_to_time::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
VSec9 sec(thd, args[0], "seconds", LONGLONG_MAX);
|
|
if ((null_value= sec.is_null()))
|
|
return true;
|
|
sec.round(decimals, thd->temporal_round_mode());
|
|
if (sec.sec_to_time(ltime, decimals) && !sec.truncated())
|
|
sec.make_truncated_warning(thd, "seconds");
|
|
return false;
|
|
}
|
|
|
|
bool Item_func_date_format::fix_length_and_dec()
|
|
{
|
|
THD* thd= current_thd;
|
|
if (!is_time_format)
|
|
{
|
|
if (arg_count < 3)
|
|
locale= thd->variables.lc_time_names;
|
|
else
|
|
if (args[2]->basic_const_item())
|
|
locale= args[2]->locale_from_val_str();
|
|
}
|
|
|
|
/*
|
|
Must use this_item() in case it's a local SP variable
|
|
(for ->max_length and ->str_value)
|
|
*/
|
|
Item *arg1= args[1]->this_item();
|
|
|
|
decimals=0;
|
|
CHARSET_INFO *cs= thd->variables.collation_connection;
|
|
uint32 repertoire= arg1->collation.repertoire;
|
|
if (!thd->variables.lc_time_names->is_ascii)
|
|
repertoire|= MY_REPERTOIRE_EXTENDED;
|
|
collation.set(cs, arg1->collation.derivation, repertoire);
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer;
|
|
String *str;
|
|
if (args[1]->basic_const_item() && (str= args[1]->val_str(&buffer)))
|
|
{ // Optimize the normal case
|
|
fixed_length=1;
|
|
max_length= format_length(str) * collation.collation->mbmaxlen;
|
|
}
|
|
else
|
|
{
|
|
fixed_length=0;
|
|
max_length=MY_MIN(arg1->max_length, MAX_BLOB_WIDTH) * 10 *
|
|
collation.collation->mbmaxlen;
|
|
set_if_smaller(max_length,MAX_BLOB_WIDTH);
|
|
}
|
|
maybe_null=1; // If wrong date
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
bool Item_func_date_format::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
Item_func_date_format *item_func;
|
|
|
|
if (item->type() != FUNC_ITEM)
|
|
return 0;
|
|
if (func_name() != ((Item_func*) item)->func_name())
|
|
return 0;
|
|
if (this == item)
|
|
return 1;
|
|
item_func= (Item_func_date_format*) item;
|
|
if (arg_count != item_func->arg_count)
|
|
return 0;
|
|
if (!args[0]->eq(item_func->args[0], binary_cmp))
|
|
return 0;
|
|
/*
|
|
We must compare format string case sensitive.
|
|
This needed because format modifiers with different case,
|
|
for example %m and %M, have different meaning.
|
|
*/
|
|
if (!args[1]->eq(item_func->args[1], 1))
|
|
return 0;
|
|
if (arg_count > 2 && !args[2]->eq(item_func->args[2], 1))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
uint Item_func_date_format::format_length(const String *format)
|
|
{
|
|
uint size=0;
|
|
const char *ptr=format->ptr();
|
|
const char *end=ptr+format->length();
|
|
|
|
for (; ptr != end ; ptr++)
|
|
{
|
|
if (*ptr != '%' || ptr == end-1)
|
|
size++;
|
|
else
|
|
{
|
|
switch(*++ptr) {
|
|
case 'M': /* month, textual */
|
|
case 'W': /* day (of the week), textual */
|
|
size += 64; /* large for UTF8 locale data */
|
|
break;
|
|
case 'D': /* day (of the month), numeric plus english suffix */
|
|
case 'Y': /* year, numeric, 4 digits */
|
|
case 'x': /* Year, used with 'v' */
|
|
case 'X': /* Year, used with 'v, where week starts with Monday' */
|
|
size += 4;
|
|
break;
|
|
case 'a': /* locale's abbreviated weekday name (Sun..Sat) */
|
|
case 'b': /* locale's abbreviated month name (Jan.Dec) */
|
|
size += 32; /* large for UTF8 locale data */
|
|
break;
|
|
case 'j': /* day of year (001..366) */
|
|
size += 3;
|
|
break;
|
|
case 'U': /* week (00..52) */
|
|
case 'u': /* week (00..52), where week starts with Monday */
|
|
case 'V': /* week 1..53 used with 'x' */
|
|
case 'v': /* week 1..53 used with 'x', where week starts with Monday */
|
|
case 'y': /* year, numeric, 2 digits */
|
|
case 'm': /* month, numeric */
|
|
case 'd': /* day (of the month), numeric */
|
|
case 'h': /* hour (01..12) */
|
|
case 'I': /* --||-- */
|
|
case 'i': /* minutes, numeric */
|
|
case 'l': /* hour ( 1..12) */
|
|
case 'p': /* locale's AM or PM */
|
|
case 'S': /* second (00..61) */
|
|
case 's': /* seconds, numeric */
|
|
case 'c': /* month (0..12) */
|
|
case 'e': /* day (0..31) */
|
|
size += 2;
|
|
break;
|
|
case 'k': /* hour ( 0..23) */
|
|
case 'H': /* hour (00..23; value > 23 OK, padding always 2-digit) */
|
|
size += 7; /* docs allow > 23, range depends on sizeof(unsigned int) */
|
|
break;
|
|
case 'r': /* time, 12-hour (hh:mm:ss [AP]M) */
|
|
size += 11;
|
|
break;
|
|
case 'T': /* time, 24-hour (hh:mm:ss) */
|
|
size += 8;
|
|
break;
|
|
case 'f': /* microseconds */
|
|
size += 6;
|
|
break;
|
|
case 'w': /* day (of the week), numeric */
|
|
case '%':
|
|
default:
|
|
size++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
|
|
String *Item_func_date_format::val_str(String *str)
|
|
{
|
|
StringBuffer<64> format_buffer;
|
|
String *format;
|
|
MYSQL_TIME l_time;
|
|
uint size;
|
|
const MY_LOCALE *lc= 0;
|
|
DBUG_ASSERT(fixed == 1);
|
|
date_conv_mode_t mode= is_time_format ? TIME_TIME_ONLY : TIME_CONV_NONE;
|
|
THD *thd= current_thd;
|
|
if ((null_value= args[0]->get_date(thd, &l_time,
|
|
Temporal::Options(mode, thd))))
|
|
return 0;
|
|
|
|
if (!(format= args[1]->val_str(&format_buffer)) || !format->length())
|
|
goto null_date;
|
|
|
|
if (!is_time_format && !(lc= locale) && !(lc= args[2]->locale_from_val_str()))
|
|
goto null_date; // invalid locale
|
|
|
|
if (fixed_length)
|
|
size=max_length;
|
|
else
|
|
size=format_length(format);
|
|
|
|
if (size < MAX_DATE_STRING_REP_LENGTH)
|
|
size= MAX_DATE_STRING_REP_LENGTH;
|
|
|
|
DBUG_ASSERT(format != str);
|
|
if (str->alloc(size))
|
|
goto null_date;
|
|
|
|
/* Create the result string */
|
|
str->set_charset(collation.collation);
|
|
if (!make_date_time(format->lex_cstring(), format->charset(), &l_time,
|
|
is_time_format ? MYSQL_TIMESTAMP_TIME :
|
|
MYSQL_TIMESTAMP_DATE,
|
|
lc, str))
|
|
return str;
|
|
|
|
null_date:
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_func_from_unixtime::fix_length_and_dec()
|
|
{
|
|
THD *thd= current_thd;
|
|
thd->time_zone_used= 1;
|
|
tz= thd->variables.time_zone;
|
|
fix_attributes_datetime_not_fixed_dec(args[0]->decimals);
|
|
maybe_null= true;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
bool Item_func_from_unixtime::get_date(THD *thd, MYSQL_TIME *ltime,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
bzero((char *)ltime, sizeof(*ltime));
|
|
ltime->time_type= MYSQL_TIMESTAMP_TIME;
|
|
|
|
VSec9 sec(thd, args[0], "unixtime", TIMESTAMP_MAX_VALUE);
|
|
DBUG_ASSERT(sec.is_null() || sec.sec() <= TIMESTAMP_MAX_VALUE);
|
|
|
|
if (sec.is_null() || sec.truncated() || sec.neg())
|
|
return (null_value= 1);
|
|
|
|
sec.round(MY_MIN(decimals, TIME_SECOND_PART_DIGITS), thd->temporal_round_mode());
|
|
if (sec.sec() > TIMESTAMP_MAX_VALUE)
|
|
return (null_value= true); // Went out of range after rounding
|
|
|
|
tz->gmt_sec_to_TIME(ltime, (my_time_t) sec.sec());
|
|
ltime->second_part= sec.usec();
|
|
|
|
return (null_value= 0);
|
|
}
|
|
|
|
|
|
bool Item_func_convert_tz::get_date(THD *thd, MYSQL_TIME *ltime,
|
|
date_mode_t fuzzydate __attribute__((unused)))
|
|
{
|
|
my_time_t my_time_tmp;
|
|
String str;
|
|
|
|
if (!from_tz_cached)
|
|
{
|
|
from_tz= my_tz_find(thd, args[1]->val_str_ascii(&str));
|
|
from_tz_cached= args[1]->const_item();
|
|
}
|
|
|
|
if (!to_tz_cached)
|
|
{
|
|
to_tz= my_tz_find(thd, args[2]->val_str_ascii(&str));
|
|
to_tz_cached= args[2]->const_item();
|
|
}
|
|
|
|
if ((null_value= (from_tz == 0 || to_tz == 0)))
|
|
return true;
|
|
|
|
Datetime::Options opt(TIME_NO_ZEROS, thd);
|
|
Datetime *dt= new(ltime) Datetime(thd, args[0], opt);
|
|
if ((null_value= !dt->is_valid_datetime()))
|
|
return true;
|
|
|
|
{
|
|
uint not_used;
|
|
my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, ¬_used);
|
|
ulong sec_part= ltime->second_part;
|
|
/* my_time_tmp is guaranteed to be in the allowed range */
|
|
if (my_time_tmp)
|
|
to_tz->gmt_sec_to_TIME(ltime, my_time_tmp);
|
|
/* we rely on the fact that no timezone conversion can change sec_part */
|
|
ltime->second_part= sec_part;
|
|
}
|
|
|
|
return (null_value= 0);
|
|
}
|
|
|
|
|
|
void Item_func_convert_tz::cleanup()
|
|
{
|
|
from_tz_cached= to_tz_cached= 0;
|
|
Item_datetimefunc::cleanup();
|
|
}
|
|
|
|
|
|
bool Item_date_add_interval::fix_length_and_dec()
|
|
{
|
|
enum_field_types arg0_field_type;
|
|
|
|
if (!args[0]->type_handler()->is_traditional_type())
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
|
|
args[0]->type_handler()->name().ptr(),
|
|
"interval", func_name());
|
|
return TRUE;
|
|
}
|
|
/*
|
|
The field type for the result of an Item_datefunc is defined as
|
|
follows:
|
|
|
|
- If first arg is a MYSQL_TYPE_DATETIME result is MYSQL_TYPE_DATETIME
|
|
- If first arg is a MYSQL_TYPE_DATE and the interval type uses hours,
|
|
minutes or seconds then type is MYSQL_TYPE_DATETIME
|
|
otherwise it's MYSQL_TYPE_DATE
|
|
- if first arg is a MYSQL_TYPE_TIME and the interval type isn't using
|
|
anything larger than days, then the result is MYSQL_TYPE_TIME,
|
|
otherwise - MYSQL_TYPE_DATETIME.
|
|
- Otherwise the result is MYSQL_TYPE_STRING
|
|
(This is because you can't know if the string contains a DATE,
|
|
MYSQL_TIME or DATETIME argument)
|
|
*/
|
|
arg0_field_type= args[0]->field_type();
|
|
|
|
if (arg0_field_type == MYSQL_TYPE_DATETIME ||
|
|
arg0_field_type == MYSQL_TYPE_TIMESTAMP)
|
|
{
|
|
set_func_handler(&func_handler_date_add_interval_datetime);
|
|
}
|
|
else if (arg0_field_type == MYSQL_TYPE_DATE)
|
|
{
|
|
if (int_type <= INTERVAL_DAY || int_type == INTERVAL_YEAR_MONTH)
|
|
set_func_handler(&func_handler_date_add_interval_date);
|
|
else
|
|
set_func_handler(&func_handler_date_add_interval_datetime);
|
|
}
|
|
else if (arg0_field_type == MYSQL_TYPE_TIME)
|
|
{
|
|
if (int_type >= INTERVAL_DAY && int_type != INTERVAL_YEAR_MONTH)
|
|
set_func_handler(&func_handler_date_add_interval_time);
|
|
else
|
|
set_func_handler(&func_handler_date_add_interval_datetime_arg0_time);
|
|
}
|
|
else
|
|
{
|
|
set_func_handler(&func_handler_date_add_interval_string);
|
|
}
|
|
maybe_null= true;
|
|
return m_func_handler->fix_length_and_dec(this);
|
|
}
|
|
|
|
|
|
bool Func_handler_date_add_interval_datetime_arg0_time::
|
|
get_date(THD *thd, Item_handled_func *item,
|
|
MYSQL_TIME *to, date_mode_t fuzzy) const
|
|
{
|
|
// time_expr + INTERVAL {YEAR|QUARTER|MONTH|WEEK|YEAR_MONTH}
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_DATETIME_FUNCTION_OVERFLOW,
|
|
ER_THD(thd, ER_DATETIME_FUNCTION_OVERFLOW), "time");
|
|
return (item->null_value= true);
|
|
}
|
|
|
|
|
|
bool Item_date_add_interval::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
if (!Item_func::eq(item, binary_cmp))
|
|
return 0;
|
|
Item_date_add_interval *other= (Item_date_add_interval*) item;
|
|
return ((int_type == other->int_type) &&
|
|
(date_sub_interval == other->date_sub_interval));
|
|
}
|
|
|
|
/*
|
|
'interval_names' reflects the order of the enumeration interval_type.
|
|
See item_timefunc.h
|
|
*/
|
|
|
|
static const char *interval_names[]=
|
|
{
|
|
"year", "quarter", "month", "week", "day",
|
|
"hour", "minute", "second", "microsecond",
|
|
"year_month", "day_hour", "day_minute",
|
|
"day_second", "hour_minute", "hour_second",
|
|
"minute_second", "day_microsecond",
|
|
"hour_microsecond", "minute_microsecond",
|
|
"second_microsecond"
|
|
};
|
|
|
|
void Item_date_add_interval::print(String *str, enum_query_type query_type)
|
|
{
|
|
args[0]->print_parenthesised(str, query_type, INTERVAL_PRECEDENCE);
|
|
str->append(date_sub_interval?" - interval ":" + interval ");
|
|
args[1]->print(str, query_type);
|
|
str->append(' ');
|
|
str->append(interval_names[int_type]);
|
|
}
|
|
|
|
void Item_extract::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(STRING_WITH_LEN("extract("));
|
|
str->append(interval_names[int_type]);
|
|
str->append(STRING_WITH_LEN(" from "));
|
|
args[0]->print(str, query_type);
|
|
str->append(')');
|
|
}
|
|
|
|
bool Item_extract::fix_length_and_dec()
|
|
{
|
|
maybe_null=1; // If wrong date
|
|
uint32 daylen= args[0]->cmp_type() == TIME_RESULT ? 2 :
|
|
TIME_MAX_INTERVAL_DAY_CHAR_LENGTH;
|
|
switch (int_type) {
|
|
case INTERVAL_YEAR: set_date_length(4); break; // YYYY
|
|
case INTERVAL_YEAR_MONTH: set_date_length(6); break; // YYYYMM
|
|
case INTERVAL_QUARTER: set_date_length(2); break; // 1..4
|
|
case INTERVAL_MONTH: set_date_length(2); break; // MM
|
|
case INTERVAL_WEEK: set_date_length(2); break; // 0..52
|
|
case INTERVAL_DAY: set_day_length(daylen); break; // DD
|
|
case INTERVAL_DAY_HOUR: set_day_length(daylen+2); break; // DDhh
|
|
case INTERVAL_DAY_MINUTE: set_day_length(daylen+4); break; // DDhhmm
|
|
case INTERVAL_DAY_SECOND: set_day_length(daylen+6); break; // DDhhmmss
|
|
case INTERVAL_HOUR: set_time_length(2); break; // hh
|
|
case INTERVAL_HOUR_MINUTE: set_time_length(4); break; // hhmm
|
|
case INTERVAL_HOUR_SECOND: set_time_length(6); break; // hhmmss
|
|
case INTERVAL_MINUTE: set_time_length(2); break; // mm
|
|
case INTERVAL_MINUTE_SECOND: set_time_length(4); break; // mmss
|
|
case INTERVAL_SECOND: set_time_length(2); break; // ss
|
|
case INTERVAL_MICROSECOND: set_time_length(6); break; // ffffff
|
|
case INTERVAL_DAY_MICROSECOND: set_time_length(daylen+12); break; // DDhhmmssffffff
|
|
case INTERVAL_HOUR_MICROSECOND: set_time_length(12); break; // hhmmssffffff
|
|
case INTERVAL_MINUTE_MICROSECOND: set_time_length(10); break; // mmssffffff
|
|
case INTERVAL_SECOND_MICROSECOND: set_time_length(8); break; // ssffffff
|
|
case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
uint Extract_source::week(THD *thd) const
|
|
{
|
|
DBUG_ASSERT(is_valid_extract_source());
|
|
uint year;
|
|
ulong week_format= current_thd->variables.default_week_format;
|
|
return calc_week(this, week_mode(week_format), &year);
|
|
}
|
|
|
|
|
|
longlong Item_extract::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Extract_source dt(thd, args[0], m_date_mode);
|
|
if ((null_value= !dt.is_valid_extract_source()))
|
|
return 0;
|
|
switch (int_type) {
|
|
case INTERVAL_YEAR: return dt.year();
|
|
case INTERVAL_YEAR_MONTH: return dt.year_month();
|
|
case INTERVAL_QUARTER: return dt.quarter();
|
|
case INTERVAL_MONTH: return dt.month();
|
|
case INTERVAL_WEEK: return dt.week(thd);
|
|
case INTERVAL_DAY: return dt.day();
|
|
case INTERVAL_DAY_HOUR: return dt.day_hour();
|
|
case INTERVAL_DAY_MINUTE: return dt.day_minute();
|
|
case INTERVAL_DAY_SECOND: return dt.day_second();
|
|
case INTERVAL_HOUR: return dt.hour();
|
|
case INTERVAL_HOUR_MINUTE: return dt.hour_minute();
|
|
case INTERVAL_HOUR_SECOND: return dt.hour_second();
|
|
case INTERVAL_MINUTE: return dt.minute();
|
|
case INTERVAL_MINUTE_SECOND: return dt.minute_second();
|
|
case INTERVAL_SECOND: return dt.second();
|
|
case INTERVAL_MICROSECOND: return dt.microsecond();
|
|
case INTERVAL_DAY_MICROSECOND: return dt.day_microsecond();
|
|
case INTERVAL_HOUR_MICROSECOND: return dt.hour_microsecond();
|
|
case INTERVAL_MINUTE_MICROSECOND: return dt.minute_microsecond();
|
|
case INTERVAL_SECOND_MICROSECOND: return dt.second_microsecond();
|
|
case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */
|
|
}
|
|
return 0; // Impossible
|
|
}
|
|
|
|
bool Item_extract::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
if (this == item)
|
|
return 1;
|
|
if (item->type() != FUNC_ITEM ||
|
|
functype() != ((Item_func*)item)->functype())
|
|
return 0;
|
|
|
|
Item_extract* ie= (Item_extract*)item;
|
|
if (ie->int_type != int_type)
|
|
return 0;
|
|
|
|
if (!args[0]->eq(ie->args[0], binary_cmp))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
bool Item_char_typecast::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
if (this == item)
|
|
return 1;
|
|
if (item->type() != FUNC_ITEM ||
|
|
functype() != ((Item_func*)item)->functype())
|
|
return 0;
|
|
|
|
Item_char_typecast *cast= (Item_char_typecast*)item;
|
|
if (cast_length != cast->cast_length ||
|
|
cast_cs != cast->cast_cs)
|
|
return 0;
|
|
|
|
if (!args[0]->eq(cast->args[0], binary_cmp))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
void Item_func::print_cast_temporal(String *str, enum_query_type query_type)
|
|
{
|
|
char buf[32];
|
|
str->append(STRING_WITH_LEN("cast("));
|
|
args[0]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" as "));
|
|
const Name name= type_handler()->name();
|
|
str->append(name.ptr(), name.length());
|
|
if (decimals && decimals != NOT_FIXED_DEC)
|
|
{
|
|
str->append('(');
|
|
str->append(llstr(decimals, buf));
|
|
str->append(')');
|
|
}
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
void Item_char_typecast::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(STRING_WITH_LEN("cast("));
|
|
args[0]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" as char"));
|
|
if (cast_length != ~0U)
|
|
{
|
|
str->append('(');
|
|
char buffer[20];
|
|
// my_charset_bin is good enough for numbers
|
|
String st(buffer, sizeof(buffer), &my_charset_bin);
|
|
st.set(static_cast<ulonglong>(cast_length), &my_charset_bin);
|
|
str->append(st);
|
|
str->append(')');
|
|
}
|
|
if (cast_cs)
|
|
{
|
|
str->append(STRING_WITH_LEN(" charset "));
|
|
str->append(cast_cs->csname);
|
|
}
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
void Item_char_typecast::check_truncation_with_warn(String *src, size_t dstlen)
|
|
{
|
|
if (dstlen < src->length())
|
|
{
|
|
THD *thd= current_thd;
|
|
char char_type[40];
|
|
ErrConvString err(src);
|
|
bool save_abort_on_warning= thd->abort_on_warning;
|
|
thd->abort_on_warning&= !m_suppress_warning_to_error_escalation;
|
|
my_snprintf(char_type, sizeof(char_type), "%s(%lu)",
|
|
cast_cs == &my_charset_bin ? "BINARY" : "CHAR",
|
|
(ulong) cast_length);
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_TRUNCATED_WRONG_VALUE,
|
|
ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), char_type,
|
|
err.ptr());
|
|
thd->abort_on_warning= save_abort_on_warning;
|
|
}
|
|
}
|
|
|
|
|
|
String *Item_char_typecast::reuse(String *src, size_t length)
|
|
{
|
|
DBUG_ASSERT(length <= src->length());
|
|
check_truncation_with_warn(src, length);
|
|
tmp_value.set(src->ptr(), length, cast_cs);
|
|
return &tmp_value;
|
|
}
|
|
|
|
|
|
/*
|
|
Make a copy, to handle conversion or fix bad bytes.
|
|
*/
|
|
String *Item_char_typecast::copy(String *str, CHARSET_INFO *strcs)
|
|
{
|
|
String_copier_for_item copier(current_thd);
|
|
if (copier.copy_with_warn(cast_cs, &tmp_value, strcs,
|
|
str->ptr(), str->length(), cast_length))
|
|
{
|
|
null_value= 1; // EOM
|
|
return 0;
|
|
}
|
|
check_truncation_with_warn(str, (uint)(copier.source_end_pos() - str->ptr()));
|
|
return &tmp_value;
|
|
}
|
|
|
|
|
|
uint Item_char_typecast::adjusted_length_with_warn(uint length)
|
|
{
|
|
if (length <= current_thd->variables.max_allowed_packet)
|
|
return length;
|
|
|
|
THD *thd= current_thd;
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_ALLOWED_PACKET_OVERFLOWED,
|
|
ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED),
|
|
cast_cs == &my_charset_bin ?
|
|
"cast_as_binary" : func_name(),
|
|
thd->variables.max_allowed_packet);
|
|
return thd->variables.max_allowed_packet;
|
|
}
|
|
|
|
|
|
String *Item_char_typecast::val_str(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
String *res;
|
|
|
|
if (has_explicit_length())
|
|
cast_length= adjusted_length_with_warn(cast_length);
|
|
|
|
if (!(res= args[0]->val_str(str)))
|
|
{
|
|
null_value= 1;
|
|
return 0;
|
|
}
|
|
|
|
if (cast_cs == &my_charset_bin &&
|
|
has_explicit_length() &&
|
|
cast_length > res->length())
|
|
{
|
|
// Special case: pad binary value with trailing 0x00
|
|
DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet);
|
|
if (res->alloced_length() < cast_length)
|
|
{
|
|
str_value.alloc(cast_length);
|
|
str_value.copy(*res);
|
|
res= &str_value;
|
|
}
|
|
bzero((char*) res->ptr() + res->length(), cast_length - res->length());
|
|
res->length(cast_length);
|
|
res->set_charset(&my_charset_bin);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
from_cs is 0 in the case where the result set may vary between calls,
|
|
for example with dynamic columns.
|
|
*/
|
|
CHARSET_INFO *cs= from_cs ? from_cs : res->charset();
|
|
if (!charset_conversion)
|
|
{
|
|
// Try to reuse the original string (if well formed).
|
|
Well_formed_prefix prefix(cs, res->ptr(), res->end(), cast_length);
|
|
if (!prefix.well_formed_error_pos())
|
|
res= reuse(res, prefix.length());
|
|
goto end;
|
|
}
|
|
// Character set conversion, or bad bytes were found.
|
|
if (!(res= copy(res, cs)))
|
|
return 0;
|
|
}
|
|
|
|
end:
|
|
return ((null_value= (res->length() >
|
|
adjusted_length_with_warn(res->length())))) ? 0 : res;
|
|
}
|
|
|
|
|
|
void Item_char_typecast::fix_length_and_dec_numeric()
|
|
{
|
|
fix_length_and_dec_internal(from_cs= cast_cs->mbminlen == 1 ?
|
|
cast_cs :
|
|
&my_charset_latin1);
|
|
}
|
|
|
|
|
|
void Item_char_typecast::fix_length_and_dec_generic()
|
|
{
|
|
fix_length_and_dec_internal(from_cs= args[0]->dynamic_result() ?
|
|
0 :
|
|
args[0]->collation.collation);
|
|
}
|
|
|
|
|
|
void Item_char_typecast::fix_length_and_dec_internal(CHARSET_INFO *from_cs)
|
|
{
|
|
uint32 char_length;
|
|
/*
|
|
We always force character set conversion if cast_cs
|
|
is a multi-byte character set. It guarantees that the
|
|
result of CAST is a well-formed string.
|
|
For single-byte character sets we allow just to copy
|
|
from the argument. A single-byte character sets string
|
|
is always well-formed.
|
|
|
|
There is a special trick to convert form a number to ucs2.
|
|
As numbers have my_charset_bin as their character set,
|
|
it wouldn't do conversion to ucs2 without an additional action.
|
|
To force conversion, we should pretend to be non-binary.
|
|
Let's choose from_cs this way:
|
|
- If the argument in a number and cast_cs is ucs2 (i.e. mbminlen > 1),
|
|
then from_cs is set to latin1, to perform latin1 -> ucs2 conversion.
|
|
- If the argument is a number and cast_cs is ASCII-compatible
|
|
(i.e. mbminlen == 1), then from_cs is set to cast_cs,
|
|
which allows just to take over the args[0]->val_str() result
|
|
and thus avoid unnecessary character set conversion.
|
|
- If the argument is not a number, then from_cs is set to
|
|
the argument's charset.
|
|
- If argument has a dynamic collation (can change from call to call)
|
|
we set from_cs to 0 as a marker that we have to take the collation
|
|
from the result string.
|
|
|
|
Note (TODO): we could use repertoire technique here.
|
|
*/
|
|
charset_conversion= !from_cs || (cast_cs->mbmaxlen > 1) ||
|
|
(!my_charset_same(from_cs, cast_cs) &&
|
|
from_cs != &my_charset_bin &&
|
|
cast_cs != &my_charset_bin);
|
|
collation.set(cast_cs, DERIVATION_IMPLICIT);
|
|
char_length= ((cast_length != ~0U) ? cast_length :
|
|
args[0]->max_length /
|
|
(cast_cs == &my_charset_bin ? 1 :
|
|
args[0]->collation.collation->mbmaxlen));
|
|
max_length= char_length * cast_cs->mbmaxlen;
|
|
}
|
|
|
|
|
|
bool Item_time_typecast::get_date(THD *thd, MYSQL_TIME *to, date_mode_t mode)
|
|
{
|
|
Time *tm= new(to) Time(thd, args[0], Time::Options_for_cast(mode, thd),
|
|
MY_MIN(decimals, TIME_SECOND_PART_DIGITS));
|
|
return (null_value= !tm->is_valid_time());
|
|
}
|
|
|
|
|
|
Sql_mode_dependency Item_time_typecast::value_depends_on_sql_mode() const
|
|
{
|
|
return Item_timefunc::value_depends_on_sql_mode() |
|
|
Sql_mode_dependency(decimals < args[0]->decimals ?
|
|
MODE_TIME_ROUND_FRACTIONAL : 0, 0);
|
|
}
|
|
|
|
|
|
bool Item_date_typecast::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
date_mode_t tmp= (fuzzydate | sql_mode_for_dates(thd)) & ~TIME_TIME_ONLY;
|
|
// Force truncation
|
|
Date *d= new(ltime) Date(thd, args[0], Date::Options(date_conv_mode_t(tmp)));
|
|
return (null_value= !d->is_valid_date());
|
|
}
|
|
|
|
|
|
bool Item_datetime_typecast::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
date_mode_t tmp= (fuzzydate | sql_mode_for_dates(thd)) & ~TIME_TIME_ONLY;
|
|
// Force rounding if the current sql_mode says so
|
|
Datetime::Options opt(date_conv_mode_t(tmp), thd);
|
|
Datetime *dt= new(ltime) Datetime(thd, args[0], opt,
|
|
MY_MIN(decimals, TIME_SECOND_PART_DIGITS));
|
|
return (null_value= !dt->is_valid_datetime());
|
|
}
|
|
|
|
|
|
Sql_mode_dependency Item_datetime_typecast::value_depends_on_sql_mode() const
|
|
{
|
|
return Item_datetimefunc::value_depends_on_sql_mode() |
|
|
Sql_mode_dependency(decimals < args[0]->decimals ?
|
|
MODE_TIME_ROUND_FRACTIONAL : 0, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
MAKEDATE(a,b) is a date function that creates a date value
|
|
from a year and day value.
|
|
|
|
NOTES:
|
|
As arguments are integers, we can't know if the year is a 2 digit
|
|
or 4 digit year. In this case we treat all years < 100 as 2 digit
|
|
years. Ie, this is not safe for dates between 0000-01-01 and
|
|
0099-12-31
|
|
*/
|
|
|
|
bool Item_func_makedate::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
long year, days, daynr= (long) args[1]->val_int();
|
|
|
|
VYear vyear(args[0]);
|
|
if (vyear.is_null() || args[1]->null_value || vyear.truncated() || daynr <= 0)
|
|
goto err;
|
|
|
|
if ((year= (long) vyear.year()) < 100)
|
|
year= year_2000_handling(year);
|
|
days= calc_daynr(year,1,1) + daynr - 1;
|
|
if (get_date_from_daynr(days, <ime->year, <ime->month, <ime->day))
|
|
goto err;
|
|
ltime->time_type= MYSQL_TIMESTAMP_DATE;
|
|
ltime->neg= 0;
|
|
ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
|
|
return (null_value= 0);
|
|
|
|
err:
|
|
return (null_value= 1);
|
|
}
|
|
|
|
|
|
bool Item_func_add_time::fix_length_and_dec()
|
|
{
|
|
enum_field_types arg0_field_type;
|
|
|
|
if (!args[0]->type_handler()->is_traditional_type() ||
|
|
!args[1]->type_handler()->is_traditional_type())
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
|
|
args[0]->type_handler()->name().ptr(),
|
|
args[1]->type_handler()->name().ptr(), func_name());
|
|
return TRUE;
|
|
}
|
|
/*
|
|
The field type for the result of an Item_func_add_time function is defined
|
|
as follows:
|
|
|
|
- If first arg is a MYSQL_TYPE_DATETIME or MYSQL_TYPE_TIMESTAMP
|
|
result is MYSQL_TYPE_DATETIME
|
|
- If first arg is a MYSQL_TYPE_TIME result is MYSQL_TYPE_TIME
|
|
- Otherwise the result is MYSQL_TYPE_STRING
|
|
*/
|
|
|
|
arg0_field_type= args[0]->field_type();
|
|
if (arg0_field_type == MYSQL_TYPE_DATE ||
|
|
arg0_field_type == MYSQL_TYPE_DATETIME ||
|
|
arg0_field_type == MYSQL_TYPE_TIMESTAMP)
|
|
{
|
|
set_func_handler(sign > 0 ? &func_handler_add_time_datetime_add :
|
|
&func_handler_add_time_datetime_sub);
|
|
}
|
|
else if (arg0_field_type == MYSQL_TYPE_TIME)
|
|
{
|
|
set_func_handler(sign > 0 ? &func_handler_add_time_time_add :
|
|
&func_handler_add_time_time_sub);
|
|
}
|
|
else
|
|
{
|
|
set_func_handler(sign > 0 ? &func_handler_add_time_string_add :
|
|
&func_handler_add_time_string_sub);
|
|
}
|
|
|
|
maybe_null= true;
|
|
return m_func_handler->fix_length_and_dec(this);
|
|
}
|
|
|
|
|
|
/**
|
|
TIMEDIFF(t,s) is a time function that calculates the
|
|
time value between a start and end time.
|
|
|
|
t and s: time_or_datetime_expression
|
|
Result: Time value
|
|
*/
|
|
|
|
bool Item_func_timediff::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int l_sign= 1;
|
|
MYSQL_TIME l_time1,l_time2,l_time3;
|
|
|
|
/* the following may be true in, for example, date_add(timediff(...), ... */
|
|
if (fuzzydate & TIME_NO_ZERO_IN_DATE)
|
|
return (null_value= 1);
|
|
|
|
if (args[0]->get_time(thd, &l_time1) ||
|
|
args[1]->get_time(thd, &l_time2) ||
|
|
l_time1.time_type != l_time2.time_type)
|
|
return (null_value= 1);
|
|
|
|
if (l_time1.neg != l_time2.neg)
|
|
l_sign= -l_sign;
|
|
|
|
if (calc_time_diff(&l_time1, &l_time2, l_sign, &l_time3, fuzzydate))
|
|
return (null_value= 1);
|
|
|
|
*ltime= l_time3;
|
|
return (null_value= adjust_time_range_with_warn(thd, ltime, decimals));
|
|
}
|
|
|
|
|
|
/**
|
|
MAKETIME(h,m,s) is a time function that calculates a time value
|
|
from the total number of hours, minutes, and seconds.
|
|
Result: Time value
|
|
*/
|
|
|
|
bool Item_func_maketime::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Longlong_hybrid hour(args[0]->val_int(), args[0]->unsigned_flag);
|
|
longlong minute= args[1]->val_int();
|
|
VSec9 sec(thd, args[2], "seconds", 59);
|
|
|
|
DBUG_ASSERT(sec.is_null() || sec.sec() <= 59);
|
|
if (args[0]->null_value || args[1]->null_value || sec.is_null() ||
|
|
minute < 0 || minute > 59 || sec.neg() || sec.truncated())
|
|
return (null_value= 1);
|
|
|
|
int warn;
|
|
new(ltime) Time(&warn, hour.neg(), hour.abs(), (uint) minute,
|
|
sec.to_const_sec9(), thd->temporal_round_mode(), decimals);
|
|
if (warn)
|
|
{
|
|
// use check_time_range() to set ltime to the max value depending on dec
|
|
int unused;
|
|
ltime->hour= TIME_MAX_HOUR + 1;
|
|
check_time_range(ltime, decimals, &unused);
|
|
char buf[28];
|
|
char *ptr= longlong10_to_str(hour.value(), buf, hour.is_unsigned() ? 10 : -10);
|
|
int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u",
|
|
(uint) minute, (uint) sec.sec());
|
|
ErrConvString err(buf, len, &my_charset_bin);
|
|
thd->push_warning_truncated_wrong_value("time", err.ptr());
|
|
}
|
|
|
|
return (null_value= 0);
|
|
}
|
|
|
|
|
|
/**
|
|
MICROSECOND(a) is a function ( extraction) that extracts the microseconds
|
|
from a.
|
|
|
|
a: Datetime or time value
|
|
Result: int value
|
|
*/
|
|
|
|
longlong Item_func_microsecond::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
THD *thd= current_thd;
|
|
Time tm(thd, args[0], Time::Options_for_cast(thd));
|
|
return ((null_value= !tm.is_valid_time())) ?
|
|
0 : tm.get_mysql_time()->second_part;
|
|
}
|
|
|
|
|
|
longlong Item_func_timestamp_diff::val_int()
|
|
{
|
|
MYSQL_TIME ltime1, ltime2;
|
|
ulonglong seconds;
|
|
ulong microseconds;
|
|
long months= 0;
|
|
int neg= 1;
|
|
THD *thd= current_thd;
|
|
Datetime::Options opt(TIME_NO_ZEROS, thd);
|
|
|
|
null_value= 0;
|
|
|
|
if (Datetime(thd, args[0], opt).copy_to_mysql_time(<ime1) ||
|
|
Datetime(thd, args[1], opt).copy_to_mysql_time(<ime2))
|
|
goto null_date;
|
|
|
|
if (calc_time_diff(<ime2,<ime1, 1,
|
|
&seconds, µseconds))
|
|
neg= -1;
|
|
|
|
if (int_type == INTERVAL_YEAR ||
|
|
int_type == INTERVAL_QUARTER ||
|
|
int_type == INTERVAL_MONTH)
|
|
{
|
|
uint year_beg, year_end, month_beg, month_end, day_beg, day_end;
|
|
uint years= 0;
|
|
uint second_beg, second_end, microsecond_beg, microsecond_end;
|
|
|
|
if (neg == -1)
|
|
{
|
|
year_beg= ltime2.year;
|
|
year_end= ltime1.year;
|
|
month_beg= ltime2.month;
|
|
month_end= ltime1.month;
|
|
day_beg= ltime2.day;
|
|
day_end= ltime1.day;
|
|
second_beg= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
|
|
second_end= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
|
|
microsecond_beg= ltime2.second_part;
|
|
microsecond_end= ltime1.second_part;
|
|
}
|
|
else
|
|
{
|
|
year_beg= ltime1.year;
|
|
year_end= ltime2.year;
|
|
month_beg= ltime1.month;
|
|
month_end= ltime2.month;
|
|
day_beg= ltime1.day;
|
|
day_end= ltime2.day;
|
|
second_beg= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
|
|
second_end= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
|
|
microsecond_beg= ltime1.second_part;
|
|
microsecond_end= ltime2.second_part;
|
|
}
|
|
|
|
/* calc years */
|
|
years= year_end - year_beg;
|
|
if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
|
|
years-= 1;
|
|
|
|
/* calc months */
|
|
months= 12*years;
|
|
if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
|
|
months+= 12 - (month_beg - month_end);
|
|
else
|
|
months+= (month_end - month_beg);
|
|
|
|
if (day_end < day_beg)
|
|
months-= 1;
|
|
else if ((day_end == day_beg) &&
|
|
((second_end < second_beg) ||
|
|
(second_end == second_beg && microsecond_end < microsecond_beg)))
|
|
months-= 1;
|
|
}
|
|
|
|
switch (int_type) {
|
|
case INTERVAL_YEAR:
|
|
return months/12*neg;
|
|
case INTERVAL_QUARTER:
|
|
return months/3*neg;
|
|
case INTERVAL_MONTH:
|
|
return months*neg;
|
|
case INTERVAL_WEEK:
|
|
return ((longlong) (seconds / SECONDS_IN_24H / 7L)) * neg;
|
|
case INTERVAL_DAY:
|
|
return ((longlong) (seconds / SECONDS_IN_24H)) * neg;
|
|
case INTERVAL_HOUR:
|
|
return ((longlong) (seconds / 3600L)) * neg;
|
|
case INTERVAL_MINUTE:
|
|
return ((longlong) (seconds / 60L)) * neg;
|
|
case INTERVAL_SECOND:
|
|
return ((longlong) seconds) * neg;
|
|
case INTERVAL_MICROSECOND:
|
|
/*
|
|
In MySQL difference between any two valid datetime values
|
|
in microseconds fits into longlong.
|
|
*/
|
|
return ((longlong) ((ulonglong) seconds * 1000000L + microseconds)) * neg;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
null_date:
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item_func_timestamp_diff::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
str->append('(');
|
|
|
|
switch (int_type) {
|
|
case INTERVAL_YEAR:
|
|
str->append(STRING_WITH_LEN("YEAR"));
|
|
break;
|
|
case INTERVAL_QUARTER:
|
|
str->append(STRING_WITH_LEN("QUARTER"));
|
|
break;
|
|
case INTERVAL_MONTH:
|
|
str->append(STRING_WITH_LEN("MONTH"));
|
|
break;
|
|
case INTERVAL_WEEK:
|
|
str->append(STRING_WITH_LEN("WEEK"));
|
|
break;
|
|
case INTERVAL_DAY:
|
|
str->append(STRING_WITH_LEN("DAY"));
|
|
break;
|
|
case INTERVAL_HOUR:
|
|
str->append(STRING_WITH_LEN("HOUR"));
|
|
break;
|
|
case INTERVAL_MINUTE:
|
|
str->append(STRING_WITH_LEN("MINUTE"));
|
|
break;
|
|
case INTERVAL_SECOND:
|
|
str->append(STRING_WITH_LEN("SECOND"));
|
|
break;
|
|
case INTERVAL_MICROSECOND:
|
|
str->append(STRING_WITH_LEN("MICROSECOND"));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (uint i=0 ; i < 2 ; i++)
|
|
{
|
|
str->append(',');
|
|
args[i]->print(str, query_type);
|
|
}
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
String *Item_func_get_format::val_str_ascii(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
const char *format_name;
|
|
KNOWN_DATE_TIME_FORMAT *format;
|
|
String *val= args[0]->val_str_ascii(str);
|
|
ulong val_len;
|
|
|
|
if ((null_value= args[0]->null_value))
|
|
return 0;
|
|
|
|
val_len= val->length();
|
|
for (format= &known_date_time_formats[0];
|
|
(format_name= format->format_name);
|
|
format++)
|
|
{
|
|
uint format_name_len;
|
|
format_name_len= (uint) strlen(format_name);
|
|
if (val_len == format_name_len &&
|
|
!my_strnncoll(&my_charset_latin1,
|
|
(const uchar *) val->ptr(), val_len,
|
|
(const uchar *) format_name, val_len))
|
|
{
|
|
const char *format_str= get_date_time_format_str(format, type);
|
|
str->set(format_str, (uint) strlen(format_str), &my_charset_numeric);
|
|
return str;
|
|
}
|
|
}
|
|
|
|
null_value= 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item_func_get_format::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
str->append('(');
|
|
|
|
switch (type) {
|
|
case MYSQL_TIMESTAMP_DATE:
|
|
str->append(STRING_WITH_LEN("DATE, "));
|
|
break;
|
|
case MYSQL_TIMESTAMP_DATETIME:
|
|
str->append(STRING_WITH_LEN("DATETIME, "));
|
|
break;
|
|
case MYSQL_TIMESTAMP_TIME:
|
|
str->append(STRING_WITH_LEN("TIME, "));
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
args[0]->print(str, query_type);
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
/**
|
|
Get type of datetime value (DATE/TIME/...) which will be produced
|
|
according to format string.
|
|
|
|
@param format format string
|
|
@param length length of format string
|
|
|
|
@note
|
|
We don't process day format's characters('D', 'd', 'e') because day
|
|
may be a member of all date/time types.
|
|
|
|
@note
|
|
Format specifiers supported by this function should be in sync with
|
|
specifiers supported by extract_date_time() function.
|
|
|
|
@return
|
|
A function handler corresponding the given format
|
|
*/
|
|
|
|
static const Item_handled_func::Handler *
|
|
get_date_time_result_type(const char *format, uint length)
|
|
{
|
|
const char *time_part_frms= "HISThiklrs";
|
|
const char *date_part_frms= "MVUXYWabcjmvuxyw";
|
|
bool date_part_used= 0, time_part_used= 0, frac_second_used= 0;
|
|
|
|
const char *val= format;
|
|
const char *end= format + length;
|
|
|
|
for (; val != end; val++)
|
|
{
|
|
if (*val == '%' && val+1 != end)
|
|
{
|
|
val++;
|
|
if (*val == 'f')
|
|
frac_second_used= time_part_used= 1;
|
|
else if (!time_part_used && strchr(time_part_frms, *val))
|
|
time_part_used= 1;
|
|
else if (!date_part_used && strchr(date_part_frms, *val))
|
|
date_part_used= 1;
|
|
if (date_part_used && frac_second_used)
|
|
{
|
|
/*
|
|
frac_second_used implies time_part_used, and thus we already
|
|
have all types of date-time components and can end our search.
|
|
*/
|
|
return &func_handler_str_to_date_datetime_usec;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We don't have all three types of date-time components */
|
|
if (frac_second_used)
|
|
return &func_handler_str_to_date_time_usec;
|
|
if (time_part_used)
|
|
{
|
|
if (date_part_used)
|
|
return &func_handler_str_to_date_datetime_sec;
|
|
return &func_handler_str_to_date_time_sec;
|
|
}
|
|
return &func_handler_str_to_date_date;
|
|
}
|
|
|
|
|
|
bool Item_func_str_to_date::fix_length_and_dec()
|
|
{
|
|
if (!args[0]->type_handler()->is_traditional_type() ||
|
|
!args[1]->type_handler()->is_traditional_type())
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
|
|
args[0]->type_handler()->name().ptr(),
|
|
args[1]->type_handler()->name().ptr(), func_name());
|
|
return TRUE;
|
|
}
|
|
if (agg_arg_charsets(collation, args, 2, MY_COLL_ALLOW_CONV, 1))
|
|
return TRUE;
|
|
if (collation.collation->mbminlen > 1)
|
|
internal_charset= &my_charset_utf8mb4_general_ci;
|
|
|
|
maybe_null= true;
|
|
set_func_handler(&func_handler_str_to_date_datetime_usec);
|
|
|
|
if ((const_item= args[1]->const_item()))
|
|
{
|
|
StringBuffer<64> format_str;
|
|
String *format= args[1]->val_str(&format_str, &format_converter,
|
|
internal_charset);
|
|
if (!args[1]->null_value)
|
|
set_func_handler(get_date_time_result_type(format->ptr(), format->length()));
|
|
}
|
|
return m_func_handler->fix_length_and_dec(this);
|
|
}
|
|
|
|
|
|
bool Item_func_str_to_date::get_date_common(THD *thd, MYSQL_TIME *ltime,
|
|
date_mode_t fuzzydate,
|
|
timestamp_type tstype)
|
|
{
|
|
DATE_TIME_FORMAT date_time_format;
|
|
StringBuffer<64> val_string, format_str;
|
|
String *val, *format;
|
|
|
|
val= args[0]->val_str(&val_string, &subject_converter, internal_charset);
|
|
format= args[1]->val_str(&format_str, &format_converter, internal_charset);
|
|
if (args[0]->null_value || args[1]->null_value)
|
|
return (null_value=1);
|
|
|
|
date_time_format.format.str= (char*) format->ptr();
|
|
date_time_format.format.length= format->length();
|
|
if (extract_date_time(thd, &date_time_format, val->ptr(), val->length(),
|
|
ltime, tstype, 0, "datetime",
|
|
date_conv_mode_t(fuzzydate) |
|
|
sql_mode_for_dates(thd)))
|
|
return (null_value=1);
|
|
return (null_value= 0);
|
|
}
|
|
|
|
|
|
bool Item_func_last_day::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
|
|
{
|
|
Datetime::Options opt(date_conv_mode_t(fuzzydate & ~TIME_TIME_ONLY),
|
|
time_round_mode_t(fuzzydate));
|
|
Datetime *d= new(ltime) Datetime(thd, args[0], opt);
|
|
if ((null_value= (!d->is_valid_datetime() || ltime->month == 0)))
|
|
return true;
|
|
uint month_idx= ltime->month-1;
|
|
ltime->day= days_in_month[month_idx];
|
|
if ( month_idx == 1 && calc_days_in_year(ltime->year) == 366)
|
|
ltime->day= 29;
|
|
ltime->hour= ltime->minute= ltime->second= 0;
|
|
ltime->second_part= 0;
|
|
ltime->time_type= MYSQL_TIMESTAMP_DATE;
|
|
return (null_value= 0);
|
|
}
|