mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			4290 lines
		
	
	
	
		
			119 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4290 lines
		
	
	
	
		
			119 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
 | |
| */
 | |
| 
 | |
| #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->scan(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_charset_latin1.strnncoll(val, 2, "PM", 2))
 | |
| 	  daypart= 12;
 | |
| 	else if (my_charset_latin1.strnncoll(val, 2, "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, &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(THD *thd, const String *format,
 | |
|                            const 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;
 | |
|   struct my_tz curr_tz;
 | |
|   Time_zone* curr_timezone= 0;
 | |
| 
 | |
|   str->length(0);
 | |
| 
 | |
|   if (l_time->neg)
 | |
|     str->append_wc('-');
 | |
| 
 | |
|   end= (ptr= (const uchar *) format->ptr()) + 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;
 | |
| 
 | |
|       case 'z':
 | |
|       {
 | |
|         if (!curr_timezone)
 | |
|         {
 | |
|           curr_timezone= thd->variables.time_zone;
 | |
|           curr_timezone->get_timezone_information(&curr_tz, l_time);
 | |
|         }
 | |
|         long minutes= labs(curr_tz.seconds_offset)/60, diff_hr, diff_min;
 | |
|         diff_hr= minutes/60;
 | |
|         diff_min= minutes%60;
 | |
| 
 | |
|         str->append(curr_tz.seconds_offset < 0 ? '-' : '+');
 | |
|         str->append(static_cast<char>('0' + diff_hr/10));
 | |
|         str->append(static_cast<char>('0' + diff_hr%10));
 | |
|         str->append(static_cast<char>('0' + diff_min/10));
 | |
|         str->append(static_cast<char>('0' + diff_min%10));
 | |
|         break;
 | |
|       }
 | |
|       case 'Z':
 | |
|         if (!curr_timezone)
 | |
|         {
 | |
|           curr_timezone= thd->variables.time_zone;
 | |
|           curr_timezone->get_timezone_information(&curr_tz, l_time);
 | |
|         }
 | |
|         str->append(curr_tz.abbreviation, strlen(curr_tz.abbreviation));
 | |
|         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());
 | |
|   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());
 | |
|   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());
 | |
|   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());
 | |
|   // 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());
 | |
|   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());
 | |
|   // 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());
 | |
|   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());
 | |
|   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());
 | |
|   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)
 | |
| {
 | |
|   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;
 | |
|   set_maybe_null();
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| String* Item_func_monthname::val_str(String* str)
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   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_utf8mb3_bin,
 | |
| 	    collation.collation, &err);
 | |
|   return str;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Returns the quarter of the year.
 | |
| */
 | |
| 
 | |
| longlong Item_func_quarter::val_int()
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   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());
 | |
|   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());
 | |
|   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());
 | |
|   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 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());
 | |
|   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());
 | |
|   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());
 | |
|   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)
 | |
| {
 | |
|   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;
 | |
|   set_maybe_null();
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| String* Item_func_dayname::val_str(String* str)
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   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_utf8mb3_bin,
 | |
| 	    collation.collation, &err);
 | |
|   return str;
 | |
| }
 | |
| 
 | |
| 
 | |
| longlong Item_func_year::val_int()
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   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());
 | |
|   // 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());
 | |
|   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->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= (my_time_t) tm.tv_sec;
 | |
|   *second_part= tm.tv_usec;
 | |
|   if ((null_value= (tm.tv_sec == 0 && tm.tv_usec == 0)))
 | |
|   {
 | |
|     /*
 | |
|       The value {0,0}='1970-01-01 00:00:00.000000 GMT' cannot be
 | |
|       stored in a TIMESTAMP field. Return SQL NULL.
 | |
|       Simmetrically, UNIX_TIMESTAMP(0) also returns SQL NULL.
 | |
|     */
 | |
|     return true;
 | |
|   }
 | |
|   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(0, 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());
 | |
|   DBUG_ASSERT(arg_count == 1 &&
 | |
|               args[0]->type() == Item::FIELD_ITEM &&
 | |
|               args[0]->field_type() == MYSQL_TYPE_TIMESTAMP);
 | |
|   Field *field= ((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());
 | |
|   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());
 | |
|   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);
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline
 | |
| uint32 adjust_interval_field_uint32(ulonglong value, int32 multiplier)
 | |
| {
 | |
|   return value > ((ulonglong) (uint32) (UINT_MAX32)) / multiplier ?
 | |
|          (uint32) UINT_MAX32 :
 | |
|          (uint32) (value * multiplier);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   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];
 | |
|   ulonglong 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)
 | |
|   {
 | |
|     /*
 | |
|       Let's use Longlong_hybrid_null to handle correctly:
 | |
|       - signed and unsigned values
 | |
|       - the corner case with LONGLONG_MIN
 | |
|         (avoid undefined behavior with its negation)
 | |
|     */
 | |
|     const Longlong_hybrid_null nr= args->to_longlong_hybrid_null();
 | |
|     if (nr.is_null())
 | |
|       return true;
 | |
|     value= nr.abs();
 | |
|     interval->neg= nr.neg() ? 1 : 0;
 | |
|   }
 | |
|   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= adjust_interval_field_uint32(value, 3);
 | |
|     break;
 | |
|   case INTERVAL_MONTH:
 | |
|     interval->month= (ulong) value;
 | |
|     break;
 | |
|   case INTERVAL_WEEK:
 | |
|     interval->day= adjust_interval_field_uint32(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;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_current_timestamp::val_native(THD *thd, Native *to)
 | |
| {
 | |
|   Timestamp ts(Timeval(thd->query_start(), thd->query_start_sec_part()));
 | |
|   /*
 | |
|     to_native() can fail in case of EOM. Don't set null_value on EOM,
 | |
|     because CURRENT_TIMESTAMP is NOT NULL. The statement will fail anyway.
 | |
|   */
 | |
|   return ts.trunc(decimals).to_native(to, decimals);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|     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->used |= THD::TIME_ZONE_USED;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|     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)
 | |
| {
 | |
|   return check_fsp_or_error() ||
 | |
|          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_cstring());
 | |
|   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->used|= THD::TIME_ZONE_USED;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|     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)
 | |
| {
 | |
|   return check_fsp_or_error() ||
 | |
|          Item_datetimefunc::fix_fields(thd, items);
 | |
| }
 | |
| 
 | |
| void Item_func_now::print(String *str, enum_query_type query_type)
 | |
| {
 | |
|   str->append(func_name_cstring());
 | |
|   str->append('(');
 | |
|   if (decimals)
 | |
|     str->append_ulonglong(decimals);
 | |
|   str->append(')');
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|     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->used|= THD::TIME_ZONE_USED;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|     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.
 | |
| */
 | |
| bool Item_func_sysdate_local::val_native(THD *thd, Native *to)
 | |
| {
 | |
|   my_hrtime_t now= my_hrtime();
 | |
|   Timestamp ts(hrtime_to_my_time(now), hrtime_sec_part(now));
 | |
|   /*
 | |
|     to_native() can fail on EOM. Don't set null_value here,
 | |
|     because SYSDATE is NOT NULL. The statement will fail anyway.
 | |
|   */
 | |
|   return ts.trunc(decimals).to_native(to, decimals);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_sec_to_time::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   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)
 | |
| {
 | |
|   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;
 | |
|   my_repertoire_t 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);
 | |
|   }
 | |
|   set_maybe_null(); // If wrong date
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_date_format::eq(const Item *item, const Eq_config &config) 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], config))
 | |
|     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 'Z': /* time zone abbreviation */
 | |
|       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 'z': /* time zone offset */
 | |
|         size += 5;
 | |
|         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());
 | |
|   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(thd, format, &l_time,
 | |
|                       is_time_format ? MYSQL_TIMESTAMP_TIME :
 | |
|                                        MYSQL_TIMESTAMP_DATE,
 | |
|                       lc, str))
 | |
|     return str;
 | |
| 
 | |
| null_date:
 | |
|   null_value=1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* A class to print TO_CHAR(date_time, format) */
 | |
| class Date_time_format_oracle
 | |
| {
 | |
|   // m_fm is true if "FM" was found in the format string odd number of times
 | |
|   bool m_fm;
 | |
| public:
 | |
|   Date_time_format_oracle()
 | |
|    :m_fm(false)
 | |
|   { }
 | |
| 
 | |
|   /*
 | |
|     Append a numeric value to a String.
 | |
|     If m_fm is false, then left-pad the numeric value with '0'.
 | |
| 
 | |
|     @param val       - the numeric value to be appended to str
 | |
|     @param size      - the maximum number of digits possible in val
 | |
|     @param [OUT] str - the result String (val will be appended to it)
 | |
| 
 | |
|     @retval false    - on success
 | |
|     @retval true     - on error (e.g. EOM)
 | |
|   */
 | |
|   bool append_val(int val, uint size, String *str) const
 | |
|   {
 | |
|     if (m_fm)
 | |
|       return str->append_longlong(val);
 | |
|     return str->append_zerofill(val, size);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Append a LEX_CSTRING value to a String.
 | |
|     If m_fm is false, then right-pad the appended value with spaces.
 | |
| 
 | |
|     @param ls              - the LEX_CSTRING to be append to str
 | |
|     @param max_char_length - the maximum possible length of ls, in characters
 | |
|     @param [OUT] str       - the result String (ls will be appended to it)
 | |
| 
 | |
|     @retval false          - on success
 | |
|     @retval true           - on error (e.g. EOM)
 | |
|  */
 | |
|   bool append_lex_cstring(const LEX_CSTRING ls, uint max_char_length,
 | |
|                           String *str) const
 | |
|   {
 | |
|     // Locale data uses utf8mb3
 | |
|     static constexpr CHARSET_INFO *cs= &my_charset_utf8mb3_general_ci;
 | |
|     str->append(ls.str, ls.length, cs);
 | |
|     if (!m_fm)
 | |
|     {
 | |
|       size_t char_length= cs->numchars(ls.str, ls.str + ls.length);
 | |
|       if (char_length < max_char_length)
 | |
|         return str->fill(str->length() + max_char_length - char_length, ' ');
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Print a date/time value to a String according to the given format
 | |
|     @param fmt_array - the format array
 | |
|     @param l_time    - the date/time value
 | |
|     @param locale    - the locale to use for textual components
 | |
|                        (MONTH and DAY)
 | |
|     @param [OUT] str - the string to print into.
 | |
| 
 | |
|     @retval false    - on success
 | |
|     @retval true     - on error (e.g. EOM)
 | |
|   */
 | |
|   bool format(const uint16 *fmt_array,
 | |
|               const MYSQL_TIME *l_time,
 | |
|               const MY_LOCALE *locale,
 | |
|               String *str);
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Oracle has many formatting models, we list all but only part of them
 | |
|   are implemented, because some models depend on oracle functions
 | |
|   which mariadb is not supported.
 | |
| 
 | |
|   Models for datetime, used by TO_CHAR/TO_DATE. Normal format characters are
 | |
|   stored as short integer < 128, while format characters are stored as a
 | |
|   integer > 128
 | |
| */
 | |
| 
 | |
| enum enum_tochar_formats
 | |
| {
 | |
|   FMT_BASE= 128,
 | |
|   FMT_AD,
 | |
|   FMT_AD_DOT,
 | |
|   FMT_AM,
 | |
|   FMT_AM_DOT,
 | |
|   FMT_BC,
 | |
|   FMT_BC_DOT,
 | |
|   FMT_CC,
 | |
|   FMT_SCC,
 | |
|   FMT_D,
 | |
|   FMT_DAY,
 | |
|   FMT_DD,
 | |
|   FMT_DDD,
 | |
|   FMT_DL,
 | |
|   FMT_DS,
 | |
|   FMT_DY,
 | |
|   FMT_E,
 | |
|   FMT_EE,
 | |
|   FMT_FF,
 | |
|   FMT_FM,
 | |
|   FMT_FX,
 | |
|   FMT_HH,
 | |
|   FMT_HH12,
 | |
|   FMT_HH24,
 | |
|   FMT_IW,
 | |
|   FMT_I,
 | |
|   FMT_IY,
 | |
|   FMT_IYY,
 | |
|   FMT_IYYY,
 | |
|   FMT_J,
 | |
|   FMT_MI,
 | |
|   FMT_MM,
 | |
|   FMT_MON,
 | |
|   FMT_MONTH,
 | |
|   FMT_PM,
 | |
|   FMT_PM_DOT,
 | |
|   FMT_RM,
 | |
|   FMT_RR,
 | |
|   FMT_RRRR,
 | |
|   FMT_SS,
 | |
|   FMT_SSSSSS,
 | |
|   FMT_TS,
 | |
|   FMT_TZD,
 | |
|   FMT_TZH,
 | |
|   FMT_TZM,
 | |
|   FMT_TZR,
 | |
|   FMT_W,
 | |
|   FMT_WW,
 | |
|   FMT_X,
 | |
|   FMT_Y,
 | |
|   FMT_YY,
 | |
|   FMT_YYY,
 | |
|   FMT_YYYY,
 | |
|   FMT_YYYY_COMMA,
 | |
|   FMT_YEAR,
 | |
|   FMT_SYYYY,
 | |
|   FMT_SYEAR
 | |
| };
 | |
| 
 | |
| /**
 | |
|    Flip 'quotation_flag' if we found a quote (") character.
 | |
| 
 | |
|    @param cftm             Character or FMT... format descriptor
 | |
|    @param quotation_flag   Points to 'true' if we are inside a quoted string
 | |
| 
 | |
|    @return true  If we are inside a quoted string or if we found a '"' character
 | |
|    @return false Otherwise
 | |
| */
 | |
| 
 | |
| static inline bool check_quotation(uint16 cfmt, bool *quotation_flag)
 | |
| {
 | |
|   if (cfmt == '"')
 | |
|   {
 | |
|     *quotation_flag= !*quotation_flag;
 | |
|     return true;
 | |
|   }
 | |
|   return *quotation_flag;
 | |
| }
 | |
| 
 | |
| #define INVALID_CHARACTER(x) (((x) >= 'A' && (x) <= 'Z') ||((x) >= '0' && (x) <= '9') || (x) >= 127 || ((x) < 32))
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Special characters are directly output in the result
 | |
| 
 | |
|   @return 0  If found not acceptable character
 | |
|   @return #  Number of copied characters
 | |
| */
 | |
| 
 | |
| static uint parse_special(char cfmt, const char *ptr, const char *end,
 | |
|                          uint16 *array)
 | |
| {
 | |
|   int offset= 0;
 | |
|   char tmp1;
 | |
| 
 | |
|   /* Non-printable character and Multibyte encoded characters */
 | |
|   if (INVALID_CHARACTER(cfmt))
 | |
|     return 0;
 | |
| 
 | |
|   /*
 | |
|    * '&' with text is used for variable input, but '&' with other
 | |
|    * special characters like '|'. '*' is used as separator
 | |
|    */
 | |
|   if (cfmt == '&' && ptr + 1 < end)
 | |
|   {
 | |
|     tmp1= my_toupper(system_charset_info, *(ptr+1));
 | |
|     if (tmp1 >= 'A' && tmp1 <= 'Z')
 | |
|       return 0;
 | |
|   }
 | |
| 
 | |
|   do {
 | |
|     /*
 | |
|       Continuously store the special characters in fmt_array until non-special
 | |
|       characters appear
 | |
|      */
 | |
|     *array++= (uint16) (uchar) *ptr++;
 | |
|     offset++;
 | |
|     if (ptr == end)
 | |
|       break;
 | |
|     tmp1= my_toupper(system_charset_info, *ptr);
 | |
|   } while (!INVALID_CHARACTER(tmp1) && tmp1 != '"');
 | |
|   return offset;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Parse the format string, convert it to an compact array and calculate the
 | |
|   length of output string
 | |
| 
 | |
|   @param format   Format string
 | |
|   @param fmt_len  Function will store max length of formated date string here
 | |
| 
 | |
|   @return 0 ok. fmt_len is updated
 | |
|   @return 1 error.  In this case 'warning_string' is set to error message
 | |
| */
 | |
| 
 | |
| bool Item_func_tochar::parse_format_string(const String *format, uint *fmt_len)
 | |
| {
 | |
|   const char *ptr, *end;
 | |
|   uint16 *tmp_fmt= fmt_array;
 | |
|   uint tmp_len= 0;
 | |
|   int offset= 0;
 | |
|   bool quotation_flag= false;
 | |
| 
 | |
|   ptr= format->ptr();
 | |
|   end= ptr + format->length();
 | |
| 
 | |
|   if (format->length() > MAX_DATETIME_FORMAT_MODEL_LEN)
 | |
|   {
 | |
|     warning_message.append(STRING_WITH_LEN("datetime format string is too "
 | |
|                                            "long"));
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   for (; ptr < end; ptr++, tmp_fmt++)
 | |
|   {
 | |
|     uint ulen;
 | |
|     char cfmt, next_char;
 | |
| 
 | |
|     cfmt= my_toupper(system_charset_info, *ptr);
 | |
| 
 | |
|     /*
 | |
|       Oracle datetime format support text in double quotation marks like
 | |
|       'YYYY"abc"MM"xyz"DD', When this happens, store the text and quotation
 | |
|       marks, and use the text as a separator in
 | |
|       Date_time_format_oracle::format().
 | |
| 
 | |
|       NOTE: the quotation mark is not print in return value. for example:
 | |
|       select TO_CHAR(sysdate, 'YYYY"abc"MM"xyzDD"') will return 2021abc01xyz11
 | |
|      */
 | |
|     if (check_quotation(cfmt, "ation_flag))
 | |
|     {
 | |
|       *tmp_fmt= *ptr;
 | |
|       tmp_len+= 1;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     switch (cfmt) {
 | |
|     case 'A':                                   // AD/A.D./AM/A.M.
 | |
|       if (ptr+1 >= end)
 | |
|         goto error;
 | |
|       next_char= my_toupper(system_charset_info, *(ptr+1));
 | |
|       if (next_char == 'D')
 | |
|       {
 | |
|         *tmp_fmt= FMT_AD;
 | |
|         ptr+= 1;
 | |
|         tmp_len+= 2;
 | |
|       }
 | |
|       else if (next_char == 'M')
 | |
|       {
 | |
|         *tmp_fmt= FMT_AM;
 | |
|         ptr+= 1;
 | |
|         tmp_len+= 2;
 | |
|       }
 | |
|       else if (next_char == '.' && ptr+3 < end && *(ptr+3) == '.')
 | |
|       {
 | |
|         if (my_toupper(system_charset_info, *(ptr+2)) == 'D')
 | |
|         {
 | |
|           *tmp_fmt= FMT_AD_DOT;
 | |
|           ptr+= 3;
 | |
|           tmp_len+= 4;
 | |
|         }
 | |
|         else if (my_toupper(system_charset_info, *(ptr+2)) == 'M')
 | |
|         {
 | |
|           *tmp_fmt= FMT_AM_DOT;
 | |
|           ptr+= 3;
 | |
|           tmp_len+= 4;
 | |
|         }
 | |
|         else
 | |
|           goto error;
 | |
|       }
 | |
|       else
 | |
|         goto error;
 | |
|       break;
 | |
|     case 'B':                                     // BC and B.C
 | |
|       if (ptr+1 >= end)
 | |
|         goto error;
 | |
|       next_char= my_toupper(system_charset_info, *(ptr+1));
 | |
|       if (next_char == 'C')
 | |
|       {
 | |
|         *tmp_fmt= FMT_BC;
 | |
|         ptr+= 1;
 | |
|         tmp_len+= 2;
 | |
|       }
 | |
|       else if (next_char == '.' && ptr+3 < end &&
 | |
|                my_toupper(system_charset_info, *(ptr+2)) == 'C' &&
 | |
|                *(ptr+3) == '.')
 | |
|       {
 | |
|         *tmp_fmt= FMT_BC_DOT;
 | |
|         ptr+= 3;
 | |
|         tmp_len+= 4;
 | |
|       }
 | |
|       else
 | |
|         goto error;
 | |
|       break;
 | |
|     case 'P':                                   // PM or P.M.
 | |
|       next_char= my_toupper(system_charset_info, *(ptr+1));
 | |
|       if (next_char == 'M')
 | |
|       {
 | |
|         *tmp_fmt= FMT_PM;
 | |
|         ptr+= 1;
 | |
|         tmp_len+= 2;
 | |
|       }
 | |
|       else if (next_char == '.' &&
 | |
|                my_toupper(system_charset_info, *(ptr+2)) == 'M' &&
 | |
|                my_toupper(system_charset_info, *(ptr+3)) == '.')
 | |
|       {
 | |
|         *tmp_fmt= FMT_PM_DOT;
 | |
|         ptr+= 3;
 | |
|         tmp_len+= 4;
 | |
|       }
 | |
|       else
 | |
|         goto error;
 | |
|       break;
 | |
|     case 'Y':                                   // Y, YY, YYY o YYYYY
 | |
|       if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'Y')
 | |
|       {
 | |
|         *tmp_fmt= FMT_Y;
 | |
|         tmp_len+= 1;
 | |
|         break;
 | |
|       }
 | |
|       if (ptr + 2 == end ||
 | |
|           my_toupper(system_charset_info, *(ptr+2)) != 'Y') /* YY */
 | |
|       {
 | |
|         *tmp_fmt= FMT_YY;
 | |
|         ulen= 2;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (ptr + 3 < end && my_toupper(system_charset_info, *(ptr+3)) == 'Y')
 | |
|         {
 | |
|           *tmp_fmt= FMT_YYYY;
 | |
|           ulen= 4;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           *tmp_fmt= FMT_YYY;
 | |
|           ulen= 3;
 | |
|         }
 | |
|       }
 | |
|       ptr+= ulen-1;
 | |
|       tmp_len+= ulen;
 | |
|       break;
 | |
| 
 | |
|     case 'R':                                   // RR or RRRR
 | |
|       if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'R')
 | |
|         goto error;
 | |
| 
 | |
|       if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'R')
 | |
|       {
 | |
|         *tmp_fmt= FMT_RR;
 | |
|         ulen= 2;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (ptr + 3 >= end || my_toupper(system_charset_info, *(ptr+3)) != 'R')
 | |
|           goto error;
 | |
|         *tmp_fmt= FMT_RRRR;
 | |
|         ulen= 4;
 | |
|       }
 | |
|       ptr+= ulen-1;
 | |
|       tmp_len+= ulen;
 | |
|       break;
 | |
|     case 'M':
 | |
|     {
 | |
|       char tmp1;
 | |
|       if (ptr + 1 >= end)
 | |
|         goto error;
 | |
| 
 | |
|       tmp1= my_toupper(system_charset_info, *(ptr+1));
 | |
|       if (tmp1 == 'M')
 | |
|       {
 | |
|         *tmp_fmt= FMT_MM;
 | |
|         tmp_len+= 2;
 | |
|         ptr+= 1;
 | |
|       }
 | |
|       else if (tmp1 == 'I')
 | |
|       {
 | |
|         *tmp_fmt= FMT_MI;
 | |
|         tmp_len+= 2;
 | |
|         ptr+= 1;
 | |
|       }
 | |
|       else if (tmp1 == 'O')
 | |
|       {
 | |
|         if (ptr + 2 >= end)
 | |
|           goto error;
 | |
|         char tmp2= my_toupper(system_charset_info, *(ptr+2));
 | |
|         if (tmp2 != 'N')
 | |
|           goto error;
 | |
| 
 | |
|         if (ptr + 4 >= end ||
 | |
|             my_toupper(system_charset_info, *(ptr+3)) != 'T' ||
 | |
|             my_toupper(system_charset_info, *(ptr+4)) != 'H')
 | |
|         {
 | |
|           *tmp_fmt= FMT_MON;
 | |
|           tmp_len+= 3;
 | |
|           ptr+= 2;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           *tmp_fmt= FMT_MONTH;
 | |
|           tmp_len+= (locale->max_month_name_length *
 | |
|                      my_charset_utf8mb3_bin.mbmaxlen);
 | |
|           ptr+= 4;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|         goto error;
 | |
|     }
 | |
|     break;
 | |
|     case 'D':                                   // DD, DY, or DAY
 | |
|     {
 | |
|       if (ptr + 1 >= end)
 | |
|         goto error;
 | |
|       char tmp1= my_toupper(system_charset_info, *(ptr+1));
 | |
| 
 | |
|       if (tmp1 == 'D')
 | |
|       {
 | |
|         *tmp_fmt= FMT_DD;
 | |
|         tmp_len+= 2;
 | |
|       }
 | |
|       else if (tmp1 == 'Y')
 | |
|       {
 | |
|         *tmp_fmt= FMT_DY;
 | |
|         tmp_len+= 3;
 | |
|       }
 | |
|       else if (tmp1 == 'A')                     // DAY
 | |
|       {
 | |
|         if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'Y')
 | |
|           goto error;
 | |
|         *tmp_fmt= FMT_DAY;
 | |
|         tmp_len+= locale->max_day_name_length * my_charset_utf8mb3_bin.mbmaxlen;
 | |
|         ptr+= 1;
 | |
|       }
 | |
|       else
 | |
|         goto error;
 | |
|       ptr+= 1;
 | |
|     }
 | |
|     break;
 | |
|     case 'H':                                   // HH, HH12 or HH23
 | |
|     {
 | |
|       char tmp1, tmp2, tmp3;
 | |
|       if (ptr + 1 >= end)
 | |
|         goto error;
 | |
|       tmp1= my_toupper(system_charset_info, *(ptr+1));
 | |
| 
 | |
|       if (tmp1 != 'H')
 | |
|         goto error;
 | |
| 
 | |
|       if (ptr+3 >= end)
 | |
|       {
 | |
|         *tmp_fmt= FMT_HH;
 | |
|         ptr+= 1;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         tmp2= *(ptr+2);
 | |
|         tmp3= *(ptr+3);
 | |
| 
 | |
|         if (tmp2 == '1' && tmp3 == '2')
 | |
|         {
 | |
|           *tmp_fmt= FMT_HH12;
 | |
|           ptr+= 3;
 | |
|         }
 | |
|         else if (tmp2 == '2' && tmp3 == '4')
 | |
|         {
 | |
|           *tmp_fmt= FMT_HH24;
 | |
|           ptr+= 3;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           *tmp_fmt= FMT_HH;
 | |
|           ptr+= 1;
 | |
|         }
 | |
|       }
 | |
|       tmp_len+= 2;
 | |
|       break;
 | |
|     }
 | |
|     case 'S':                                   // SS
 | |
|       if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'S')
 | |
|         goto error;
 | |
| 
 | |
|       *tmp_fmt= FMT_SS;
 | |
|       tmp_len+= 2;
 | |
|       ptr+= 1;
 | |
|       break;
 | |
|     case '|':
 | |
|       /*
 | |
|         If only one '|' just ignore it, else append others, for example:
 | |
|         TO_CHAR('2000-11-05', 'YYYY|MM||||DD') --> 200011|||05
 | |
|       */
 | |
|       if (ptr + 1 == end || *(ptr+1) != '|')
 | |
|       {
 | |
|         tmp_fmt--;
 | |
|         break;
 | |
|       }
 | |
|       ptr++;                                    // Skip first '|'
 | |
|       do
 | |
|       {
 | |
|         *tmp_fmt++= *ptr++;
 | |
|         tmp_len++;
 | |
|       } while ((ptr < end) && *ptr == '|');
 | |
|       ptr--;                                    // Fix ptr for above for loop
 | |
|       tmp_fmt--;
 | |
|       break;
 | |
| 
 | |
|     case 'F':
 | |
|       if (ptr + 1 == end)
 | |
|         goto error;
 | |
|       if (my_toupper(system_charset_info, ptr[1]) == 'M')
 | |
|       {
 | |
|         *tmp_fmt= FMT_FM;
 | |
|         ptr+= 1;
 | |
|         continue;
 | |
|       }
 | |
|       goto error;
 | |
| 
 | |
|     default:
 | |
|       offset= parse_special(cfmt, ptr, end, tmp_fmt);
 | |
|       if (!offset)
 | |
|         goto error;
 | |
|       /* ptr++ is in the for loop, so we must move ptr to offset-1 */
 | |
|       ptr+= (offset-1);
 | |
|       tmp_fmt+= (offset-1);
 | |
|       tmp_len+= offset;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   *fmt_len= tmp_len;
 | |
|   *tmp_fmt= 0;
 | |
|   return 0;
 | |
| 
 | |
| error:
 | |
|   warning_message.append(STRING_WITH_LEN("date format not recognized at "));
 | |
|   warning_message.append(ptr, MY_MIN(8, end- ptr));
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Date_time_format_oracle::format(const uint16 *fmt_array,
 | |
|                                      const MYSQL_TIME *l_time,
 | |
|                                      const MY_LOCALE *locale,
 | |
|                                      String *str)
 | |
| {
 | |
|   bool quotation_flag= false;
 | |
|   const uint16 *ptr= fmt_array;
 | |
|   uint hours_i;
 | |
|   uint weekday;
 | |
| 
 | |
|   str->length(0);
 | |
| 
 | |
|   while (*ptr)
 | |
|   {
 | |
|     if (check_quotation(*ptr, "ation_flag))
 | |
|     {
 | |
|       /* don't display '"' in the result, so if it is '"', skip it */
 | |
|       if (*ptr != '"')
 | |
|       {
 | |
|         DBUG_ASSERT(*ptr <= 255);
 | |
|         str->append((char) *ptr);
 | |
|       }
 | |
|       ptr++;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     switch (*ptr) {
 | |
| 
 | |
|     case FMT_AM:
 | |
|     case FMT_PM:
 | |
|       if (l_time->hour > 11)
 | |
|         str->append("PM", 2);
 | |
|       else
 | |
|         str->append("AM", 2);
 | |
|       break;
 | |
| 
 | |
|     case FMT_AM_DOT:
 | |
|     case FMT_PM_DOT:
 | |
|       if (l_time->hour > 11)
 | |
|         str->append(STRING_WITH_LEN("P.M."));
 | |
|       else
 | |
|         str->append(STRING_WITH_LEN("A.M."));
 | |
|       break;
 | |
| 
 | |
|     case FMT_AD:
 | |
|     case FMT_BC:
 | |
|       if (l_time->year > 0)
 | |
|         str->append(STRING_WITH_LEN("AD"));
 | |
|       else
 | |
|         str->append(STRING_WITH_LEN("BC"));
 | |
|       break;
 | |
| 
 | |
|     case FMT_AD_DOT:
 | |
|     case FMT_BC_DOT:
 | |
|       if (l_time->year > 0)
 | |
|         str->append(STRING_WITH_LEN("A.D."));
 | |
|       else
 | |
|         str->append(STRING_WITH_LEN("B.C."));
 | |
|       break;
 | |
| 
 | |
|     case FMT_Y:
 | |
|       if (append_val(l_time->year%10, 1, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_YY:
 | |
|     case FMT_RR:
 | |
|       if (append_val(l_time->year%100, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_YYY:
 | |
|       if (append_val(l_time->year%1000, 3, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_YYYY:
 | |
|     case FMT_RRRR:
 | |
|       if (append_val(l_time->year, 4, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_MM:
 | |
|       if (append_val(l_time->month, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_MON:
 | |
|       {
 | |
|         if (l_time->month == 0)
 | |
|         {
 | |
|           str->append("00", 2);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           const char *month_name= (locale->ab_month_names->
 | |
|                                    type_names[l_time->month-1]);
 | |
|           size_t m_len= strlen(month_name);
 | |
|           str->append(month_name, m_len, system_charset_info);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case FMT_MONTH:
 | |
|       {
 | |
|         if (l_time->month == 0)
 | |
|         {
 | |
|           str->append("00", 2);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           if (append_lex_cstring(locale->month_name(l_time->month - 1),
 | |
|                                  locale->max_month_name_length, str))
 | |
|             goto err_exit;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case FMT_DD:
 | |
|       if (append_val(l_time->day, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_DY:
 | |
|       {
 | |
|         if (l_time->day == 0)
 | |
|           str->append("00", 2);
 | |
|         else
 | |
|         {
 | |
|           weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
 | |
|                                           l_time->day), 0);
 | |
|           const char *day_name= locale->ab_day_names->type_names[weekday];
 | |
|           str->append(day_name, strlen(day_name), system_charset_info);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case FMT_DAY:
 | |
|       {
 | |
|         if (l_time->day == 0)
 | |
|           str->append("00", 2, system_charset_info);
 | |
|         else
 | |
|         {
 | |
|           weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
 | |
|                                           l_time->day), 0);
 | |
|           if (append_lex_cstring(locale->day_name(weekday),
 | |
|                                  locale->max_day_name_length, str))
 | |
|             goto err_exit;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case FMT_HH12:
 | |
|     case FMT_HH:
 | |
|       hours_i= (l_time->hour%24 + 11)%12+1;
 | |
|       if (append_val(hours_i, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_HH24:
 | |
|       if (append_val(l_time->hour, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_MI:
 | |
|       if (append_val(l_time->minute, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_SS:
 | |
|       if (append_val(l_time->second, 2, str))
 | |
|         goto err_exit;
 | |
|       break;
 | |
| 
 | |
|     case FMT_FM:
 | |
|       m_fm= !m_fm;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       str->append((char) *ptr);
 | |
|     }
 | |
| 
 | |
|     ptr++;
 | |
|   };
 | |
|   return false;
 | |
| 
 | |
| err_exit:
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_tochar::fix_length_and_dec(THD *thd)
 | |
| {
 | |
|   CHARSET_INFO *cs= thd->variables.collation_connection;
 | |
|   Item *arg1= args[1]->this_item();
 | |
|   my_repertoire_t repertoire= arg1->collation.repertoire;
 | |
|   StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer;
 | |
|   String *str;
 | |
| 
 | |
|   locale= thd->variables.lc_time_names;
 | |
|   if (!thd->variables.lc_time_names->is_ascii)
 | |
|     repertoire|= MY_REPERTOIRE_EXTENDED;
 | |
|   collation.set(cs, arg1->collation.derivation, repertoire);
 | |
| 
 | |
|   /* first argument must be datetime or string */
 | |
|   enum_field_types arg0_mysql_type= args[0]->field_type();
 | |
| 
 | |
|   max_length= 0;
 | |
|   switch (arg0_mysql_type) {
 | |
|   case MYSQL_TYPE_TIME:
 | |
|   case MYSQL_TYPE_DATE:
 | |
|   case MYSQL_TYPE_DATETIME:
 | |
|   case MYSQL_TYPE_TIMESTAMP:
 | |
|   case MYSQL_TYPE_VARCHAR:
 | |
|   case MYSQL_TYPE_STRING:
 | |
|     break;
 | |
|   default:
 | |
|   {
 | |
|     my_printf_error(ER_STD_INVALID_ARGUMENT,
 | |
|                     ER(ER_STD_INVALID_ARGUMENT),
 | |
|                     MYF(0),
 | |
|                     "data type of first argument must be type "
 | |
|                     "date/datetime/time or string",
 | |
|                     func_name());
 | |
|     return TRUE;
 | |
|   }
 | |
|   }
 | |
|   if (args[1]->basic_const_item() && (str= args[1]->val_str(&buffer)))
 | |
|   {
 | |
|     uint ulen;
 | |
|     fixed_length= 1;
 | |
|     if (parse_format_string(str, &ulen))
 | |
|     {
 | |
|       my_printf_error(ER_STD_INVALID_ARGUMENT,
 | |
|                       ER(ER_STD_INVALID_ARGUMENT),
 | |
|                       MYF(0),
 | |
|                       warning_message.c_ptr(),
 | |
|                       func_name());
 | |
|       return TRUE;
 | |
|     }
 | |
|     max_length= (uint32) (ulen * collation.collation->mbmaxlen);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     fixed_length= 0;
 | |
|     max_length= (uint32) MY_MIN(arg1->max_length * 10 *
 | |
|                                 collation.collation->mbmaxlen,
 | |
|                                 MAX_BLOB_WIDTH);
 | |
|   }
 | |
|   set_maybe_null();
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| String *Item_func_tochar::val_str(String* str)
 | |
|  {
 | |
|   THD *thd= current_thd;
 | |
|   StringBuffer<64> format_buffer;
 | |
|   String *format;
 | |
|   MYSQL_TIME l_time;
 | |
|   const MY_LOCALE *lc= locale;
 | |
|   date_conv_mode_t mode= TIME_CONV_NONE;
 | |
|   size_t max_result_length= max_length;
 | |
| 
 | |
|   if (warning_message.length())
 | |
|     goto null_date;
 | |
| 
 | |
|   if ((null_value= args[0]->get_date(thd, &l_time,
 | |
|                                      Temporal::Options(mode, thd))))
 | |
|     return 0;
 | |
| 
 | |
|   if (!fixed_length)
 | |
|   {
 | |
|     uint ulen;
 | |
|     if (!(format= args[1]->val_str(&format_buffer)) || !format->length() ||
 | |
|         parse_format_string(format, &ulen))
 | |
|       goto null_date;
 | |
|     max_result_length= ((size_t) ulen) * collation.collation->mbmaxlen;
 | |
|   }
 | |
| 
 | |
|   if (str->alloc(max_result_length))
 | |
|     goto null_date;
 | |
| 
 | |
|   /* Create the result string */
 | |
|   str->set_charset(collation.collation);
 | |
|   if (!Date_time_format_oracle().format(fmt_array, &l_time, lc, str))
 | |
|     return str;
 | |
| 
 | |
| null_date:
 | |
| 
 | |
|   if (warning_message.length())
 | |
|   {
 | |
|     push_warning_printf(thd,
 | |
|                         Sql_condition::WARN_LEVEL_WARN,
 | |
|                         ER_STD_INVALID_ARGUMENT,
 | |
|                         ER_THD(thd, ER_STD_INVALID_ARGUMENT),
 | |
|                         warning_message.c_ptr(),
 | |
|                         func_name());
 | |
|     if (!fixed_length)
 | |
|       warning_message.length(0);
 | |
|   }
 | |
| 
 | |
|   null_value= 1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_from_unixtime::fix_length_and_dec(THD *thd)
 | |
| {
 | |
|   tz= thd->variables.time_zone;
 | |
|   Type_std_attributes::set(
 | |
|     Type_temporal_attributes_not_fixed_dec(MAX_DATETIME_WIDTH,
 | |
|                                            args[0]->decimals, false),
 | |
|     DTCollation_numeric());
 | |
|   set_maybe_null();
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_from_unixtime::val_native(THD *thd, Native *to)
 | |
| {
 | |
|   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);
 | |
| 
 | |
|   // decimals can be NOT_FIXED_DEC
 | |
|   decimal_digits_t fixed_decimals= MY_MIN(decimals, TIME_SECOND_PART_DIGITS);
 | |
| 
 | |
|   sec.round(fixed_decimals, thd->temporal_round_mode());
 | |
| 
 | |
|   if (sec.sec() == 0 && sec.usec() == 0)
 | |
|   {
 | |
|     /*
 | |
|       The value {0,0}='1970-01-01 00:00:00.000000 GMT' cannot be
 | |
|       stored in a TIMESTAMP field. Return SQL NULL.
 | |
|       Simmetrically, UNIX_TIMESTAMP('1970-01-01 00:00:00')
 | |
|       also returns SQL NULL (assuming time_zone='+00:00').
 | |
|     */
 | |
|     thd->push_warning_truncated_wrong_value("unixtime", "0.0");
 | |
|     return (null_value= true); // 0.0 after rounding
 | |
|   }
 | |
| 
 | |
|   if (sec.sec() > TIMESTAMP_MAX_VALUE)
 | |
|     return (null_value= true); // Went out of range after rounding
 | |
| 
 | |
|   const Timestamp ts(Timeval(sec.sec(), sec.usec()));
 | |
|   return null_value= ts.to_native(to, fixed_decimals);
 | |
| }
 | |
| 
 | |
| 
 | |
| 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(THD *thd)
 | |
| {
 | |
|   enum_field_types arg0_field_type;
 | |
| 
 | |
|   if (!args[0]->type_handler()->is_traditional_scalar_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);
 | |
|   }
 | |
|   set_maybe_null();
 | |
|   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, const Eq_config &config) const
 | |
| {
 | |
|   if (!Item_func::eq(item, config))
 | |
|     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);
 | |
|   static LEX_CSTRING minus_interval= { STRING_WITH_LEN(" - interval ") };
 | |
|   static LEX_CSTRING plus_interval=  { STRING_WITH_LEN(" + interval ") };
 | |
|   LEX_CSTRING *tmp= date_sub_interval ? &minus_interval : &plus_interval;
 | |
|   str->append(tmp);
 | |
|   args[1]->print(str, query_type);
 | |
|   str->append(' ');
 | |
|   str->append(interval_names[int_type], strlen(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], strlen(interval_names[int_type]));
 | |
|   str->append(STRING_WITH_LEN(" from "));
 | |
|   args[0]->print(str, query_type);
 | |
|   str->append(')');
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_extract::check_arguments() const
 | |
| {
 | |
|   if (!args[0]->type_handler()->can_return_extract_source(int_type))
 | |
|   {
 | |
|     char tmp[64];
 | |
|     my_snprintf(tmp, sizeof(tmp), "extract(%s)", interval_names[int_type]);
 | |
|     my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0),
 | |
|              args[0]->type_handler()->name().ptr(), tmp);
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_extract::fix_length_and_dec(THD *thd)
 | |
| {
 | |
|   set_maybe_null(); // 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(1); 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());
 | |
|   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, const Eq_config &config) 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], config))
 | |
|       return 0;
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_char_typecast::eq(const Item *item, const Eq_config &config) 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], config))
 | |
|       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('(');
 | |
|     size_t length= (size_t) (longlong10_to_str(decimals, buf, -10) - buf);
 | |
|     str->append(buf, length);
 | |
|     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)
 | |
|   {
 | |
|     char buf[20];
 | |
|     size_t length= (size_t) (longlong10_to_str(cast_length, buf, 10) - buf);
 | |
|     str->append('(');
 | |
|     str->append(buf, length);
 | |
|     str->append(')');
 | |
|   }
 | |
|   if (cast_cs)
 | |
|   {
 | |
|     str->append(STRING_WITH_LEN(" charset "));
 | |
|     str->append(cast_cs->cs_name);
 | |
|     /*
 | |
|       Print the "binary" keyword in cases like:
 | |
|         CAST('str' AS CHAR CHARACTER SET latin1 BINARY)
 | |
|     */
 | |
|     if ((cast_cs->state & MY_CS_BINSORT) &&
 | |
|         Charset(cast_cs).can_have_collate_clause())
 | |
|       str->append(STRING_WITH_LEN(" binary"));
 | |
|   }
 | |
|   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_generic(String *str)
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   String *res;
 | |
| 
 | |
|   if (!(res= args[0]->val_str(str)))
 | |
|   {
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   return val_str_generic_finalize(res, str);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Adjust the result of: res= args[0]->val_str(str);
 | |
|   according to the cast length.
 | |
|   @param res - the value returned from val_str()
 | |
|   @param str - the value passed to val_str() as a buffer.
 | |
| */
 | |
| String *Item_char_typecast::val_str_generic_finalize(String *res, String *str)
 | |
| {
 | |
|   if (has_explicit_length())
 | |
|     cast_length= adjusted_length_with_warn(cast_length);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| 
 | |
| String *Item_char_typecast::val_str_binary_from_native(String *str)
 | |
| {
 | |
|   DBUG_ASSERT(fixed());
 | |
|   DBUG_ASSERT(cast_cs == &my_charset_bin);
 | |
|   NativeBuffer<STRING_BUFFER_USUAL_SIZE> native;
 | |
| 
 | |
|   if (args[0]->val_native(current_thd, &native))
 | |
|   {
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (has_explicit_length())
 | |
|   {
 | |
|     cast_length= adjusted_length_with_warn(cast_length);
 | |
|     if (cast_length > native.length())
 | |
|     {
 | |
|       // add trailing 0x00s
 | |
|       DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet);
 | |
|       str->alloc(cast_length);
 | |
|       str->copy(native.ptr(), native.length(), &my_charset_bin);
 | |
|       bzero((char*) str->end(), cast_length - str->length());
 | |
|       str->length(cast_length);
 | |
|     }
 | |
|     else
 | |
|       str->copy(native.ptr(), cast_length, &my_charset_bin);
 | |
|   }
 | |
|   else
 | |
|     str->copy(native.ptr(), native.length(), &my_charset_bin);
 | |
| 
 | |
|   return ((null_value= (str->length() >
 | |
|                         adjusted_length_with_warn(str->length())))) ? 0 : str;
 | |
| }
 | |
| 
 | |
| 
 | |
| class Item_char_typecast_func_handler: public Item_handled_func::Handler_str
 | |
| {
 | |
| public:
 | |
|   const Type_handler *return_type_handler(const Item_handled_func *item) const override
 | |
|   {
 | |
|     return Type_handler::string_type_handler(item->max_length);
 | |
|   }
 | |
|   const Type_handler *
 | |
|     type_handler_for_create_select(const Item_handled_func *item) const override
 | |
|   {
 | |
|     return return_type_handler(item)->type_handler_for_tmp_table(item);
 | |
|   }
 | |
| 
 | |
|   bool fix_length_and_dec(Item_handled_func *item) const override
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
|   String *val_str(Item_handled_func *item, String *to) const override
 | |
|   {
 | |
|     DBUG_ASSERT(dynamic_cast<const Item_char_typecast*>(item));
 | |
|     return static_cast<Item_char_typecast*>(item)->val_str_generic(to);
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| static Item_char_typecast_func_handler item_char_typecast_func_handler;
 | |
| 
 | |
| 
 | |
| 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);
 | |
|   set_func_handler(&item_char_typecast_func_handler);
 | |
| }
 | |
| 
 | |
| 
 | |
| 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);
 | |
|   set_func_handler(&item_char_typecast_func_handler);
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_char_typecast::fix_length_and_dec_str()
 | |
| {
 | |
|   fix_length_and_dec_generic();
 | |
|   m_suppress_warning_to_error_escalation= true;
 | |
|   set_func_handler(&item_char_typecast_func_handler);
 | |
| }
 | |
| 
 | |
| 
 | |
| void
 | |
| Item_char_typecast::fix_length_and_dec_native_to_binary(uint32 octet_length)
 | |
| {
 | |
|   collation.set(&my_charset_bin, DERIVATION_IMPLICIT);
 | |
|   max_length= has_explicit_length() ? (uint32) cast_length : octet_length;
 | |
|   if (current_thd->is_strict_mode())
 | |
|     set_maybe_null();
 | |
| }
 | |
| 
 | |
| 
 | |
| 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= DTCollation::string_typecast(cast_cs);
 | |
|   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;
 | |
|   // Add NULL-ability in strict mode. See Item_str_func::fix_fields()
 | |
|   if (current_thd->is_strict_mode())
 | |
|     set_maybe_null();
 | |
| }
 | |
| 
 | |
| 
 | |
| 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 & ~TIME_INTERVAL_DAY;
 | |
|   // 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 & ~TIME_INTERVAL_DAY;
 | |
|   // 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());
 | |
|   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(THD *thd)
 | |
| {
 | |
|   enum_field_types arg0_field_type;
 | |
| 
 | |
|   if (!args[0]->type_handler()->is_traditional_scalar_type() ||
 | |
|       !args[1]->type_handler()->is_traditional_scalar_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);
 | |
|   }
 | |
| 
 | |
|   set_maybe_null();
 | |
|   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());
 | |
|   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 (l_time1.time_type == MYSQL_TIMESTAMP_TIME)
 | |
|   {
 | |
|     /*
 | |
|       In case of TIME-alike arguments:
 | |
|         TIMEDIFF('38:59:59', '839:00:00')
 | |
|       let's truncate extra fractional seconds that might appear if the argument
 | |
|       values were out of the supported TIME range. For example, args[n]->get_time()
 | |
|       for the string literal '839:00:00' returns TIME'838:59:59.999999'.
 | |
|       The fractional part must be truncated according to this->decimals,
 | |
|       to avoid returning more fractional seconds than it was detected
 | |
|       during this->fix_length_and_dec().
 | |
|       Note, the thd rounding mode should not be important here, as we're removing
 | |
|       redundant digits from the maximum possible value: '838:59:59.999999'.
 | |
|     */
 | |
|     my_time_trunc(&l_time1, decimals);
 | |
|     my_time_trunc(&l_time2, decimals);
 | |
|   }
 | |
| 
 | |
|   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());
 | |
|   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());
 | |
|   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_cstring());
 | |
|   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());
 | |
|   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_charset_latin1.strnncoll(val->ptr(), val_len, 
 | |
| 		                     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_cstring());
 | |
|   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(THD *thd)
 | |
| {
 | |
|   if (!args[0]->type_handler()->is_traditional_scalar_type() ||
 | |
|       !args[1]->type_handler()->is_traditional_scalar_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;
 | |
| 
 | |
|   set_maybe_null();
 | |
|   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;
 | |
|   ltime->day= calc_days_in_month(ltime->year, ltime->month);
 | |
|   ltime->hour= ltime->minute= ltime->second= 0;
 | |
|   ltime->second_part= 0;
 | |
|   ltime->time_type= MYSQL_TIMESTAMP_DATE;
 | |
|   return (null_value= 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* enum for Oracle TRUNC function */
 | |
| 
 | |
| struct TRUNC_FORMAT
 | |
| {
 | |
|   const LEX_CSTRING name;
 | |
|   Item_func_trunc::enum_trunc format;
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Formats in sorted order. Only the Oracle formats that truncates to year,
 | |
|   month or day are supported
 | |
| */
 | |
| static struct TRUNC_FORMAT trunc_options[]=
 | |
| {
 | |
|   {{STRING_WITH_LEN("DD")}, Item_func_trunc::TRUNC_DAY },
 | |
|   {{STRING_WITH_LEN("DDD")}, Item_func_trunc::TRUNC_DAY },
 | |
|   {{STRING_WITH_LEN("J")}, Item_func_trunc::TRUNC_DAY },
 | |
|   {{STRING_WITH_LEN("MM")}, Item_func_trunc::TRUNC_MONTH },
 | |
|   {{STRING_WITH_LEN("MON")}, Item_func_trunc::TRUNC_MONTH },
 | |
|   {{STRING_WITH_LEN("MONTH")}, Item_func_trunc::TRUNC_MONTH },
 | |
|   {{STRING_WITH_LEN("RM")}, Item_func_trunc::TRUNC_MONTH },
 | |
|   {{STRING_WITH_LEN("SYEAR")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("SYYYY")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("Y")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("YEAR")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("YY")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("YYY")}, Item_func_trunc::TRUNC_YEAR },
 | |
|   {{STRING_WITH_LEN("YYYY")}, Item_func_trunc::TRUNC_YEAR },
 | |
| };
 | |
| 
 | |
| 
 | |
| Item_func_trunc::enum_trunc
 | |
| Item_func_trunc::get_trunc_option(const LEX_CSTRING format)
 | |
| {
 | |
|   uint low=0, high= array_elements(trunc_options) - 1;
 | |
| 
 | |
|   /* Use binary search to find the format */
 | |
|   do
 | |
|   {
 | |
|     uint mid= (low+high)/2;
 | |
|     int cmp= my_charset_latin1.strnncoll(format, trunc_options[mid].name);
 | |
|     if (!cmp)
 | |
|       return trunc_options[mid].format;
 | |
|     if (cmp > 0)
 | |
|       low= mid+1;
 | |
|     else
 | |
|       high= mid-1;
 | |
|   } while ((int) low <= (int) high);
 | |
|   return TRUNC_IMPOSSIBLE;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_trunc::fix_length_and_dec(THD *thd)
 | |
| {
 | |
|   fix_attributes_datetime(args[0]->datetime_precision(thd));
 | |
|   set_maybe_null();
 | |
|   if (args[1]->can_eval_in_optimize())
 | |
|   {
 | |
|     String tmp, *res;
 | |
|     if ((res= args[1]->val_str_ascii(&tmp)))
 | |
|     {
 | |
|       const_format= get_trunc_option(res->to_lex_cstring());
 | |
|       if (const_format == TRUNC_IMPOSSIBLE)
 | |
|         const_format= TRUNC_UNINIT;             // Error handling in get_date()
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_func_trunc::get_date(THD *thd, MYSQL_TIME *ltime,
 | |
|                                date_mode_t fuzzydate)
 | |
| {
 | |
|   Datetime::Options opt(TIME_NO_ZEROS, TIME_FRAC_TRUNCATE);
 | |
|   enum_trunc format= TRUNC_IMPOSSIBLE;
 | |
|   Datetime *dt= new(ltime) Datetime(thd, args[0], opt);
 | |
|   if ((null_value= !dt->is_valid_datetime()))
 | |
|     return true;
 | |
|   if (const_format != TRUNC_UNINIT)
 | |
|     format= const_format;
 | |
|   else
 | |
|   {
 | |
|     String tmp, *res;
 | |
|     if ((res= args[1]->val_str_ascii(&tmp)))
 | |
|       format= get_trunc_option(res->to_lex_cstring());
 | |
|     if (format == TRUNC_IMPOSSIBLE)
 | |
|     {
 | |
|       thd->push_warning_wrong_value(Sql_condition::WARN_LEVEL_WARN, "TRUNC",
 | |
|                                     res ? res->c_ptr() : "<NULL>");
 | |
|       goto error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   null_value= 0;
 | |
|   switch (format)
 | |
|   {
 | |
|   case TRUNC_UNINIT:
 | |
|   case TRUNC_IMPOSSIBLE:
 | |
|     DBUG_ASSERT(0);
 | |
|     goto error;
 | |
|   case TRUNC_YEAR:
 | |
|     ltime->month= 1;
 | |
|     /* fall through */
 | |
|   case TRUNC_MONTH:
 | |
|     ltime->day= 1;
 | |
|     /* fall through */
 | |
|   case TRUNC_DAY:
 | |
|     ltime->hour= ltime->minute= ltime->second= 0;
 | |
|     ltime->second_part= 0;
 | |
|     break;
 | |
|   }
 | |
|   return false;
 | |
| 
 | |
|   error:
 | |
|   null_value= 1;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Help functions for months_between()
 | |
|   Note that by compiling with EXTENDED_MONTHS_BETWEEN MariaDB would
 | |
|   take into account alse the time part when comparing dates.
 | |
| */
 | |
| 
 | |
| static ulonglong months_between_rank(const MYSQL_TIME *t)
 | |
| {
 | |
|   ulonglong days= (((t->year * 366LL) + t->month) * 31 + t->day);
 | |
| #ifndef EXTENDED_MONTHS_BETWEEN
 | |
|   return days;
 | |
| #else
 | |
|   ulonglong rank= (((((days*24 + t->hour) * 60) + t->minute) * 60 +
 | |
|                     t->second)*1000000LL + t->second_part);
 | |
|   return rank;
 | |
| #endif /* EXTENDED_MONTHS_BETWEEN */
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get the fractional day based on hour, minute, second and
 | |
|   fractional second. The return value is in milliseconds.
 | |
| */
 | |
| 
 | |
| static double fractional_day(const MYSQL_TIME *t)
 | |
| {
 | |
|   /* Normalize all components to the fractional part of the day */
 | |
|   ulonglong milliseconds= (t->hour * 3600LL + t->minute * 60LL +
 | |
|                            t->second)*1000;
 | |
| 
 | |
| #ifdef EXTENDED_MONTHS_BETWEEN
 | |
|   milliseconds+= t->second_part / 1000;
 | |
| #endif
 | |
| 
 | |
|   /*
 | |
|     Normalize by 86400000
 | |
|     (which is 24 hours * 60 minutes * 60 seconds * 1000 milliseconds)
 | |
|   */
 | |
|   return milliseconds / 86400000.0; // fractional day
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Check if it's the last day of the month */
 | |
| static inline int is_last_day(const MYSQL_TIME *ltime)
 | |
| {
 | |
|   uint last_day= calc_days_in_month(ltime->year, ltime->month);
 | |
|   return ltime->day == last_day;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Calculate months_between() according to how Oracle does it.
 | |
| 
 | |
|   "If date1 is earlier than date2, then the result is negative.  If
 | |
|   date1 and date2 are either the same days of the month or both last
 | |
|   days of months, then the result is always an integer.  If not, then
 | |
|   a fractional portion is added based on a 31-day month.
 | |
| 
 | |
|   One difference between the MariaDB and Oracle implementation is that
 | |
|   MariaDB takes hours, minutes, seconds and fractional seconds into
 | |
|   account when comparing dates when computing the fractional months.
 | |
| */
 | |
| 
 | |
| double Item_func_months_between::val_real()
 | |
| {
 | |
|   double frac;
 | |
|   int invert = 1, months;
 | |
|   ulonglong dt1, dt2;
 | |
|   MYSQL_TIME ltime1, ltime2, *d1, *d2;
 | |
|   THD *thd= current_thd;
 | |
|   Datetime::Options opt(TIME_NO_ZEROS, thd);
 | |
| 
 | |
|   if (Datetime(thd, args[0], opt).copy_to_mysql_time(<ime1) ||
 | |
|       Datetime(thd, args[1], opt).copy_to_mysql_time(<ime2))
 | |
|   {
 | |
|     null_value= 1;
 | |
|     return 0.0;
 | |
|   }
 | |
|   null_value= 0;
 | |
| 
 | |
|   /* Get earlier time in d1 */
 | |
|   d1= <ime1;
 | |
|   d2= <ime2;
 | |
| 
 | |
|   dt1= months_between_rank(d1);
 | |
|   dt2= months_between_rank(d2);
 | |
| 
 | |
|   if (dt1 < dt2)
 | |
|   {
 | |
|     invert= -1;
 | |
|     swap_variables(MYSQL_TIME *, d1, d2);
 | |
|   }
 | |
| 
 | |
|   /* Calculate months */
 | |
|   months= (d1->year - d2->year) * 12 + (d1->month - d2->month);
 | |
| 
 | |
|   /*
 | |
|     If days are the same or day is last day of the month they are
 | |
|     regarded as equal
 | |
|   */
 | |
|   if (d1->day == d2->day || (is_last_day(d1) && is_last_day(d2)))
 | |
|     return months * invert;
 | |
| 
 | |
|   double frac_d1, frac_d2;
 | |
|   frac_d1= fractional_day(d1);
 | |
|   frac_d2= fractional_day(d2);
 | |
| 
 | |
|   /* Compute fractional month using 31-day assumption */
 | |
|   if (d1->day > d2->day)
 | |
|     frac= (d1->day + frac_d1 - d2->day - frac_d2) / 31.0;
 | |
|   else
 | |
|   {
 | |
|     months--;
 | |
|     frac= (31 - d2->day - frac_d2 + d1->day + frac_d1) / 31.0;
 | |
|   }
 | |
| 
 | |
|   return invert * (months + frac);
 | |
| }
 | 
