diff options
Diffstat (limited to 'sql')
54 files changed, 3935 insertions, 504 deletions
diff --git a/sql/Makefile.am b/sql/Makefile.am index cdbf78bed0e..66ebed4658f 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -26,7 +26,8 @@ INCLUDES = @MT_INCLUDES@ \ WRAPLIBS= @WRAPLIBS@ SUBDIRS = share libexec_PROGRAMS = mysqld -noinst_PROGRAMS = gen_lex_hash +bin_PROGRAMS = mysql_tzinfo_to_sql +noinst_PROGRAMS = gen_lex_hash test_time gen_lex_hash_LDFLAGS = @NOINST_LDFLAGS@ LDADD = @isam_libs@ \ ../myisam/libmyisam.a \ @@ -57,8 +58,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ lex.h lex_symbol.h sql_acl.h sql_crypt.h \ log_event.h sql_repl.h slave.h \ stacktrace.h sql_sort.h sql_cache.h set_var.h \ - spatial.h gstream.h client_settings.h \ - examples/ha_example.h examples/ha_archive.h + spatial.h gstream.h client_settings.h tzfile.h \ + tztime.h examples/ha_example.h examples/ha_archive.h mysqld_SOURCES = sql_lex.cc sql_handler.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \ @@ -88,10 +89,18 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \ client.c sql_client.cc mini_client_errors.c pack.c\ stacktrace.c repl_failsafe.h repl_failsafe.cc \ gstream.cc spatial.cc sql_help.cc protocol_cursor.cc \ - examples/ha_example.cc examples/ha_archive.cc + tztime.cc examples/ha_example.cc examples/ha_archive.cc gen_lex_hash_SOURCES = gen_lex_hash.cc gen_lex_hash_LDADD = $(LDADD) $(CXXLDFLAGS) +mysql_tzinfo_to_sql_SOURCES = tztime.cc tzfile.h +mysql_tzinfo_to_sql_CPPFLAGS = -DTZINFO2SQL $(AM_CPPFLAGS) +mysql_tzinfo_to_sql_LDADD = $(LDADD) $(CXXLDFLAGS) + +test_time_SOURCES = tztime.cc time.cc tzfile.h +test_time_CPPFLAGS = -DTESTTIME $(AM_CPPFLAGS) +test_time_LDADD = $(LDADD) $(CXXLDFLAGS) + DEFS = -DMYSQL_SERVER \ -DDEFAULT_MYSQL_HOME="\"$(MYSQLBASEdir)\"" \ -DDATADIR="\"$(MYSQLDATAdir)\"" \ diff --git a/sql/field.cc b/sql/field.cc index b76e6781247..21256b2bbcd 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -398,7 +398,7 @@ bool Field::get_date(TIME *ltime,uint fuzzydate) char buff[40]; String tmp(buff,sizeof(buff),&my_charset_bin),*res; if (!(res=val_str(&tmp)) || - str_to_TIME(res->ptr(),res->length(),ltime,fuzzydate) <= + str_to_TIME_with_warn(res->ptr(), res->length(), ltime, fuzzydate) <= TIMESTAMP_DATETIME_ERROR) return 1; return 0; @@ -409,7 +409,7 @@ bool Field::get_time(TIME *ltime) char buff[40]; String tmp(buff,sizeof(buff),&my_charset_bin),*res; if (!(res=val_str(&tmp)) || - str_to_time(res->ptr(),res->length(),ltime)) + str_to_time_with_warn(res->ptr(), res->length(), ltime)) return 1; return 0; } @@ -462,7 +462,7 @@ void Field_decimal::overflow(bool negative) uint len=field_length; char *to=ptr, filler= '9'; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); if (negative) { if (!unsigned_flag) @@ -546,7 +546,7 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) char *left_wall,*right_wall; char tmp_char; /* - To remember if current_thd->cuted_fields has already been incremented, + To remember if table->in_use->cuted_fields has already been incremented, to do that only once */ bool is_cuted_fields_incr=0; @@ -572,7 +572,7 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) from++; if (from == end) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); is_cuted_fields_incr=1; } else if (*from == '+' || *from == '-') // Found some sign ? @@ -642,13 +642,13 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) it makes the code easer to read. */ - if (current_thd->count_cuted_fields) + if (table->in_use->count_cuted_fields) { // Skip end spaces for (;from != end && my_isspace(&my_charset_bin, *from); from++) ; if (from != end) // If still something left, warn { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); is_cuted_fields_incr=1; } } @@ -794,7 +794,7 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) /* Write digits of the frac_% parts ; - Depending on current_thd->count_cutted_fields, we may also want + Depending on table->in_use->count_cutted_fields, we may also want to know if some non-zero tail of these parts will be truncated (for example, 0.002->0.00 will generate a warning, while 0.000->0.00 will not) @@ -812,7 +812,7 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) { if (pos == right_wall) { - if (current_thd->count_cuted_fields && !is_cuted_fields_incr) + if (table->in_use->count_cuted_fields && !is_cuted_fields_incr) break; // Go on below to see if we lose non zero digits return 0; } @@ -826,7 +826,8 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) if (tmp_char != '0') // Losing a non zero digit ? { if (!is_cuted_fields_incr) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, 1); return 0; } continue; @@ -843,7 +844,7 @@ int Field_decimal::store(const char *from, uint len, CHARSET_INFO *cs) if (tmp_char != '0') // Losing a non zero digit ? { if (!is_cuted_fields_incr) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); return 0; } continue; @@ -1061,18 +1062,18 @@ int Field_tiny::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < 0) { tmp=0; /* purecov: inspected */ - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp > 255) { tmp= 255; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1081,18 +1082,18 @@ int Field_tiny::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < -128) { tmp= -128; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp >= 128) { tmp= 127; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1110,13 +1111,13 @@ int Field_tiny::store(double nr) if (nr < 0.0) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 255.0) { *ptr=(char) 255; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1127,13 +1128,13 @@ int Field_tiny::store(double nr) if (nr < -128.0) { *ptr= (char) -128; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 127.0) { *ptr=127; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1150,13 +1151,13 @@ int Field_tiny::store(longlong nr) if (nr < 0L) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 255L) { *ptr= (char) 255; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1167,13 +1168,13 @@ int Field_tiny::store(longlong nr) if (nr < -128L) { *ptr= (char) -128; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 127L) { *ptr=127; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1265,18 +1266,18 @@ int Field_short::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < 0) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp > (uint16) ~0) { tmp=(uint16) ~0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1285,18 +1286,18 @@ int Field_short::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < INT_MIN16) { tmp= INT_MIN16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp > INT_MAX16) { tmp=INT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1322,13 +1323,13 @@ int Field_short::store(double nr) if (nr < 0) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) (uint16) ~0) { res=(int16) (uint16) ~0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1339,13 +1340,13 @@ int Field_short::store(double nr) if (nr < (double) INT_MIN16) { res=INT_MIN16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) INT_MAX16) { res=INT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1371,13 +1372,13 @@ int Field_short::store(longlong nr) if (nr < 0L) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (longlong) (uint16) ~0) { res=(int16) (uint16) ~0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1388,13 +1389,13 @@ int Field_short::store(longlong nr) if (nr < INT_MIN16) { res=INT_MIN16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > INT_MAX16) { res=INT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1540,18 +1541,18 @@ int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < 0) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp >= (long) (1L << 24)) { tmp=(long) (1L << 24)-1L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1560,18 +1561,18 @@ int Field_medium::store(const char *from,uint len,CHARSET_INFO *cs) if (tmp < INT_MIN24) { tmp= INT_MIN24; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (tmp > INT_MAX24) { tmp=INT_MAX24; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) + else if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } } @@ -1590,14 +1591,14 @@ int Field_medium::store(double nr) if (nr < 0) { int3store(ptr,0); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (double) (long) (1L << 24)) { uint32 tmp=(uint32) (1L << 24)-1L; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1609,14 +1610,14 @@ int Field_medium::store(double nr) { long tmp=(long) INT_MIN24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) INT_MAX24) { long tmp=(long) INT_MAX24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1633,14 +1634,14 @@ int Field_medium::store(longlong nr) if (nr < 0L) { int3store(ptr,0); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (longlong) (long) (1L << 24)) { long tmp=(long) (1L << 24)-1L;; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1652,14 +1653,14 @@ int Field_medium::store(longlong nr) { long tmp=(long) INT_MIN24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (longlong) INT_MAX24) { long tmp=(long) INT_MAX24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1771,10 +1772,10 @@ int Field_long::store(const char *from,uint len,CHARSET_INFO *cs) else tmp=my_strntol(cs,from,len,10,&end,&error); if (error || - (from+len != end && current_thd->count_cuted_fields && + (from+len != end && table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs))) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } #ifdef WORDS_BIGENDIAN @@ -1799,13 +1800,13 @@ int Field_long::store(double nr) if (nr < 0) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) (ulong) ~0L) { res=(int32) (uint32) ~0L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1816,13 +1817,13 @@ int Field_long::store(double nr) if (nr < (double) INT_MIN32) { res=(int32) INT_MIN32; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) INT_MAX32) { res=(int32) INT_MAX32; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1844,18 +1845,26 @@ int Field_long::store(longlong nr) { int error= 0; int32 res; + + /* + This assert has nothing to do with this method per se, it was put here + only because it is one of the best places for catching places there its + condition is broken. + */ + DBUG_ASSERT(table->in_use == current_thd); + if (unsigned_flag) { if (nr < 0) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (LL(1) << 32)) { res=(int32) (uint32) ~0L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1866,13 +1875,13 @@ int Field_long::store(longlong nr) if (nr < (longlong) INT_MIN32) { res=(int32) INT_MIN32; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (longlong) INT_MAX32) { res=(int32) INT_MAX32; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -1905,6 +1914,8 @@ double Field_long::val_real(void) longlong Field_long::val_int(void) { int32 j; + /* See the comment in Field_long::store(long long) */ + DBUG_ASSERT(table->in_use == current_thd); #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) j=sint4korr(ptr); @@ -2029,10 +2040,10 @@ int Field_longlong::store(const char *from,uint len,CHARSET_INFO *cs) else tmp=my_strntoll(cs,from,len,10,&end,&error); if (error || - (from+len != end && current_thd->count_cuted_fields && + (from+len != end && table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs))) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } #ifdef WORDS_BIGENDIAN @@ -2057,13 +2068,13 @@ int Field_longlong::store(double nr) if (nr < 0) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (double) ~ (ulonglong) 0) { res= ~(longlong) 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -2074,13 +2085,13 @@ int Field_longlong::store(double nr) if (nr <= (double) LONGLONG_MIN) { res=(longlong) LONGLONG_MIN; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (double) LONGLONG_MAX) { res=(longlong) LONGLONG_MAX; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -2251,10 +2262,10 @@ int Field_float::store(const char *from,uint len,CHARSET_INFO *cs) int error; char *end; double nr= my_strntod(cs,(char*) from,len,&end,&error); - if (error || ((uint) (end-from) != len && current_thd->count_cuted_fields)) + if (error || ((uint) (end-from) != len && table->in_use->count_cuted_fields)) { error= 1; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } Field_float::store(nr); return error; @@ -2270,13 +2281,13 @@ int Field_float::store(double nr) { j= 0; set_null(); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (unsigned_flag && nr < 0) { j= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -2300,13 +2311,13 @@ int Field_float::store(double nr) if (nr < -max_value) { j= (float)-max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > max_value) { j= (float)max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -2331,7 +2342,7 @@ int Field_float::store(longlong nr) float j= (float) nr; if (unsigned_flag && j < 0) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); j=0; error= 1; } @@ -2553,10 +2564,10 @@ int Field_double::store(const char *from,uint len,CHARSET_INFO *cs) int error; char *end; double nr= my_strntod(cs,(char*) from, len, &end, &error); - if (error || ((uint) (end-from) != len && current_thd->count_cuted_fields)) + if (error || ((uint) (end-from) != len && table->in_use->count_cuted_fields)) { error= 1; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } Field_double::store(nr); return error; @@ -2571,13 +2582,13 @@ int Field_double::store(double nr) { nr= 0; set_null(); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (unsigned_flag && nr < 0) { nr= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -2597,13 +2608,13 @@ int Field_double::store(double nr) if (nr < -max_value) { nr= -max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > max_value) { nr= max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } } @@ -2626,7 +2637,7 @@ int Field_double::store(longlong nr) int error= 0; if (unsigned_flag && j < 0) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; j=0; } @@ -2900,7 +2911,39 @@ void Field_timestamp::set_timestamp_offsets() int Field_timestamp::store(const char *from,uint len,CHARSET_INFO *cs) { - long tmp=(long) str_to_timestamp(from,len); + TIME l_time; + my_time_t tmp= 0; + int error; + bool have_smth_to_conv; + bool in_dst_time_gap; + THD *thd= table->in_use; + + have_smth_to_conv= (str_to_TIME(from, len, &l_time, 0, &error) > + TIMESTAMP_DATETIME_ERROR); + + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, + from, len, TIMESTAMP_DATETIME, 1); + + if (have_smth_to_conv) + { + if (!(tmp= TIME_to_timestamp(thd, &l_time, &in_dst_time_gap))) + { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + from, len, TIMESTAMP_DATETIME, !error); + + error= 1; + } + else if (in_dst_time_gap) + { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_INVALID_TIMESTAMP, + from, len, TIMESTAMP_DATETIME, !error); + error= 1; + } + } + #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -2909,7 +2952,7 @@ int Field_timestamp::store(const char *from,uint len,CHARSET_INFO *cs) else #endif longstore(ptr,tmp); - return 0; + return error; } int Field_timestamp::store(double nr) @@ -2917,8 +2960,10 @@ int Field_timestamp::store(double nr) int error= 0; if (nr < 0 || nr > 99991231235959.0) { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + nr, TIMESTAMP_DATETIME); nr= 0; // Avoid overflow on buff - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); error= 1; } error|= Field_timestamp::store((longlong) rint(nr)); @@ -2926,96 +2971,37 @@ int Field_timestamp::store(double nr) } -/* - Convert a datetime of formats YYMMDD, YYYYMMDD or YYMMDDHHMSS to - YYYYMMDDHHMMSS. The high date '99991231235959' is checked before this - function. -*/ - -static longlong fix_datetime(longlong nr, TIME *time_res, - const char *field_name, bool *error) -{ - long part1,part2; - - *error= 0; - if (nr == LL(0) || nr >= LL(10000101000000)) - goto ok; - if (nr < 101) - goto err; - if (nr <= (YY_PART_YEAR-1)*10000L+1231L) - { - nr= (nr+20000000L)*1000000L; // YYMMDD, year: 2000-2069 - goto ok; - } - if (nr < (YY_PART_YEAR)*10000L+101L) - goto err; - if (nr <= 991231L) - { - nr= (nr+19000000L)*1000000L; // YYMMDD, year: 1970-1999 - goto ok; - } - if (nr < 10000101L) - goto err; - if (nr <= 99991231L) - { - nr= nr*1000000L; - goto ok; - } - if (nr < 101000000L) - goto err; - if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959)) - { - nr= nr+LL(20000000000000); // YYMMDDHHMMSS, 2000-2069 - goto ok; - } - if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000)) - goto err; - if (nr <= LL(991231235959)) - nr= nr+LL(19000000000000); // YYMMDDHHMMSS, 1970-1999 - - ok: - part1=(long) (nr/LL(1000000)); - part2=(long) (nr - (longlong) part1*LL(1000000)); - time_res->year= (int) (part1/10000L); part1%=10000L; - time_res->month= (int) part1 / 100; - time_res->day= (int) part1 % 100; - time_res->hour= (int) (part2/10000L); part2%=10000L; - time_res->minute=(int) part2 / 100; - time_res->second=(int) part2 % 100; - - if (time_res->year <= 9999 && time_res->month <= 12 && - time_res->day <= 31 && time_res->hour <= 23 && - time_res->minute <= 59 && time_res->second <= 59) - return nr; - - err: - THD *thd= current_thd; - if (thd->count_cuted_fields) - { - thd->cuted_fields++; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DATA_TRUNCATED, ER(ER_WARN_DATA_TRUNCATED), - field_name, thd->row_count); - } - *error= 1; - return LL(0); -} - - int Field_timestamp::store(longlong nr) { TIME l_time; - time_t timestamp= 0; - bool error; + my_time_t timestamp= 0; + int error; + bool in_dst_time_gap; + THD *thd= table->in_use; - if ((nr= fix_datetime(nr, &l_time, field_name, &error))) + if (number_to_TIME(nr, &l_time, 0, &error)) { - long not_used; - - if (!(timestamp= my_gmt_sec(&l_time, ¬_used))) - goto err; - } + if (!(timestamp= TIME_to_timestamp(thd, &l_time, &in_dst_time_gap))) + { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + nr, TIMESTAMP_DATETIME, 1); + error= 1; + } + if (in_dst_time_gap) + { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_INVALID_TIMESTAMP, + nr, TIMESTAMP_DATETIME, !error); + error= 1; + } + } + else if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, + nr, TIMESTAMP_DATETIME, 1); + #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3024,12 +3010,8 @@ int Field_timestamp::store(longlong nr) else #endif longstore(ptr,(uint32) timestamp); + return error; - -err: - longstore(ptr,(uint32) 0); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); - return 1; } @@ -3040,12 +3022,9 @@ double Field_timestamp::val_real(void) longlong Field_timestamp::val_int(void) { - int part_time; uint32 temp; - time_t time_arg; - struct tm *l_time; - longlong res; - struct tm tm_tmp; + TIME time_tmp; + THD *thd= table->in_use; #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) @@ -3056,32 +3035,21 @@ longlong Field_timestamp::val_int(void) if (temp == 0L) // No time return(0); /* purecov: inspected */ - time_arg=(time_t) temp; - localtime_r(&time_arg,&tm_tmp); - l_time=&tm_tmp; - - part_time= l_time->tm_year % 100; - res= ((longlong) (part_time+ ((part_time < YY_PART_YEAR) ? 2000 : 1900))* - LL(10000000000)); - part_time= l_time->tm_mon+1; - res+= (longlong) part_time * LL(100000000); - part_time=l_time->tm_mday; - res+= (longlong) ((long) part_time * 1000000L); - part_time=l_time->tm_hour; - res+= (longlong) (part_time * 10000L); - part_time=l_time->tm_min; - res+= (longlong) (part_time * 100); - part_time=l_time->tm_sec; - return res+part_time; + + thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, (my_time_t)temp); + thd->time_zone_used= 1; + + return time_tmp.year * LL(10000000000) + time_tmp.month * LL(100000000) + + time_tmp.day * 1000000L + time_tmp.hour * 10000L + + time_tmp.minute * 100 + time_tmp.second; } String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) { uint32 temp, temp2; - time_t time_arg; - struct tm *l_time; - struct tm tm_tmp; + TIME time_tmp; + THD *thd= table->in_use; char *to; val_buffer->alloc(field_length+1); @@ -3101,11 +3069,11 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) return val_ptr; } val_buffer->set_charset(&my_charset_bin); // Safety - time_arg=(time_t) temp; - localtime_r(&time_arg,&tm_tmp); - l_time=&tm_tmp; + + thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp,(my_time_t)temp); + thd->time_zone_used= 1; - temp= l_time->tm_year % 100; + temp= time_tmp.year % 100; if (temp < YY_PART_YEAR) { *to++= '2'; @@ -3120,27 +3088,27 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); *to++= '-'; - temp=l_time->tm_mon+1; + temp=time_tmp.month; temp2=temp/10; temp=temp-temp2*10; *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); *to++= '-'; - temp=l_time->tm_mday; + temp=time_tmp.day; temp2=temp/10; temp=temp-temp2*10; *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); *to++= ' '; - temp=l_time->tm_hour; + temp=time_tmp.hour; temp2=temp/10; temp=temp-temp2*10; *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); *to++= ':'; - temp=l_time->tm_min; + temp=time_tmp.minute; temp2=temp/10; temp=temp-temp2*10; *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); *to++= ':'; - temp=l_time->tm_sec; + temp=time_tmp.second; temp2=temp/10; temp=temp-temp2*10; *to++= (char) ('0'+(char) (temp2)); *to++= (char) ('0'+(char) (temp)); @@ -3152,6 +3120,7 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) bool Field_timestamp::get_date(TIME *ltime, uint fuzzydate) { long temp; + THD *thd= table->in_use; #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) temp=uint4korr(ptr); @@ -3166,19 +3135,8 @@ bool Field_timestamp::get_date(TIME *ltime, uint fuzzydate) } else { - struct tm tm_tmp; - time_t time_arg= (time_t) temp; - localtime_r(&time_arg,&tm_tmp); - struct tm *start= &tm_tmp; - ltime->year= start->tm_year+1900; - ltime->month= start->tm_mon+1; - ltime->day= start->tm_mday; - ltime->hour= start->tm_hour; - ltime->minute= start->tm_min; - ltime->second= start->tm_sec; - ltime->second_part= 0; - ltime->neg= 0; - ltime->time_type=TIMESTAMP_DATETIME; + thd->variables.time_zone->gmt_sec_to_TIME(ltime, (my_time_t)temp); + thd->time_zone_used= 1; } return 0; } @@ -3245,7 +3203,7 @@ void Field_timestamp::sql_type(String &res) const void Field_timestamp::set_time() { - long tmp= (long) current_thd->query_start(); + long tmp= (long) table->in_use->query_start(); #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3267,25 +3225,35 @@ int Field_time::store(const char *from,uint len,CHARSET_INFO *cs) { TIME ltime; long tmp; - int error= 0; - if (str_to_time(from,len,<ime)) + int error; + + if (str_to_time(from, len, <ime, &error)) { tmp=0L; error= 1; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, + from, len, TIMESTAMP_TIME, 1); } else { + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, + from, len, TIMESTAMP_TIME, 1); + if (ltime.month) ltime.day=0; tmp=(ltime.day*24L+ltime.hour)*10000L+(ltime.minute*100+ltime.second); if (tmp > 8385959) { tmp=8385959; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + from, len, TIMESTAMP_TIME, !error); error= 1; } } + if (ltime.neg) tmp= -tmp; error |= Field_time::store((longlong) tmp); @@ -3300,13 +3268,15 @@ int Field_time::store(double nr) if (nr > 8385959.0) { tmp=8385959L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME); error= 1; } else if (nr < -8385959.0) { tmp= -8385959L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME); error= 1; } else @@ -3317,7 +3287,8 @@ int Field_time::store(double nr) if (tmp % 100 > 59 || tmp/100 % 100 > 59) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME); error= 1; } } @@ -3333,13 +3304,15 @@ int Field_time::store(longlong nr) if (nr > (longlong) 8385959L) { tmp=8385959L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME, 1); error= 1; } else if (nr < (longlong) -8385959L) { tmp= -8385959L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME, 1); error= 1; } else @@ -3348,7 +3321,8 @@ int Field_time::store(longlong nr) if (tmp % 100 > 59 || tmp/100 % 100 > 59) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_TIME, 1); error= 1; } } @@ -3460,11 +3434,11 @@ int Field_year::store(const char *from, uint len,CHARSET_INFO *cs) if (nr < 0 || nr >= 100 && nr <= 1900 || nr > 2155) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } - if (current_thd->count_cuted_fields && !test_if_int(from,len,end,cs)) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + if (table->in_use->count_cuted_fields && !test_if_int(from,len,end,cs)) + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); if (nr != 0 || len != 4) { if (nr < YY_PART_YEAR) @@ -3493,7 +3467,7 @@ int Field_year::store(longlong nr) if (nr < 0 || nr >= 100 && nr <= 1900 || nr > 2155) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } if (nr != 0 || field_length != 4) // 0000 -> 0; 00 -> 2000 @@ -3557,15 +3531,21 @@ int Field_date::store(const char *from, uint len,CHARSET_INFO *cs) { TIME l_time; uint32 tmp; - int error= 0; - if (str_to_TIME(from,len,&l_time,1) <= TIMESTAMP_DATETIME_ERROR) + int error; + + if (str_to_TIME(from, len, &l_time, 1, &error) <= + TIMESTAMP_DATETIME_ERROR) { tmp=0; error= 1; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); } else tmp=(uint32) l_time.year*10000L + (uint32) (l_time.month*100+l_time.day); + + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, + from, len, TIMESTAMP_DATE, 1); + #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3587,7 +3567,9 @@ int Field_date::store(double nr) if (nr < 0.0 || nr > 99991231.0) { tmp=0L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + nr, TIMESTAMP_DATE); error= 1; } else @@ -3613,7 +3595,9 @@ int Field_date::store(longlong nr) if (nr < 0 || nr > LL(99991231)) { tmp=0L; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + nr, TIMESTAMP_DATE, 0); error= 1; } else @@ -3740,15 +3724,20 @@ int Field_newdate::store(const char *from,uint len,CHARSET_INFO *cs) { TIME l_time; long tmp; - int error= 0; - if (str_to_TIME(from,len,&l_time,1) <= TIMESTAMP_DATETIME_ERROR) + int error; + if (str_to_TIME(from, len, &l_time, 1, &error) <= + TIMESTAMP_DATETIME_ERROR) { tmp=0L; error= 1; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); } else tmp= l_time.day + l_time.month*32 + l_time.year*16*32; + + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, + from, len, TIMESTAMP_DATE, 1); + int3store(ptr,tmp); return error; } @@ -3758,7 +3747,8 @@ int Field_newdate::store(double nr) if (nr < 0.0 || nr > 99991231235959.0) { (void) Field_newdate::store((longlong) -1); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, nr, TIMESTAMP_DATE); return 1; } else @@ -3775,7 +3765,8 @@ int Field_newdate::store(longlong nr) if (nr < 0L || nr > 99991231L) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_DATE, 1); error= 1; } else @@ -3793,7 +3784,8 @@ int Field_newdate::store(longlong nr) if (month > 12 || day > 31) { tmp=0L; // Don't allow date to change - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, nr, TIMESTAMP_DATE, 1); error= 1; } else @@ -3811,7 +3803,7 @@ void Field_newdate::store_time(TIME *ltime,timestamp_type type) else { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } int3store(ptr,tmp); } @@ -3910,12 +3902,19 @@ void Field_newdate::sql_type(String &res) const int Field_datetime::store(const char *from,uint len,CHARSET_INFO *cs) { - longlong tmp=str_to_datetime(from,len,1); - if (tmp < 0) - { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); - tmp= 0; - } + TIME time_tmp; + int error; + ulonglong tmp= 0; + + if (str_to_TIME(from, len, &time_tmp, 1, &error) > + TIMESTAMP_DATETIME_ERROR) + tmp= TIME_to_ulonglong_datetime(&time_tmp); + + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + from, len, TIMESTAMP_DATETIME, 1); + #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3924,7 +3923,7 @@ int Field_datetime::store(const char *from,uint len,CHARSET_INFO *cs) else #endif longlongstore(ptr,tmp); - return 0; + return error; } @@ -3933,8 +3932,10 @@ int Field_datetime::store(double nr) int error= 0; if (nr < 0.0 || nr > 99991231235959.0) { + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + nr, TIMESTAMP_DATETIME); nr=0.0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE); error= 1; } error |= Field_datetime::store((longlong) rint(nr)); @@ -3945,9 +3946,16 @@ int Field_datetime::store(double nr) int Field_datetime::store(longlong nr) { TIME not_used; - bool error; + int error; + longlong initial_nr= nr; - nr= fix_datetime(nr, ¬_used, field_name, &error); + nr= number_to_TIME(nr, ¬_used, 1, &error); + + if (error) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, initial_nr, + TIMESTAMP_DATETIME, 1); + #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3969,7 +3977,7 @@ void Field_datetime::store_time(TIME *ltime,timestamp_type type) else { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) @@ -4146,7 +4154,10 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) char buff[80]; String tmpstr(buff,sizeof(buff), &my_charset_bin); uint copy_length; - + + /* See the comment for Field_long::store(long long) */ + DBUG_ASSERT(table->in_use == current_thd); + /* Convert character set if nesessary */ if (String::needs_conversion(length, cs, field_charset, ¬_used)) { @@ -4168,7 +4179,7 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) field_charset->cset->fill(field_charset,ptr+copy_length, field_length-copy_length,' '); - if ((copy_length < length) && current_thd->count_cuted_fields) + if ((copy_length < length) && table->in_use->count_cuted_fields) { // Check if we loosed some info const char *end=from+length; from+= copy_length; @@ -4176,7 +4187,7 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) MY_SEQ_SPACES); if (from != end) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error=1; } } @@ -4224,6 +4235,8 @@ String *Field_string::val_str(String *val_buffer __attribute__((unused)), String *val_ptr) { uint length= field_charset->cset->lengthsp(field_charset, ptr, field_length); + /* See the comment for Field_long::store(long long) */ + DBUG_ASSERT(table->in_use == current_thd); val_ptr->set((const char*) ptr, length, field_charset); return val_ptr; } @@ -4351,7 +4364,7 @@ int Field_varstring::store(const char *from,uint length,CHARSET_INFO *cs) if (length > field_length) { length=field_length; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } memcpy(ptr+HA_KEY_BLOB_LENGTH,from,length); @@ -4669,7 +4682,7 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) min(length, copy_length), copy_length); if (copy_length < length) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); Field_blob::store_length(copy_length); if (was_conversion || table->copy_blobs || copy_length <= MAX_FIELD_WIDTH) @@ -5228,11 +5241,11 @@ int Field_enum::store(const char *from,uint length,CHARSET_INFO *cs) if (err || end != from+length || tmp > typelib->count) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } } else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } store_type((ulonglong) tmp); return err; @@ -5250,7 +5263,7 @@ int Field_enum::store(longlong nr) int error= 0; if ((uint) nr > typelib->count || nr == 0) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); nr=0; error=1; } @@ -5406,11 +5419,11 @@ int Field_set::store(const char *from,uint length,CHARSET_INFO *cs) tmp > (ulonglong) (((longlong) 1 << typelib->count) - (longlong) 1)) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); } } else if (got_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); store_type(tmp); return err; } @@ -5423,7 +5436,7 @@ int Field_set::store(longlong nr) (longlong) 1)) { nr&= (longlong) (((longlong) 1 << typelib->count) - (longlong) 1); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error=1; } store_type((ulonglong) nr); @@ -5802,14 +5815,122 @@ create_field::create_field(Field *old_field,Field *orig_field) /* Warning handling */ -void Field::set_warning(const uint level, const uint code) + +/* + Produce warning or note about data saved into field + + SYNOPSYS + set_warning() + level - level of message (Note/Warning/Error) + code - error code of message to be produced + cuted_increment - whenever we should increase cut fields count or not + + NOTE + This function won't produce warning and increase cut fields counter + if count_cuted_fields == FIELD_CHECK_IGNORE for current thread. + + RETURN VALUE + true - if count_cuted_fields == FIELD_CHECK_IGNORE + false - otherwise +*/ +bool +Field::set_warning(const uint level, const uint code, int cuted_increment) { - THD *thd= current_thd; + THD *thd= table->in_use; if (thd->count_cuted_fields) { - thd->cuted_fields++; - push_warning_printf(thd, (MYSQL_ERROR::enum_warning_level) level, - code, ER(code), field_name, thd->row_count); + thd->cuted_fields+= cuted_increment; + push_warning_printf(thd, (MYSQL_ERROR::enum_warning_level) level, + code, ER(code), field_name, thd->row_count); + return 0; + } + return 1; +} + + +/* + Produce warning or note about datetime string data saved into field + + SYNOPSYS + set_warning() + level - level of message (Note/Warning/Error) + code - error code of message to be produced + str - string value which we tried to save + str_len - length of string which we tried to save + ts_type - type of datetime value (datetime/date/time) + cuted_increment - whenever we should increase cut fields count or not + + NOTE + This function will always produce some warning but won't increase cut + fields counter if count_cuted_fields == FIELD_CHECK_IGNORE for current + thread. +*/ +void +Field::set_datetime_warning(const uint level, const uint code, + const char *str, uint str_length, + timestamp_type ts_type, int cuted_increment) +{ + if (set_warning(level, code, cuted_increment)) + make_truncated_value_warning(table->in_use, str, str_length, ts_type); +} + + +/* + Produce warning or note about integer datetime value saved into field + + SYNOPSYS + set_warning() + level - level of message (Note/Warning/Error) + code - error code of message to be produced + nr - numeric value which we tried to save + ts_type - type of datetime value (datetime/date/time) + cuted_increment - whenever we should increase cut fields count or not + + NOTE + This function will always produce some warning but won't increase cut + fields counter if count_cuted_fields == FIELD_CHECK_IGNORE for current + thread. +*/ +void +Field::set_datetime_warning(const uint level, const uint code, + longlong nr, timestamp_type ts_type, + int cuted_increment) +{ + if (set_warning(level, code, cuted_increment)) + { + char str_nr[22]; + char *str_end= longlong10_to_str(nr, str_nr, -10); + make_truncated_value_warning(table->in_use, str_nr, str_end - str_nr, + ts_type); + } +} + + +/* + Produce warning or note about double datetime data saved into field + + SYNOPSYS + set_warning() + level - level of message (Note/Warning/Error) + code - error code of message to be produced + nr - double value which we tried to save + ts_type - type of datetime value (datetime/date/time) + + NOTE + This function will always produce some warning but won't increase cut + fields counter if count_cuted_fields == FIELD_CHECK_IGNORE for current + thread. +*/ +void +Field::set_datetime_warning(const uint level, const uint code, + double nr, timestamp_type ts_type) +{ + if (set_warning(level, code, 1)) + { + /* DBL_DIG is enough to print '-[digits].E+###' */ + char str_nr[DBL_DIG + 8]; + uint str_len= my_sprintf(str_nr, (str_nr, "%g", nr)); + make_truncated_value_warning(table->in_use, str_nr, str_len, ts_type); } } diff --git a/sql/field.h b/sql/field.h index 2b6ef28c184..f2a166d29c3 100644 --- a/sql/field.h +++ b/sql/field.h @@ -45,6 +45,10 @@ public: char *ptr; // Position to field in record uchar *null_ptr; // Byte where null_bit is + /* + Note that you can use table->in_use as replacement for current_thd member + only inside of val_*() and store() members (e.g. you can't use it in cons) + */ struct st_table *table; // Pointer for table struct st_table *orig_table; // Pointer to original table const char *table_name,*field_name; @@ -264,7 +268,16 @@ public: virtual CHARSET_INFO *charset(void) const { return &my_charset_bin; } virtual bool has_charset(void) const { return FALSE; } virtual void set_charset(CHARSET_INFO *charset) { } - void set_warning(const unsigned int level, const unsigned int code); + bool set_warning(const unsigned int level, const unsigned int code, + int cuted_increment); + void set_datetime_warning(const uint level, const uint code, + const char *str, uint str_len, + timestamp_type ts_type, int cuted_increment); + void set_datetime_warning(const uint level, const uint code, + longlong nr, timestamp_type ts_type, + int cuted_increment); + void set_datetime_warning(const uint level, const uint code, + double nr, timestamp_type ts_type); virtual field_cast_enum field_cast_type()= 0; bool field_cast_compatible(field_cast_enum type); /* maximum possible display length */ diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 0974c552364..e98068ef974 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -121,7 +121,8 @@ set_field_to_null(Field *field) field->reset(); if (current_thd->count_cuted_fields == CHECK_FIELD_WARN) { - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,ER_WARN_DATA_TRUNCATED); + field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_DATA_TRUNCATED, 1); return 0; } if (!current_thd->no_errors) @@ -178,7 +179,8 @@ set_field_to_null_with_conversions(Field *field, bool no_conversions) } if (current_thd->count_cuted_fields == CHECK_FIELD_WARN) { - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,ER_WARN_NULL_TO_NOTNULL); + field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_NULL_TO_NOTNULL, 1); return 0; } if (!current_thd->no_errors) @@ -229,7 +231,7 @@ static void do_copy_not_null(Copy_field *copy) if (*copy->from_null_ptr & copy->from_bit) { copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DATA_TRUNCATED); + ER_WARN_DATA_TRUNCATED, 1); copy->to_field->reset(); } else @@ -329,7 +331,7 @@ static void do_cut_string(Copy_field *copy) if (!my_isspace(system_charset_info, *ptr)) // QQ: ucs incompatible { copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DATA_TRUNCATED); + ER_WARN_DATA_TRUNCATED, 1); break; } } @@ -350,7 +352,7 @@ static void do_varstring(Copy_field *copy) length=copy->to_length-2; if (current_thd->count_cuted_fields) copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DATA_TRUNCATED); + ER_WARN_DATA_TRUNCATED, 1); } int2store(copy->to_ptr,length); memcpy(copy->to_ptr+2, copy->from_ptr,length); diff --git a/sql/ha_berkeley.cc b/sql/ha_berkeley.cc index 4dde893116f..a13b6147468 100644 --- a/sql/ha_berkeley.cc +++ b/sql/ha_berkeley.cc @@ -843,8 +843,8 @@ int ha_berkeley::write_row(byte * record) else { DB_TXN *sub_trans = transaction; - /* Don't use sub transactions in temporary tables (in_use == 0) */ - ulong thd_options = table->in_use ? table->in_use->options : 0; + /* Don't use sub transactions in temporary tables */ + ulong thd_options = table->tmp_table == NO_TMP_TABLE ? table->in_use->options : 0; for (uint retry=0 ; retry < berkeley_trans_retry ; retry++) { key_map changed_keys(0); @@ -1067,7 +1067,7 @@ int ha_berkeley::update_row(const byte * old_row, byte * new_row) DBT prim_key, key, old_prim_key; int error; DB_TXN *sub_trans; - ulong thd_options = table->in_use ? table->in_use->options : 0; + ulong thd_options = table->tmp_table == NO_TMP_TABLE ? table->in_use->options : 0; bool primary_key_changed; DBUG_ENTER("update_row"); LINT_INIT(error); @@ -1260,7 +1260,7 @@ int ha_berkeley::delete_row(const byte * record) int error; DBT row, prim_key; key_map keys=table->keys_in_use; - ulong thd_options = table->in_use ? table->in_use->options : 0; + ulong thd_options = table->tmp_table == NO_TMP_TABLE ? table->in_use->options : 0; DBUG_ENTER("delete_row"); statistic_increment(ha_delete_count,&LOCK_status); diff --git a/sql/item.cc b/sql/item.cc index 853f5bc8ecc..d1a911aacaf 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -228,7 +228,7 @@ bool Item::get_date(TIME *ltime,uint fuzzydate) char buff[40]; String tmp(buff,sizeof(buff), &my_charset_bin),*res; if (!(res=val_str(&tmp)) || - str_to_TIME(res->ptr(),res->length(),ltime,fuzzydate) <= + str_to_TIME_with_warn(res->ptr(),res->length(),ltime,fuzzydate) <= TIMESTAMP_DATETIME_ERROR) { bzero((char*) ltime,sizeof(*ltime)); @@ -247,7 +247,7 @@ bool Item::get_time(TIME *ltime) char buff[40]; String tmp(buff,sizeof(buff),&my_charset_bin),*res; if (!(res=val_str(&tmp)) || - str_to_time(res->ptr(),res->length(),ltime)) + str_to_time_with_warn(res->ptr(), res->length(), ltime)) { bzero((char*) ltime,sizeof(*ltime)); return 1; diff --git a/sql/item_create.cc b/sql/item_create.cc index 53d4f14d1ee..4977ba2c5d3 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -89,6 +89,11 @@ Item *create_func_conv(Item* a, Item *b, Item *c) return new Item_func_conv(a,b,c); } +Item *create_func_convert_tz(Item* a, Item *b, Item *c) +{ + return new Item_func_convert_tz(a,b,c); +} + Item *create_func_cos(Item* a) { return new Item_func_cos(a); diff --git a/sql/item_create.h b/sql/item_create.h index 7577627ef04..19f0c9133f2 100644 --- a/sql/item_create.h +++ b/sql/item_create.h @@ -31,6 +31,7 @@ Item *create_func_char_length(Item* a); Item *create_func_cast(Item *a, Cast_target cast_type, int len, CHARSET_INFO *cs); Item *create_func_connection_id(void); Item *create_func_conv(Item* a, Item *b, Item *c); +Item *create_func_convert_tz(Item* a, Item *b, Item *c); Item *create_func_cos(Item* a); Item *create_func_cot(Item* a); Item *create_func_crc32(Item* a); diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 45d66addc9f..a5ea72374c1 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -894,6 +894,9 @@ longlong Item_func_year::val_int() longlong Item_func_unix_timestamp::val_int() { + TIME ltime; + bool not_used; + DBUG_ASSERT(fixed == 1); if (arg_count == 0) return (longlong) current_thd->query_start(); @@ -903,12 +906,19 @@ longlong Item_func_unix_timestamp::val_int() if (field->type() == FIELD_TYPE_TIMESTAMP) return ((Field_timestamp*) field)->get_timestamp(); } - String *str=args[0]->val_str(&value); - if ((null_value=args[0]->null_value)) + + if (get_arg0_date(<ime, 0)) { - return 0; /* purecov: inspected */ + /* + We have to set null_value again because get_arg0_date will also set it + to true if we have wrong datetime parameter (and we should return 0 in + this case). + */ + null_value= args[0]->null_value; + return 0; } - return (longlong) str_to_timestamp(str->ptr(),str->length()); + + return (longlong) TIME_to_timestamp(current_thd, <ime, ¬_used); } @@ -1126,23 +1136,14 @@ bool Item_func_from_days::get_date(TIME *ltime, uint fuzzy_date) void Item_func_curdate::fix_length_and_dec() { - struct tm start; - collation.set(&my_charset_bin); decimals=0; max_length=MAX_DATE_WIDTH*MY_CHARSET_BIN_MB_MAXLEN; - store_now_in_tm(current_thd->query_start(),&start); + store_now_in_TIME(<ime); - /* For getdate */ - ltime.year= start.tm_year+1900; - ltime.month= start.tm_mon+1; - ltime.day= start.tm_mday; - ltime.hour= 0; - ltime.minute= 0; - ltime.second= 0; - ltime.second_part=0; - ltime.neg=0; + /* We don't need to set second_part and neg because they already 0 */ + ltime.hour= ltime.minute= ltime.second= 0; ltime.time_type=TIMESTAMP_DATE; value= (longlong) TIME_to_ulonglong_date(<ime); } @@ -1159,31 +1160,39 @@ String *Item_func_curdate::val_str(String *str) return str; } -bool Item_func_curdate::get_date(TIME *res, - uint fuzzy_date __attribute__((unused))) +/* + Converts current time in my_time_t to TIME represenatation for local + time zone. Defines time zone (local) used for whole CURDATE function. +*/ +void Item_func_curdate_local::store_now_in_TIME(TIME *now_time) { - *res=ltime; - return 0; + THD *thd= current_thd; + thd->variables.time_zone->gmt_sec_to_TIME(now_time, + (my_time_t)thd->query_start()); + thd->time_zone_used= 1; } /* - Converts time in time_t to struct tm represenatation for local timezone. - Defines timezone (local) used for whole CURDATE function + Converts current time in my_time_t to TIME represenatation for UTC + time zone. Defines time zone (UTC) used for whole UTC_DATE function. */ -void Item_func_curdate_local::store_now_in_tm(time_t now, struct tm *now_tm) +void Item_func_curdate_utc::store_now_in_TIME(TIME *now_time) { - localtime_r(&now,now_tm); + my_tz_UTC->gmt_sec_to_TIME(now_time, + (my_time_t)(current_thd->query_start())); + /* + We are not flagging this query as using time zone, since it uses fixed + UTC-SYSTEM time-zone. + */ } -/* - Converts time in time_t to struct tm represenatation for UTC - Defines timezone (UTC) used for whole UTC_DATE function -*/ -void Item_func_curdate_utc::store_now_in_tm(time_t now, struct tm *now_tm) +bool Item_func_curdate::get_date(TIME *res, + uint fuzzy_date __attribute__((unused))) { - gmtime_r(&now,now_tm); + *res=ltime; + return 0; } @@ -1197,17 +1206,12 @@ String *Item_func_curtime::val_str(String *str) void Item_func_curtime::fix_length_and_dec() { - struct tm start; - String tmp((char*) buff,sizeof(buff), &my_charset_bin); TIME ltime; + String tmp((char*) buff,sizeof(buff), &my_charset_bin); - decimals=0; - store_now_in_tm(current_thd->query_start(),&start); - ltime.hour= start.tm_hour; - ltime.minute= start.tm_min; - ltime.second= start.tm_sec; - ltime.second_part= 0; - ltime.neg= 0; + decimals=0; + collation.set(&my_charset_bin); + store_now_in_TIME(<ime); value= TIME_to_ulonglong_time(<ime); make_time((DATE_TIME_FORMAT *) 0, <ime, &tmp); max_length= buff_length= tmp.length(); @@ -1215,22 +1219,30 @@ void Item_func_curtime::fix_length_and_dec() /* - Converts time in time_t to struct tm represenatation for local timezone. - Defines timezone (local) used for whole CURTIME function + Converts current time in my_time_t to TIME represenatation for local + time zone. Defines time zone (local) used for whole CURTIME function. */ -void Item_func_curtime_local::store_now_in_tm(time_t now, struct tm *now_tm) +void Item_func_curtime_local::store_now_in_TIME(TIME *now_time) { - localtime_r(&now,now_tm); + THD *thd= current_thd; + thd->variables.time_zone->gmt_sec_to_TIME(now_time, + (my_time_t)thd->query_start()); + thd->time_zone_used= 1; } /* - Converts time in time_t to struct tm represenatation for UTC. - Defines timezone (UTC) used for whole UTC_TIME function + Converts current time in my_time_t to TIME represenatation for UTC + time zone. Defines time zone (UTC) used for whole UTC_TIME function. */ -void Item_func_curtime_utc::store_now_in_tm(time_t now, struct tm *now_tm) +void Item_func_curtime_utc::store_now_in_TIME(TIME *now_time) { - gmtime_r(&now,now_tm); + my_tz_UTC->gmt_sec_to_TIME(now_time, + (my_time_t)(current_thd->query_start())); + /* + We are not flagging this query as using time zone, since it uses fixed + UTC-SYSTEM time-zone. + */ } @@ -1244,18 +1256,12 @@ String *Item_func_now::val_str(String *str) void Item_func_now::fix_length_and_dec() { - struct tm start; String tmp((char*) buff,sizeof(buff),&my_charset_bin); decimals=0; collation.set(&my_charset_bin); - store_now_in_tm(current_thd->query_start(),&start); - - /* For getdate */ - localtime_to_TIME(<ime, &start); - ltime.time_type= TIMESTAMP_DATETIME; - + store_now_in_TIME(<ime); value= (longlong) TIME_to_ulonglong_datetime(<ime); make_datetime((DATE_TIME_FORMAT *) 0, <ime, &tmp); @@ -1263,39 +1269,47 @@ void Item_func_now::fix_length_and_dec() } -bool Item_func_now::get_date(TIME *res, - uint fuzzy_date __attribute__((unused))) +/* + Converts current time in my_time_t to TIME represenatation for local + time zone. Defines time zone (local) used for whole NOW function. +*/ +void Item_func_now_local::store_now_in_TIME(TIME *now_time) { - *res=ltime; - return 0; + THD *thd= current_thd; + thd->variables.time_zone->gmt_sec_to_TIME(now_time, + (my_time_t)thd->query_start()); + thd->time_zone_used= 1; } -int Item_func_now::save_in_field(Field *to, bool no_conversions) +/* + Converts current time in my_time_t to TIME represenatation for UTC + time zone. Defines time zone (UTC) used for whole UTC_TIMESTAMP function. +*/ +void Item_func_now_utc::store_now_in_TIME(TIME *now_time) { - to->set_notnull(); - to->store_time(<ime,TIMESTAMP_DATETIME); - return 0; + my_tz_UTC->gmt_sec_to_TIME(now_time, + (my_time_t)(current_thd->query_start())); + /* + We are not flagging this query as using time zone, since it uses fixed + UTC-SYSTEM time-zone. + */ } -/* - Converts time in time_t to struct tm represenatation for local timezone. - Defines timezone (local) used for whole CURRENT_TIMESTAMP function -*/ -void Item_func_now_local::store_now_in_tm(time_t now, struct tm *now_tm) +bool Item_func_now::get_date(TIME *res, + uint fuzzy_date __attribute__((unused))) { - localtime_r(&now,now_tm); + *res=ltime; + return 0; } -/* - Converts time in time_t to struct tm represenatation for UTC. - Defines timezone (UTC) used for whole UTC_TIMESTAMP function -*/ -void Item_func_now_utc::store_now_in_tm(time_t now, struct tm *now_tm) +int Item_func_now::save_in_field(Field *to, bool no_conversions) { - gmtime_r(&now,now_tm); + to->set_notnull(); + to->store_time(<ime,TIMESTAMP_DATETIME); + return 0; } @@ -1455,7 +1469,7 @@ String *Item_func_date_format::val_str(String *str) { String *res; if (!(res=args[0]->val_str(str)) || - (str_to_time(res->ptr(),res->length(),&l_time))) + (str_to_time_with_warn(res->ptr(), res->length(), &l_time))) goto null_date; l_time.year=l_time.month=l_time.day=0; @@ -1489,24 +1503,31 @@ null_date: } +void Item_func_from_unixtime::fix_length_and_dec() +{ + thd= current_thd; + collation.set(&my_charset_bin); + decimals=0; + max_length=MAX_DATETIME_WIDTH*MY_CHARSET_BIN_MB_MAXLEN; + thd->time_zone_used= 1; +} + + String *Item_func_from_unixtime::val_str(String *str) { - struct tm tm_tmp; - time_t tmp; - TIME ltime; + TIME time_tmp; + my_time_t tmp; DBUG_ASSERT(fixed == 1); tmp= (time_t) args[0]->val_int(); if ((null_value=args[0]->null_value)) goto null_date; - - localtime_r(&tmp,&tm_tmp); - - localtime_to_TIME(<ime, &tm_tmp); - + + thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, tmp); + if (str->alloc(20*MY_CHARSET_BIN_MB_MAXLEN)) goto null_date; - make_datetime((DATE_TIME_FORMAT *) 0, <ime, str); + make_datetime((DATE_TIME_FORMAT *) 0, &time_tmp, str); return str; null_date: @@ -1517,28 +1538,109 @@ null_date: longlong Item_func_from_unixtime::val_int() { - TIME ltime; - struct tm tm_tmp; - time_t tmp; + TIME time_tmp; + my_time_t tmp; + DBUG_ASSERT(fixed == 1); tmp= (time_t) (ulong) args[0]->val_int(); if ((null_value=args[0]->null_value)) return 0; - localtime_r(&tmp,&tm_tmp); - localtime_to_TIME(<ime, &tm_tmp); - return (longlong) TIME_to_ulonglong_datetime(<ime); + + current_thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, tmp); + + return (longlong) TIME_to_ulonglong_datetime(&time_tmp); } bool Item_func_from_unixtime::get_date(TIME *ltime, uint fuzzy_date __attribute__((unused))) { - time_t tmp=(time_t) (ulong) args[0]->val_int(); + my_time_t tmp=(my_time_t) args[0]->val_int(); if ((null_value=args[0]->null_value)) return 1; - struct tm tm_tmp; - localtime_r(&tmp,&tm_tmp); - localtime_to_TIME(ltime, &tm_tmp); + + current_thd->variables.time_zone->gmt_sec_to_TIME(ltime, tmp); + + return 0; +} + + +void Item_func_convert_tz::fix_length_and_dec() +{ + String str; + + thd= current_thd; + collation.set(&my_charset_bin); + decimals= 0; + max_length= MAX_DATETIME_WIDTH*MY_CHARSET_BIN_MB_MAXLEN; + + if (args[1]->const_item()) + from_tz= my_tz_find(thd, args[1]->val_str(&str)); + + if (args[2]->const_item()) + to_tz= my_tz_find(thd, args[2]->val_str(&str)); +} + + +String *Item_func_convert_tz::val_str(String *str) +{ + TIME time_tmp; + + if (get_date(&time_tmp, 0)) + return 0; + + if (str->alloc(20*MY_CHARSET_BIN_MB_MAXLEN)) + { + null_value= 1; + return 0; + } + + make_datetime((DATE_TIME_FORMAT *) 0, &time_tmp, str); + return str; +} + + +longlong Item_func_convert_tz::val_int() +{ + TIME time_tmp; + + if (get_date(&time_tmp, 0)) + return 0; + + return (longlong)TIME_to_ulonglong_datetime(&time_tmp); +} + + +bool Item_func_convert_tz::get_date(TIME *ltime, + uint fuzzy_date __attribute__((unused))) +{ + my_time_t my_time_tmp; + bool not_used; + String str; + + if (!args[1]->const_item()) + from_tz= my_tz_find(thd, args[1]->val_str(&str)); + + if (!args[2]->const_item()) + to_tz= my_tz_find(thd, args[2]->val_str(&str)); + + if (from_tz==0 || to_tz==0 || get_arg0_date(ltime, 0)) + { + null_value= 1; + return 1; + } + + /* Check if we in range where we treat datetime values as non-UTC */ + if (ltime->year < TIMESTAMP_MAX_YEAR && ltime->year > TIMESTAMP_MIN_YEAR || + ltime->year==TIMESTAMP_MAX_YEAR && ltime->month==1 && ltime->day==1 || + ltime->year==TIMESTAMP_MIN_YEAR && ltime->month==12 && ltime->day==31) + { + my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, ¬_used); + if (my_time_tmp >= TIMESTAMP_MIN_VALUE && my_time_tmp <= TIMESTAMP_MAX_VALUE) + to_tz->gmt_sec_to_TIME(ltime, my_time_tmp); + } + + null_value= 0; return 0; } @@ -1795,7 +1897,7 @@ longlong Item_extract::val_int() else { String *res= args[0]->val_str(&value); - if (!res || str_to_time(res->ptr(),res->length(),<ime)) + if (!res || str_to_time_with_warn(res->ptr(), res->length(), <ime)) { null_value=1; return 0; diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index f00eb93e0e5..a7ff2924786 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -348,6 +348,7 @@ public: Item_date_func() :Item_str_func() {} Item_date_func(Item *a) :Item_str_func(a) {} Item_date_func(Item *a,Item *b) :Item_str_func(a,b) {} + Item_date_func(Item *a,Item *b, Item *c) :Item_str_func(a,b,c) {} enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } Field *tmp_table_field(TABLE *t_arg) { @@ -356,7 +357,7 @@ public: }; -/* Abstract CURTIME function. Children should define what timezone is used */ +/* Abstract CURTIME function. Children should define what time zone is used */ class Item_func_curtime :public Item_func { @@ -378,10 +379,10 @@ public: } /* Abstract method that defines which time zone is used for conversion. - Converts time from time_t representation to broken down representation - in struct tm using gmtime_r or localtime_r functions. + Converts time current time in my_time_t representation to broken-down + TIME representation using UTC-SYSTEM or per-thread time zone. */ - virtual void store_now_in_tm(time_t now, struct tm *now_tm)=0; + virtual void store_now_in_TIME(TIME *now_time)=0; }; @@ -391,7 +392,7 @@ public: Item_func_curtime_local() :Item_func_curtime() {} Item_func_curtime_local(Item *a) :Item_func_curtime(a) {} const char *func_name() const { return "curtime"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + virtual void store_now_in_TIME(TIME *now_time); }; @@ -401,7 +402,7 @@ public: Item_func_curtime_utc() :Item_func_curtime() {} Item_func_curtime_utc(Item *a) :Item_func_curtime(a) {} const char *func_name() const { return "utc_time"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + virtual void store_now_in_TIME(TIME *now_time); }; @@ -413,12 +414,11 @@ class Item_func_curdate :public Item_date TIME ltime; public: Item_func_curdate() :Item_date() {} - void set_result_from_tm(struct tm *now); longlong val_int() { DBUG_ASSERT(fixed == 1); return (value) ; } String *val_str(String *str); void fix_length_and_dec(); bool get_date(TIME *res, uint fuzzy_date); - virtual void store_now_in_tm(time_t now, struct tm *now_tm)=0; + virtual void store_now_in_TIME(TIME *now_time)=0; }; @@ -427,7 +427,7 @@ class Item_func_curdate_local :public Item_func_curdate public: Item_func_curdate_local() :Item_func_curdate() {} const char *func_name() const { return "curdate"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + void store_now_in_TIME(TIME *now_time); }; @@ -436,7 +436,7 @@ class Item_func_curdate_utc :public Item_func_curdate public: Item_func_curdate_utc() :Item_func_curdate() {} const char *func_name() const { return "utc_date"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + void store_now_in_TIME(TIME *now_time); }; @@ -458,7 +458,7 @@ public: String *val_str(String *str); void fix_length_and_dec(); bool get_date(TIME *res, uint fuzzy_date); - virtual void store_now_in_tm(time_t now, struct tm *now_tm)=0; + virtual void store_now_in_TIME(TIME *now_time)=0; }; @@ -468,7 +468,7 @@ public: Item_func_now_local() :Item_func_now() {} Item_func_now_local(Item *a) :Item_func_now(a) {} const char *func_name() const { return "now"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + virtual void store_now_in_TIME(TIME *now_time); virtual enum Functype functype() const { return NOW_FUNC; } }; @@ -479,7 +479,7 @@ public: Item_func_now_utc() :Item_func_now() {} Item_func_now_utc(Item *a) :Item_func_now(a) {} const char *func_name() const { return "utc_timestamp"; } - void store_now_in_tm(time_t now, struct tm *now_tm); + virtual void store_now_in_TIME(TIME *now_time); }; @@ -509,6 +509,7 @@ public: class Item_func_from_unixtime :public Item_date_func { + THD *thd; public: Item_func_from_unixtime(Item *a) :Item_date_func(a) {} double val() @@ -519,12 +520,29 @@ class Item_func_from_unixtime :public Item_date_func longlong val_int(); String *val_str(String *str); const char *func_name() const { return "from_unixtime"; } - void fix_length_and_dec() - { - collation.set(&my_charset_bin); - decimals=0; - max_length=MAX_DATETIME_WIDTH*MY_CHARSET_BIN_MB_MAXLEN; - } + void fix_length_and_dec(); + bool get_date(TIME *res, uint fuzzy_date); +}; + + +/* + We need Time_zone class declaration for storing pointers in + Item_func_convert_tz. +*/ +class Time_zone; + +class Item_func_convert_tz :public Item_date_func +{ + THD *thd; + Time_zone *from_tz, *to_tz; + public: + Item_func_convert_tz(Item *a, Item *b, Item *c): + Item_date_func(a, b, c) {} + longlong val_int(); + double val() { return (double) val_int(); } + String *val_str(String *str); + const char *func_name() const { return "convert_tz"; } + void fix_length_and_dec(); bool get_date(TIME *res, uint fuzzy_date); }; diff --git a/sql/lex.h b/sql/lex.h index fde5076a25e..b1626c75c28 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -499,6 +499,7 @@ static SYMBOL sql_functions[] = { { "CONNECTION_ID", F_SYM(FUNC_ARG0),0,CREATE_FUNC(create_func_connection_id)}, { "CONTAINS", F_SYM(FUNC_ARG2),0,CREATE_FUNC_GEOM(create_func_contains)}, { "CONV", F_SYM(FUNC_ARG3),0,CREATE_FUNC(create_func_conv)}, + { "CONVERT_TZ", F_SYM(FUNC_ARG3),0,CREATE_FUNC(create_func_convert_tz)}, { "COUNT", SYM(COUNT_SYM)}, { "COS", F_SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_cos)}, { "COT", F_SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_cot)}, diff --git a/sql/log.cc b/sql/log.cc index 47a6a4a9b4c..09e83392dac 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1269,10 +1269,24 @@ COLLATION_CONNECTION=%lu,COLLATION_DATABASE=%lu,COLLATION_SERVER=%lu", if (e.write(file)) goto err; } + /* + We use the same ONE_SHOT trick for making replication of time zones + working in 4.1. Again in 5.0 we have better means for doing this. + */ + if (thd->time_zone_used && + thd->variables.time_zone != global_system_variables.time_zone) + { + char buf[MAX_TIME_ZONE_NAME_LENGTH + 26]; + char *buf_end= strxmov(buf, "SET ONE_SHOT TIME_ZONE='", + thd->variables.time_zone->get_name()->ptr(), + "'", NullS); + Query_log_event e(thd, buf, buf_end - buf, 0); + e.set_log_pos(this); + if (e.write(file)) + goto err; + } #endif - /* Add logging of timezones here */ - if (thd->last_insert_id_used) { Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index b2d21c3fb55..db8d534064d 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -39,6 +39,14 @@ extern const key_map key_map_empty; extern const key_map key_map_full; extern const char *primary_key_name; +/* + Portable time_t replacement. + Should be signed and hold seconds for 1902-2038 range. +*/ +typedef long my_time_t; +#define MY_TIME_T_MAX LONG_MAX +#define MY_TIME_T_MIN LONG_MIN + #include "mysql_com.h" #include <violite.h> #include "unireg.h" @@ -349,6 +357,7 @@ inline THD *_current_thd(void) #include "sql_udf.h" class user_var_entry; #include "item.h" +#include "tztime.h" typedef Comp_creator* (*chooser_compare_func_creator)(bool invert); /* sql_parse.cc */ void free_items(Item *item); @@ -378,6 +387,7 @@ struct Query_cache_query_flags uint character_set_results_num; uint collation_connection_num; ha_rows limit; + Time_zone *time_zone; }; #define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags) #include "sql_cache.h" @@ -822,7 +832,7 @@ extern Le_creator le_creator; extern uchar *days_in_month; extern char language[LIBLEN],reg_ext[FN_EXTLEN]; extern char glob_hostname[FN_REFLEN], mysql_home[FN_REFLEN]; -extern char pidfile_name[FN_REFLEN], time_zone[30], *opt_init_file; +extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file; extern char log_error_file[FN_REFLEN]; extern double log_10[32]; extern ulonglong log_10_int[20]; @@ -878,6 +888,7 @@ extern my_bool opt_enable_named_pipe, opt_sync_frm; extern my_bool opt_secure_auth; extern char *shared_memory_base_name, *mysqld_unix_port; extern bool opt_enable_shared_memory; +extern char *default_tz_name; extern MYSQL_LOG mysql_log,mysql_update_log,mysql_slow_log,mysql_bin_log; extern FILE *bootstrap_file; @@ -988,12 +999,16 @@ uint calc_days_in_year(uint year); void get_date_from_daynr(long daynr,uint *year, uint *month, uint *day); void init_time(void); -long my_gmt_sec(TIME *, long *current_timezone); -time_t str_to_timestamp(const char *str,uint length); -bool str_to_time(const char *str,uint length,TIME *l_time); -longlong str_to_datetime(const char *str,uint length, uint fuzzy_date); +my_time_t my_system_gmt_sec(const TIME *, long *current_timezone, bool *not_exist); +my_time_t TIME_to_timestamp(THD *thd, const TIME *t, bool *not_exist); +bool str_to_time(const char *str,uint length,TIME *l_time, int *was_cut); +bool str_to_time_with_warn(const char *str,uint length,TIME *l_time); timestamp_type str_to_TIME(const char *str, uint length, TIME *l_time, - uint flags); + uint flags, int *was_cut); +timestamp_type str_to_TIME_with_warn(const char *str, uint length, + TIME *l_time, uint flags); +longlong number_to_TIME(longlong nr, TIME *time_res, bool fuzzy_date, + int *was_cut); void localtime_to_TIME(TIME *to, struct tm *from); void calc_time_from_sec(TIME *to, long seconds, long microseconds); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 841898ac505..782f4021ea9 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -324,7 +324,8 @@ ulonglong log_10_int[20]= time_t start_time; -char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], time_zone[30]; +char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30]; +char *default_tz_name; char log_error_file[FN_REFLEN], glob_hostname[FN_REFLEN]; char* log_error_file_ptr= log_error_file; char mysql_real_data_home[FN_REFLEN], @@ -911,6 +912,7 @@ void clean_up(bool print_message) if (use_slave_mask) bitmap_free(&slave_error_mask); #endif + my_tz_free(); #ifndef NO_EMBEDDED_ACCESS_CHECKS acl_free(1); grant_free(); @@ -2270,9 +2272,17 @@ static int init_common_variables(const char *conf_file_name, int argc, { struct tm tm_tmp; localtime_r(&start_time,&tm_tmp); - strmov(time_zone,tzname[tm_tmp.tm_isdst != 0 ? 1 : 0]); + strmov(system_time_zone, tzname[tm_tmp.tm_isdst != 0 ? 1 : 0]); } #endif + /* + We set SYSTEM time zone as reasonable default and + also for failure of my_tz_init() and bootstrap mode. + If user explicitly set time zone with --default-time-zone + option we will change this value in my_tz_init(). + */ + global_system_variables.time_zone= my_tz_SYSTEM; + /* Init mutexes for the global MYSQL_LOG objects. @@ -2810,7 +2820,8 @@ we force server id to 2, but this MySQL server will not act as a slave."); */ error_handler_hook = my_message_sql; start_signal_handler(); // Creates pidfile - if (acl_init((THD *)0, opt_noacl)) + if (acl_init((THD *)0, opt_noacl) || + my_tz_init((THD *)0, default_tz_name, opt_bootstrap)) { abort_loop=1; select_thread_in_use=0; @@ -3897,7 +3908,8 @@ enum options_mysqld OPT_DATE_FORMAT, OPT_TIME_FORMAT, OPT_DATETIME_FORMAT, - OPT_LOG_QUERIES_NOT_USING_INDEXES + OPT_LOG_QUERIES_NOT_USING_INDEXES, + OPT_DEFAULT_TIME_ZONE }; @@ -4010,6 +4022,9 @@ Disable with --skip-bdb (will save memory).", {"default-table-type", OPT_STORAGE_ENGINE, "(deprecated) Use default-storage-engine.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default-time-zone", OPT_DEFAULT_TIME_ZONE, "Set the default time zone.", + (gptr*) &default_tz_name, (gptr*) &default_tz_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, {"delay-key-write", OPT_DELAY_KEY_WRITE, "Type of DELAY_KEY_WRITE.", 0,0,0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"delay-key-write-for-all-tables", OPT_DELAY_KEY_WRITE_ALL, diff --git a/sql/set_var.cc b/sql/set_var.cc index 590b550ac3a..aa45afc3c30 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -441,6 +441,7 @@ static sys_var_thd_ulong sys_default_week_format("default_week_format", sys_var_thd_ulong sys_group_concat_max_len("group_concat_max_len", &SV::group_concat_max_len); +sys_var_thd_time_zone sys_time_zone("time_zone"); /* Read only variables */ @@ -586,6 +587,7 @@ sys_var *sys_variables[]= &sys_thread_cache_size, &sys_time_format, &sys_timestamp, + &sys_time_zone, &sys_tmp_table_size, &sys_trans_alloc_block_size, &sys_trans_prealloc_size, @@ -809,8 +811,9 @@ struct show_var_st init_vars[]= { {"thread_stack", (char*) &thread_stack, SHOW_LONG}, {sys_time_format.name, (char*) &sys_time_format, SHOW_SYS}, #ifdef HAVE_TZNAME - {"timezone", time_zone, SHOW_CHAR}, + {"system_time_zone", system_time_zone, SHOW_CHAR}, #endif + {"time_zone", (char*) &sys_time_zone, SHOW_SYS}, {sys_tmp_table_size.name, (char*) &sys_tmp_table_size, SHOW_SYS}, {"tmpdir", (char*) &opt_mysql_tmpdir, SHOW_CHAR_PTR}, {sys_trans_alloc_block_size.name, (char*) &sys_trans_alloc_block_size, @@ -2352,6 +2355,77 @@ bool sys_var_rand_seed2::update(THD *thd, set_var *var) } +bool sys_var_thd_time_zone::check(THD *thd, set_var *var) +{ + char buff[MAX_TIME_ZONE_NAME_LENGTH]; + String str(buff, sizeof(buff), &my_charset_latin1); + String *res= var->value->val_str(&str); + +#if defined(HAVE_REPLICATION) && (MYSQL_VERSION_ID < 50000) + if ((var->type == OPT_GLOBAL) && + (mysql_bin_log.is_open() || + active_mi->slave_running || active_mi->rli.slave_running)) + { + my_printf_error(0, "Binary logging and replication forbid changing " + "of the global server time zone", MYF(0)); + return 1; + } +#endif + + if (!(var->save_result.time_zone= my_tz_find(thd, res))) + { + my_error(ER_UNKNOWN_TIME_ZONE, MYF(0), res ? res->c_ptr() : "NULL"); + return 1; + } + return 0; +} + + +bool sys_var_thd_time_zone::update(THD *thd, set_var *var) +{ + /* We are using Time_zone object found during check() phase */ + *get_tz_ptr(thd,var->type)= var->save_result.time_zone; + return 0; +} + + +byte *sys_var_thd_time_zone::value_ptr(THD *thd, enum_var_type type, + LEX_STRING *base) +{ + /* + We can use ptr() instead of c_ptr() here because String contaning + time zone name is guaranteed to be zero ended. + */ + return (byte *)((*get_tz_ptr(thd,type))->get_name()->ptr()); +} + + +Time_zone** sys_var_thd_time_zone::get_tz_ptr(THD *thd, + enum_var_type type) +{ + if (type == OPT_GLOBAL) + return &global_system_variables.time_zone; + else + return &thd->variables.time_zone; +} + + +void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type) +{ + if (type == OPT_GLOBAL) + { + if (default_tz_name) + { + String str(default_tz_name, &my_charset_latin1); + global_system_variables.time_zone= my_tz_find(thd, &str); + } + else + global_system_variables.time_zone= my_tz_SYSTEM; + } + else + thd->variables.time_zone= global_system_variables.time_zone; +} + /* Functions to update thd->options bits */ diff --git a/sql/set_var.h b/sql/set_var.h index 9bed6f01dcc..c2b4ca34b2d 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -716,6 +716,29 @@ public: SHOW_TYPE type() { return show_type; } }; +class sys_var_thd_time_zone :public sys_var_thd +{ +public: + sys_var_thd_time_zone(const char *name_arg): + sys_var_thd(name_arg) + { +#if MYSQL_VERSION_ID < 50000 + no_support_one_shot= 0; +#endif + } + bool check(THD *thd, set_var *var); + SHOW_TYPE type() { return SHOW_CHAR; } + bool check_update_type(Item_result type) + { + return type != STRING_RESULT; /* Only accept strings */ + } + bool check_default(enum_var_type type) { return 0; } + bool update(THD *thd, set_var *var); + byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); + virtual void set_default(THD *thd, enum_var_type type); + Time_zone **get_tz_ptr(THD *thd, enum_var_type type); +}; + /**************************************************************************** Classes for parsing of the SET command ****************************************************************************/ @@ -749,6 +772,7 @@ public: ulong ulong_value; ulonglong ulonglong_value; DATE_TIME_FORMAT *date_time_format; + Time_zone *time_zone; } save_result; LEX_STRING base; /* for structs */ diff --git a/sql/share/czech/errmsg.txt b/sql/share/czech/errmsg.txt index 6b9b81ec87d..2d377929229 100644 --- a/sql/share/czech/errmsg.txt +++ b/sql/share/czech/errmsg.txt @@ -310,3 +310,5 @@ character-set=latin2 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/danish/errmsg.txt b/sql/share/danish/errmsg.txt index 767b1ee5f7a..af7b8263e6b 100644 --- a/sql/share/danish/errmsg.txt +++ b/sql/share/danish/errmsg.txt @@ -304,3 +304,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Modtog fejl %d '%-.100s' fra %s", "Modtog temporary fejl %d '%-.100s' fra %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/dutch/errmsg.txt b/sql/share/dutch/errmsg.txt index 14aa6ea7922..aa20996680e 100644 --- a/sql/share/dutch/errmsg.txt +++ b/sql/share/dutch/errmsg.txt @@ -312,3 +312,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/english/errmsg.txt b/sql/share/english/errmsg.txt index 8e3af2afe2a..b5a7f7962cf 100644 --- a/sql/share/english/errmsg.txt +++ b/sql/share/english/errmsg.txt @@ -301,3 +301,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/estonian/errmsg.txt b/sql/share/estonian/errmsg.txt index c6fc4987fb8..0cc6e06ab26 100644 --- a/sql/share/estonian/errmsg.txt +++ b/sql/share/estonian/errmsg.txt @@ -306,3 +306,5 @@ character-set=latin7 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/french/errmsg.txt b/sql/share/french/errmsg.txt index e6bda1b35a4..2e23db62ddb 100644 --- a/sql/share/french/errmsg.txt +++ b/sql/share/french/errmsg.txt @@ -301,3 +301,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/german/errmsg.txt b/sql/share/german/errmsg.txt index e51ff9d1554..c63162c84f6 100644 --- a/sql/share/german/errmsg.txt +++ b/sql/share/german/errmsg.txt @@ -313,3 +313,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/greek/errmsg.txt b/sql/share/greek/errmsg.txt index 42381c4d198..fa94b0f5107 100644 --- a/sql/share/greek/errmsg.txt +++ b/sql/share/greek/errmsg.txt @@ -301,3 +301,5 @@ character-set=greek "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/hungarian/errmsg.txt b/sql/share/hungarian/errmsg.txt index e0f0b3aa1ba..56fae82c438 100644 --- a/sql/share/hungarian/errmsg.txt +++ b/sql/share/hungarian/errmsg.txt @@ -303,3 +303,5 @@ character-set=latin2 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/italian/errmsg.txt b/sql/share/italian/errmsg.txt index 36204da1120..31768f172b4 100644 --- a/sql/share/italian/errmsg.txt +++ b/sql/share/italian/errmsg.txt @@ -301,3 +301,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/japanese/errmsg.txt b/sql/share/japanese/errmsg.txt index dbf05026da0..4385f25c991 100644 --- a/sql/share/japanese/errmsg.txt +++ b/sql/share/japanese/errmsg.txt @@ -303,3 +303,5 @@ character-set=ujis "This command is not supported in the prepared statement protocol yet", "Got NDB error %d '%-.100s'", "Got temporary NDB error %d '%-.100s'", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/korean/errmsg.txt b/sql/share/korean/errmsg.txt index 4f66ac1c1f3..a6e84fad01e 100644 --- a/sql/share/korean/errmsg.txt +++ b/sql/share/korean/errmsg.txt @@ -301,3 +301,5 @@ character-set=euckr "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/norwegian-ny/errmsg.txt b/sql/share/norwegian-ny/errmsg.txt index 64385ce17ab..eaf7b3482ee 100644 --- a/sql/share/norwegian-ny/errmsg.txt +++ b/sql/share/norwegian-ny/errmsg.txt @@ -303,3 +303,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Mottok feil %d '%-.100s' fra %s", "Mottok temporary feil %d '%-.100s' fra %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/norwegian/errmsg.txt b/sql/share/norwegian/errmsg.txt index c841c0e6458..692c10db58f 100644 --- a/sql/share/norwegian/errmsg.txt +++ b/sql/share/norwegian/errmsg.txt @@ -303,3 +303,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Mottok feil %d '%-.100s' fa %s", "Mottok temporary feil %d '%-.100s' fra %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/polish/errmsg.txt b/sql/share/polish/errmsg.txt index da2af606f97..19f2c1c6983 100644 --- a/sql/share/polish/errmsg.txt +++ b/sql/share/polish/errmsg.txt @@ -305,3 +305,5 @@ character-set=latin2 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/portuguese/errmsg.txt b/sql/share/portuguese/errmsg.txt index a999b765c91..c77d10d83de 100644 --- a/sql/share/portuguese/errmsg.txt +++ b/sql/share/portuguese/errmsg.txt @@ -302,3 +302,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/romanian/errmsg.txt b/sql/share/romanian/errmsg.txt index ca82b19215a..5ee4efd0063 100644 --- a/sql/share/romanian/errmsg.txt +++ b/sql/share/romanian/errmsg.txt @@ -305,3 +305,5 @@ character-set=latin2 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/russian/errmsg.txt b/sql/share/russian/errmsg.txt index 32da15409d0..20188723f6d 100644 --- a/sql/share/russian/errmsg.txt +++ b/sql/share/russian/errmsg.txt @@ -303,3 +303,5 @@ character-set=koi8r "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/serbian/errmsg.txt b/sql/share/serbian/errmsg.txt index c143e65461b..cc822431464 100644 --- a/sql/share/serbian/errmsg.txt +++ b/sql/share/serbian/errmsg.txt @@ -307,3 +307,5 @@ character-set=cp1250 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/slovak/errmsg.txt b/sql/share/slovak/errmsg.txt index 87fbeb5b6ab..ee6aac5081b 100644 --- a/sql/share/slovak/errmsg.txt +++ b/sql/share/slovak/errmsg.txt @@ -309,3 +309,5 @@ character-set=latin2 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/spanish/errmsg.txt b/sql/share/spanish/errmsg.txt index 3c4e2bacb9c..483ec7068a2 100644 --- a/sql/share/spanish/errmsg.txt +++ b/sql/share/spanish/errmsg.txt @@ -303,3 +303,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/swedish/errmsg.txt b/sql/share/swedish/errmsg.txt index 7ee4eabd6c3..d9f3adf92d4 100644 --- a/sql/share/swedish/errmsg.txt +++ b/sql/share/swedish/errmsg.txt @@ -301,3 +301,5 @@ character-set=latin1 "This command is not supported in the prepared statement protocol yet", "Fick felkod %d '%-.100s' från %s", "Fick tilfällig felkod %d '%-.100s' från %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/share/ukrainian/errmsg.txt b/sql/share/ukrainian/errmsg.txt index 958b6bf8674..acf6f5121e8 100644 --- a/sql/share/ukrainian/errmsg.txt +++ b/sql/share/ukrainian/errmsg.txt @@ -306,3 +306,5 @@ character-set=koi8u "This command is not supported in the prepared statement protocol yet", "Got error %d '%-.100s' from %s", "Got temporary error %d '%-.100s' from %s", +"Unknown or incorrect time zone: '%-.64s'", +"Invalid TIMESTAMP value in column '%s' at row %ld", diff --git a/sql/slave.cc b/sql/slave.cc index 1a59e5b2b5b..a1bbebca743 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1248,7 +1248,30 @@ be equal for replication to work"; mysql_free_result(master_res); } - /* Add a timezones check here */ + /* + Perform analogous check for time zone. Theoretically we also should + perform check here to verify that SYSTEM time zones are the same on + slave and master, but we can't rely on value of @@system_time_zone + variable (it is time zone abbreviation) since it determined at start + time and so could differ for slave and master even if they are really + in the same system time zone. So we are omiting this check and just + relying on documentation. Also according to Monty there are many users + who are using replication between servers in various time zones. Hence + such check will broke everything for them. (And now everything will + work for them because by default both their master and slave will have + 'SYSTEM' time zone). + */ + if (!mysql_real_query(mysql, "SELECT @@GLOBAL.TIME_ZONE", 25) && + (master_res= mysql_store_result(mysql))) + { + if ((master_row= mysql_fetch_row(master_res)) && + strcmp(master_row[0], + global_system_variables.time_zone->get_name()->ptr())) + errmsg= "The slave I/O thread stops because master and slave have \ +different values for the TIME_ZONE global variable. The values must \ +be equal for replication to work"; + mysql_free_result(master_res); + } if (errmsg) { diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 889c95125c5..f705b592e5a 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1760,6 +1760,7 @@ TABLE *open_temporary_table(THD *thd, const char *path, const char *db, } tmp_table->reginfo.lock_type=TL_WRITE; // Simulate locked + tmp_table->in_use= thd; tmp_table->tmp_table = (tmp_table->file->has_transactions() ? TRANSACTIONAL_TMP_TABLE : TMP_TABLE); tmp_table->table_cache_key=(char*) (tmp_table+1); diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index b9fe61ac48a..5c6215e6fb9 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -786,6 +786,7 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) flags.collation_connection_num= thd->variables.collation_connection->number; flags.limit= thd->variables.select_limit; + flags.time_zone= thd->variables.time_zone; STRUCT_LOCK(&structure_guard_mutex); if (query_cache_size == 0) @@ -972,6 +973,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) UINT_MAX); flags.collation_connection_num= thd->variables.collation_connection->number; flags.limit= thd->variables.select_limit; + flags.time_zone= thd->variables.time_zone; memcpy((void *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)), &flags, QUERY_CACHE_FLAGS_SIZE); query_block = (Query_cache_block *) hash_search(&queries, (byte*) sql, @@ -3231,9 +3233,10 @@ void Query_cache::queries_dump() Query_cache_query_flags flags; memcpy(&flags, str+len, QUERY_CACHE_FLAGS_SIZE); str[len]= 0; // make zero ending DB name - DBUG_PRINT("qcache", ("F:%u C:%u L:%lu (%u) '%s' '%s'", + DBUG_PRINT("qcache", ("F:%u C:%u L:%lu T:'%s' (%u) '%s' '%s'", flags.client_long_flag, - flags.character_set_client_num, (ulong)flags.limit, + flags.character_set_client_num, + (ulong)flags.limit, flags.time_zone->get_name(), len, str, strend(str)+1)); DBUG_PRINT("qcache", ("-b- 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", (ulong) block, (ulong) block->next, (ulong) block->prev, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 704662fa4bf..a04823c6c43 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -157,8 +157,8 @@ bool foreign_key_prefix(Key *a, Key *b) THD::THD():user_time(0), current_statement(0), is_fatal_error(0), last_insert_id_used(0), - insert_id_used(0), rand_used(0), in_lock_tables(0), - global_read_lock(0), bootstrap(0) + insert_id_used(0), rand_used(0), time_zone_used(0), + in_lock_tables(0), global_read_lock(0), bootstrap(0) { host= user= priv_user= db= ip=0; host_or_ip= "connecting host"; diff --git a/sql/sql_class.h b/sql/sql_class.h index e9ad659a2cc..cd4849d13ae 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -405,6 +405,8 @@ struct system_variables CHARSET_INFO *collation_database; CHARSET_INFO *collation_connection; + Time_zone *time_zone; + /* DATE, DATETIME and TIME formats */ DATE_TIME_FORMAT *date_format; DATE_TIME_FORMAT *datetime_format; @@ -826,6 +828,7 @@ public: bool last_cuted_field; bool no_errors, password, is_fatal_error; bool query_start_used,last_insert_id_used,insert_id_used,rand_used; + bool time_zone_used; bool in_lock_tables,global_read_lock; bool query_error, bootstrap, cleanup_done; bool tmp_table_used; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 037dd99d3b6..f7f30b079b8 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -945,6 +945,10 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) /* _rowid is not used with delayed insert */ copy->rowid_field=0; + + /* Adjust in_use for pointing to client thread */ + copy->in_use= client_thd; + return copy; /* Got fatal error */ diff --git a/sql/sql_load.cc b/sql/sql_load.cc index f72fab9ea3a..167fb2daf8b 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -530,7 +530,7 @@ read_sep_field(THD *thd,COPY_INFO &info,TABLE *table, ((Field_timestamp*) field)->set_time(); else if (field != table->next_number_field) field->set_warning((uint) MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_NULL_TO_NOTNULL); + ER_WARN_NULL_TO_NOTNULL, 1); } continue; } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 28e833b8421..10d780bca48 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3489,7 +3489,8 @@ purposes internal to the MySQL server", MYF(0)); thd->variables.collation_server= global_system_variables.collation_server; thd->update_charset(); - /* Add timezone stuff here */ + thd->variables.time_zone= + global_system_variables.time_zone; thd->one_shot_set= 0; } } @@ -3847,7 +3848,7 @@ mysql_init_query(THD *thd) thd->total_warn_count=0; // Warnings for this query thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0; thd->sent_row_count= thd->examined_row_count= 0; - thd->is_fatal_error= thd->rand_used= 0; + thd->is_fatal_error= thd->rand_used= thd->time_zone_used= 0; thd->server_status&= ~ (SERVER_MORE_RESULTS_EXISTS | SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f7a0d5259a6..b32cb228c72 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -4975,6 +4975,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, table->db_low_byte_first=1; // True for HEAP and MyISAM table->temp_pool_slot = temp_pool_slot; table->copy_blobs= 1; + table->in_use= thd; table->keys_for_keyread.init(); table->keys_in_use.init(); table->read_only_keys.init(); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 0b565968b08..4642abfcfc4 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -529,7 +529,6 @@ int mysqld_extend_show_tables(THD *thd,const char *db,const char *wild) } else { - struct tm tm_tmp; const char *str; handler *file=table->file; file->info(HA_STATUS_VARIABLE | HA_STATUS_TIME | HA_STATUS_NO_LOCK); @@ -562,24 +561,21 @@ int mysqld_extend_show_tables(THD *thd,const char *db,const char *wild) protocol->store_null(); else { - localtime_r(&file->create_time,&tm_tmp); - localtime_to_TIME(&time, &tm_tmp); + thd->variables.time_zone->gmt_sec_to_TIME(&time, file->create_time); protocol->store(&time); } if (!file->update_time) protocol->store_null(); else { - localtime_r(&file->update_time,&tm_tmp); - localtime_to_TIME(&time, &tm_tmp); + thd->variables.time_zone->gmt_sec_to_TIME(&time, file->update_time); protocol->store(&time); } if (!file->check_time) protocol->store_null(); else { - localtime_r(&file->check_time,&tm_tmp); - localtime_to_TIME(&time, &tm_tmp); + thd->variables.time_zone->gmt_sec_to_TIME(&time, file->check_time); protocol->store(&time); } str= (table->table_charset ? table->table_charset->name : "default"); diff --git a/sql/time.cc b/sql/time.cc index 992f1afc4af..6a57dd1038c 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -23,16 +23,26 @@ static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */ uchar *days_in_month= (uchar*) "\037\034\037\036\037\036\037\037\036\037\036\037"; - /* Init some variabels needed when using my_local_time */ - /* Currently only my_time_zone is inited */ +/* + Offset of system time zone from UTC in seconds used to speed up + work of my_system_gmt_sec() function. +*/ static long my_time_zone=0; + +/* + Prepare offset of system time zone from UTC for my_system_gmt_sec() func. + + SYNOPSIS + init_time() +*/ void init_time(void) { time_t seconds; struct tm *l_time,tm_tmp;; TIME my_time; + bool not_used; seconds= (time_t) time((time_t*) 0); localtime_r(&seconds,&tm_tmp); @@ -44,33 +54,40 @@ void init_time(void) my_time.hour= (uint) l_time->tm_hour; my_time.minute= (uint) l_time->tm_min; my_time.second= (uint) l_time->tm_sec; - my_gmt_sec(&my_time, &my_time_zone); /* Init my_time_zone */ + my_system_gmt_sec(&my_time, &my_time_zone, ¬_used); /* Init my_time_zone */ } + /* - Convert current time to sec. since 1970.01.01 - This code handles also day light saving time. - The idea is to cache the time zone (including daylight saving time) - for the next call to make things faster. + Convert time in TIME representation in system time zone to its + my_time_t form (number of seconds in UTC since begginning of Unix Epoch). -*/ + SYNOPSIS + my_system_gmt_sec() + t - time value to be converted + my_timezone - pointer to long where offset of system time zone + from UTC will be stored for caching + in_dst_time_gap - set to true if time falls into spring time-gap -long my_gmt_sec(TIME *t, long *my_timezone) + NOTES + The idea is to cache the time zone offset from UTC (including daylight + saving time) for the next call to make things faster. But currently we + just calculate this offset during startup (by calling init_time() + function) and use it all the time. + Time value provided should be legal time value (e.g. '2003-01-01 25:00:00' + is not allowed). + + RETURN VALUE + Time in UTC seconds since Unix Epoch representation. +*/ +my_time_t +my_system_gmt_sec(const TIME *t, long *my_timezone, bool *in_dst_time_gap) { uint loop; time_t tmp; struct tm *l_time,tm_tmp; long diff, current_timezone; - if (t->year > TIMESTAMP_MAX_YEAR || t->year < TIMESTAMP_MIN_YEAR) - return 0; - - if (t->hour >= 24) - { /* Fix for time-loop */ - t->day+=t->hour/24; - t->hour%=24; - } - /* Calculate the gmt time based on current time and timezone The -1 on the end is to ensure that if have a date that exists twice @@ -125,14 +142,13 @@ long my_gmt_sec(TIME *t, long *my_timezone) tmp+=3600 - t->minute*60 - t->second; // Move to next hour else if (diff == -3600) tmp-=t->minute*60 + t->second; // Move to previous hour + + *in_dst_time_gap= 1; } *my_timezone= current_timezone; - if (tmp < TIMESTAMP_MIN_VALUE || tmp > TIMESTAMP_MAX_VALUE) - tmp= 0; - - return (long) tmp; -} /* my_gmt_sec */ + return (my_time_t) tmp; +} /* my_system_gmt_sec */ /* Some functions to calculate dates */ @@ -164,6 +180,7 @@ long calc_daynr(uint year,uint month,uint day) } /* calc_daynr */ +#ifndef TESTTIME /* Calc weekday from daynr */ /* Returns 0 for monday, 1 for tuesday .... */ @@ -346,6 +363,8 @@ static char time_separator=':'; flags Bitmap of following items TIME_FUZZY_DATE Set if we should allow partial dates TIME_DATETIME_ONLY Set if we only allow full datetimes. + was_cut Set to 1 if value was cut during conversion or to 0 + otherwise. DESCRIPTION At least the following formats are recogniced (based on number of digits) @@ -383,7 +402,8 @@ static char time_separator=':'; #define MAX_DATE_PARTS 8 timestamp_type -str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) +str_to_TIME(const char *str, uint length, TIME *l_time, uint flags, + int *was_cut) { uint field_length, year_length, digits, i, number_of_fields; uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS]; @@ -403,11 +423,16 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) LINT_INIT(year_length); LINT_INIT(last_field_pos); + *was_cut= 0; + // Skip space at start for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++) ; if (str == end || ! my_isdigit(&my_charset_latin1, *str)) + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); + } is_internal_format= 0; /* This has to be changed if want to activate different timestamp formats */ @@ -449,7 +474,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) if (pos == end) { if (flags & TIME_DATETIME_ONLY) + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); // Can't be a full datetime + } /* Date field. Set hour, minutes and seconds to 0 */ date[0]= date[1]= date[2]= date[3]= date[4]= 0; start_loop= 5; // Start with first date part @@ -486,7 +514,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) } date_len[i]= (uint) (str - start); if (tmp_value > 999999) // Impossible date part + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); + } date[i]=tmp_value; not_zero_date|= tmp_value; @@ -523,7 +554,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) if (my_isspace(&my_charset_latin1,*str)) { if (!(allow_space & (1 << i))) + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); + } found_space= 1; } str++; @@ -551,7 +585,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) last_field_pos= str; } if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY)) + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); // Can't be a datetime + } str= last_field_pos; @@ -566,7 +603,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) { year_length= date_len[(uint) format_position[0]]; if (!year_length) // Year must be specified + { + *was_cut= 1; DBUG_RETURN(TIMESTAMP_NONE); + } l_time->year= date[(uint) format_position[0]]; l_time->month= date[(uint) format_position[1]]; @@ -584,7 +624,10 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) if (format_position[7] != (uchar) 255) { if (l_time->hour > 12) + { + *was_cut= 1; goto err; + } l_time->hour= l_time->hour%12 + add_hours; } } @@ -624,7 +667,7 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) } } if (not_zero_date) - current_thd->cuted_fields++; + *was_cut= 1; goto err; } @@ -635,8 +678,7 @@ str_to_TIME(const char *str, uint length, TIME *l_time, uint flags) { if (!my_isspace(&my_charset_latin1,*str)) { - make_truncated_value_warning(current_thd, str_begin, length, - l_time->time_type); + *was_cut= 1; break; } } @@ -650,43 +692,59 @@ err: } -time_t str_to_timestamp(const char *str,uint length) +/* + Convert a timestamp string to a TIME value and produce a warning + if string was truncated during conversion. + + NOTE + See description of str_to_TIME() for more information. +*/ +timestamp_type +str_to_TIME_with_warn(const char *str, uint length, TIME *l_time, uint flags) { - TIME l_time; - long not_used; - time_t timestamp= 0; - - if (str_to_TIME(str,length,&l_time,0) > TIMESTAMP_DATETIME_ERROR && - !(timestamp= my_gmt_sec(&l_time, ¬_used))) - current_thd->cuted_fields++; - return timestamp; + int was_cut; + timestamp_type ts_type= str_to_TIME(str, length, l_time, flags, &was_cut); + if (was_cut) + make_truncated_value_warning(current_thd, str, length, ts_type); + return ts_type; } /* - Convert a string to datetime. + Convert a datetime from broken-down TIME representation to corresponding + TIMESTAMP value. SYNOPSIS - str_to_datetime() - str String to parse (see str_to_TIME() synopsis) - length Length of str - fuzzy_date Flags (see str_to_TIME() synopsis) - + TIME_to_timestamp() + thd - current thread + t - datetime in broken-down representation, + in_dst_time_gap - pointer to bool which is set to true if t represents + value which doesn't exists (falls into the spring + time-gap) or to false otherwise. + RETURN - -1 if error - datetime value otherwise + Number seconds in UTC since start of Unix Epoch corresponding to t. + 0 - t contains datetime value which is out of TIMESTAMP range. + */ - -longlong str_to_datetime(const char *str,uint length, uint fuzzy_date) +my_time_t TIME_to_timestamp(THD *thd, const TIME *t, bool *in_dst_time_gap) { - TIME l_time; - if (str_to_TIME(str,length,&l_time,fuzzy_date) <= TIMESTAMP_DATETIME_ERROR) - return -1; - return (longlong) (l_time.year*LL(10000000000) + - l_time.month*LL(100000000)+ - l_time.day*LL(1000000)+ - l_time.hour*LL(10000)+ - (longlong) (l_time.minute*100+l_time.second)); + my_time_t timestamp; + + *in_dst_time_gap= 0; + + if (t->year < TIMESTAMP_MAX_YEAR && t->year > TIMESTAMP_MIN_YEAR || + t->year == TIMESTAMP_MAX_YEAR && t->month == 1 && t->day == 1 || + t->year == TIMESTAMP_MIN_YEAR && t->month == 12 && t->day == 31) + { + thd->time_zone_used= 1; + timestamp= thd->variables.time_zone->TIME_to_gmt_sec(t, in_dst_time_gap); + if (timestamp >= TIMESTAMP_MIN_VALUE && timestamp <= TIMESTAMP_MAX_VALUE) + return timestamp; + } + + /* If we are here we have range error. */ + return(0); } @@ -701,6 +759,8 @@ longlong str_to_datetime(const char *str,uint length, uint fuzzy_date) There may be an optional [.second_part] after seconds length Length of str l_time Store result here + was_cut Set to 1 if value was cut during conversion or to 0 + otherwise. NOTES Because of the extra days argument, this function can only @@ -711,7 +771,7 @@ longlong str_to_datetime(const char *str,uint length, uint fuzzy_date) 1 error */ -bool str_to_time(const char *str,uint length,TIME *l_time) +bool str_to_time(const char *str, uint length, TIME *l_time, int *was_cut) { long date[5],value; const char *end=str+length, *end_of_days; @@ -720,6 +780,7 @@ bool str_to_time(const char *str,uint length,TIME *l_time) uint state; l_time->neg=0; + *was_cut= 0; for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++) length--; if (str != end && *str == '-') @@ -736,9 +797,12 @@ bool str_to_time(const char *str,uint length,TIME *l_time) { // Probably full timestamp enum timestamp_type res= str_to_TIME(str,length,l_time, (TIME_FUZZY_DATE | - TIME_DATETIME_ONLY)); + TIME_DATETIME_ONLY), + was_cut); if ((int) res >= (int) TIMESTAMP_DATETIME_ERROR) return res == TIMESTAMP_DATETIME_ERROR; + /* We need to restore was_cut flag since str_to_TIME can modify it */ + *was_cut= 0; } /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ @@ -841,7 +905,7 @@ fractional: /* Some simple checks */ if (date[2] >= 60 || date[3] >= 60) { - current_thd->cuted_fields++; + *was_cut= 1; return 1; } l_time->year= 0; // For protocol::store_time @@ -860,8 +924,7 @@ fractional: { if (!my_isspace(&my_charset_latin1,*str)) { - make_truncated_value_warning(current_thd, str_begin, length, - TIMESTAMP_TIME); + *was_cut= 1; break; } } while (++str != end); @@ -871,6 +934,113 @@ fractional: /* + Convert a time string to a TIME struct and produce a warning + if string was cut during conversion. + + NOTE + See str_to_time() for more info. +*/ +bool +str_to_time_with_warn(const char *str, uint length, TIME *l_time) +{ + int was_cut; + bool ret_val= str_to_time(str, length, l_time, &was_cut); + if (was_cut) + make_truncated_value_warning(current_thd, str, length, TIMESTAMP_TIME); + return ret_val; +} + + +/* + Convert datetime value specified as number to broken-down TIME + representation and form value of DATETIME type as side-effect. + + SYNOPSIS + number_to_TIME() + nr - datetime value as number + time_res - pointer for structure for broken-down representation + fuzzy_date - indicates whenever we allow fuzzy dates + was_cut - set ot 1 if there was some kind of error during + conversion or to 0 if everything was OK. + + DESCRIPTION + Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS, + YYYYMMDDHHMMSS to broken-down TIME representation. Return value in + YYYYMMDDHHMMSS format as side-effect. + + This function also checks if datetime value fits in DATETIME range. + + RETURN VALUE + Datetime value in YYYYMMDDHHMMSS format. + If input value is not valid datetime value then 0 is returned. +*/ + +longlong number_to_TIME(longlong nr, TIME *time_res, bool fuzzy_date, + int *was_cut) +{ + long part1,part2; + + *was_cut= 0; + + if (nr == LL(0) || nr >= LL(10000101000000)) + goto ok; + if (nr < 101) + goto err; + if (nr <= (YY_PART_YEAR-1)*10000L+1231L) + { + nr= (nr+20000000L)*1000000L; // YYMMDD, year: 2000-2069 + goto ok; + } + if (nr < (YY_PART_YEAR)*10000L+101L) + goto err; + if (nr <= 991231L) + { + nr= (nr+19000000L)*1000000L; // YYMMDD, year: 1970-1999 + goto ok; + } + if (nr < 10000101L) + goto err; + if (nr <= 99991231L) + { + nr= nr*1000000L; + goto ok; + } + if (nr < 101000000L) + goto err; + if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959)) + { + nr= nr+LL(20000000000000); // YYMMDDHHMMSS, 2000-2069 + goto ok; + } + if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000)) + goto err; + if (nr <= LL(991231235959)) + nr= nr+LL(19000000000000); // YYMMDDHHMMSS, 1970-1999 + + ok: + part1=(long) (nr/LL(1000000)); + part2=(long) (nr - (longlong) part1*LL(1000000)); + time_res->year= (int) (part1/10000L); part1%=10000L; + time_res->month= (int) part1 / 100; + time_res->day= (int) part1 % 100; + time_res->hour= (int) (part2/10000L); part2%=10000L; + time_res->minute=(int) part2 / 100; + time_res->second=(int) part2 % 100; + + if (time_res->year <= 9999 && time_res->month <= 12 && + time_res->day <= 31 && time_res->hour <= 23 && + time_res->minute <= 59 && time_res->second <= 59 && + (fuzzy_date || (time_res->month != 0 && time_res->day != 0) || nr==0)) + return nr; + + err: + + *was_cut= 1; + return LL(0); +} + + +/* Convert a system time structure to TIME */ @@ -1307,6 +1477,7 @@ void make_datetime(const DATE_TIME_FORMAT *format __attribute__((unused)), str->set_charset(&my_charset_bin); } + void make_truncated_value_warning(THD *thd, const char *str_val, uint str_length, timestamp_type time_type) { @@ -1323,19 +1494,17 @@ void make_truncated_value_warning(THD *thd, const char *str_val, case TIMESTAMP_DATE: type_str= "date"; break; - case TIMESTAMP_DATETIME: - type_str= "datetime"; - break; case TIMESTAMP_TIME: type_str= "time"; break; + case TIMESTAMP_DATETIME: // FALLTHROUGH default: - type_str= "string"; + type_str= "datetime"; break; } sprintf(warn_buff, ER(ER_TRUNCATED_WRONG_VALUE), type_str, str.ptr()); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, warn_buff); } @@ -1446,3 +1615,5 @@ void TIME_to_string(const TIME *time, String *str) DBUG_ASSERT(0); } } + +#endif diff --git a/sql/tzfile.h b/sql/tzfile.h new file mode 100644 index 00000000000..623cddc1f12 --- /dev/null +++ b/sql/tzfile.h @@ -0,0 +1,137 @@ +/* Copyright (C) 2004 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + This file is based on public domain code from ftp://elsie.ncih.nist.gov/ + Initial source code is in the public domain, so clarified as of + 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). +*/ + +/* + Information about time zone files. +*/ + +#ifndef TZDIR +#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */ +#endif /* !defined TZDIR */ + +/* + Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_reserved[16]; /* reserved for future use */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* + . . .followed by. . . + + tzh_timecnt (char [4])s coded transition times a la time(2) + tzh_timecnt (unsigned char)s types of local time starting at above + tzh_typecnt repetitions of + one (char [4]) coded UTC offset in seconds + one (unsigned char) used to set tm_isdst + one (unsigned char) that's an abbreviation list index + tzh_charcnt (char)s '\0'-terminated zone abbreviations + tzh_leapcnt repetitions of + one (char [4]) coded leap second transition times + one (char [4]) total correction after above + tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition + time is standard time, if FALSE, + transition time is wall clock time + if absent, transition times are + assumed to be wall clock time + tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition + time is UTC, if FALSE, + transition time is local time + if absent, transition times are + assumed to be local time +*/ + +/* + In the current implementation, we refuse to deal with files that + exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +/* + The TZ_MAX_TIMES value below is enough to handle a bit more than a + year's worth of solar time (corrected daily to the nearest second) or + 138 years of Pacific Presidential Election time + (where there are three time zone transitions every fourth year). +*/ +#define TZ_MAX_TIMES 370 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +#ifdef SOLAR +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#else +/* + Must be at least 14 for Europe/Riga as of Jan 12 1995, + as noted by Earl Chew <earl@hpato.aus.hp.com>. +*/ +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ +#endif /* defined SOLAR */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#ifndef TZ_MAX_REV_RANGES +#ifdef SOLAR +/* Solar (Asia/RiyadhXX) zones need significantly bigger TZ_MAX_REV_RANGES */ +#define TZ_MAX_REV_RANGES (TZ_MAX_TIMES*2+TZ_MAX_LEAPS*2+2) +#else +#define TZ_MAX_REV_RANGES (TZ_MAX_TIMES+TZ_MAX_LEAPS+2) +#endif +#endif + +#define SECS_PER_MIN 60 +#define MINS_PER_HOUR 60 +#define HOURS_PER_DAY 24 +#define DAYS_PER_WEEK 7 +#define DAYS_PER_NYEAR 365 +#define DAYS_PER_LYEAR 366 +#define SECS_PER_HOUR (SECS_PER_MIN * MINS_PER_HOUR) +#define SECS_PER_DAY ((long) SECS_PER_HOUR * HOURS_PER_DAY) +#define MONS_PER_YEAR 12 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 + +/* + Accurate only for the past couple of centuries, + that will probably do. +*/ + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) diff --git a/sql/tztime.cc b/sql/tztime.cc new file mode 100644 index 00000000000..5a23c90a574 --- /dev/null +++ b/sql/tztime.cc @@ -0,0 +1,2559 @@ +/* Copyright (C) 2004 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Most of the following code and structures were derived from + public domain code from ftp://elsie.nci.nih.gov/pub + (We will refer to this code as to elsie-code further.) +*/ + +#ifdef __GNUC__ +#pragma implementation // gcc: Class implementation +#endif + + +#include "mysql_priv.h" +#include "tzfile.h" +#include <m_string.h> +#include <my_dir.h> + + +/* + Now we don't use abbreviations in server but we will do this in future. +*/ +#if defined(TZINFO2SQL) || defined(TESTTIME) +#define ABBR_ARE_USED +#endif + +/* For debug purposes only */ +#undef ABBR_ARE_USED +#define ABBR_ARE_USED + +/* Structure describing local time type (e.g. Moscow summer time (MSD)) */ +typedef struct ttinfo +{ + long tt_gmtoff; // Offset from UTC in seconds + int tt_isdst; // Is daylight saving time or not. Used to set tm_isdst +#ifdef ABBR_ARE_USED + int tt_abbrind; // Index of start of abbreviation for this time type. +#endif + /* + We don't use tt_ttisstd and tt_ttisgmt members of original elsie-code struct + since we don't support POSIX-style TZ descriptions in variables. + */ +} TRAN_TYPE_INFO; + +/* Structure describing leap-second corrections. */ +typedef struct lsinfo +{ + my_time_t ls_trans; // Transition time + long ls_corr; // Correction to apply +} LS_INFO; + +/* + Structure with information describing ranges of my_time_t shifted to local + time (my_time_t + offset). Used for local TIME -> my_time_t conversion. + See comments for TIME_to_gmt_sec() for more info. +*/ +typedef struct revtinfo +{ + long rt_offset; // Offset of local time from UTC in seconds + int rt_type; // Type of period 0 - Normal period. 1 - Spring time-gap +} REVT_INFO; + +#ifdef TZNAME_MAX +#define MY_TZNAME_MAX TZNAME_MAX +#endif +#ifndef TZNAME_MAX +#define MY_TZNAME_MAX 255 +#endif + +/* + Structure which fully describes time zone which is + described in our db or in zoneinfo files. +*/ +typedef struct st_time_zone_info +{ + int leapcnt; // Number of leap-second corrections + int timecnt; // Number of transitions between time types + int typecnt; // Number of local time types + int charcnt; // Number of characters used for abbreviations + int revcnt; // Number of transition descr. for TIME->my_time_t conversion + /* The following are dynamical arrays are allocated in MEM_ROOT */ + my_time_t *ats; // Times of transitions between time types + unsigned char *types; // Local time types for transitions + TRAN_TYPE_INFO *ttis; // Local time types descriptions +#ifdef ABBR_ARE_USED + /* Storage for local time types abbreviations. They are stored as ASCIIZ */ + char *chars; +#endif + /* + Leap seconds corrections descriptions, this array is shared by + all time zones who use leap seconds. + */ + LS_INFO *lsis; + /* + Starting points and descriptions of shifted my_time_t (my_time_t + offset) + ranges on which shifted my_time_t -> my_time_t mapping is linear or undefined. + Used for tm -> my_time_t conversion. + */ + my_time_t *revts; + REVT_INFO *revtis; + /* + Time type which is used for times smaller than first transition or if + there are no transitions at all. + */ + TRAN_TYPE_INFO *fallback_tti; + +} TIME_ZONE_INFO; + + +static my_bool prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage); + + +#if defined(TZINFO2SQL) || defined(TESTTIME) + +/* + Load time zone description from zoneinfo (TZinfo) file. + + SYNOPSIS + tz_load() + name - path to zoneinfo file + sp - TIME_ZONE_INFO structure to fill + + RETURN VALUES + 0 - Ok + 1 - Error +*/ +static my_bool +tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage) +{ + char *p; + int i; + FILE *file; + + if (!(file= my_fopen(name, O_RDONLY|O_BINARY, MYF(MY_WME)))) + return 1; + { + struct tzhead *tzhp; + union + { + struct tzhead tzhead; + char buf[sizeof(struct tzhead) + sizeof(my_time_t) * TZ_MAX_TIMES + + TZ_MAX_TIMES + sizeof(TRAN_TYPE_INFO) * TZ_MAX_TYPES + +#ifdef ABBR_ARE_USED + max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) + +#endif + sizeof(LS_INFO) * TZ_MAX_LEAPS]; + } u; + int ttisstdcnt; + int ttisgmtcnt; + char *tzinfo_buf; + + i= my_fread(file, u.buf, sizeof(u.buf), MYF(MY_WME)); + + if (my_fclose(file, MYF(MY_WME)) != 0) + return 1; + + if (i < (int)sizeof(struct tzhead)) + return 1; + + ttisstdcnt= int4net(u.tzhead.tzh_ttisgmtcnt); + ttisgmtcnt= int4net(u.tzhead.tzh_ttisstdcnt); + sp->leapcnt= int4net(u.tzhead.tzh_leapcnt); + sp->timecnt= int4net(u.tzhead.tzh_timecnt); + sp->typecnt= int4net(u.tzhead.tzh_typecnt); + sp->charcnt= int4net(u.tzhead.tzh_charcnt); + p= u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt; + if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || + sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || + sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || + sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || + (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || + (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) + return 1; + if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */ + sp->timecnt + /* types */ + sp->typecnt * (4 + 2) + /* ttinfos */ + sp->charcnt + /* chars */ + sp->leapcnt * (4 + 4) + /* lsinfos */ + ttisstdcnt + /* ttisstds */ + ttisgmtcnt) /* ttisgmts */ + return 1; + + if (!(tzinfo_buf= (char *)alloc_root(storage, + ALIGN_SIZE(sp->timecnt * + sizeof(my_time_t)) + + ALIGN_SIZE(sp->timecnt) + + ALIGN_SIZE(sp->typecnt * + sizeof(TRAN_TYPE_INFO)) + +#ifdef ABBR_ARE_USED + ALIGN_SIZE(sp->charcnt) + +#endif + sp->leapcnt * sizeof(LS_INFO)))) + return 1; + + sp->ats= (my_time_t *)tzinfo_buf; + tzinfo_buf+= ALIGN_SIZE(sp->timecnt * sizeof(my_time_t)); + sp->types= (unsigned char *)tzinfo_buf; + tzinfo_buf+= ALIGN_SIZE(sp->timecnt); + sp->ttis= (TRAN_TYPE_INFO *)tzinfo_buf; + tzinfo_buf+= ALIGN_SIZE(sp->typecnt * sizeof(TRAN_TYPE_INFO)); +#ifdef ABBR_ARE_USED + sp->chars= tzinfo_buf; + tzinfo_buf+= ALIGN_SIZE(sp->charcnt); +#endif + sp->lsis= (LS_INFO *)tzinfo_buf; + + for (i= 0; i < sp->timecnt; i++, p+= 4) + sp->ats[i]= int4net(p); + + for (i= 0; i < sp->timecnt; i++) + { + sp->types[i]= (unsigned char) *p++; + if (sp->types[i] >= sp->typecnt) + return 1; + } + for (i= 0; i < sp->typecnt; i++) + { + TRAN_TYPE_INFO * ttisp; + + ttisp= &sp->ttis[i]; + ttisp->tt_gmtoff= int4net(p); + p+= 4; + ttisp->tt_isdst= (unsigned char) *p++; + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) + return 1; + ttisp->tt_abbrind= (unsigned char) *p++; + if (ttisp->tt_abbrind < 0 || + ttisp->tt_abbrind > sp->charcnt) + return 1; + } + for (i= 0; i < sp->charcnt; i++) + sp->chars[i]= *p++; + sp->chars[i]= '\0'; /* ensure '\0' at end */ + for (i= 0; i < sp->leapcnt; i++) + { + LS_INFO *lsisp; + + lsisp= &sp->lsis[i]; + lsisp->ls_trans= int4net(p); + p+= 4; + lsisp->ls_corr= int4net(p); + p+= 4; + } + /* + Since we don't support POSIX style TZ definitions in variables we + don't read further like glibc or elsie code. + */ + } + + return prepare_tz_info(sp, storage); +} +#endif /* defined(TZINFO2SQL) || defined(TESTTIME) */ + + +/* + Finish preparation of time zone description for use in TIME_to_gmt_sec() + and gmt_sec_to_TIME() functions. + + SYNOPSIS + prepare_tz_info() + sp - pointer to time zone description + storage - pointer to MEM_ROOT where arrays for map allocated + + DESCRIPTION + First task of this function is to find fallback time type which will + be used if there are no transitions or we have moment in time before + any transitions. + Second task is to build "shifted my_time_t" -> my_time_t map used in + TIME -> my_time_t conversion. + Note: See description of TIME_to_gmt_sec() function first. + In order to perform TIME -> my_time_t conversion we need to build table + which defines "shifted by tz offset and leap seconds my_time_t" -> + my_time_t function wich is almost the same (except ranges of ambiguity) + as reverse function to piecewise linear function used for my_time_t -> + "shifted my_time_t" conversion and which is also specified as table in + zoneinfo file or in our db (It is specified as start of time type ranges + and time type offsets). So basic idea is very simple - let us iterate + through my_time_t space from one point of discontinuity of my_time_t -> + "shifted my_time_t" function to another and build our approximation of + reverse function. (Actually we iterate through ranges on which + my_time_t -> "shifted my_time_t" is linear function). + + RETURN VALUES + 0 Ok + 1 Error +*/ +static my_bool +prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage) +{ + my_time_t cur_t= MY_TIME_T_MIN; + my_time_t cur_l, end_t, end_l; + my_time_t cur_max_seen_l= MY_TIME_T_MIN; + long cur_offset, cur_corr, cur_off_and_corr; + int next_trans_idx, next_leap_idx; + int i; + /* + Temporary arrays where we will store tables. Needed because + we don't know table sizes ahead. (Well we can estimate their + upper bound but this will take extra space.) + */ + my_time_t revts[TZ_MAX_REV_RANGES]; + REVT_INFO revtis[TZ_MAX_REV_RANGES]; + + LINT_INIT(end_l); + + /* + Let us setup fallback time type which will be used if we have not any + transitions or if we have moment of time before first transition. + We will find first non-DST local time type and use it (or use first + local time type if all of them are DST types). + */ + for (i= 0; i < sp->typecnt && sp->ttis[i].tt_isdst; i++) + /* no-op */ ; + if (i == sp->typecnt) + i= 0; + sp->fallback_tti= &(sp->ttis[i]); + + + /* + Let us build shifted my_time_t -> my_time_t map. + */ + sp->revcnt= 0; + + /* Let us find initial offset */ + if (sp->timecnt == 0 || cur_t < sp->ats[0]) + { + /* + If we have not any transitions or t is before first transition we are using + already found fallback time type which index is already in i. + */ + next_trans_idx= 0; + } + else + { + /* cur_t == sp->ats[0] so we found transition */ + i= sp->types[0]; + next_trans_idx= 1; + } + + cur_offset= sp->ttis[i].tt_gmtoff; + + + /* let us find leap correction... unprobable, but... */ + for (next_leap_idx= 0; next_leap_idx < sp->leapcnt && + cur_t >= sp->lsis[next_leap_idx].ls_trans; + ++next_leap_idx) + continue; + + if (next_leap_idx > 0) + cur_corr= sp->lsis[next_leap_idx - 1].ls_corr; + else + cur_corr= 0; + + /* Iterate trough t space */ + while (sp->revcnt < TZ_MAX_REV_RANGES - 1) + { + cur_off_and_corr= cur_offset - cur_corr; + + /* + We assuming that cur_t could be only overflowed downwards, + we also assume that end_t won't be overflowed in this case. + */ + if (cur_off_and_corr < 0 && + cur_t < MY_TIME_T_MIN - cur_off_and_corr) + cur_t= MY_TIME_T_MIN - cur_off_and_corr; + + cur_l= cur_t + cur_off_and_corr; + + /* + Let us choose end_t as point before next time type change or leap + second correction. + */ + end_t= min((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1: + MY_TIME_T_MAX, + (next_leap_idx < sp->leapcnt) ? + sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX); + /* + again assuming that end_t can be overlowed only in positive side + we also assume that end_t won't be overflowed in this case. + */ + if (cur_off_and_corr > 0 && + end_t > MY_TIME_T_MAX - cur_off_and_corr) + end_t= MY_TIME_T_MAX - cur_off_and_corr; + + end_l= end_t + cur_off_and_corr; + + + if (end_l > cur_max_seen_l) + { + /* We want special handling in the case of first range */ + if (cur_max_seen_l == MY_TIME_T_MIN) + { + revts[sp->revcnt]= cur_l; + revtis[sp->revcnt].rt_offset= cur_off_and_corr; + revtis[sp->revcnt].rt_type= 0; + sp->revcnt++; + cur_max_seen_l= end_l; + } + else + { + if (cur_l > cur_max_seen_l + 1) + { + /* We have a spring time-gap and we are not at the first range */ + revts[sp->revcnt]= cur_max_seen_l + 1; + revtis[sp->revcnt].rt_offset= revtis[sp->revcnt-1].rt_offset; + revtis[sp->revcnt].rt_type= 1; + sp->revcnt++; + if (sp->revcnt == TZ_MAX_TIMES + TZ_MAX_LEAPS + 1) + break; /* That was too much */ + cur_max_seen_l= cur_l - 1; + } + + /* Assume here end_l > cur_max_seen_l (because end_l>=cur_l) */ + + revts[sp->revcnt]= cur_max_seen_l + 1; + revtis[sp->revcnt].rt_offset= cur_off_and_corr; + revtis[sp->revcnt].rt_type= 0; + sp->revcnt++; + cur_max_seen_l= end_l; + } + } + + if (end_t == MY_TIME_T_MAX || + (cur_off_and_corr > 0) && + (end_t >= MY_TIME_T_MAX - cur_off_and_corr)) + /* end of t space */ + break; + + cur_t= end_t + 1; + + /* + Let us find new offset and correction. Because of our choice of end_t + cur_t can only be point where new time type starts or/and leap + correction is performed. + */ + if (sp->timecnt != 0 && cur_t >= sp->ats[0]) /* else reuse old offset */ + if (next_trans_idx < sp->timecnt && + cur_t == sp->ats[next_trans_idx]) + { + /* We are at offset point */ + cur_offset= sp->ttis[sp->types[next_trans_idx]].tt_gmtoff; + ++next_trans_idx; + } + + if (next_leap_idx < sp->leapcnt && + cur_t == sp->lsis[next_leap_idx].ls_trans) + { + /* we are at leap point */ + cur_corr= sp->lsis[next_leap_idx].ls_corr; + ++next_leap_idx; + } + } + + /* check if we have had enough space */ + if (sp->revcnt == TZ_MAX_REV_RANGES - 1) + return 1; + + /* set maximum end_l as finisher */ + revts[sp->revcnt]= end_l; + + /* Allocate arrays of proper size in sp and copy result there */ + if (!(sp->revts= (my_time_t *)alloc_root(storage, + sizeof(my_time_t) * (sp->revcnt + 1))) || + !(sp->revtis= (REVT_INFO *)alloc_root(storage, + sizeof(REVT_INFO) * sp->revcnt))) + return 1; + + memcpy(sp->revts, revts, sizeof(my_time_t) * (sp->revcnt + 1)); + memcpy(sp->revtis, revtis, sizeof(REVT_INFO) * sp->revcnt); + + return 0; +} + + +static const uint mon_lengths[2][MONS_PER_YEAR]= +{ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const uint mon_starts[2][MONS_PER_YEAR]= +{ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } +}; + +static const uint year_lengths[2]= +{ + DAYS_PER_NYEAR, DAYS_PER_LYEAR +}; + +#define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400) + + +/* + Converts time from my_time_t representation (seconds in UTC since Epoch) + to broken down representation using given local time zone offset. + + SYNOPSIS + sec_to_TIME() + tmp - pointer to structure for broken down representation + t - my_time_t value to be converted + offset - local time zone offset + + DESCRIPTION + Convert my_time_t with offset to TIME struct. Differs from timesub + (from elsie code) because doesn't contain any leap correction and + TM_GMTOFF and is_dst setting and contains some MySQL specific + initialization. Funny but with removing of these we almost have + glibc's offtime function. +*/ +static void +sec_to_TIME(TIME * tmp, my_time_t t, long offset) +{ + long days; + long rem; + int y; + int yleap; + const uint *ip; + + days= t / SECS_PER_DAY; + rem= t % SECS_PER_DAY; + + /* + We do this as separate step after dividing t, because this + allows us handle times near my_time_t bounds without overflows. + */ + rem+= offset; + while (rem < 0) + { + rem+= SECS_PER_DAY; + days--; + } + while (rem >= SECS_PER_DAY) + { + rem -= SECS_PER_DAY; + days++; + } + tmp->hour= (uint)(rem / SECS_PER_HOUR); + rem= rem % SECS_PER_HOUR; + tmp->minute= (uint)(rem / SECS_PER_MIN); + /* + A positive leap second requires a special + representation. This uses "... ??:59:60" et seq. + */ + tmp->second= (uint)(rem % SECS_PER_MIN); + + y= EPOCH_YEAR; + while (days < 0 || days >= (long)year_lengths[yleap= isleap(y)]) + { + int newy; + + newy= y + days / DAYS_PER_NYEAR; + if (days < 0) + newy--; + days-= (newy - y) * DAYS_PER_NYEAR + + LEAPS_THRU_END_OF(newy - 1) - + LEAPS_THRU_END_OF(y - 1); + y= newy; + } + tmp->year= y; + + ip= mon_lengths[yleap]; + for (tmp->month= 0; days >= (long) ip[tmp->month]; tmp->month++) + days= days - (long) ip[tmp->month]; + tmp->month++; + tmp->day= (uint)(days + 1); + + /* filling MySQL specific TIME members */ + tmp->neg= 0; tmp->second_part= 0; + tmp->time_type= TIMESTAMP_DATETIME; +} + + +/* + Find time range wich contains given my_time_t value + + SYNOPSIS + find_time_range() + t - my_time_t value for which we looking for range + range_boundaries - sorted array of range starts. + higher_bound - number of ranges + + DESCRIPTION + Performs binary search for range which contains given my_time_t value. + It has sense if number of ranges is greater than zero and my_time_t value + is greater or equal than beginning of first range. It also assumes that + t belongs to some range specified or end of last is MY_TIME_T_MAX. + + With this localtime_r on real data may takes less time than with linear + search (I've seen 30% speed up). + + RETURN VALUE + Index of range to which t belongs +*/ +static uint +find_time_range(my_time_t t, const my_time_t *range_boundaries, + uint higher_bound) +{ + uint i, lower_bound= 0; + + /* + Function will work without this assertion but result would be meaningless. + */ + DBUG_ASSERT(higher_bound > 0 && t >= range_boundaries[0]); + + /* + Do binary search for minimal interval which contain t. We preserve: + range_boundaries[lower_bound] <= t < range_boundaries[higher_bound] + invariant and decrease this higher_bound - lower_bound gap twice + times on each step. + */ + + while (higher_bound - lower_bound > 1) + { + i= (lower_bound + higher_bound) >> 1; + if (range_boundaries[i] <= t) + lower_bound= i; + else + higher_bound= i; + } + return lower_bound; +} + +/* + Find local time transition for given my_time_t. + + SYNOPSIS + find_transition_type() + t - my_time_t value to be converted + sp - pointer to struct with time zone description + + RETURN VALUE + Pointer to structure in time zone description describing + local time type for given my_time_t. +*/ +static +const TRAN_TYPE_INFO * +find_transition_type(my_time_t t, const TIME_ZONE_INFO *sp) +{ + if (unlikely(sp->timecnt == 0 || t < sp->ats[0])) + { + /* + If we have not any transitions or t is before first transition let + us use fallback time type. + */ + return sp->fallback_tti; + } + + /* + Do binary search for minimal interval between transitions which + contain t. With this localtime_r on real data may takes less + time than with linear search (I've seen 30% speed up). + */ + return &(sp->ttis[sp->types[find_time_range(t, sp->ats, sp->timecnt)]]); +} + + +/* + Converts time in my_time_t representation (seconds in UTC since Epoch) to + broken down TIME representation in local time zone. + + SYNOPSIS + gmt_sec_to_TIME() + tmp - pointer to structure for broken down represenatation + sec_in_utc - my_time_t value to be converted + sp - pointer to struct with time zone description + + TODO + We can improve this function by creating joined array of transitions and + leap corrections. This will require adding extra field to TRAN_TYPE_INFO + for storing number of "extra" seconds to minute occured due to correction + (60th and 61st second, look how we calculate them as "hit" in this + function). + Under realistic assumptions about frequency of transitions the same array + can be used fot TIME -> my_time_t conversion. For this we need to + implement tweaked binary search which will take into account that some + TIME has two matching my_time_t ranges and some of them have none. +*/ +static void +gmt_sec_to_TIME(TIME *tmp, my_time_t sec_in_utc, const TIME_ZONE_INFO *sp) +{ + const TRAN_TYPE_INFO *ttisp; + const LS_INFO *lp; + long corr= 0; + int hit= 0; + int i; + + /* + Find proper transition (and its local time type) for our sec_in_utc value. + Funny but again by separating this step in function we receive code + which very close to glibc's code. No wonder since they obviously use + the same base and all steps are sensible. + */ + ttisp= find_transition_type(sec_in_utc, sp); + + /* + Let us find leap correction for our sec_in_utc value and number of extra + secs to add to this minute. + This loop is rarely used because most users will use time zones without + leap seconds, and even in case when we have such time zone there won't + be many iterations (we have about 22 corrections at this moment (2004)). + */ + for ( i= sp->leapcnt; i-- > 0; ) + { + lp= &sp->lsis[i]; + if (sec_in_utc >= lp->ls_trans) + { + if (sec_in_utc == lp->ls_trans) + { + hit= ((i == 0 && lp->ls_corr > 0) || + lp->ls_corr > sp->lsis[i - 1].ls_corr); + if (hit) + { + while (i > 0 && + sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 && + sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1) + { + hit++; + i--; + } + } + } + corr= lp->ls_corr; + break; + } + } + + sec_to_TIME(tmp, sec_in_utc, ttisp->tt_gmtoff - corr); + + tmp->second+= hit; +} + + +/* + Converts local time in broken down representation to local + time zone analog of my_time_t represenation. + + SYNOPSIS + sec_since_epoch() + year, mon, mday, hour, min, sec - broken down representation. + + DESCRIPTION + Converts time in broken down representation to my_time_t representation + ignoring time zone. Note that we cannot convert back some valid _local_ + times near ends of my_time_t range because of my_time_t overflow. But we + ignore this fact now since MySQL will never pass such argument. + + RETURN VALUE + Seconds since epoch time representation. +*/ +static my_time_t +sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec) +{ +#ifndef WE_WANT_TO_HANDLE_UNORMALIZED_DATES + /* + It turns out that only whenever month is normalized or unnormalized + plays role. + */ + DBUG_ASSERT(mon > 0 && mon < 13); + long days= year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR + + LEAPS_THRU_END_OF(year - 1) - + LEAPS_THRU_END_OF(EPOCH_YEAR - 1); + days+= mon_starts[isleap(year)][mon - 1]; +#else + long norm_month= (mon - 1) % MONS_PER_YEAR; + long a_year= year + (mon - 1)/MONS_PER_YEAR - (int)(norm_month < 0); + long days= a_year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR + + LEAPS_THRU_END_OF(a_year - 1) - + LEAPS_THRU_END_OF(EPOCH_YEAR - 1); + days+= mon_starts[isleap(a_year)] + [norm_month + (norm_month < 0 ? MONS_PER_YEAR : 0)]; +#endif + days+= mday - 1; + + return ((days * HOURS_PER_DAY + hour) * MINS_PER_HOUR + min) * + SECS_PER_MIN + sec; +} + + +/* + Converts local time in broken down TIME representation to my_time_t + representation. + + SYNOPSIS + TIME_to_gmt_sec() + t - pointer to structure for broken down represenatation + sp - pointer to struct with time zone description + in_dst_time_gap - pointer to bool which is set to true if datetime + value passed doesn't really exist (i.e. falls into + spring time-gap) and is not touched otherwise. + + DESCRIPTION + This is mktime analog for MySQL. It is essentially different + from mktime (or hypotetical my_mktime) because: + - It has no idea about tm_isdst member so if it + has two answers it will give the smaller one + - If we are in spring time gap then it will return + beginning of the gap + - It can give wrong results near the ends of my_time_t due to + overflows, but we are safe since in MySQL we will never + call this function for such dates (its restriction for year + between 1970 and 2038 gives us several days of reserve). + - By default it doesn't support un-normalized input. But if + sec_since_epoch() function supports un-normalized dates + then this function should handle un-normalized input right, + altough it won't normalize structure TIME. + + Traditional approach to problem of conversion from broken down + representation to time_t is iterative. Both elsie's and glibc + implementation try to guess what time_t value should correspond to + this broken-down value. They perform localtime_r function on their + guessed value and then calculate the difference and try to improve + their guess. Elsie's code guesses time_t value in bit by bit manner, + Glibc's code tries to add difference between broken-down value + corresponding to guess and target broken-down value to current guess. + It also uses caching of last found correction... So Glibc's approach + is essentially faster but introduces some undetermenism (in case if + is_dst member of broken-down representation (tm struct) is not known + and we have two possible answers). + + We use completely different approach. It is better since it is both + faster than iterative implementations and fully determenistic. If you + look at my_time_t to TIME conversion then you'll find that it consist + of two steps: + The first is calculating shifted my_time_t value and the second - TIME + calculation from shifted my_time_t value (well it is a bit simplified + picture). The part in which we are interested in is my_time_t -> shifted + my_time_t conversion. It is piecewise linear function which is defined + by combination of transition times as break points and times offset + as changing function parameter. The possible inverse function for this + converison would be ambiguos but with MySQL's restrictions we can use + some function which is the same as inverse function on unambigiuos + ranges and coincides with one of branches of inverse function in + other ranges. Thus we just need to build table which will determine + this shifted my_time_t -> my_time_t conversion similar to existing + (my_time_t -> shifted my_time_t table). We do this in + prepare_tz_info function. + + TODO + If we can even more improve this function. For doing this we will need to + build joined map of transitions and leap corrections for gmt_sec_to_TIME() + function (similar to revts/revtis). Under realistic assumptions about + frequency of transitions we can use the same array for TIME_to_gmt_sec(). + We need to implement special version of binary search for this. Such step + will be beneficial to CPU cache since we will decrease data-set used for + conversion twice. + + RETURN VALUE + Seconds in UTC since Epoch. + 0 in case of error. +*/ +static my_time_t +TIME_to_gmt_sec(const TIME *t, const TIME_ZONE_INFO *sp, bool *in_dst_time_gap) +{ + my_time_t local_t; + uint saved_seconds; + uint i; + + DBUG_ENTER("TIME_to_gmt_sec"); + + /* We need this for correct leap seconds handling */ + if (t->second < SECS_PER_MIN) + saved_seconds= 0; + else + saved_seconds= t->second; + + /* + NOTE If we want to convert full my_time_t range without MySQL + restrictions we should catch overflow here somehow. + */ + + local_t= sec_since_epoch(t->year, t->month, t->day, + t->hour, t->minute, + saved_seconds ? 0 : t->second); + + /* We have at least one range */ + DBUG_ASSERT(sp->revcnt >= 1); + + if (local_t < sp->revts[0] || local_t > sp->revts[sp->revcnt]) + { + /* + This means that source time can't be represented as my_time_t due to + limited my_time_t range. + */ + DBUG_RETURN(0); + } + + /* binary search for our range */ + i= find_time_range(local_t, sp->revts, sp->revcnt); + + if (sp->revtis[i].rt_type) + { + /* + Oops! We are in spring time gap. + May be we should return error here? + Now we are returning my_time_t value corresponding to the + beginning of the gap. + */ + *in_dst_time_gap= 1; + DBUG_RETURN(sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds); + } + else + DBUG_RETURN(local_t - sp->revtis[i].rt_offset + saved_seconds); +} + + +/* + End of elsie derived code. +*/ + + +#if !defined(TESTTIME) && !defined(TZINFO2SQL) + +/* + String with names of SYSTEM time zone. +*/ +static const String tz_SYSTEM_name("SYSTEM", 6, &my_charset_latin1); + + +/* + Instance of this class represents local time zone used on this system + (specified by TZ environment variable or via any other system mechanism). + It uses system functions (localtime_r, my_system_gmt_sec) for conversion + and is always available. Because of this it is used by default - if there + were no explicit time zone specified. On the other hand because of this + conversion methods provided by this class is significantly slower and + possibly less multi-threaded-friendly than corresponding Time_zone_db + methods so the latter should be preffered there it is possible. +*/ +class Time_zone_system : public Time_zone +{ +public: + virtual my_time_t TIME_to_gmt_sec(const TIME *t, + bool *in_dst_time_gap) const; + virtual void gmt_sec_to_TIME(TIME *tmp, my_time_t t) const; + virtual const String * get_name() const; +}; + + +/* + Converts local time in system time zone in TIME representation + to its my_time_t representation. + + SYNOPSIS + TIME_to_gmt_sec() + t - pointer to TIME structure with local time in + broken-down representation. + in_dst_time_gap - pointer to bool which is set to true if datetime + value passed doesn't really exist (i.e. falls into + spring time-gap) and is not touched otherwise. + + DESCRIPTION + This method uses system function (localtime_r()) for conversion + local time in system time zone in TIME structure to its my_time_t + representation. Unlike the same function for Time_zone_db class + it it won't handle unnormalized input properly. Still it will + return lowest possible my_time_t in case of ambiguity or if we + provide time corresponding to the time-gap. + + You should call init_time() function before using this function. + + RETURN VALUE + Corresponding my_time_t value or 0 in case of error +*/ +my_time_t +Time_zone_system::TIME_to_gmt_sec(const TIME *t, bool *in_dst_time_gap) const +{ + long not_used; + return my_system_gmt_sec(t, ¬_used, in_dst_time_gap); +} + + +/* + Converts time from UTC seconds since Epoch (my_time_t) representation + to system local time zone broken-down representation. + + SYNOPSIS + gmt_sec_to_TIME() + tmp - pointer to TIME structure to fill-in + t - my_time_t value to be converted + + NOTE + We assume that value passed to this function will fit into time_t range + supported by localtime_r. This conversion is putting restriction on + TIMESTAMP range in MySQL. If we can get rid of SYSTEM time zone at least + for interaction with client then we can extend TIMESTAMP range down to + the 1902 easily. +*/ +void +Time_zone_system::gmt_sec_to_TIME(TIME *tmp, my_time_t t) const +{ + struct tm tmp_tm; + time_t tmp_t= (time_t)t; + + localtime_r(&tmp_t, &tmp_tm); + localtime_to_TIME(tmp, &tmp_tm); + tmp->time_type= TIMESTAMP_DATETIME; +} + + +/* + Get name of time zone + + SYNOPSIS + get_name() + + RETURN VALUE + Name of time zone as String +*/ +const String * +Time_zone_system::get_name() const +{ + return &tz_SYSTEM_name; +} + + +/* + Instance of this class represents UTC time zone. It uses system gmtime_r + function for conversions and is always available. It is used only for + my_time_t -> TIME conversions in various UTC_... functions, it is not + intended for TIME -> my_time_t conversions and shouldn't be exposed to user. +*/ +class Time_zone_utc : public Time_zone +{ +public: + virtual my_time_t TIME_to_gmt_sec(const TIME *t, + bool *in_dst_time_gap) const; + virtual void gmt_sec_to_TIME(TIME *tmp, my_time_t t) const; + virtual const String * get_name() const; +}; + + +/* + Convert UTC time from TIME representation to its my_time_t representation. + + SYNOPSIS + TIME_to_gmt_sec() + t - pointer to TIME structure with local time + in broken-down representation. + in_dst_time_gap - pointer to bool which is set to true if datetime + value passed doesn't really exist (i.e. falls into + spring time-gap) and is not touched otherwise. + + DESCRIPTION + Since Time_zone_utc is used only internally for my_time_t -> TIME + conversions, this function of Time_zone interface is not implemented for + this class and should not be called. + + RETURN VALUE + 0 +*/ +my_time_t +Time_zone_utc::TIME_to_gmt_sec(const TIME *t, bool *in_dst_time_gap) const +{ + /* Should be never called */ + DBUG_ASSERT(0); + return 0; +} + + +/* + Converts time from UTC seconds since Epoch (my_time_t) representation + to broken-down representation (also in UTC). + + SYNOPSIS + gmt_sec_to_TIME() + tmp - pointer to TIME structure to fill-in + t - my_time_t value to be converted + + NOTE + See note for apropriate Time_zone_system method. +*/ +void +Time_zone_utc::gmt_sec_to_TIME(TIME *tmp, my_time_t t) const +{ + struct tm tmp_tm; + time_t tmp_t= (time_t)t; + gmtime_r(&tmp_t, &tmp_tm); + localtime_to_TIME(tmp, &tmp_tm); + tmp->time_type= TIMESTAMP_DATETIME; +} + + +/* + Get name of time zone + + SYNOPSIS + get_name() + + DESCRIPTION + Since Time_zone_utc is used only internally by SQL's UTC_* functions it + is not accessible directly, and hence this function of Time_zone + interface is not implemented for this class and should not be called. + + RETURN VALUE + 0 +*/ +const String * +Time_zone_utc::get_name() const +{ + /* Should be never called */ + DBUG_ASSERT(0); + return 0; +} + + +/* + Instance of this class represents some time zone which is + described in mysql.time_zone family of tables. +*/ +class Time_zone_db : public Time_zone +{ +public: + Time_zone_db(TIME_ZONE_INFO *tz_info_arg, const String * tz_name_arg); + virtual my_time_t TIME_to_gmt_sec(const TIME *t, + bool *in_dst_time_gap) const; + virtual void gmt_sec_to_TIME(TIME *tmp, my_time_t t) const; + virtual const String * get_name() const; +private: + TIME_ZONE_INFO *tz_info; + const String *tz_name; +}; + + +/* + Initializes object representing time zone described by mysql.time_zone + tables. + + SYNOPSIS + Time_zone_db() + tz_info_arg - pointer to TIME_ZONE_INFO structure which is filled + according to db or other time zone description + (for example by my_tz_init()). + Several Time_zone_db instances can share one + TIME_ZONE_INFO structure. + tz_name_arg - name of time zone. +*/ +Time_zone_db::Time_zone_db(TIME_ZONE_INFO *tz_info_arg, + const String *tz_name_arg): + tz_info(tz_info_arg), tz_name(tz_name_arg) +{ +} + + +/* + Converts local time in time zone described from TIME + representation to its my_time_t representation. + + SYNOPSIS + TIME_to_gmt_sec() + t - pointer to TIME structure with local time + in broken-down representation. + in_dst_time_gap - pointer to bool which is set to true if datetime + value passed doesn't really exist (i.e. falls into + spring time-gap) and is not touched otherwise. + + DESCRIPTION + Please see ::TIME_to_gmt_sec for function description and + parameter restrictions. + + RETURN VALUE + Corresponding my_time_t value or 0 in case of error +*/ +my_time_t +Time_zone_db::TIME_to_gmt_sec(const TIME *t, bool *in_dst_time_gap) const +{ + return ::TIME_to_gmt_sec(t, tz_info, in_dst_time_gap); +} + + +/* + Converts time from UTC seconds since Epoch (my_time_t) representation + to local time zone described in broken-down representation. + + SYNOPSIS + gmt_sec_to_TIME() + tmp - pointer to TIME structure to fill-in + t - my_time_t value to be converted +*/ +void +Time_zone_db::gmt_sec_to_TIME(TIME *tmp, my_time_t t) const +{ + ::gmt_sec_to_TIME(tmp, t, tz_info); +} + + +/* + Get name of time zone + + SYNOPSIS + get_name() + + RETURN VALUE + Name of time zone as ASCIIZ-string +*/ +const String * +Time_zone_db::get_name() const +{ + return tz_name; +} + + +/* + Instance of this class represents time zone which + was specified as offset from UTC. +*/ +class Time_zone_offset : public Time_zone +{ +public: + Time_zone_offset(long tz_offset_arg); + virtual my_time_t TIME_to_gmt_sec(const TIME *t, + bool *in_dst_time_gap) const; + virtual void gmt_sec_to_TIME(TIME *tmp, my_time_t t) const; + virtual const String * get_name() const; + /* + This have to be public because we want to be able to access it from + my_offset_tzs_get_key() function + */ + long offset; +private: + /* Extra reserve because of snprintf */ + char name_buff[7+16]; + String name; +}; + + +/* + Initializes object representing time zone described by its offset from UTC. + + SYNOPSIS + Time_zone_offset() + tz_offset_arg - offset from UTC in seconds. + Positive for direction to east. +*/ +Time_zone_offset::Time_zone_offset(long tz_offset_arg): + offset(tz_offset_arg) +{ + uint hours= abs(offset / SECS_PER_HOUR); + uint minutes= abs(offset % SECS_PER_HOUR / SECS_PER_MIN); + ulong length= my_snprintf(name_buff, sizeof(name_buff), "%s%02d:%02d", + (offset>=0) ? "+" : "-", hours, minutes); + name.set(name_buff, length, &my_charset_latin1); +} + + +/* + Converts local time in time zone described as offset from UTC + from TIME representation to its my_time_t representation. + + SYNOPSIS + TIME_to_gmt_sec() + t - pointer to TIME structure with local time + in broken-down representation. + in_dst_time_gap - pointer to bool which should be set to true if + datetime value passed doesn't really exist + (i.e. falls into spring time-gap) and is not + touched otherwise. + It is not really used in this class. + + RETURN VALUE + Corresponding my_time_t value or 0 in case of error +*/ +my_time_t +Time_zone_offset::TIME_to_gmt_sec(const TIME *t, bool *in_dst_time_gap) const +{ + return sec_since_epoch(t->year, t->month, t->day, + t->hour, t->minute, t->second) - + offset; +} + + +/* + Converts time from UTC seconds since Epoch (my_time_t) representation + to local time zone described as offset from UTC and in broken-down + representation. + + SYNOPSIS + gmt_sec_to_TIME() + tmp - pointer to TIME structure to fill-in + t - my_time_t value to be converted +*/ +void +Time_zone_offset::gmt_sec_to_TIME(TIME *tmp, my_time_t t) const +{ + sec_to_TIME(tmp, t, offset); +} + + +/* + Get name of time zone + + SYNOPSIS + get_name() + + RETURN VALUE + Name of time zone as pointer to String object +*/ +const String * +Time_zone_offset::get_name() const +{ + return &name; +} + + +static Time_zone_utc tz_UTC; +static Time_zone_system tz_SYSTEM; + +Time_zone *my_tz_UTC= &tz_UTC; +Time_zone *my_tz_SYSTEM= &tz_SYSTEM; + +static HASH tz_names; +static HASH offset_tzs; +static MEM_ROOT tz_storage; + +/* + These mutex protects offset_tzs and tz_storage. + These protection needed only when we are trying to set + time zone which is specified as offset, and searching for existing + time zone in offset_tzs or creating if it didn't existed before in + tz_storage. So contention is low. +*/ +static pthread_mutex_t tz_LOCK; + +/* + This two static variables are inteded for holding info about leap seconds + shared by all time zones. +*/ +static uint tz_leapcnt= 0; +static LS_INFO *tz_lsis= 0; + + +typedef struct st_tz_names_entry: public Sql_alloc +{ + String name; + Time_zone *tz; +} TZ_NAMES_ENTRY; + + +/* + We are going to call both of these functions from C code so + they should obey C calling conventions. +*/ + +extern "C" byte* my_tz_names_get_key(TZ_NAMES_ENTRY *entry, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length= entry->name.length(); + return (byte*) entry->name.ptr(); +} + +extern "C" byte* my_offset_tzs_get_key(Time_zone_offset *entry, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length= sizeof(long); + return (byte*) &entry->offset; +} + + +/* + Initialize time zone support infrastructure. + + SYNOPSIS + my_tz_init() + thd - current thread object + default_tzname - default time zone or 0 if none. + bootstrap - indicates whenever we are in bootstrap mode + + DESCRIPTION + This function will init memory structures needed for time zone support, + it will register mandatory SYSTEM time zone in them. It will try to open + mysql.time_zone_leap_seconds table and and load information which further + will be shared among all time zones loaded. It will also try to load + information about default time zone. If system tables with time zone + descriptions don't exist it won't fail (unless default_tzname is time zone + from tables). If bootstrap parameter is true then this routine assumes that + we are in bootstrap mode and won't load time zone descriptions unless someone + specifies default time zone which is supposedly stored in those tables. + It'll also set default time zone if it is specified. + + RETURN VALUES + 0 - ok + 1 - Error +*/ +my_bool +my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) +{ + THD *thd; + TABLE_LIST tables; + TABLE *table; + TABLE *lock_ptr; + MYSQL_LOCK *lock; + TZ_NAMES_ENTRY *tmp_tzname; + my_bool return_val= 1; + int res; + uint not_used; + + DBUG_ENTER("my_tz_init"); + + /* + To be able to run this from boot, we allocate a temporary THD + */ + if (!(thd= new THD)) + DBUG_RETURN(1); + thd->store_globals(); + + /* Init all memory structures that require explicit destruction */ + if (hash_init(&tz_names, &my_charset_latin1, 20, + 0, 0, (hash_get_key)my_tz_names_get_key, 0, 0)) + { + sql_print_error("Fatal error: OOM while initializing time zones"); + goto end; + } + if (hash_init(&offset_tzs, &my_charset_latin1, 26, 0, 0, + (hash_get_key)my_offset_tzs_get_key, 0, 0)) + { + sql_print_error("Fatal error: OOM while initializing time zones"); + hash_free(&tz_names); + goto end; + } + init_alloc_root(&tz_storage, 32 * 1024, 0); + VOID(pthread_mutex_init(&tz_LOCK, MY_MUTEX_INIT_FAST)); + + /* Add 'SYSTEM' time zone to tz_names hash */ + if (!(tmp_tzname= new (&tz_storage) TZ_NAMES_ENTRY())) + { + sql_print_error("Fatal error: OOM while initializing time zones"); + goto end_with_cleanup; + } + tmp_tzname->name.set("SYSTEM", 6, &my_charset_latin1); + tmp_tzname->tz= my_tz_SYSTEM; + if (my_hash_insert(&tz_names, (const byte *)tmp_tzname)) + { + sql_print_error("Fatal error: OOM while initializing time zones"); + goto end_with_cleanup; + } + + if (bootstrap) + { + /* If we are in bootstrap mode we should not load time zone tables */ + return_val= 0; + goto end_with_setting_default_tz; + } + + /* + After this point all memory structures are inited and we even can live + without time zone description tables. Now try to load information about + leap seconds shared by all time zones. + */ + + thd->db= my_strdup("mysql",MYF(0)); + thd->db_length= 5; // Safety + bzero((char*) &tables,sizeof(tables)); + tables.alias= tables.real_name= (char*)"time_zone_leap_second"; + tables.lock_type= TL_READ; + tables.db= thd->db; + + if (open_tables(thd, &tables, ¬_used)) + { + sql_print_error("Warning: Can't open time zone table: %s " + "trying to live without them", thd->net.last_error); + /* We will try emulate that everything is ok */ + return_val= 0; + goto end_with_setting_default_tz; + } + + lock_ptr= tables.table; + if (!(lock= mysql_lock_tables(thd, &lock_ptr, 1))) + { + sql_print_error("Fatal error: Can't lock time zone table: %s", + thd->net.last_error); + goto end_with_cleanup; + } + + + /* + Now we are going to load leap seconds descriptions that are shared + between all time zones that use them. We are using index for getting + records in proper order. Since we share the same MEM_ROOT between + all time zones we just allocate enough memory for it first. + */ + if (!(tz_lsis= (LS_INFO*) alloc_root(&tz_storage, + sizeof(LS_INFO) * TZ_MAX_LEAPS))) + { + sql_print_error("Fatal error: Out of memory while loading " + "mysql.time_zone_leap_second table"); + goto end_with_unlock; + } + + table= tables.table; + table->file->index_init(0); + tz_leapcnt= 0; + + res= table->file->index_first(table->record[0]); + + while (!res) + { + if (tz_leapcnt + 1 > TZ_MAX_LEAPS) + { + sql_print_error("Fatal error: While loading mysql.time_zone_leap_second" + " table: too much leaps"); + table->file->index_end(); + goto end_with_unlock; + } + + tz_lsis[tz_leapcnt].ls_trans= (my_time_t)table->field[0]->val_int(); + tz_lsis[tz_leapcnt].ls_corr= (long)table->field[1]->val_int(); + + tz_leapcnt++; + + DBUG_PRINT("info", + ("time_zone_leap_second table: tz_leapcnt=%u tt_time=%lld offset=%ld", + tz_leapcnt, (longlong)tz_lsis[tz_leapcnt-1].ls_trans, + tz_lsis[tz_leapcnt-1].ls_corr)); + + res= table->file->index_next(table->record[0]); + } + + table->file->index_end(); + + if (res != HA_ERR_END_OF_FILE) + { + sql_print_error("Fatal error: Error while loading " + "mysql.time_zone_leap_second table"); + goto end_with_unlock; + } + + /* + Loading of info about leap seconds succeeded + */ + + return_val= 0; + + +end_with_unlock: + mysql_unlock_tables(thd, lock); + thd->version--; /* Force close to free memory */ + +end_with_setting_default_tz: + /* If not an error and have default time zone try to load it */ + if (!return_val && default_tzname) + { + String tzname(default_tzname, &my_charset_latin1); + if (!(global_system_variables.time_zone= my_tz_find(thd, &tzname))) + { + sql_print_error("Fatal error: Illegal or unknown default time zone '%s'", + default_tzname); + return_val= 1; + } + } + +end_with_cleanup: + + /* if there were error free time zone describing structs */ + if (return_val) + my_tz_free(); +end: + close_thread_tables(thd); + delete thd; + if (org_thd) + org_thd->store_globals(); /* purecov: inspected */ + else + { + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, 0); + my_pthread_setspecific_ptr(THR_MALLOC, 0); + } + DBUG_RETURN(return_val); +} + + +/* + Free resources used by time zone support infrastructure. + + SYNOPSIS + my_tz_free() +*/ +void my_tz_free() +{ + VOID(pthread_mutex_destroy(&tz_LOCK)); + hash_free(&offset_tzs); + hash_free(&tz_names); + free_root(&tz_storage, MYF(0)); +} + + +/* + Load time zone description from system tables. + + SYNOPSIS + tz_load_from_db() + thd - current thread object + tz_name - name of time zone that should be loaded. + + DESCRIPTION + This function will try to open system tables describing time zones + and to load information about time zone specified. It will also update + information in hash used for time zones lookup. + + RETURN VALUES + Returns pointer to newly created Time_zone object or 0 in case of error. + +*/ +static Time_zone* +tz_load_from_db(THD *thd, const String *tz_name) +{ + TABLE_LIST tables[4]; + TABLE *table= 0; + TABLE *lock_ptr[4]; + MYSQL_LOCK *lock; + char system_db_name[]= "mysql"; + char *db_save; + uint db_length_save; + TIME_ZONE_INFO *tz_info; + TZ_NAMES_ENTRY *tmp_tzname; + Time_zone *return_val= 0; + int res; + uint tzid, ttid; + bool uses_leap_seconds; + my_time_t ttime; + char buff[MAX_FIELD_WIDTH]; + String abbr(buff, sizeof(buff), &my_charset_latin1); + char *alloc_buff, *tz_name_buff; + /* + Temporary arrays that are used for loading of data for filling + TIME_ZONE_INFO structure + */ + my_time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + TRAN_TYPE_INFO ttis[TZ_MAX_TYPES]; +#ifdef ABBR_ARE_USED + char chars[max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))]; +#endif + uint not_used; + + DBUG_ENTER("tz_load_from_db"); + + + /* Prepare tz_info for loading also let us make copy of time zone name */ + if (!(alloc_buff= alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) + + tz_name->length() + 1))) + { + sql_print_error("Error: Out of memory while loading time zone " + "description"); + return 0; + } + tz_info= (TIME_ZONE_INFO *)alloc_buff; + bzero(tz_info, sizeof(TIME_ZONE_INFO)); + tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO); + /* + By writing zero to the end we guarantee that we can call ptr() + instead of c_ptr() for time zone name. + */ + strmake(tz_name_buff, tz_name->ptr(), tz_name->length()); + + /* + Open and lock time zone description tables + */ + db_save= thd->db; + db_length_save= thd->db_length; + thd->db= system_db_name; + thd->db_length= 5; + + bzero((char*) &tables,sizeof(tables)); + tables[0].alias= tables[0].real_name= (char*)"time_zone_name"; + tables[1].alias= tables[1].real_name= (char*)"time_zone"; + tables[2].alias= tables[2].real_name= (char*)"time_zone_transition"; + tables[3].alias= tables[3].real_name= (char*)"time_zone_transition_type"; + tables[0].next= tables+1; + tables[1].next= tables+2; + tables[2].next= tables+3; + tables[0].lock_type= tables[1].lock_type= tables[2].lock_type= + tables[3].lock_type= TL_READ; + tables[0].db= tables[1].db= tables[2].db= tables[3].db= thd->db; + if (open_tables(thd, tables, ¬_used)) + { + sql_print_error("Error: Can't open time zone tables: %s", + thd->net.last_error); + goto end; + } + + lock_ptr[0]= tables[0].table; + lock_ptr[1]= tables[1].table; + lock_ptr[2]= tables[2].table; + lock_ptr[3]= tables[3].table; + if (!(lock= mysql_lock_tables(thd, lock_ptr, 4))) + { + sql_print_error("Error: Can't lock time zone tables: %s", + thd->net.last_error); + goto end_with_close; + } + + /* + Let us find out time zone id by its name (there is only one index + and it is specifically for this purpose). + */ + table= tables[0].table; + + table->field[0]->store(tz_name->ptr(), tz_name->length(), &my_charset_latin1); + table->file->index_init(0); + + if (table->file->index_read(table->record[0], (byte*)table->field[0]->ptr, + 0, HA_READ_KEY_EXACT)) + { + sql_print_error("Error: Can't find description of time zone."); + goto end_with_unlock; + } + + tzid= table->field[1]->val_int(); + + table->file->index_end(); + + /* + Now we need to lookup record in mysql.time_zone table in order to + understand whenever this timezone uses leap seconds (again we are + using the only index in this table). + */ + table= tables[1].table; + table->field[0]->store((longlong)tzid); + table->file->index_init(0); + + if (table->file->index_read(table->record[0], (byte*)table->field[0]->ptr, + 0, HA_READ_KEY_EXACT)) + { + sql_print_error("Error: Can't find description of time zone."); + goto end_with_unlock; + } + + /* If Uses_leap_seconds == 'Y' */ + if (table->field[1]->val_int() == 1) + { + tz_info->leapcnt= tz_leapcnt; + tz_info->lsis= tz_lsis; + } + + table->file->index_end(); + + /* + Now we will iterate through records for out time zone in + mysql.time_zone_transition_type table. Because we want records + only for our time zone guess what are we doing? + Right - using special index. + */ + table= tables[3].table; + table->field[0]->store((longlong)tzid); + table->file->index_init(0); + + // FIXME Is there any better approach than explicitly specifying 4 ??? + res= table->file->index_read(table->record[0], (byte*)table->field[0]->ptr, + 4, HA_READ_KEY_EXACT); + while (!res) + { + ttid= table->field[1]->val_int(); + + if (ttid > TZ_MAX_TYPES) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition_type table: too big " + "transition type id"); + goto end_with_unlock; + } + + ttis[ttid].tt_gmtoff= table->field[2]->val_int(); + ttis[ttid].tt_isdst= (table->field[3]->val_int() > 0); + +#ifdef ABBR_ARE_USED + // FIXME should we do something with duplicates here ? + table->field[4]->val_str(&abbr, &abbr); + if (tz_info->charcnt + abbr.length() + 1 > sizeof(chars)) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition_type table: not enough " + "room for abbreviations"); + goto end_with_unlock; + } + ttis[ttid].tt_abbrind= tz_info->charcnt; + memcpy(chars + tz_info->charcnt, abbr.ptr(), abbr.length()); + tz_info->charcnt+= abbr.length(); + chars[tz_info->charcnt]= 0; + tz_info->charcnt++; + + DBUG_PRINT("info", + ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld " + "abbr='%s' tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, + chars + ttis[ttid].tt_abbrind, ttis[ttid].tt_isdst)); +#else + DBUG_PRINT("info", + ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld " + "tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, ttis[ttid].tt_isdst)); +#endif + + /* ttid is increasing because we are reading using index */ + DBUG_ASSERT(ttid >= tz_info->typecnt); + + tz_info->typecnt= ttid + 1; + + res= table->file->index_next_same(table->record[0], + (byte*)table->field[0]->ptr, 4); + } + + if (res != HA_ERR_END_OF_FILE) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition_type table"); + goto end_with_unlock; + } + + table->file->index_end(); + + + /* + At last we are doing the same thing for records in + mysql.time_zone_transition table. Here we additionaly need records + in ascending order by index scan also satisfies us. + */ + table= tables[2].table; + table->field[0]->store((longlong)tzid); + table->file->index_init(0); + + // FIXME Is there any better approach than explicitly specifying 4 ??? + res= table->file->index_read(table->record[0], (byte*)table->field[0]->ptr, + 4, HA_READ_KEY_EXACT); + while (!res) + { + ttime= (my_time_t)table->field[1]->val_int(); + ttid= (uint)table->field[2]->val_int(); + + if (tz_info->timecnt + 1 > TZ_MAX_TIMES) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition table: " + "too much transitions"); + goto end_with_unlock; + } + if (ttid + 1 > tz_info->typecnt) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition table: " + "bad transition type id"); + goto end_with_unlock; + } + + ats[tz_info->timecnt]= ttime; + types[tz_info->timecnt]= ttid; + tz_info->timecnt++; + + DBUG_PRINT("info", + ("time_zone_transition table: tz_id=%u tt_time=%lld tt_id=%u", + tzid, (longlong)ttime, ttid)); + + res= table->file->index_next_same(table->record[0], + (byte*)table->field[0]->ptr, 4); + } + + /* + We have to allow HA_ERR_KEY_NOT_FOUND because some time zones + for example UTC have no transitons. + */ + if (res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND) + { + sql_print_error("Error while loading time zone description from " + "mysql.time_zone_transition table"); + goto end_with_unlock; + } + + table->file->index_end(); + table= 0; + + /* + Now we will allocate memory and init TIME_ZONE_INFO structure. + */ + if (!(alloc_buff= alloc_root(&tz_storage, + ALIGN_SIZE(sizeof(my_time_t) * + tz_info->timecnt) + + ALIGN_SIZE(tz_info->timecnt) + +#ifdef ABBR_ARE_USED + ALIGN_SIZE(tz_info->charcnt) + +#endif + sizeof(TRAN_TYPE_INFO) * tz_info->typecnt))) + { + sql_print_error("Error: Out of memory while loading time zone " + "description"); + goto end_with_unlock; + } + + + tz_info->ats= (my_time_t *)alloc_buff; + memcpy(tz_info->ats, ats, tz_info->timecnt * sizeof(my_time_t)); + alloc_buff+= ALIGN_SIZE(sizeof(my_time_t) * tz_info->timecnt); + tz_info->types= (unsigned char *)alloc_buff; + memcpy(tz_info->types, types, tz_info->timecnt); + alloc_buff+= ALIGN_SIZE(tz_info->timecnt); +#ifdef ABBR_ARE_USED + tz_info->chars= alloc_buff; + memcpy(tz_info->chars, chars, tz_info->charcnt); + alloc_buff+= ALIGN_SIZE(tz_info->charcnt); +#endif + tz_info->ttis= (TRAN_TYPE_INFO *)alloc_buff; + memcpy(tz_info->ttis, ttis, tz_info->typecnt * sizeof(TRAN_TYPE_INFO)); + + /* + Let us check how correct our time zone description and build + reversed map. We don't check for tz->timecnt < 1 since it ok for GMT. + */ + if (tz_info->typecnt < 1) + { + sql_print_error("Error: loading time zone without transition types"); + goto end_with_unlock; + } + if (prepare_tz_info(tz_info, &tz_storage)) + { + sql_print_error("Error: Unable to build mktime map for time zone"); + goto end_with_unlock; + } + + + if (!(tmp_tzname= new (&tz_storage) TZ_NAMES_ENTRY()) || + !(tmp_tzname->tz= new (&tz_storage) Time_zone_db(tz_info, + &(tmp_tzname->name))) || + (tmp_tzname->name.set(tz_name_buff, tz_name->length(), + &my_charset_latin1), + my_hash_insert(&tz_names, (const byte *)tmp_tzname))) + { + sql_print_error("Error: Out of memory while loading time zone"); + goto end_with_unlock; + } + + /* + Loading of time zone succeeded + */ + return_val= tmp_tzname->tz; + +end_with_unlock: + + if (table) + table->file->index_end(); + + mysql_unlock_tables(thd, lock); + +end_with_close: + close_thread_tables(thd); + +end: + thd->db= db_save; + thd->db_length= db_length_save; + DBUG_RETURN(return_val); +} + + +/* + Parse string that specifies time zone as offset from UTC. + + SYNOPSIS + str_to_offset() + str - pointer to string which contains offset + length - length of string + offset - out parameter for storing found offset in seconds. + + DESCRIPTION + This function parses string which contains time zone offset + in form similar to '+10:00' and converts found value to + seconds from UTC form (east is positive). + + RETURN VALUE + 0 - Ok + 1 - String doesn't contain valid time zone offset +*/ +my_bool +str_to_offset(const char *str, uint length, long *offset) +{ + const char *end= str + length; + my_bool negative; + ulong number_tmp; + long offset_tmp; + + if (length < 4) + return 1; + + if (*str == '+') + negative= 0; + else if (*str == '-') + negative= 1; + else + return 1; + str++; + + number_tmp= 0; + + while (str < end && my_isdigit(&my_charset_latin1, *str)) + { + number_tmp= number_tmp*10 + *str - '0'; + str++; + } + + if (str + 1 >= end || *str != ':') + return 1; + str++; + + offset_tmp = number_tmp * MINS_PER_HOUR; number_tmp= 0; + + while (str < end && my_isdigit(&my_charset_latin1, *str)) + { + number_tmp= number_tmp * 10 + *str - '0'; + str++; + } + + if (str != end) + return 1; + + offset_tmp= (offset_tmp + number_tmp) * SECS_PER_MIN; + + if (negative) + offset_tmp= -offset_tmp; + + /* + Check if offset is in range prescribed by standard + (from -12:59 to 13:00). + */ + + if (number_tmp > 59 || offset_tmp < -13 * SECS_PER_HOUR + 1 || + offset_tmp > 13 * SECS_PER_HOUR) + return 1; + + *offset= offset_tmp; + + return 0; +} + + +/* + Get Time_zone object for specified time zone. + + SYNOPSIS + my_tz_find() + thd - current thread + name - time zone specification + + DESCRIPTION + This function checks if name is one of time zones described in db, + predefined SYSTEM time zone or valid time zone specification as + offset from UTC (In last case it will create proper Time_zone_offset + object if there were not any.). If name is ok it returns corresponding + Time_zone object. + + Clients of this function are not responsible for releasing resources + occupied by returned Time_zone object so they can just forget pointers + to Time_zone object if they are not needed longer. + + Other important property of this function: if some Time_zone found once + it will be for sure found later, so this function can also be used for + checking if proper Time_zone object exists (and if there will be error + it will be reported during first call). + + If name pointer is 0 then this function returns 0 (this allows to pass 0 + values as parameter without additional external check and this property + is used by @@time_zone variable handling code). + + It will perform lookup in system tables (mysql.time_zone*) if needed. + + RETURN VALUE + Pointer to corresponding Time_zone object. 0 - in case of bad time zone + specification or other error. + +*/ +Time_zone * +my_tz_find(THD *thd, const String * name) +{ + TZ_NAMES_ENTRY *tmp_tzname; + Time_zone *result_tz= 0; + long offset; + + DBUG_ENTER("my_tz_find"); + DBUG_PRINT("enter", ("time zone name='%s'", + name ? ((String *)name)->c_ptr() : "NULL")); + + if (!name) + DBUG_RETURN(0); + + VOID(pthread_mutex_lock(&tz_LOCK)); + + if (!str_to_offset(name->ptr(), name->length(), &offset)) + { + + if (!(result_tz= (Time_zone_offset *)hash_search(&offset_tzs, + (const byte *)&offset, + sizeof(long)))) + { + DBUG_PRINT("info", ("Creating new Time_zone_offset object")); + + if (!(result_tz= new (&tz_storage) Time_zone_offset(offset)) || + my_hash_insert(&offset_tzs, (const byte *) result_tz)) + { + sql_print_error("Fatal error: Out of memory " + "while setting new time zone"); + result_tz= 0; + } + } + } else { + if ((tmp_tzname= (TZ_NAMES_ENTRY *)hash_search(&tz_names, name->ptr(), + name->length()))) + result_tz= tmp_tzname->tz; + else + result_tz= tz_load_from_db(current_thd, name); + } + + VOID(pthread_mutex_unlock(&tz_LOCK)); + + DBUG_RETURN(result_tz); +} + +#endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */ + + +#ifdef TZINFO2SQL +/* + This code belongs to mysql_tzinfo_to_sql converter command line utility. + This utility should be used by db admin for populating mysql.time_zone + tables. +*/ + + +/* + Print info about time zone described by TIME_ZONE_INFO struct as + SQL statements populating mysql.time_zone* tables. + + SYNOPSIS + print_tz_as_sql() + tz_name - name of time zone + sp - structure describing time zone +*/ +void +print_tz_as_sql(const char* tz_name, const TIME_ZONE_INFO *sp) +{ + uint i; + + /* Here we assume that all time zones have same leap correction tables */ + printf("INSERT INTO time_zone (Use_leap_seconds) VALUES ('%s');\n", + sp->leapcnt ? "Y" : "N"); + printf("SET @time_zone_id= LAST_INSERT_ID();\n"); + printf("INSERT INTO time_zone_name (Name, Time_zone_id) VALUES \ +('%s', @time_zone_id);\n", tz_name); + + if (sp->timecnt) + { + printf("INSERT INTO time_zone_transition \ +(Time_zone_id, Transition_time, Transition_type_id) VALUES\n"); + for (i= 0; i < sp->timecnt; i++) + printf("%s(@time_zone_id, %ld, %u)\n", (i == 0 ? " " : ","), sp->ats[i], + (uint)sp->types[i]); + printf(";\n"); + } + + printf("INSERT INTO time_zone_transition_type \ +(Time_zone_id, Transition_type_id, Offset, Is_DST, Abbreviation) VALUES\n"); + + for (i= 0; i < sp->typecnt; i++) + printf("%s(@time_zone_id, %u, %ld, %d, '%s')\n", (i == 0 ? " " : ","), i, + sp->ttis[i].tt_gmtoff, sp->ttis[i].tt_isdst, + sp->chars + sp->ttis[i].tt_abbrind); + printf(";\n"); +} + + +/* + Print info about leap seconds in time zone as SQL statements + populating mysql.time_zone_leap_second table. + + SYNOPSIS + print_tz_leaps_as_sql() + sp - structure describing time zone +*/ +void +print_tz_leaps_as_sql(const TIME_ZONE_INFO *sp) +{ + uint i; + + /* + We are assuming that there are only one list of leap seconds + For all timezones. + */ + printf("TRUNCATE TABLE time_zone_leap_second;\n"); + + if (sp->leapcnt) + { + printf("INSERT INTO time_zone_leap_second \ +(Transition_time, Correction) VALUES\n"); + for (i= 0; i < sp->leapcnt; i++) + printf("%s(%ld, %ld)\n", (i == 0 ? " " : ","), + sp->lsis[i].ls_trans, sp->lsis[i].ls_corr); + printf(";\n"); + } + + printf("ALTER TABLE time_zone_leap_second ORDER BY Transition_time;\n"); +} + + +/* + Some variables used as temporary or as parameters + in recursive scan_tz_dir() code. +*/ +TIME_ZONE_INFO tz_info; +MEM_ROOT tz_storage; +char fullname[FN_REFLEN + 1]; +char *root_name_end; + + +/* + Recursively scan zoneinfo directory and print all found time zone + descriptions as SQL. + + SYNOPSIS + scan_tz_dir() + name_end - pointer to end of path to directory to be searched. + + DESCRIPTION + This auxiliary recursive function also uses several global + variables as in parameters and for storing temporary values. + + fullname - path to directory that should be scanned. + root_name_end - pointer to place in fullname where part with + path to initial directory ends. + current_tz_id - last used time zone id + + RETURN VALUE + 0 - Ok, 1 - Fatal error + +*/ +my_bool +scan_tz_dir(char * name_end) +{ + MY_DIR *cur_dir; + char *name_end_tmp; + uint i; + + if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT)))) + return 1; + + name_end= strmake(name_end, "/", FN_REFLEN - (name_end - fullname)); + + for (i= 0; i < cur_dir->number_off_files; i++) + { + if (cur_dir->dir_entry[i].name[0] != '.') + { + name_end_tmp= strmake(name_end, cur_dir->dir_entry[i].name, + FN_REFLEN - (name_end - fullname)); + + if (MY_S_ISDIR(cur_dir->dir_entry[i].mystat->st_mode)) + { + if (scan_tz_dir(name_end_tmp)) + { + my_dirend(cur_dir); + return 1; + } + } + else if (MY_S_ISREG(cur_dir->dir_entry[i].mystat->st_mode)) + { + init_alloc_root(&tz_storage, 32768, 0); + if (!tz_load(fullname, &tz_info, &tz_storage)) + print_tz_as_sql(root_name_end + 1, &tz_info); + else + fprintf(stderr, + "Warning: Unable to load '%s' as time zone. Skipping it.\n", + fullname); + free_root(&tz_storage, MYF(0)); + } + else + fprintf(stderr, "Warning: '%s' is not regular file or directory\n", + fullname); + } + } + + my_dirend(cur_dir); + + return 0; +} + + +int +main(int argc, char **argv) +{ + MY_INIT(argv[0]); + + if (argc != 2 && argc != 3) + { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s timezonedir\n", argv[0]); + fprintf(stderr, " %s timezonefile timezonename\n", argv[0]); + fprintf(stderr, " %s --leap timezonefile\n", argv[0]); + return 1; + } + + if (argc == 2) + { + root_name_end= strmake(fullname, argv[1], FN_REFLEN); + + printf("TRUNCATE TABLE time_zone;\n"); + printf("TRUNCATE TABLE time_zone_name;\n"); + printf("TRUNCATE TABLE time_zone_transition;\n"); + printf("TRUNCATE TABLE time_zone_transition_type;\n"); + + if (scan_tz_dir(root_name_end)) + { + fprintf(stderr, "There were fatal errors during processing " + "of zoneinfo directory\n"); + return 1; + } + + printf("ALTER TABLE time_zone_transition " + "ORDER BY Time_zone_id, Transition_time;\n"); + printf("ALTER TABLE time_zone_transition_type " + "ORDER BY Time_zone_id, Transition_type_id;\n"); + } + else + { + init_alloc_root(&tz_storage, 32768, 0); + + if (strcmp(argv[1], "--leap") == 0) + { + if (tz_load(argv[2], &tz_info, &tz_storage)) + { + fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[2]); + return 1; + } + print_tz_leaps_as_sql(&tz_info); + } + else + { + if (tz_load(argv[1], &tz_info, &tz_storage)) + { + fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[2]); + return 1; + } + print_tz_as_sql(argv[2], &tz_info); + } + + free_root(&tz_storage, MYF(0)); + } + + return 0; +} + +#endif /* defined(TZINFO2SQL) */ + + +#ifdef TESTTIME + +/* + Some simple brute-force test wich allowed to catch a pair of bugs. + Also can provide interesting facts about system's time zone support + implementation. +*/ + +#ifndef CHAR_BIT +#define CHAR_BIT 8 +#endif + +#ifndef TYPE_BIT +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif + +#ifndef TYPE_SIGNED +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif + +my_bool +is_equal_TIME_tm(const TIME* time_arg, const struct tm * tm_arg) +{ + return (time_arg->year == (uint)tm_arg->tm_year+TM_YEAR_BASE) && + (time_arg->month == (uint)tm_arg->tm_mon+1) && + (time_arg->day == (uint)tm_arg->tm_mday) && + (time_arg->hour == (uint)tm_arg->tm_hour) && + (time_arg->minute == (uint)tm_arg->tm_min) && + (time_arg->second == (uint)tm_arg->tm_sec) && + time_arg->second_part == 0; +} + + +int +main(int argc, char **argv) +{ + my_bool localtime_negative; + TIME_ZONE_INFO tz_info; + struct tm tmp; + TIME time_tmp; + my_time_t t, t1, t2; + char fullname[FN_REFLEN+1]; + char *str_end; + long not_used; + bool not_used_2; + MEM_ROOT tz_storage; + + MY_INIT(argv[0]); + + init_alloc_root(&tz_storage, 32768, 0); + + /* let us set some well known timezone */ + setenv("TZ", "MET", 1); + tzset(); + + /* Some initial time zone related system info */ + printf("time_t: %s %u bit\n", TYPE_SIGNED(time_t) ? "signed" : "unsigned", + (uint)TYPE_BIT(time_t)); + if (TYPE_SIGNED(time_t)) + { + t= -100; + localtime_negative= test(localtime_r(&t, &tmp) != 0); + printf("localtime_r %s negative params \ + (time_t=%d is %d-%d-%d %d:%d:%d)\n", + (localtime_negative ? "supports" : "doesn't support"), (int)t, + TM_YEAR_BASE + tmp.tm_year, tmp.tm_mon + 1, tmp.tm_mday, + tmp.tm_hour, tmp.tm_min, tmp.tm_sec); + + printf("mktime %s negative results (%d)\n", + (t == mktime(&tmp) ? "doesn't support" : "supports"), + (int)mktime(&tmp)); + } + + tmp.tm_year= 103; tmp.tm_mon= 2; tmp.tm_mday= 30; + tmp.tm_hour= 2; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= -1; + t= mktime(&tmp); + printf("mktime returns %s for spring time gap (%d)\n", + (t != (time_t)-1 ? "something" : "error"), (int)t); + + tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1; + tmp.tm_hour= 0; tmp.tm_min= 0; tmp.tm_sec= 0; tmp.tm_isdst= 0; + t= mktime(&tmp); + printf("mktime returns %s for non existing date (%d)\n", + (t != (time_t)-1 ? "something" : "error"), (int)t); + + tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1; + tmp.tm_hour= 25; tmp.tm_min=0; tmp.tm_sec=0; tmp.tm_isdst=1; + t= mktime(&tmp); + printf("mktime %s unnormalized input (%d)\n", + (t != (time_t)-1 ? "handles" : "doesn't handle"), (int)t); + + tmp.tm_year= 103; tmp.tm_mon= 9; tmp.tm_mday= 26; + tmp.tm_hour= 0; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= 1; + mktime(&tmp); + tmp.tm_hour= 2; tmp.tm_isdst= -1; + t=mktime(&tmp); + tmp.tm_hour= 4; tmp.tm_isdst= 0; + mktime(&tmp); + tmp.tm_hour= 2; tmp.tm_isdst= -1; + t1=mktime(&tmp); + printf("mktime is %s (%d %d)\n", + (t == t1 ? "determenistic" : "is non-determenistic"), + (int)t, (int)t1); + + /* Let us load time zone description */ + str_end= strmake(fullname, TZDIR, FN_REFLEN); + strmake(str_end, "/MET", FN_REFLEN - (str_end - fullname)); + + if (tz_load(fullname, &tz_info, &tz_storage)) + { + printf("Unable to load time zone info from '%s'\n", fullname); + free_root(&tz_storage, MYF(0)); + return 1; + } + + printf("Testing our implementation\n"); + + if (TYPE_SIGNED(time_t) && localtime_negative) + { + for (t= -40000; t < 20000; t++) + { + localtime_r(&t,&tmp); + gmt_sec_to_TIME(&time_tmp, t, &tz_info); + if (!is_equal_TIME_tm(&time_tmp, &tmp)) + { + printf("Problem with negative time_t = %d\n", (int)t); + free_root(&tz_storage, MYF(0)); + return 1; + } + } + printf("gmt_sec_to_TIME = localtime for time_t in [-40000,20000) range\n"); + } + + for (t= 1000000000; t < 1100000000; t+= 13) + { + localtime_r(&t,&tmp); + gmt_sec_to_TIME(&time_tmp, t, &tz_info); + + if (!is_equal_TIME_tm(&time_tmp, &tmp)) + { + printf("Problem with time_t = %d\n", (int)t); + free_root(&tz_storage, MYF(0)); + return 1; + } + } + printf("gmt_sec_to_TIME = localtime for time_t in [1000000000,1100000000) range\n"); + + init_time(); + + /* + Be careful here! my_system_gmt_sec doesn't fully handle unnormalized + dates. + */ + for (time_tmp.year= 1980; time_tmp.year < 2010; time_tmp.year++) + for (time_tmp.month= 1; time_tmp.month < 13; time_tmp.month++) + for (time_tmp.day= 1; + time_tmp.day < mon_lengths[isleap(time_tmp.year)][time_tmp.month-1]; + time_tmp.day++) + for (time_tmp.hour= 0; time_tmp.hour < 24; time_tmp.hour++) + for (time_tmp.minute= 0; time_tmp.minute < 60; time_tmp.minute+= 5) + for (time_tmp.second=0; time_tmp.second<60; time_tmp.second+=25) + { + t= my_system_gmt_sec(&time_tmp, ¬_used, ¬_used_2); + t1= TIME_to_gmt_sec(&time_tmp, &tz_info, ¬_used_2); + if (t != t1) + { + /* + We need special handling during autumn since my_system_gmt_sec + prefers greater time_t values (in MET) for ambiguity. + And BTW that is a bug which should be fixed !!! + */ + tmp.tm_year= time_tmp.year - TM_YEAR_BASE; + tmp.tm_mon= time_tmp.month - 1; + tmp.tm_mday= time_tmp.day; + tmp.tm_hour= time_tmp.hour; + tmp.tm_min= time_tmp.minute; + tmp.tm_sec= time_tmp.second; + tmp.tm_isdst= 1; + + t2= mktime(&tmp); + + if (t1 == t2) + continue; + + printf("Problem: %u/%u/%u %u:%u:%u with times t=%d, t1=%d\n", + time_tmp.year, time_tmp.month, time_tmp.day, + time_tmp.hour, time_tmp.minute, time_tmp.second, + (int)t,(int)t1); + + free_root(&tz_storage, MYF(0)); + return 1; + } + } + + printf("TIME_to_gmt_sec = my_system_gmt_sec for test range\n"); + + free_root(&tz_storage, MYF(0)); + return 0; +} + +#endif /* defined(TESTTIME) */ diff --git a/sql/tztime.h b/sql/tztime.h new file mode 100644 index 00000000000..ed92441d790 --- /dev/null +++ b/sql/tztime.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2004 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifdef __GNUC__ +#pragma interface /* gcc class interface */ +#endif + +#if !defined(TESTTIME) && !defined(TZINFO2SQL) +/* + This class represents abstract time zone and provides + basic interface for TIME <-> my_time_t conversion. + Actual time zones which are specified by DB, or via offset + or use system functions are its descendants. +*/ +class Time_zone: public Sql_alloc +{ +public: + /* + Converts local time in broken down TIME representation to + my_time_t (UTC seconds since Epoch) represenation. + Returns 0 in case of error. Sets in_dst_time_gap to true if date provided + falls into spring time-gap (or lefts it untouched otherwise). + */ + virtual my_time_t TIME_to_gmt_sec(const TIME *t, + bool *in_dst_time_gap) const = 0; + /* + Converts time in my_time_t representation to local time in + broken down TIME representation. + */ + virtual void gmt_sec_to_TIME(TIME *tmp, my_time_t t) const = 0; + /* + Because of constness of String returned by get_name() time zone name + have to be already zeroended to be able to use String::ptr() instead + of c_ptr(). + */ + virtual const String * get_name() const = 0; + + /* + We need this only for surpressing warnings, objects of this type are + allocated on MEM_ROOT and should not require destruction. + */ + virtual ~Time_zone() {}; +}; + +extern Time_zone * my_tz_UTC; +extern Time_zone * my_tz_SYSTEM; +extern Time_zone * my_tz_find(THD *thd, const String *name); +extern my_bool my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap); +extern void my_tz_free(); + +/* + Maximum length of time zone name that we support + (Time zone name is char(64) in db) +*/ +#define MAX_TIME_ZONE_NAME_LENGTH 72 + +#endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */ diff --git a/sql/unireg.cc b/sql/unireg.cc index bab021aed59..c2666be804d 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -633,6 +633,7 @@ static bool make_empty_rec(File file,enum db_type table_type, DBUG_RETURN(1); } + table.in_use= current_thd; table.db_low_byte_first= handler->low_byte_first(); table.blob_ptr_size=portable_sizeof_char_ptr; |