diff options
author | Sergei Golubchik <sergii@pisem.net> | 2012-08-29 10:59:51 +0200 |
---|---|---|
committer | Sergei Golubchik <sergii@pisem.net> | 2012-08-29 10:59:51 +0200 |
commit | a44331ab3407488368c9984258ce4c3160872816 (patch) | |
tree | 65721ec29cd9112f7a246db3928a79eb8202eccc | |
parent | 95ee3fbf306d28ba315992ea4af458a4fcfb081b (diff) | |
download | mariadb-git-a44331ab3407488368c9984258ce4c3160872816.tar.gz |
MDEV-456 An out-of-range datetime value (with a 5-digit year) can be created and cause troubles
fix Item_func_add_time::get_date() to generate valid dates.
Move the validity check inside get_date_from_daynr()
instead of relying on callers
(5 that had it, and 2 that did not, but should've)
-rw-r--r-- | mysql-test/r/datetime_456.result | 8 | ||||
-rw-r--r-- | mysql-test/t/datetime_456.test | 8 | ||||
-rw-r--r-- | sql/item_timefunc.cc | 35 | ||||
-rw-r--r-- | sql/mysql_priv.h | 2 | ||||
-rw-r--r-- | sql/time.cc | 69 |
5 files changed, 65 insertions, 57 deletions
diff --git a/mysql-test/r/datetime_456.result b/mysql-test/r/datetime_456.result new file mode 100644 index 00000000000..ba020a250b7 --- /dev/null +++ b/mysql-test/r/datetime_456.result @@ -0,0 +1,8 @@ +create table t1 (d datetime); +insert t1 values (addtime('9999-12-31 23:59:59', '00:00:01')), +(from_days(3652499)); +select * from t1; +d +NULL +NULL +drop table t1; diff --git a/mysql-test/t/datetime_456.test b/mysql-test/t/datetime_456.test new file mode 100644 index 00000000000..0c187959d52 --- /dev/null +++ b/mysql-test/t/datetime_456.test @@ -0,0 +1,8 @@ +# +# MDEV-456 An out-of-range datetime value (with a 5-digit year) can be created and cause troubles +# +create table t1 (d datetime); +insert t1 values (addtime('9999-12-31 23:59:59', '00:00:01')), + (from_days(3652499)); +select * from t1; +drop table t1; diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index eec8dde080c..2cd8b3215c4 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -341,9 +341,8 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, { uint days; days= calc_daynr(l_time->year,1,1) + yearday - 1; - if (days <= 0 || days > MAX_DAY_NUMBER) + if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day)) goto err; - get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day); } if (week_number >= 0 && weekday) @@ -388,9 +387,8 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, (weekday - 1); } - if (days <= 0 || days > MAX_DAY_NUMBER) + if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day)) goto err; - get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day); } if (l_time->month > 12 || l_time->day > 31 || l_time->hour > 23 || @@ -1385,13 +1383,16 @@ bool Item_func_from_days::get_date(MYSQL_TIME *ltime, uint fuzzy_date) if ((null_value=args[0]->null_value)) return 1; bzero(ltime, sizeof(MYSQL_TIME)); - get_date_from_daynr((long) value, <ime->year, <ime->month, <ime->day); - if ((null_value= ((fuzzy_date & TIME_NO_ZERO_DATE) && ltime->year == 0))) - return TRUE; + if (get_date_from_daynr((long) value, <ime->year, <ime->month, + <ime->day)) + return (null_value= 1); + + if ((fuzzy_date & TIME_NO_ZERO_DATE) && ltime->year == 0) + return (null_value= 1); ltime->time_type= MYSQL_TIMESTAMP_DATE; - return 0; + return (null_value= 0); } @@ -2388,14 +2389,12 @@ bool Item_func_makedate::get_date(MYSQL_TIME *ltime, uint fuzzy_date) year= year_2000_handling(year); days= calc_daynr(year,1,1) + daynr - 1; - /* Day number from year 0 to 9999-12-31 */ - if (days >= 0 && days <= MAX_DAY_NUMBER) - { - bzero(ltime, sizeof(*ltime)); - ltime->time_type= MYSQL_TIMESTAMP_DATE; - get_date_from_daynr(days, <ime->year, <ime->month, <ime->day); - return (null_value= 0); - } + 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); @@ -2489,8 +2488,8 @@ bool Item_func_add_time::get_date(MYSQL_TIME *ltime, uint fuzzy_date) if (!is_time) { - get_date_from_daynr(days,<ime->year,<ime->month,<ime->day); - if (!ltime->day) + if (get_date_from_daynr(days,<ime->year,<ime->month,<ime->day) || + !ltime->day) return (null_value= 1); return (null_value= 0); } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 605d9353cae..14810fc7119 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2445,7 +2445,7 @@ void free_field_buffers_larger_than(TABLE *table, uint32 size); int set_zone(int nr,int min_zone,int max_zone); ulong convert_period_to_month(ulong period); ulong convert_month_to_period(ulong month); -void get_date_from_daynr(long daynr,uint *year, uint *month, +bool get_date_from_daynr(long daynr,uint *year, uint *month, uint *day); my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error); bool str_to_time_with_warn(const char *str,uint length,MYSQL_TIME *l_time, diff --git a/sql/time.cc b/sql/time.cc index 778f8b8f313..21ecc3f8050 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -24,9 +24,9 @@ #include <m_ctype.h> - /* Some functions to calculate dates */ +#define MAX_DAY_NUMBER 3652424L -#ifndef TESTTIME + /* Some functions to calculate dates */ /* Name description of interval names used in statements. @@ -146,46 +146,42 @@ uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year) /* Change a daynr to year, month and day */ /* Daynr 0 is returned as date 00.00.00 */ -void get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month, +bool get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month, uint *ret_day) { uint year,temp,leap_day,day_of_year,days_in_year; uchar *month_pos; DBUG_ENTER("get_date_from_daynr"); - if (daynr <= 365L || daynr >= 3652500) - { /* Fix if wrong daynr */ - *ret_year= *ret_month = *ret_day =0; + if (daynr < 365 || daynr > MAX_DAY_NUMBER) + DBUG_RETURN(1); + + year= (uint) (daynr*100 / 36525L); + temp=(((year-1)/100+1)*3)/4; + day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp; + while (day_of_year > (days_in_year= calc_days_in_year(year))) + { + day_of_year-=days_in_year; + (year)++; } - else + leap_day=0; + if (days_in_year == 366) { - year= (uint) (daynr*100 / 36525L); - temp=(((year-1)/100+1)*3)/4; - day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp; - while (day_of_year > (days_in_year= calc_days_in_year(year))) + if (day_of_year > 31+28) { - day_of_year-=days_in_year; - (year)++; + day_of_year--; + if (day_of_year == 31+28) + leap_day=1; /* Handle leapyears leapday */ } - leap_day=0; - if (days_in_year == 366) - { - if (day_of_year > 31+28) - { - day_of_year--; - if (day_of_year == 31+28) - leap_day=1; /* Handle leapyears leapday */ - } - } - *ret_month=1; - for (month_pos= days_in_month ; - day_of_year > (uint) *month_pos ; - day_of_year-= *(month_pos++), (*ret_month)++) - ; - *ret_year=year; - *ret_day=day_of_year+leap_day; } - DBUG_VOID_RETURN; + *ret_month=1; + for (month_pos= days_in_month ; + day_of_year > (uint) *month_pos ; + day_of_year-= *(month_pos++), (*ret_month)++) + ; + *ret_year=year; + *ret_day=day_of_year+leap_day; + DBUG_RETURN(0); } /* Functions to handle periods */ @@ -805,7 +801,6 @@ void make_truncated_value_warning(THD *thd, /* Daynumber from year 0 to 9999-12-31 */ -#define MAX_DAY_NUMBER 3652424L #define COMBINE(X) \ (((((X)->day * 24LL + (X)->hour) * 60LL + \ (X)->minute) * 60LL + (X)->second)*1000000LL + \ @@ -872,19 +867,18 @@ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, daynr= usec; /* Day number from year 0 to 9999-12-31 */ - if ((ulonglong) daynr > MAX_DAY_NUMBER) + if (get_date_from_daynr((long) daynr, <ime->year, <ime->month, + <ime->day)) goto invalid_date; - get_date_from_daynr((long) daynr, <ime->year, <ime->month, - <ime->day); break; } case INTERVAL_WEEK: period= (calc_daynr(ltime->year,ltime->month,ltime->day) + sign * (long) interval.day); /* Daynumber from year 0 to 9999-12-31 */ - if ((ulong) period > MAX_DAY_NUMBER) + if (get_date_from_daynr((long) period,<ime->year,<ime->month, + <ime->day)) goto invalid_date; - get_date_from_daynr((long) period,<ime->year,<ime->month,<ime->day); break; case INTERVAL_YEAR: ltime->year+= sign * (long) interval.year; @@ -1034,4 +1028,3 @@ int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b) return 0; } -#endif |