diff options
author | Seppo Jaakola <seppo.jaakola@codership.com> | 2013-07-13 13:30:03 +0300 |
---|---|---|
committer | Seppo Jaakola <seppo.jaakola@codership.com> | 2013-07-13 13:30:03 +0300 |
commit | db0cfba638383569b1e30765e2d36e0707bfb930 (patch) | |
tree | d56bf3ec5c5804b2bd15a5eceddb14b5520b3d2e /sql | |
parent | 0a9216835f406947fb4d492616da4cda75e5e113 (diff) | |
parent | a057b504904c3e6ab1e3006c081b4fb23faaf1d4 (diff) | |
download | mariadb-git-db0cfba638383569b1e30765e2d36e0707bfb930.tar.gz |
Merged with lp:maria revision #3766
Diffstat (limited to 'sql')
93 files changed, 4585 insertions, 1207 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index ffd8fab16fd..ff566cefc33 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -54,7 +54,7 @@ IF(WITH_WSREP) ENDIF() SET (SQL_SOURCE - ../sql-common/client.c derror.cc des_key_file.cc + ../sql-common/client.c compat56.cc derror.cc des_key_file.cc discover.cc ../libmysql/errmsg.c field.cc field_conv.cc filesort_utils.cc filesort.cc gstream.cc sha2.cc @@ -294,12 +294,9 @@ ADD_CUSTOM_TARGET(distclean IF(INSTALL_LAYOUT STREQUAL "STANDALONE") -# We need to create empty directories (data/test) the installation. -# This does not work with current CPack due to http://www.cmake.org/Bug/view.php?id=8767 -# Avoid completely empty directories and install dummy file instead. -SET(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/db.opt ) -FILE(WRITE ${DUMMY_FILE} "") -INSTALL(FILES ${DUMMY_FILE} DESTINATION data/test COMPONENT DataFiles) +# Copy db.opt into data/test/ +SET(DBOPT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/db.opt ) +INSTALL(FILES ${DBOPT_FILE} DESTINATION data/test COMPONENT DataFiles) # Install initial database on windows IF(NOT CMAKE_CROSSCOMPILING) diff --git a/sql/compat56.cc b/sql/compat56.cc new file mode 100644 index 00000000000..3bd6b21a154 --- /dev/null +++ b/sql/compat56.cc @@ -0,0 +1,445 @@ +/* + Copyright (c) 2004, 2012, Oracle and/or its affiliates. + Copyright (c) 2013, MariaDB Foundation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "my_global.h" +#include "compat56.h" +#include "myisampack.h" +#include "my_time.h" + +/*** MySQL56 TIME low-level memory and disk representation routines ***/ + +/* + In-memory format: + + 1 bit sign (Used for sign, when on disk) + 1 bit unused (Reserved for wider hour range, e.g. for intervals) + 10 bit hour (0-836) + 6 bit minute (0-59) + 6 bit second (0-59) + 24 bits microseconds (0-999999) + + Total: 48 bits = 6 bytes + Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff +*/ + + +/** + Convert time value to MySQL56 numeric packed representation. + + @param ltime The value to convert. + @return Numeric packed representation. +*/ +longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime) +{ + /* If month is 0, we mix day with hours: "1 00:10:10" -> "24:00:10" */ + long hms= (((ltime->month ? 0 : ltime->day * 24) + ltime->hour) << 12) | + (ltime->minute << 6) | ltime->second; + longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part); + return ltime->neg ? -tmp : tmp; +} + + + +/** + Convert MySQL56 time packed numeric representation to time. + + @param OUT ltime The MYSQL_TIME variable to set. + @param tmp The packed numeric representation. +*/ +void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp) +{ + long hms; + if ((ltime->neg= (tmp < 0))) + tmp= -tmp; + hms= MY_PACKED_TIME_GET_INT_PART(tmp); + ltime->year= (uint) 0; + ltime->month= (uint) 0; + ltime->day= (uint) 0; + ltime->hour= (uint) (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */ + ltime->minute= (uint) (hms >> 6) % (1 << 6); /* 6 bits starting at 6th */ + ltime->second= (uint) hms % (1 << 6); /* 6 bits starting at 0th */ + ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp); + ltime->time_type= MYSQL_TIMESTAMP_TIME; +} + + +/** + Calculate binary size of MySQL56 packed numeric time representation. + + @param dec Precision. +*/ +uint my_time_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 3 + (dec + 1) / 2; +} + + +/* + On disk we convert from signed representation to unsigned + representation using TIMEF_OFS, so all values become binary comparable. +*/ +#define TIMEF_OFS 0x800000000000LL +#define TIMEF_INT_OFS 0x800000LL + + +/** + Convert MySQL56 in-memory numeric time representation to on-disk representation + + @param nr Value in packed numeric time format. + @param OUT ptr The buffer to put value at. + @param dec Precision. +*/ +void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* Make sure the stored value was previously properly rounded or truncated */ + DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + + switch (dec) + { + case 0: + default: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + break; + + case 1: + case 2: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + ptr[3]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000); + break; + + case 4: + case 3: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + mi_int2store(ptr + 3, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100); + break; + + case 5: + case 6: + mi_int6store(ptr, nr + TIMEF_OFS); + break; + } +} + + +/** + Convert MySQL56 on-disk time representation to in-memory packed numeric + representation. + + @param ptr The pointer to read the value at. + @param dec Precision. + @return Packed numeric time representation. +*/ +longlong my_time_packed_from_binary(const uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + + switch (dec) + { + case 0: + default: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + return MY_PACKED_TIME_MAKE_INT(intpart); + } + case 1: + case 2: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + int frac= (uint) ptr[3]; + if (intpart < 0 && frac) + { + /* + Negative values are stored with reverse fractional part order, + for binary sort compatibility. + + Disk value intpart frac Time value Memory value + 800000.00 0 0 00:00:00.00 0000000000.000000 + 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0 + 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0 + 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000 + 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0 + 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960 + + Formula to convert fractional part from disk format + (now stored in "frac" variable) to absolute value: "0x100 - frac". + To reconstruct in-memory value, we shift + to the next integer value and then substruct fractional part. + */ + intpart++; /* Shift to the next integer value */ + frac-= 0x100; /* -(0x100 - frac) */ + } + return MY_PACKED_TIME_MAKE(intpart, frac * 10000); + } + + case 3: + case 4: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + int frac= mi_uint2korr(ptr + 3); + if (intpart < 0 && frac) + { + /* + Fix reverse fractional part order: "0x10000 - frac". + See comments for FSP=1 and FSP=2 above. + */ + intpart++; /* Shift to the next integer value */ + frac-= 0x10000; /* -(0x10000-frac) */ + } + return MY_PACKED_TIME_MAKE(intpart, frac * 100); + } + + case 5: + case 6: + return ((longlong) mi_uint6korr(ptr)) - TIMEF_OFS; + } +} + + +/*** MySQL56 DATETIME low-level memory and disk representation routines ***/ + +/* + 1 bit sign (used when on disk) + 17 bits year*13+month (year 0-9999, month 0-12) + 5 bits day (0-31) + 5 bits hour (0-23) + 6 bits minute (0-59) + 6 bits second (0-59) + 24 bits microseconds (0-999999) + + Total: 64 bits = 8 bytes + + SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff +*/ + +/** + Convert datetime to MySQL56 packed numeric datetime representation. + @param ltime The value to convert. + @return Packed numeric representation of ltime. +*/ +longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *ltime) +{ + longlong ymd= ((ltime->year * 13 + ltime->month) << 5) | ltime->day; + longlong hms= (ltime->hour << 12) | (ltime->minute << 6) | ltime->second; + longlong tmp= MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime->second_part); + DBUG_ASSERT(!check_datetime_range(ltime)); /* Make sure no overflow */ + return ltime->neg ? -tmp : tmp; +} + + +/** + Convert MySQL56 packed numeric datetime representation to MYSQL_TIME. + @param OUT ltime The datetime variable to convert to. + @param tmp The packed numeric datetime value. +*/ +void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp) +{ + longlong ymd, hms; + longlong ymdhms, ym; + if ((ltime->neg= (tmp < 0))) + tmp= -tmp; + + ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp); + ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp); + + ymd= ymdhms >> 17; + ym= ymd >> 5; + hms= ymdhms % (1 << 17); + + ltime->day= ymd % (1 << 5); + ltime->month= ym % 13; + ltime->year= ym / 13; + + ltime->second= hms % (1 << 6); + ltime->minute= (hms >> 6) % (1 << 6); + ltime->hour= (hms >> 12); + + ltime->time_type= MYSQL_TIMESTAMP_DATETIME; +} + + +/** + Calculate binary size of MySQL56 packed datetime representation. + @param dec Precision. +*/ +uint my_datetime_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 5 + (dec + 1) / 2; +} + + +/* + On disk we store as unsigned number with DATETIMEF_INT_OFS offset, + for HA_KETYPE_BINARY compatibilty purposes. +*/ +#define DATETIMEF_INT_OFS 0x8000000000LL + + +/** + Convert MySQL56 on-disk datetime representation + to in-memory packed numeric representation. + + @param ptr The pointer to read value at. + @param dec Precision. + @return In-memory packed numeric datetime representation. +*/ +longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec) +{ + longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS; + int frac; + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + switch (dec) + { + case 0: + default: + return MY_PACKED_TIME_MAKE_INT(intpart); + case 1: + case 2: + frac= ((int) (signed char) ptr[5]) * 10000; + break; + case 3: + case 4: + frac= mi_sint2korr(ptr + 5) * 100; + break; + case 5: + case 6: + frac= mi_sint3korr(ptr + 5); + break; + } + return MY_PACKED_TIME_MAKE(intpart, frac); +} + + +/** + Store MySQL56 in-memory numeric packed datetime representation to disk. + + @param nr In-memory numeric packed datetime representation. + @param OUT ptr The pointer to store at. + @param dec Precision, 1-6. +*/ +void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* The value being stored must have been properly rounded or truncated */ + DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + + mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS); + switch (dec) + { + case 0: + default: + break; + case 1: + case 2: + ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000); + break; + case 3: + case 4: + mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100); + break; + case 5: + case 6: + mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr)); + } +} + + +/*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/ + +/** + Calculate on-disk size of a timestamp value. + + @param dec Precision. +*/ +uint my_timestamp_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 4 + (dec + 1) / 2; +} + + +/** + Convert MySQL56 binary timestamp representation to in-memory representation. + + @param OUT tm The variable to convert to. + @param ptr The pointer to read the value from. + @param dec Precision. +*/ +void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + tm->tv_sec= mi_uint4korr(ptr); + switch (dec) + { + case 0: + default: + tm->tv_usec= 0; + break; + case 1: + case 2: + tm->tv_usec= ((int) ptr[4]) * 10000; + break; + case 3: + case 4: + tm->tv_usec= mi_sint2korr(ptr + 4) * 100; + break; + case 5: + case 6: + tm->tv_usec= mi_sint3korr(ptr + 4); + } +} + + +/** + Convert MySQL56 in-memory timestamp representation to on-disk representation. + + @param tm The value to convert. + @param OUT ptr The pointer to store the value to. + @param dec Precision. +*/ +void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* Stored value must have been previously properly rounded or truncated */ + DBUG_ASSERT((tm->tv_usec % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + mi_int4store(ptr, tm->tv_sec); + switch (dec) + { + case 0: + default: + break; + case 1: + case 2: + ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000); + break; + case 3: + case 4: + mi_int2store(ptr + 4, tm->tv_usec / 100); + break; + /* Impossible second precision. Fall through */ + case 5: + case 6: + mi_int3store(ptr + 4, tm->tv_usec); + } +} + +/****************************************/ diff --git a/sql/compat56.h b/sql/compat56.h new file mode 100644 index 00000000000..bb5e2670f7d --- /dev/null +++ b/sql/compat56.h @@ -0,0 +1,46 @@ +#ifndef COMPAT56_H_INCLUDED +#define COMPAT56_H_INCLUDED +/* + Copyright (c) 2004, 2012, Oracle and/or its affiliates. + Copyright (c) 2013 MariaDB Foundation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + +/** MySQL56 routines and macros **/ +#define MY_PACKED_TIME_GET_INT_PART(x) ((x) >> 24) +#define MY_PACKED_TIME_GET_FRAC_PART(x) ((x) % (1LL << 24)) +#define MY_PACKED_TIME_MAKE(i, f) ((((longlong) (i)) << 24) + (f)) +#define MY_PACKED_TIME_MAKE_INT(i) ((((longlong) (i)) << 24)) + +longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *); +longlong TIME_to_longlong_time_packed(const MYSQL_TIME *); + +void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong nr); +void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong nr); + +void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec); +longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec); +uint my_datetime_binary_length(uint dec); + +void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec); +longlong my_time_packed_from_binary(const uchar *ptr, uint dec); +uint my_time_binary_length(uint dec); + +void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec); +void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec); +uint my_timestamp_binary_length(uint dec); +/** End of MySQL routines and macros **/ + +#endif /* COMPAT56_H_INCLUDED */ diff --git a/sql/db.opt b/sql/db.opt new file mode 100644 index 00000000000..d8429c4e0de --- /dev/null +++ b/sql/db.opt @@ -0,0 +1,2 @@ +default-character-set=latin1 +default-collation=latin1_swedish_ci diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 3b4119347c4..5c77456d907 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -55,7 +55,7 @@ const TABLE_FIELD_TYPE event_table_fields[ET_FIELD_COUNT] = }, { { C_STRING_WITH_LEN("definer") }, - { C_STRING_WITH_LEN("char(77)") }, + { C_STRING_WITH_LEN("char(") }, { C_STRING_WITH_LEN("utf8") } }, { diff --git a/sql/field.cc b/sql/field.cc index e85903d76c6..e6cab7184fd 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -87,6 +87,7 @@ const char field_separator=','; #define FIELDTYPE_NUM (FIELDTYPE_TEAR_FROM + (255 - FIELDTYPE_TEAR_TO)) static inline int field_type2index (enum_field_types field_type) { + field_type= real_type_to_type(field_type); return (field_type < FIELDTYPE_TEAR_FROM ? field_type : ((int)FIELDTYPE_TEAR_FROM) + (field_type - FIELDTYPE_TEAR_TO) - 1); @@ -947,8 +948,10 @@ static enum_field_types field_types_merge_rules [FIELDTYPE_NUM][FIELDTYPE_NUM]= enum_field_types Field::field_type_merge(enum_field_types a, enum_field_types b) { - DBUG_ASSERT(a < FIELDTYPE_TEAR_FROM || a > FIELDTYPE_TEAR_TO); - DBUG_ASSERT(b < FIELDTYPE_TEAR_FROM || b > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(a) < FIELDTYPE_TEAR_FROM || + real_type_to_type(a) > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(b) < FIELDTYPE_TEAR_FROM || + real_type_to_type(b) > FIELDTYPE_TEAR_TO); return field_types_merge_rules[field_type2index(a)] [field_type2index(b)]; } @@ -1042,8 +1045,8 @@ CPP_UNNAMED_NS_END Item_result Field::result_merge_type(enum_field_types field_type) { - DBUG_ASSERT(field_type < FIELDTYPE_TEAR_FROM || field_type - > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(field_type) < FIELDTYPE_TEAR_FROM || + real_type_to_type(field_type) > FIELDTYPE_TEAR_TO); return field_types_result_type[field_type2index(field_type)]; } @@ -1130,6 +1133,111 @@ void Field::make_sort_key(uchar *buff,uint length) /** + @brief + Determine the relative position of the field value in a numeric interval + + @details + The function returns a double number between 0.0 and 1.0 as the relative + position of the value of the this field in the numeric interval of [min,max]. + If the value is not in the interval the the function returns 0.0 when + the value is less than min, and, 1.0 when the value is greater than max. + + @param min value of the left end of the interval + @param max value of the right end of the interval + + @return + relative position of the field value in the numeric interval [min,max] +*/ + +double Field::pos_in_interval_val_real(Field *min, Field *max) +{ + double n, d; + n= val_real() - min->val_real(); + if (n < 0) + return 0.0; + d= max->val_real() - min->val_real(); + if (d <= 0) + return 1.0; + return min(n/d, 1.0); +} + + +static +inline ulonglong char_prefix_to_ulonglong(uchar *src) +{ + uint sz= sizeof(ulonglong); + for (uint i= 0; i < sz/2; i++) + { + uchar tmp= src[i]; + src[i]= src[sz-1-i]; + src[sz-1-i]= tmp; + } + return uint8korr(src); +} + + +/** + @brief + Determine the relative position of the field value in a string interval + + @details + The function returns a double number between 0.0 and 1.0 as the relative + position of the value of the this field in the string interval of [min,max]. + If the value is not in the interval the the function returns 0.0 when + the value is less than min, and, 1.0 when the value is greater than max. + + @note + To calculate the relative position of the string value v in the interval + [min, max] the function first converts the beginning of these three + strings v, min, max into the strings that are used for byte comparison. + For each string not more sizeof(ulonglong) first bytes are taken + from the result of conversion. Then these bytes are interpreted as the + big-endian representation of an ulonglong integer. The values of these + integer numbers obtained for the strings v, min, max are used to calculate + the position of v in [min,max] in the same way is it's done for numeric + fields (see Field::pos_in_interval_val_real). + + @todo + Improve the procedure for the case when min and max have the same + beginning + + @param min value of the left end of the interval + @param max value of the right end of the interval + + @return + relative position of the field value in the string interval [min,max] +*/ + +double Field::pos_in_interval_val_str(Field *min, Field *max, uint data_offset) +{ + uchar mp_prefix[sizeof(ulonglong)]; + uchar minp_prefix[sizeof(ulonglong)]; + uchar maxp_prefix[sizeof(ulonglong)]; + ulonglong mp, minp, maxp; + my_strnxfrm(charset(), mp_prefix, sizeof(mp), + ptr + data_offset, + data_length()); + my_strnxfrm(charset(), minp_prefix, sizeof(minp), + min->ptr + data_offset, + min->data_length()); + my_strnxfrm(charset(), maxp_prefix, sizeof(maxp), + max->ptr + data_offset, + max->data_length()); + mp= char_prefix_to_ulonglong(mp_prefix); + minp= char_prefix_to_ulonglong(minp_prefix); + maxp= char_prefix_to_ulonglong(maxp_prefix); + double n, d; + n= mp - minp; + if (n < 0) + return 0.0; + d= maxp - minp; + if (d <= 0) + return 1.0; + return min(n/d, 1.0); +} + + +/** Numeric fields base class constructor. */ Field_num::Field_num(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, @@ -1275,36 +1383,6 @@ out_of_range: /** - @brief - Determine the relative position of the field value in a numeric interval - - @details - The function returns a double number between 0.0 and 1.0 as the relative - position of the value of the this field in the numeric interval of [min,max]. - If the value is not in the interval the the function returns 0.0 when - the value is less than min, and, 1.0 when the value is greater than max. - - @param min value of the left end of the interval - @param max value of the right end of the interval - - @return - relative position of the field value in the numeric interval [min,max] -*/ - -double Field_num::pos_in_interval(Field *min, Field *max) -{ - double n, d; - n= val_real() - min->val_real(); - if (n < 0) - return 0.0; - d= max->val_real() - min->val_real(); - if (d <= 0) - return 1.0; - return min(n/d, 1.0); -} - - -/** Process decimal library return codes and issue warnings for overflow and truncation. @@ -1822,7 +1900,7 @@ bool Field::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) String tmp(buff,sizeof(buff),&my_charset_bin),*res; if (!(res=val_str(&tmp)) || str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), - ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzydate)) return 1; return 0; } @@ -4485,13 +4563,12 @@ Field_timestamp::Field_timestamp(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, - CHARSET_INFO *cs) - :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + TABLE_SHARE *share) + :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ - flags|= UNSIGNED_FLAG | BINARY_FLAG; + flags|= UNSIGNED_FLAG; if (unireg_check != NONE) { /* @@ -4570,18 +4647,18 @@ int Field_timestamp::store_time_dec(MYSQL_TIME *ltime, uint dec) int Field_timestamp::store(const char *from,uint len,CHARSET_INFO *cs) { MYSQL_TIME l_time; - int error; - int have_smth_to_conv; + MYSQL_TIME_STATUS status; + bool have_smth_to_conv; ErrConvString str(from, len, cs); THD *thd= get_thd(); /* We don't want to store invalid or fuzzy datetime values in TIMESTAMP */ - have_smth_to_conv= (str_to_datetime(cs, from, len, &l_time, + have_smth_to_conv= !str_to_datetime(cs, from, len, &l_time, (thd->variables.sql_mode & MODE_NO_ZERO_DATE) | - MODE_NO_ZERO_IN_DATE, &error) > - MYSQL_TIMESTAMP_ERROR); - return store_TIME_with_warning(thd, &l_time, &str, error, have_smth_to_conv); + MODE_NO_ZERO_IN_DATE, &status); + return store_TIME_with_warning(thd, &l_time, &str, + status.warnings, have_smth_to_conv); } @@ -4636,6 +4713,7 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) { MYSQL_TIME ltime; uint32 temp, temp2; + uint dec; char *to; val_buffer->alloc(field_length+1); @@ -4690,6 +4768,16 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) *to++= (char) ('0'+(char) (temp)); *to= 0; val_buffer->set_charset(&my_charset_numeric); + + if ((dec= decimals())) + { + ulong sec_part= (ulong) sec_part_shift(ltime.second_part, dec); + char *buf= const_cast<char*>(val_buffer->ptr() + MAX_DATETIME_WIDTH); + for (int i= dec; i > 0; i--, sec_part/= 10) + buf[i]= (char)(sec_part % 10) + '0'; + buf[0]= '.'; + buf[dec + 1]= 0; + } return val_buffer; } @@ -4743,7 +4831,14 @@ void Field_timestamp::sort_string(uchar *to,uint length __attribute__((unused))) void Field_timestamp::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("timestamp")); + if (!decimals()) + { + res.set_ascii(STRING_WITH_LEN("timestamp")); + return; + } + CHARSET_INFO *cs=res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "timestamp(%u)", decimals())); } @@ -4783,13 +4878,6 @@ void Field_timestamp::set_explicit_default(Item *value) set_has_explicit_value(); } -void Field_timestamp_hires::sql_type(String &res) const -{ - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "timestamp(%u)", dec)); -} - #ifdef NOT_USED static void store_native(ulonglong num, uchar *to, uint bytes) { @@ -4883,7 +4971,7 @@ my_time_t Field_timestamp_hires::get_timestamp(ulong *sec_part) const return mi_uint4korr(ptr); } -double Field_timestamp_hires::val_real(void) +double Field_timestamp_with_dec::val_real(void) { MYSQL_TIME ltime; if (get_date(<ime, TIME_NO_ZERO_DATE)) @@ -4894,31 +4982,14 @@ double Field_timestamp_hires::val_real(void) ltime.minute * 1e2 + ltime.second + ltime.second_part*1e-6; } -String *Field_timestamp_hires::val_str(String *val_buffer, String *val_ptr) -{ - String *tmp= Field_timestamp::val_str(val_buffer, val_ptr); - ulong sec_part= (ulong)read_bigendian(ptr+4, sec_part_bytes[dec]); - - if (tmp->ptr() == zero_timestamp) - return tmp; - - char *buf= const_cast<char*>(tmp->ptr() + MAX_DATETIME_WIDTH); - for (int i=dec; i>0; i--, sec_part/=10) - buf[i]= (char)(sec_part % 10) + '0'; - buf[0]= '.'; - buf[dec+1]= 0; - return tmp; -} - - -my_decimal *Field_timestamp_hires::val_decimal(my_decimal *d) +my_decimal *Field_timestamp_with_dec::val_decimal(my_decimal *d) { MYSQL_TIME ltime; get_date(<ime, 0); return TIME_to_my_decimal(<ime, d); } -int Field_timestamp_hires::store_decimal(const my_decimal *d) +int Field_timestamp::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; @@ -4941,7 +5012,7 @@ int Field_timestamp_hires::store_decimal(const my_decimal *d) return store_TIME_with_warning(thd, <ime, &str, error, tmp != -1); } -int Field_timestamp_hires::set_time() +int Field_timestamp_with_dec::set_time() { THD *thd= get_thd(); set_notnull(); @@ -4949,7 +5020,7 @@ int Field_timestamp_hires::set_time() return 0; } -bool Field_timestamp_hires::send_binary(Protocol *protocol) +bool Field_timestamp_with_dec::send_binary(Protocol *protocol) { MYSQL_TIME ltime; Field_timestamp::get_date(<ime, 0); @@ -4970,23 +5041,72 @@ int Field_timestamp_hires::cmp(const uchar *a_ptr, const uchar *b_ptr) } -void Field_timestamp_hires::sort_string(uchar *to,uint length) -{ - DBUG_ASSERT(length == Field_timestamp_hires::pack_length()); - memcpy(to, ptr, length); -} - uint32 Field_timestamp_hires::pack_length() const { return 4 + sec_part_bytes[dec]; } -void Field_timestamp_hires::make_field(Send_field *field) +void Field_timestamp_with_dec::make_field(Send_field *field) { Field::make_field(field); field->decimals= dec; } + +/************************************************************* +** MySQL-5.6 compatible TIMESTAMP(N) +**************************************************************/ + +void Field_timestampf::store_TIME(my_time_t timestamp, ulong sec_part) +{ + struct timeval tm; + tm.tv_sec= timestamp; + tm.tv_usec= sec_part; + my_timeval_trunc(&tm, dec); + my_timestamp_to_binary(&tm, ptr, dec); +} + + +my_time_t Field_timestampf::get_timestamp(ulong *sec_part) const +{ + struct timeval tm; + my_timestamp_from_binary(&tm, ptr, dec); + *sec_part= tm.tv_usec; + return tm.tv_sec; +} + + +/*************************************************************/ +uint Field_temporal::is_equal(Create_field *new_field) +{ + return new_field->sql_type == real_type() && + new_field->length == max_display_length(); +} + + +void Field_temporal::set_warnings(MYSQL_ERROR::enum_warning_level trunc_level, + const ErrConv *str, int was_cut, + timestamp_type ts_type) +{ + /* + error code logic: + MYSQL_TIME_WARN_TRUNCATED means that the value was not a date/time at all. + it will be stored as zero date/time. + MYSQL_TIME_WARN_OUT_OF_RANGE means that the value was a date/time, + that is, it was parsed as such, but the value was invalid. + + Also, MYSQL_TIME_WARN_TRUNCATED is used when storing a DATETIME in + a DATE field and non-zero time part is thrown away. + */ + if (was_cut & MYSQL_TIME_WARN_TRUNCATED) + set_datetime_warning(trunc_level, WARN_DATA_TRUNCATED, + str, mysql_type_to_time_type(type()), 1); + if (was_cut & MYSQL_TIME_WARN_OUT_OF_RANGE) + set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, + str, mysql_type_to_time_type(type()), 1); +} + + /* Store string into a date/time field @@ -4997,21 +5117,21 @@ void Field_timestamp_hires::make_field(Send_field *field) 3 Datetime value that was cut (warning level NOTE) This is used by opt_range.cc:get_mm_leaf(). */ -int Field_temporal::store_TIME_with_warning(MYSQL_TIME *ltime, - const ErrConv *str, - int was_cut, int have_smth_to_conv) +int Field_temporal_with_date::store_TIME_with_warning(MYSQL_TIME *ltime, + const ErrConv *str, + int was_cut, + int have_smth_to_conv) { MYSQL_ERROR::enum_warning_level trunc_level= MYSQL_ERROR::WARN_LEVEL_WARN; int ret= 2; ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; - if (was_cut == 0 && - have_smth_to_conv == 0 && - mysql_type_to_time_type(type()) != MYSQL_TIMESTAMP_TIME) // special case: zero date + if (was_cut == 0 && have_smth_to_conv == 0) // special case: zero date + { was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE; - else - if (!have_smth_to_conv) + } + else if (!have_smth_to_conv) { bzero(ltime, sizeof(*ltime)); was_cut= MYSQL_TIME_WARN_TRUNCATED; @@ -5025,57 +5145,29 @@ int Field_temporal::store_TIME_with_warning(MYSQL_TIME *ltime, was_cut|= MYSQL_TIME_WARN_TRUNCATED; ret= 3; } - else if (!(was_cut & MYSQL_TIME_WARN_TRUNCATED) && - mysql_type_to_time_type(type()) == MYSQL_TIMESTAMP_TIME && - (ltime->year || ltime->month)) - { - ltime->year= ltime->month= ltime->day= 0; - trunc_level= MYSQL_ERROR::WARN_LEVEL_NOTE; - was_cut|= MYSQL_TIME_WARN_TRUNCATED; - ret= 3; - } - - /* - error code logic: - MYSQL_TIME_WARN_TRUNCATED means that the value was not a date/time at all. - it will be stored as zero date/time. - MYSQL_TIME_WARN_OUT_OF_RANGE means that the value was a date/time, - that is, it was parsed as such, but the value was invalid. - - Also, MYSQL_TIME_WARN_TRUNCATED is used when storing a DATETIME in - a DATE field and non-zero time part is thrown away. - */ - if (was_cut & MYSQL_TIME_WARN_TRUNCATED) - set_datetime_warning(trunc_level, WARN_DATA_TRUNCATED, - str, mysql_type_to_time_type(type()), 1); - if (was_cut & MYSQL_TIME_WARN_OUT_OF_RANGE) - set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, - str, mysql_type_to_time_type(type()), 1); - + set_warnings(trunc_level, str, was_cut, mysql_type_to_time_type(type())); store_TIME(ltime); return was_cut ? ret : 0; } -int Field_temporal::store(const char *from,uint len,CHARSET_INFO *cs) +int Field_temporal_with_date::store(const char *from, uint len, CHARSET_INFO *cs) { MYSQL_TIME ltime; - int error; - enum enum_mysql_timestamp_type func_res; + MYSQL_TIME_STATUS status; THD *thd= get_thd(); ErrConvString str(from, len, cs); - - func_res= str_to_datetime(cs, from, len, <ime, - (TIME_FUZZY_DATE | - (thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | - MODE_INVALID_DATES))), - &error); - return store_TIME_with_warning(<ime, &str, error, func_res > MYSQL_TIMESTAMP_ERROR); + bool func_res= !str_to_datetime(cs, from, len, <ime, + (TIME_FUZZY_DATE | + (thd->variables.sql_mode & + (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | + MODE_INVALID_DATES))), + &status); + return store_TIME_with_warning(<ime, &str, status.warnings, func_res); } -int Field_temporal::store(double nr) +int Field_temporal_with_date::store(double nr) { int error= 0; MYSQL_TIME ltime; @@ -5092,7 +5184,7 @@ int Field_temporal::store(double nr) } -int Field_temporal::store(longlong nr, bool unsigned_val) +int Field_temporal_with_date::store(longlong nr, bool unsigned_val) { int error; MYSQL_TIME ltime; @@ -5110,7 +5202,7 @@ int Field_temporal::store(longlong nr, bool unsigned_val) } -int Field_temporal::store_time_dec(MYSQL_TIME *ltime, uint dec) +int Field_temporal_with_date::store_time_dec(MYSQL_TIME *ltime, uint dec) { int error = 0, have_smth_to_conv= 1; MYSQL_TIME l_time= *ltime; @@ -5144,6 +5236,35 @@ my_decimal *Field_temporal::val_decimal(my_decimal *d) ** In number context: HHMMSS ** Stored as a 3 byte unsigned int ****************************************************************************/ +int Field_time::store_TIME_with_warning(MYSQL_TIME *ltime, + const ErrConv *str, + int was_cut, + int have_smth_to_conv) +{ + MYSQL_ERROR::enum_warning_level trunc_level= MYSQL_ERROR::WARN_LEVEL_WARN; + int ret= 2; + + ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; + + if (!have_smth_to_conv) + { + bzero(ltime, sizeof(*ltime)); + was_cut= MYSQL_TIME_WARN_TRUNCATED; + ret= 1; + } + else if (!(was_cut & MYSQL_TIME_WARN_TRUNCATED) && + (ltime->year || ltime->month)) + { + ltime->year= ltime->month= ltime->day= 0; + trunc_level= MYSQL_ERROR::WARN_LEVEL_NOTE; + was_cut|= MYSQL_TIME_WARN_TRUNCATED; + ret= 3; + } + set_warnings(trunc_level, str, was_cut, MYSQL_TIMESTAMP_TIME); + store_TIME(ltime); + return was_cut ? ret : 0; +} + void Field_time::store_TIME(MYSQL_TIME *ltime) { @@ -5157,16 +5278,17 @@ void Field_time::store_TIME(MYSQL_TIME *ltime) int Field_time::store(const char *from,uint len,CHARSET_INFO *cs) { MYSQL_TIME ltime; + MYSQL_TIME_STATUS status; ErrConvString str(from, len, cs); - int was_cut; - int have_smth_to_conv= - str_to_time(cs, from, len, <ime, + bool have_smth_to_conv= + !str_to_time(cs, from, len, <ime, get_thd()->variables.sql_mode & (MODE_NO_ZERO_DATE | MODE_NO_ZERO_IN_DATE | MODE_INVALID_DATES), - &was_cut) > MYSQL_TIMESTAMP_ERROR; + &status); - return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); + return store_TIME_with_warning(<ime, &str, + status.warnings, have_smth_to_conv); } @@ -5229,32 +5351,16 @@ longlong Field_time::val_int(void) my_charset_bin */ -String *Field_time::val_str(String *val_buffer, - String *val_ptr __attribute__((unused))) +String *Field_time::val_str(String *str, + String *unused __attribute__((unused))) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - long tmp=(long) sint3korr(ptr); - ltime.neg= 0; - if (tmp < 0) - { - tmp= -tmp; - ltime.neg= 1; - } - ltime.year= ltime.month= 0; - ltime.day= (uint) 0; - ltime.hour= (uint) (tmp/10000); - ltime.minute= (uint) (tmp/100 % 100); - ltime.second= (uint) (tmp % 100); - ltime.second_part= 0; - - val_buffer->alloc(MAX_DATE_STRING_REP_LENGTH); - uint length= (uint) my_time_to_str(<ime, - const_cast<char*>(val_buffer->ptr()), 0); - val_buffer->length(length); - val_buffer->set_charset(&my_charset_numeric); - - return val_buffer; + get_date(<ime, TIME_TIME_ONLY); + str->alloc(field_length + 1); + str->length(my_time_to_str(<ime, const_cast<char*>(str->ptr()), decimals())); + str->set_charset(&my_charset_numeric); + return str; } @@ -5297,8 +5403,8 @@ bool Field_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) bool Field_time::send_binary(Protocol *protocol) { MYSQL_TIME ltime; - Field_time::get_date(<ime, TIME_TIME_ONLY); - return protocol->store_time(<ime, 0); + get_date(<ime, TIME_TIME_ONLY); + return protocol->store_time(<ime, decimals()); } @@ -5319,7 +5425,14 @@ void Field_time::sort_string(uchar *to,uint length __attribute__((unused))) void Field_time::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("time")); + if (decimals() == 0) + { + res.set_ascii(STRING_WITH_LEN("time")); + return; + } + const CHARSET_INFO *cs= res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "time(%d)", decimals())); } int Field_time_hires::reset() @@ -5335,7 +5448,7 @@ void Field_time_hires::store_TIME(MYSQL_TIME *ltime) store_bigendian(packed, ptr, Field_time_hires::pack_length()); } -int Field_time_hires::store_decimal(const my_decimal *d) +int Field_time::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; @@ -5354,35 +5467,23 @@ uint32 Field_time_hires::pack_length() const return time_hires_bytes[dec]; } -longlong Field_time_hires::val_int(void) +longlong Field_time_with_dec::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); + get_date(<ime, TIME_TIME_ONLY); longlong val= TIME_to_ulonglong_time(<ime); return ltime.neg ? -val : val; } -double Field_time_hires::val_real(void) +double Field_time_with_dec::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); + get_date(<ime, TIME_TIME_ONLY); return TIME_to_double(<ime); } -String *Field_time_hires::val_str(String *str, - String *unused __attribute__((unused))) -{ - ASSERT_COLUMN_MARKED_FOR_READ; - MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); - str->alloc(field_length+1); - str->length(my_time_to_str(<ime, (char*) str->ptr(), dec)); - str->set_charset(&my_charset_bin); - return str; -} - bool Field_time_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { uint32 len= pack_length(); @@ -5402,14 +5503,6 @@ bool Field_time_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) } -bool Field_time_hires::send_binary(Protocol *protocol) -{ - MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); - return protocol->store_time(<ime, dec); -} - - int Field_time_hires::cmp(const uchar *a_ptr, const uchar *b_ptr) { ulonglong a=read_bigendian(a_ptr, Field_time_hires::pack_length()); @@ -5424,17 +5517,36 @@ void Field_time_hires::sort_string(uchar *to,uint length __attribute__((unused)) to[0]^= 128; } -void Field_time_hires::sql_type(String &res) const +void Field_time_with_dec::make_field(Send_field *field) { - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "time(%u)", dec)); + Field::make_field(field); + field->decimals= dec; +} + +/**************************************************************************** +** time type with fsp (MySQL-5.6 version) +** In string context: HH:MM:SS.FFFFFF +** In number context: HHMMSS.FFFFFF +****************************************************************************/ + +int Field_timef::reset() +{ + my_time_packed_to_binary(0, ptr, dec); + return 0; } -void Field_time_hires::make_field(Send_field *field) +void Field_timef::store_TIME(MYSQL_TIME *ltime) { - Field::make_field(field); - field->decimals= dec; + my_time_trunc(ltime, decimals()); + longlong tmp= TIME_to_longlong_time_packed(ltime); + my_time_packed_to_binary(tmp, ptr, dec); +} + +bool Field_timef::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + longlong tmp= my_time_packed_from_binary(ptr, dec); + TIME_from_longlong_time_packed(ltime, tmp); + return false; } /**************************************************************************** @@ -5792,8 +5904,8 @@ bool Field_datetime::send_binary(Protocol *protocol) Field_datetime::get_date(&tm, TIME_FUZZY_DATE); return protocol->store(&tm, 0); } - - + + double Field_datetime::val_real(void) { return (double) Field_datetime::val_int(); @@ -5901,7 +6013,14 @@ void Field_datetime::sort_string(uchar *to,uint length __attribute__((unused))) void Field_datetime::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("datetime")); + if (decimals() == 0) + { + res.set_ascii(STRING_WITH_LEN("datetime")); + return; + } + CHARSET_INFO *cs= res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "datetime(%u)", decimals())); } @@ -5924,7 +6043,7 @@ void Field_datetime_hires::store_TIME(MYSQL_TIME *ltime) store_bigendian(packed, ptr, Field_datetime_hires::pack_length()); } -int Field_datetime_hires::store_decimal(const my_decimal *d) +int Field_temporal_with_date::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; @@ -5949,38 +6068,38 @@ int Field_datetime_hires::store_decimal(const my_decimal *d) return store_TIME_with_warning(<ime, &str, error, tmp != -1); } -bool Field_datetime_hires::send_binary(Protocol *protocol) +bool Field_datetime_with_dec::send_binary(Protocol *protocol) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, TIME_FUZZY_DATE); + get_date(<ime, TIME_FUZZY_DATE); return protocol->store(<ime, dec); } -double Field_datetime_hires::val_real(void) +double Field_datetime_with_dec::val_real(void) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, TIME_FUZZY_DATE); + get_date(<ime, TIME_FUZZY_DATE); return TIME_to_double(<ime); } -longlong Field_datetime_hires::val_int(void) +longlong Field_datetime_with_dec::val_int(void) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, TIME_FUZZY_DATE); + get_date(<ime, TIME_FUZZY_DATE); return TIME_to_ulonglong_datetime(<ime); } -String *Field_datetime_hires::val_str(String *str, - String *unused __attribute__((unused))) +String *Field_datetime_with_dec::val_str(String *str, + String *unused __attribute__((unused))) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, TIME_FUZZY_DATE); + get_date(<ime, TIME_FUZZY_DATE); str->alloc(field_length+1); str->length(field_length); my_datetime_to_str(<ime, (char*) str->ptr(), dec); - str->set_charset(&my_charset_bin); + str->set_charset(&my_charset_numeric); return str; } @@ -6007,27 +6126,42 @@ int Field_datetime_hires::cmp(const uchar *a_ptr, const uchar *b_ptr) return a < b ? -1 : a > b ? 1 : 0; } -void Field_datetime_hires::sort_string(uchar *to, - uint length __attribute__((unused))) +void Field_datetime_with_dec::make_field(Send_field *field) { - DBUG_ASSERT(length == Field_datetime_hires::pack_length()); - memcpy(to, ptr, length); + Field::make_field(field); + field->decimals= dec; } -void Field_datetime_hires::sql_type(String &res) const +/**************************************************************************** +** MySQL-5.6 compatible DATETIME(N) +** +****************************************************************************/ +int Field_datetimef::reset() { - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "datetime(%u)", dec)); + my_datetime_packed_to_binary(0, ptr, dec); + return 0; } -void Field_datetime_hires::make_field(Send_field *field) +void Field_datetimef::store_TIME(MYSQL_TIME *ltime) { - Field::make_field(field); - field->decimals= dec; + my_time_trunc(ltime, decimals()); + longlong tmp= TIME_to_longlong_datetime_packed(ltime); + my_datetime_packed_to_binary(tmp, ptr, dec); +} + +bool Field_datetimef::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + longlong tmp= my_datetime_packed_from_binary(ptr, dec); + TIME_from_longlong_datetime_packed(ltime, tmp); + if (!tmp) + return fuzzydate & TIME_NO_ZERO_DATE; + if (!ltime->month || !ltime->day) + return !(fuzzydate & TIME_FUZZY_DATE); + return false; } + /**************************************************************************** ** string type ** A string may be varchar or binary @@ -6198,80 +6332,6 @@ int Field_str::store(double nr) return store(buff, length, &my_charset_numeric); } -static -inline ulonglong char_prefix_to_ulonglong(uchar *src) -{ - uint sz= sizeof(ulonglong); - for (uint i= 0; i < sz/2; i++) - { - uchar tmp= src[i]; - src[i]= src[sz-1-i]; - src[sz-1-i]= tmp; - } - return uint8korr(src); -} - -/** - @brief - Determine the relative position of the field value in a string interval - - @details - The function returns a double number between 0.0 and 1.0 as the relative - position of the value of the this field in the string interval of [min,max]. - If the value is not in the interval the the function returns 0.0 when - the value is less than min, and, 1.0 when the value is greater than max. - - @note - To calculate the relative position of the string value v in the interval - [min, max] the function first converts the beginning of these three - strings v, min, max into the strings that are used for byte comparison. - For each string not more sizeof(ulonglong) first bytes are taken - from the result of conversion. Then these bytes are interpreted as the - big-endian representation of an ulonglong integer. The values of these - integer numbers obtained for the strings v, min, max are used to calculate - the position of v in [min,max] in the same way is it's done for numeric - fields (see Field_num::pos_in_interval). - - @todo - Improve the procedure for the case when min and max have the same - beginning - - @param min value of the left end of the interval - @param max value of the right end of the interval - - @return - relative position of the field value in the string interval [min,max] -*/ - -double Field_str::pos_in_interval(Field *min, Field *max) -{ - uchar mp_prefix[sizeof(ulonglong)]; - uchar minp_prefix[sizeof(ulonglong)]; - uchar maxp_prefix[sizeof(ulonglong)]; - ulonglong mp, minp, maxp; - my_strnxfrm(charset(), mp_prefix, sizeof(mp), - ptr + length_size(), - data_length()); - my_strnxfrm(charset(), minp_prefix, sizeof(minp), - min->ptr + length_size(), - min->data_length()); - my_strnxfrm(charset(), maxp_prefix, sizeof(maxp), - max->ptr + length_size(), - max->data_length()); - mp= char_prefix_to_ulonglong(mp_prefix); - minp= char_prefix_to_ulonglong(minp_prefix); - maxp= char_prefix_to_ulonglong(maxp_prefix); - double n, d; - n= mp - minp; - if (n < 0) - return 0.0; - d= maxp - minp; - if (d <= 0) - return 1.0; - return min(n/d, 1.0); -} - - uint Field::is_equal(Create_field *new_field) { return (new_field->sql_type == real_type()); @@ -8482,36 +8542,6 @@ my_decimal *Field_bit::val_decimal(my_decimal *deciaml_value) } -/** - @brief - Determine the relative position of the field value in a bit interval - - @details - The function returns a double number between 0.0 and 1.0 as the relative - position of the value of the this field in the bit interval of [min,max]. - If the value is not in the interval the the function returns 0.0 when - the value is less than min, and, 1.0 when the value is greater than max. - - @param min value of the left end of the interval - @param max value of the right end of the interval - - @return - relative position of the field value in the bit interval [min,max] -*/ - -double Field_bit::pos_in_interval(Field *min, Field *max) -{ - double n, d; - n= val_real() - min->val_real(); - if (n < 0) - return 0.0; - d= max->val_real() - min->val_real(); - if (d <= 0) - return 1.0; - return min(n/d, 1.0); -} - - /* Compare two bit fields using pointers within the record. SYNOPSIS @@ -9144,7 +9174,7 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, it is NOT NULL, not an AUTO_INCREMENT field and not a TIMESTAMP. */ if (!fld_default_value && !(fld_type_modifier & AUTO_INCREMENT_FLAG) && - (fld_type_modifier & NOT_NULL_FLAG) && fld_type != MYSQL_TYPE_TIMESTAMP) + (fld_type_modifier & NOT_NULL_FLAG) && !is_timestamp_type(fld_type)) flags|= NO_DEFAULT_VALUE_FLAG; if (fld_length != NULL) @@ -9306,6 +9336,7 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, } break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: if (length > MAX_DATETIME_PRECISION) { my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, @@ -9323,6 +9354,7 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, length= MAX_DATE_WIDTH; break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: if (length > MAX_DATETIME_PRECISION) { my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, @@ -9332,6 +9364,7 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, length+= MIN_TIME_WIDTH + (length ? 1 : 0); break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: if (length > MAX_DATETIME_PRECISION) { my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, @@ -9413,17 +9446,6 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, DBUG_RETURN(TRUE); } - switch (fld_type) { - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - charset= &my_charset_numeric; - flags|= BINARY_FLAG; - default: break; - } - DBUG_RETURN(FALSE); /* success */ } @@ -9462,10 +9484,16 @@ uint32 calc_pack_length(enum_field_types type,uint32 length) case MYSQL_TYPE_TIME: return length > MIN_TIME_WIDTH ? time_hires_bytes[length - 1 - MIN_TIME_WIDTH] : 3; + case MYSQL_TYPE_TIME2: + return length > MIN_TIME_WIDTH ? + my_time_binary_length(length - MIN_TIME_WIDTH - 1) : 3; case MYSQL_TYPE_TIMESTAMP: return length > MAX_DATETIME_WIDTH ? 4 + sec_part_bytes[length - 1 - MAX_DATETIME_WIDTH] : 4; + case MYSQL_TYPE_TIMESTAMP2: + return length > MAX_DATETIME_WIDTH ? + my_timestamp_binary_length(length - MAX_DATETIME_WIDTH - 1) : 4; case MYSQL_TYPE_DATE: case MYSQL_TYPE_LONG : return 4; case MYSQL_TYPE_FLOAT : return sizeof(float); @@ -9474,6 +9502,9 @@ uint32 calc_pack_length(enum_field_types type,uint32 length) return length > MAX_DATETIME_WIDTH ? datetime_hires_bytes[length - 1 - MAX_DATETIME_WIDTH] : 8; + case MYSQL_TYPE_DATETIME2: + return length > MAX_DATETIME_WIDTH ? + my_datetime_binary_length(length - MAX_DATETIME_WIDTH - 1) : 5; case MYSQL_TYPE_LONGLONG: return 8; /* Don't crash if no longlong */ case MYSQL_TYPE_NULL : return 0; case MYSQL_TYPE_TINY_BLOB: return 1+portable_sizeof_char_ptr; @@ -9538,16 +9569,6 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, null_bit= ((uchar) 1) << null_bit; } - switch (field_type) { - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - field_charset= &my_charset_numeric; - default: break; - } - DBUG_PRINT("debug", ("field_type: %d, field_length: %u, interval: %p, pack_flag: %s%s%s%s%s", field_type, field_length, interval, FLAGSTR(pack_flag, FIELDFLAG_BINARY), @@ -9661,30 +9682,51 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, uint dec= field_length > MAX_DATETIME_WIDTH ? field_length - MAX_DATETIME_WIDTH - 1: 0; return new_Field_timestamp(ptr, null_pos, null_bit, unireg_check, - field_name, share, dec, field_charset); + field_name, share, dec); + } + case MYSQL_TYPE_TIMESTAMP2: + { + uint dec= field_length > MAX_DATETIME_WIDTH ? + field_length - MAX_DATETIME_WIDTH - 1: 0; + return new Field_timestampf(ptr, null_pos, null_bit, unireg_check, + field_name, share, dec); } case MYSQL_TYPE_YEAR: return new Field_year(ptr,field_length,null_pos,null_bit, unireg_check, field_name); case MYSQL_TYPE_DATE: return new Field_date(ptr,null_pos,null_bit, - unireg_check, field_name, field_charset); + unireg_check, field_name); case MYSQL_TYPE_NEWDATE: return new Field_newdate(ptr,null_pos,null_bit, - unireg_check, field_name, field_charset); + unireg_check, field_name); case MYSQL_TYPE_TIME: { uint dec= field_length > MIN_TIME_WIDTH ? field_length - MIN_TIME_WIDTH - 1: 0; return new_Field_time(ptr, null_pos, null_bit, unireg_check, - field_name, dec, field_charset); + field_name, dec); + } + case MYSQL_TYPE_TIME2: + { + uint dec= field_length > MIN_TIME_WIDTH ? + field_length - MIN_TIME_WIDTH - 1: 0; + return new Field_timef(ptr, null_pos, null_bit, unireg_check, + field_name, dec); } case MYSQL_TYPE_DATETIME: { uint dec= field_length > MAX_DATETIME_WIDTH ? field_length - MAX_DATETIME_WIDTH - 1: 0; return new_Field_datetime(ptr, null_pos, null_bit, unireg_check, - field_name, dec, field_charset); + field_name, dec); + } + case MYSQL_TYPE_DATETIME2: + { + uint dec= field_length > MAX_DATETIME_WIDTH ? + field_length - MAX_DATETIME_WIDTH - 1: 0; + return new Field_datetimef(ptr, null_pos, null_bit, unireg_check, + field_name, dec); } case MYSQL_TYPE_NULL: return new Field_null(ptr, field_length, unireg_check, field_name, diff --git a/sql/field.h b/sql/field.h index 48d873beb32..feef0cbef08 100644 --- a/sql/field.h +++ b/sql/field.h @@ -30,6 +30,7 @@ #include "sql_string.h" /* String */ #include "my_decimal.h" /* my_decimal */ #include "sql_error.h" /* MYSQL_ERROR */ +#include "compat56.h" class Send_field; class Protocol; @@ -89,6 +90,42 @@ inline uint get_set_pack_length(int elements) return len > 4 ? 8 : len; } + +/** + Recognizer for concrete data type (called real_type for some reason), + returning true if it is one of the TIMESTAMP types. +*/ +inline bool is_timestamp_type(enum_field_types type) +{ + return type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_TIMESTAMP2; +} + + +/** + Convert temporal real types as retuned by field->real_type() + to field type as returned by field->type(). + + @param real_type Real type. + @retval Field type. +*/ +inline enum_field_types real_type_to_type(enum_field_types real_type) +{ + switch (real_type) + { + case MYSQL_TYPE_TIME2: + return MYSQL_TYPE_TIME; + case MYSQL_TYPE_DATETIME2: + return MYSQL_TYPE_DATETIME; + case MYSQL_TYPE_TIMESTAMP2: + return MYSQL_TYPE_TIMESTAMP; + case MYSQL_TYPE_NEWDATE: + return MYSQL_TYPE_DATE; + /* Note: NEWDECIMAL is a type, not only a real_type */ + default: return real_type; + } +} + + /* Virtual_column_info is the class to contain additional characteristics that is specific for a virtual/computed @@ -428,6 +465,54 @@ public: virtual uint32 key_length() const { return pack_length(); } virtual enum_field_types type() const =0; virtual enum_field_types real_type() const { return type(); } + virtual enum_field_types binlog_type() const + { + /* + Binlog stores field->type() as type code by default. For example, + it puts MYSQL_TYPE_STRING in case of CHAR, VARCHAR, SET and ENUM, + with extra data type details put into metadata. + + Binlog behaviour slightly differs between various MySQL and MariaDB + versions for the temporal data types TIME, DATETIME and TIMESTAMP. + + MySQL prior to 5.6 uses MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME + and MYSQL_TYPE_TIMESTAMP type codes in binlog and stores no + additional metadata. + + MariaDB-5.3 implements new versions for TIME, DATATIME, TIMESTAMP + with fractional second precision, but uses the old format for the + types TIME(0), DATETIME(0), TIMESTAMP(0), and it still stores + MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP in binlog, + with no additional metadata. + So row-based replication between temporal data types of + different precision is not possible in MariaDB. + + MySQL-5.6 also implements a new version of TIME, DATETIME, TIMESTAMP + which support fractional second precision 0..6, and use the new + format even for the types TIME(0), DATETIME(0), TIMESTAMP(0). + For these new data types, MySQL-5.6 stores new type codes + MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, MYSQL_TYPE_TIMESTAMP2 in binlog, + with fractional precision 0..6 put into metadata. + This makes it in theory possible to do row-based replication between + columns of different fractional precision (e.g. from TIME(1) on master + to TIME(6) on slave). However, it's not currently fully implemented yet. + MySQL-5.6 can only do row-based replication from the old types + TIME, DATETIME, TIMESTAMP (represented by MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP type codes in binlog) + to the new corresponding types TIME(0), DATETIME(0), TIMESTAMP(0). + + Note: MariaDB starting from the version 10.0 understands the new + MySQL-5.6 type codes MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, + MYSQL_TYPE_TIMESTAMP2. When started over MySQL-5.6 tables both on + master and on slave, MariaDB-10.0 can also do row-based replication + from the old types TIME, DATETIME, TIMESTAMP to the new MySQL-5.6 + types TIME(0), DATETIME(0), TIMESTAMP(0). + + Note: perhaps binlog should eventually be modified to store + real_type() instead of type() for all column types. + */ + return type(); + } inline int cmp(const uchar *str) { return cmp(ptr,str); } virtual int cmp_max(const uchar *a, const uchar *b, uint max_len) { return cmp(a, b); } @@ -661,6 +746,16 @@ public: { return binary() ? &my_charset_bin : charset(); } virtual CHARSET_INFO *sort_charset(void) const { return charset(); } virtual bool has_charset(void) const { return FALSE; } + /* + match_collation_to_optimize_range() is to distinguish in + range optimizer (see opt_range.cc) between real string types: + CHAR, VARCHAR, TEXT + and the other string-alike types with result_type() == STRING_RESULT: + DATE, TIME, DATETIME, TIMESTAMP + We need it to decide whether to test if collation of the operation + matches collation of the field (needed only for real string types). + */ + virtual bool match_collation_to_optimize_range() const { return false; } virtual void set_charset(CHARSET_INFO *charset_arg) { } virtual enum Derivation derivation(void) const { return DERIVATION_IMPLICIT; } @@ -808,7 +903,8 @@ protected: { return (flags & (BINCMP_FLAG | BINARY_FLAG)) != 0; } - + double pos_in_interval_val_real(Field *min, Field *max); + double pos_in_interval_val_str(Field *min, Field *max, uint data_offset); }; @@ -847,7 +943,10 @@ public: bool get_int(CHARSET_INFO *cs, const char *from, uint len, longlong *rnd, ulonglong unsigned_max, longlong signed_min, longlong signed_max); - double pos_in_interval(Field *min, Field *max); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } }; @@ -860,23 +959,11 @@ public: uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, CHARSET_INFO *charset); Item_result result_type () const { return STRING_RESULT; } - /* - match_collation_to_optimize_range() is to distinguish in - range optimizer (see opt_range.cc) between real string types: - CHAR, VARCHAR, TEXT - and the other string-alike types with result_type() == STRING_RESULT: - DATE, TIME, DATETIME, TIMESTAMP - We need it to decide whether to test if collation of the operation - matches collation of the field (needed only for real string types). - QQ: shouldn't DATE/TIME types have their own XXX_RESULT types eventually? - */ - virtual bool match_collation_to_optimize_range() const=0; uint decimals() const { return NOT_FIXED_DEC; } int store(double nr); int store(longlong nr, bool unsigned_val)=0; int store_decimal(const my_decimal *); int store(const char *to,uint length,CHARSET_INFO *cs)=0; - uint size_of() const { return sizeof(*this); } uint repertoire(void) const { return my_charset_repertoire(field_charset); @@ -894,7 +981,10 @@ public: uint is_equal(Create_field *new_field); bool eq_cmp_as_binary() { return test(flags & BINARY_FLAG); } virtual uint length_size() { return 0; } - double pos_in_interval(Field *min, Field *max); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_str(min, max, length_size()); + } }; /* base class for Field_string, Field_varstring and Field_blob */ @@ -914,6 +1004,7 @@ public: int store_decimal(const my_decimal *d); uint32 max_data_length() const; + bool match_collation_to_optimize_range() const { return true; } }; /* base class for float and double and decimal (old one) */ @@ -1323,7 +1414,6 @@ public: unireg_check_arg, field_name_arg, cs) {} enum_field_types type() const { return MYSQL_TYPE_NULL;} - bool match_collation_to_optimize_range() const { return FALSE; } int store(const char *to, uint length, CHARSET_INFO *cs) { null[0]=1; return 0; } int store(double nr) { null[0]=1; return 0; } @@ -1345,7 +1435,67 @@ public: }; -class Field_timestamp :public Field_str { +class Field_temporal: public Field { +public: + Field_temporal(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, + uchar null_bit_arg, utype unireg_check_arg, + const char *field_name_arg) + :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, + field_name_arg) + { flags|= BINARY_FLAG; } + Item_result result_type () const { return STRING_RESULT; } + uint32 max_display_length() { return field_length; } + bool str_needs_quotes() { return TRUE; } + enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } + uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } + CHARSET_INFO *charset(void) const { return &my_charset_numeric; } + const CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; } + bool binary() const { return true; } + enum Item_result cmp_type () const { return TIME_RESULT; } + uint is_equal(Create_field *new_field); + bool eq_def(Field *field) + { + return (Field::eq_def(field) && decimals() == field->decimals()); + } + my_decimal *val_decimal(my_decimal*); + void set_warnings(MYSQL_ERROR::enum_warning_level trunc_level, + const ErrConv *str, int was_cut, timestamp_type ts_type); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } +}; + + +/** + Abstract class for: + - DATE + - DATETIME + - DATETIME(1..6) + - DATETIME(0..6) - MySQL56 version +*/ +class Field_temporal_with_date: public Field_temporal { +protected: + int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, + int was_cut, int have_smth_to_conv); + virtual void store_TIME(MYSQL_TIME *ltime) = 0; +public: + Field_temporal_with_date(uchar *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + utype unireg_check_arg, + const char *field_name_arg) + :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) + {} + int store(const char *to, uint length, CHARSET_INFO *charset); + int store(double nr); + int store(longlong nr, bool unsigned_val); + int store_time_dec(MYSQL_TIME *ltime, uint dec); + int store_decimal(const my_decimal *); +}; + + +class Field_timestamp :public Field_temporal { protected: int store_TIME_with_warning(THD *, MYSQL_TIME *, const ErrConv *, bool, bool); @@ -1353,21 +1503,14 @@ public: Field_timestamp(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, CHARSET_INFO *cs); - Field_timestamp(bool maybe_null_arg, const char *field_name_arg, - CHARSET_INFO *cs); + TABLE_SHARE *share); enum_field_types type() const { return MYSQL_TYPE_TIMESTAMP;} - bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } - enum Item_result cmp_type () const { return TIME_RESULT; } - enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } - uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } - CHARSET_INFO *charset(void) const { return &my_charset_numeric; } - bool binary() const { return 1; } int store(const char *to,uint length,CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); int store_time_dec(MYSQL_TIME *ltime, uint dec); + int store_decimal(const my_decimal *); double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1377,7 +1520,6 @@ public: uint32 pack_length() const { return 4; } void sql_type(String &str) const; bool zero_pack() const { return 0; } - uint decimals() const { return 0; } virtual int set_time(); virtual void set_default() { @@ -1418,46 +1560,109 @@ public: { return unpack_int32(to, from, from_end); } + uint size_of() const { return sizeof(*this); } }; -class Field_timestamp_hires :public Field_timestamp { +/** + Abstract class for: + - TIMESTAMP(1..6) + - TIMESTAMP(0..6) - MySQL56 version +*/ +class Field_timestamp_with_dec :public Field_timestamp { +protected: uint dec; public: - Field_timestamp_hires(uchar *ptr_arg, - uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, uint dec_arg, CHARSET_INFO *cs) : - Field_timestamp(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + 1, null_ptr_arg, - null_bit_arg, unireg_check_arg, field_name_arg, share, cs), + Field_timestamp_with_dec(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp(ptr_arg, + MAX_DATETIME_WIDTH + dec_arg + test(dec_arg), null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, share), dec(dec_arg) { - DBUG_ASSERT(dec); DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); } - void sql_type(String &str) const; - my_time_t get_timestamp(ulong *sec_part) const; - void store_TIME(my_time_t timestamp, ulong sec_part); - int store_decimal(const my_decimal *d); - double val_real(void); - String *val_str(String*,String *); - my_decimal* val_decimal(my_decimal*); - bool send_binary(Protocol *protocol); - int cmp(const uchar *,const uchar *); - void sort_string(uchar *buff,uint length); uint decimals() const { return dec; } - int set_time(); enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } - void make_field(Send_field *field); - uint32 pack_length() const; uchar *pack(uchar *to, const uchar *from, uint max_length) { return Field::pack(to, from, max_length); } const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data) { return Field::unpack(to, from, from_end, param_data); } + void make_field(Send_field *field); + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == pack_length()); + memcpy(to, ptr, length); + } + bool send_binary(Protocol *protocol); + double val_real(void); + my_decimal* val_decimal(my_decimal*); + int set_time(); +}; + + +class Field_timestamp_hires :public Field_timestamp_with_dec { +public: + Field_timestamp_hires(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, share, dec_arg) + { + DBUG_ASSERT(dec); + } + my_time_t get_timestamp(ulong *sec_part) const; + void store_TIME(my_time_t timestamp, ulong sec_part); + int cmp(const uchar *,const uchar *); + uint32 pack_length() const; + uint size_of() const { return sizeof(*this); } +}; + + +/** + TIMESTAMP(0..6) - MySQL56 version +*/ +class Field_timestampf :public Field_timestamp_with_dec { + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_timestampf(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, share, dec_arg) + {} + enum_field_types real_type() const { return MYSQL_TYPE_TIMESTAMP2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_TIMESTAMP2; } + uint32 pack_length() const + { + return my_timestamp_binary_length(dec); + } + uint row_pack_length() { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_timestampf::pack_length_from_metadata"); + uint tmp= my_timestamp_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + int cmp(const uchar *a_ptr,const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + void store_TIME(my_time_t timestamp, ulong sec_part); + my_time_t get_timestamp(ulong *sec_part) const; uint size_of() const { return sizeof(*this); } - bool eq_def(Field *field) - { return Field_str::eq_def(field) && dec == field->decimals(); } }; @@ -1484,56 +1689,24 @@ public: }; -class Field_temporal: public Field_str { -protected: - int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, - int was_cut, int have_smth_to_conv); - virtual void store_TIME(MYSQL_TIME *ltime) = 0; -public: - Field_temporal(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, - uchar null_bit_arg, utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *charset_arg) - :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, - field_name_arg, charset_arg) - { flags|= BINARY_FLAG; } - enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } - uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } - CHARSET_INFO *charset(void) const { return &my_charset_numeric; } - bool binary() const { return 1; } - bool match_collation_to_optimize_range() const { return FALSE; } - enum Item_result cmp_type () const { return TIME_RESULT; } - int store(const char *to,uint length,CHARSET_INFO *charset); - int store(double nr); - int store(longlong nr, bool unsigned_val); - int store_time_dec(MYSQL_TIME *ltime, uint dec); - my_decimal *val_decimal(my_decimal*); - bool eq_def(Field *field) - { - return (Field_str::eq_def(field) && decimals() == field->decimals()); - } -}; - -class Field_date :public Field_temporal { +class Field_date :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); public: Field_date(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - CHARSET_INFO *cs) - :Field_temporal(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) {} + enum utype unireg_check_arg, const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATE;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } int reset(void) { ptr[0]=ptr[1]=ptr[2]=ptr[3]=0; return 0; } double val_real(void); longlong val_int(void); String *val_str(String*,String *); - uint decimals() const { return 0; } bool send_binary(Protocol *protocol); int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 4; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } uchar *pack(uchar* to, const uchar *from, uint max_length __attribute__((unused))) { @@ -1544,23 +1717,22 @@ public: { return unpack_int32(to, from, from_end); } + uint size_of() const { return sizeof(*this); } }; -class Field_newdate :public Field_temporal { +class Field_newdate :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); public: Field_newdate(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - CHARSET_INFO *cs) - :Field_temporal(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + enum utype unireg_check_arg, const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATE;} enum_field_types real_type() const { return MYSQL_TYPE_NEWDATE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; } int reset(void) { ptr[0]=ptr[1]=ptr[2]=0; return 0; } - uint decimals() const { return 0; } double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1569,19 +1741,22 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + uint size_of() const { return sizeof(*this); } }; class Field_time :public Field_temporal { - void store_TIME(MYSQL_TIME *ltime); +protected: + virtual void store_TIME(MYSQL_TIME *ltime); + int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, + int was_cut, int have_smth_to_conv); public: Field_time(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *cs) + const char *field_name_arg) :Field_temporal(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_TIME;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_INT24; } @@ -1589,7 +1764,7 @@ public: int store(const char *to,uint length,CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); - uint decimals() const { return 0; } + int store_decimal(const my_decimal *); double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1599,55 +1774,122 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } + uint size_of() const { return sizeof(*this); } }; -class Field_time_hires :public Field_time { + +/** + Abstract class for: + - TIME(1..6) + - TIME(0..6) - MySQL56 version +*/ +class Field_time_with_dec :public Field_time { +protected: uint dec; +public: + Field_time_with_dec(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, const char *field_name_arg, + uint dec_arg) + :Field_time(ptr_arg, MIN_TIME_WIDTH + dec_arg + test(dec_arg), null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg), + dec(dec_arg) + { + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + } + uint decimals() const { return dec; } + enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } + longlong val_int(void); + double val_real(void); + void make_field(Send_field *); +}; + + +/** + TIME(1..6) +*/ +class Field_time_hires :public Field_time_with_dec { longlong zero_point; void store_TIME(MYSQL_TIME *ltime); public: Field_time_hires(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - uint dec_arg, CHARSET_INFO *cs) - :Field_time(ptr_arg, MIN_TIME_WIDTH + dec_arg + 1, null_ptr_arg, - null_bit_arg, unireg_check_arg, field_name_arg, cs), - dec(dec_arg) + uint dec_arg) + :Field_time_with_dec(ptr_arg, null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, + dec_arg) { DBUG_ASSERT(dec); - DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); zero_point= sec_part_shift( ((TIME_MAX_VALUE_SECONDS+1LL)*TIME_SECOND_PART_FACTOR), dec); } - enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } - uint decimals() const { return dec; } - int store_decimal(const my_decimal *d); - longlong val_int(void); - double val_real(void); - String *val_str(String*,String *); int reset(void); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); - bool send_binary(Protocol *protocol); int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const; - void sql_type(String &str) const; - void make_field(Send_field *); uint size_of() const { return sizeof(*this); } }; -class Field_datetime :public Field_temporal { + +/** + TIME(0..6) - MySQL56 version +*/ +class Field_timef :public Field_time_with_dec { + void store_TIME(MYSQL_TIME *ltime); + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_timef(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, const char *field_name_arg, + uint dec_arg) + :Field_time_with_dec(ptr_arg, null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, + dec_arg) + { + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + } + enum_field_types real_type() const { return MYSQL_TYPE_TIME2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_TIME2; } + uint32 pack_length() const + { + return my_time_binary_length(dec); + } + uint row_pack_length() { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_timef::pack_length_from_metadata"); + uint tmp= my_time_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == Field_timef::pack_length()); + memcpy(to, ptr, length); + } + int cmp(const uchar *a_ptr, const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + int reset(); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + uint size_of() const { return sizeof(*this); } +}; + + +class Field_datetime :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); public: Field_datetime(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *cs) - :Field_temporal(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATETIME;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONGLONG; } - uint decimals() const { return 0; } double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1656,7 +1898,6 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 8; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); virtual int set_time(); virtual void set_default() @@ -1690,85 +1931,149 @@ public: { return unpack_int64(to, from, from_end); } + uint size_of() const { return sizeof(*this); } }; -class Field_datetime_hires :public Field_datetime { - void store_TIME(MYSQL_TIME *ltime); +/** + Abstract class for: + - DATETIME(1..6) + - DATETIME(0..6) - MySQL56 version +*/ +class Field_datetime_with_dec :public Field_datetime { +protected: uint dec; public: - Field_datetime_hires(uchar *ptr_arg, uchar *null_ptr_arg, - uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, uint dec_arg, - CHARSET_INFO *cs) - :Field_datetime(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + 1, + Field_datetime_with_dec(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + test(dec_arg), null_ptr_arg, null_bit_arg, unireg_check_arg, - field_name_arg, cs), dec(dec_arg) + field_name_arg), dec(dec_arg) { - DBUG_ASSERT(dec); DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); } - enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } uint decimals() const { return dec; } + enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } void make_field(Send_field *field); - int store_decimal(const my_decimal *d); - double val_real(void); - longlong val_int(void); - String *val_str(String*,String *); bool send_binary(Protocol *protocol); - int cmp(const uchar *,const uchar *); - void sort_string(uchar *buff,uint length); - uint32 pack_length() const; - void sql_type(String &str) const; - bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); uchar *pack(uchar *to, const uchar *from, uint max_length) { return Field::pack(to, from, max_length); } const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data) { return Field::unpack(to, from, from_end, param_data); } + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == pack_length()); + memcpy(to, ptr, length); + } + double val_real(void); + longlong val_int(void); + String *val_str(String*,String *); +}; + + +/** + DATETIME(1..6) +*/ +class Field_datetime_hires :public Field_datetime_with_dec { + void store_TIME(MYSQL_TIME *ltime); +public: + Field_datetime_hires(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, dec_arg) + { + DBUG_ASSERT(dec); + } + int cmp(const uchar *,const uchar *); + uint32 pack_length() const; + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); uint size_of() const { return sizeof(*this); } }; + +/** + DATETIME(0..6) - MySQL56 version +*/ +class Field_datetimef :public Field_datetime_with_dec { + void store_TIME(MYSQL_TIME *ltime); + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_datetimef(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, dec_arg) + {} + enum_field_types real_type() const { return MYSQL_TYPE_DATETIME2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_DATETIME2; } + uint32 pack_length() const + { + return my_datetime_binary_length(dec); + } + uint row_pack_length() { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_datetimef::pack_length_from_metadata"); + uint tmp= my_datetime_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + int cmp(const uchar *a_ptr, const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + int reset(); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + uint size_of() const { return sizeof(*this); } +}; + + static inline Field_timestamp * new_Field_timestamp(uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, const char *field_name, - TABLE_SHARE *share, uint dec, CHARSET_INFO *cs) + TABLE_SHARE *share, uint dec) { if (dec==0) return new Field_timestamp(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, share, cs); + unireg_check, field_name, share); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; return new Field_timestamp_hires(ptr, null_ptr, null_bit, unireg_check, - field_name, share, dec, cs); + field_name, share, dec); } static inline Field_time * new_Field_time(uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, const char *field_name, - uint dec, CHARSET_INFO *cs) + uint dec) { if (dec == 0) return new Field_time(ptr, MIN_TIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, cs); + unireg_check, field_name); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; return new Field_time_hires(ptr, null_ptr, null_bit, - unireg_check, field_name, dec, cs); + unireg_check, field_name, dec); } static inline Field_datetime * new_Field_datetime(uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, - const char *field_name, uint dec, CHARSET_INFO *cs) + const char *field_name, uint dec) { if (dec == 0) return new Field_datetime(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, cs); + unireg_check, field_name); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; return new Field_datetime_hires(ptr, null_ptr, null_bit, - unireg_check, field_name, dec, cs); + unireg_check, field_name, dec); } class Field_string :public Field_longstr { @@ -1795,7 +2100,6 @@ public: orig_table->s->frm_version < FRM_VER_TRUE_VARCHAR ? MYSQL_TYPE_VAR_STRING : MYSQL_TYPE_STRING); } - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_BINARY : HA_KEYTYPE_TEXT; } bool zero_pack() const { return 0; } @@ -1876,7 +2180,6 @@ public: } enum_field_types type() const { return MYSQL_TYPE_VARCHAR; } - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const; uint row_pack_length() { return field_length; } bool zero_pack() const { return 0; } @@ -1972,7 +2275,6 @@ public: :Field_longstr((uchar*) 0, 0, (uchar*) "", 0, NONE, "temp", system_charset_info), packlength(packlength_arg) {} enum_field_types type() const { return MYSQL_TYPE_BLOB;} - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2; } int store(const char *to,uint length,CHARSET_INFO *charset); @@ -2118,7 +2420,7 @@ public: { geom_type= geom_type_arg; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_VARBINARY2; } enum_field_types type() const { return MYSQL_TYPE_GEOMETRY; } - bool match_collation_to_optimize_range() const { return FALSE; } + bool match_collation_to_optimize_range() const { return false; } void sql_type(String &str) const; int store(const char *to, uint length, CHARSET_INFO *charset); int store(double nr); @@ -2156,7 +2458,6 @@ public: } Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); enum_field_types type() const { return MYSQL_TYPE_STRING; } - bool match_collation_to_optimize_range() const { return FALSE; } enum Item_result cmp_type () const { return INT_RESULT; } enum ha_base_keytype key_type() const; int store(const char *to,uint length,CHARSET_INFO *charset); @@ -2309,7 +2610,10 @@ public: { store(*((longlong *)val), TRUE); } - double pos_in_interval(Field *min, Field *max); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } void get_image(uchar *buff, uint length, CHARSET_INFO *cs) { get_key_image(buff, length, itRAW); } void set_image(const uchar *buff,uint length, CHARSET_INFO *cs) diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index bd08cd2dc32..09ab6d48eba 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2005, 2012, Oracle and/or its affiliates. + Copyright (c) 2005, 2013, Oracle and/or its affiliates. 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 @@ -63,6 +63,8 @@ #include "key.h" #include "sql_plugin.h" #include "table.h" /* HA_DATA_PARTITION */ +#include "sql_show.h" // append_identifier +#include "sql_admin.h" // SQL_ADMIN_MSG_TEXT_SIZE #include "debug_sync.h" @@ -287,6 +289,7 @@ void ha_partition::init_handler_variables() m_rec_length= 0; m_last_part= 0; m_rec0= 0; + m_err_rec= NULL; m_curr_key_info[0]= NULL; m_curr_key_info[1]= NULL; m_part_func_monotonicity_info= NON_MONOTONIC; @@ -1046,10 +1049,11 @@ int ha_partition::preload_keys(THD *thd, HA_CHECK_OPT *check_opt) 0 Success */ -static int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, - handler *file, uint flag) +int ha_partition::handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, + uint part_id, uint flag) { int error; + handler *file= m_file[part_id]; DBUG_ENTER("handle_opt_part"); DBUG_PRINT("enter", ("flag = %u", flag)); @@ -1058,9 +1062,27 @@ static int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, else if (flag == ANALYZE_PARTS) error= file->ha_analyze(thd, check_opt); else if (flag == CHECK_PARTS) + { error= file->ha_check(thd, check_opt); + if (!error || + error == HA_ADMIN_ALREADY_DONE || + error == HA_ADMIN_NOT_IMPLEMENTED) + { + if (check_opt->flags & (T_MEDIUM | T_EXTEND)) + error= check_misplaced_rows(part_id, false); + } + } else if (flag == REPAIR_PARTS) + { error= file->ha_repair(thd, check_opt); + if (!error || + error == HA_ADMIN_ALREADY_DONE || + error == HA_ADMIN_NOT_IMPLEMENTED) + { + if (check_opt->flags & (T_MEDIUM | T_EXTEND)) + error= check_misplaced_rows(part_id, true); + } + } else if (flag == ASSIGN_KEYCACHE_PARTS) error= file->assign_to_keycache(thd, check_opt); else if (flag == PRELOAD_KEYS_PARTS) @@ -1082,11 +1104,11 @@ static int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, TODO: move this into the handler, or rewrite mysql_admin_table. */ static bool print_admin_msg(THD* thd, const char* msg_type, - const char* db_name, const char* table_name, + const char* db_name, String &table_name, const char* op_name, const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 6, 7); static bool print_admin_msg(THD* thd, const char* msg_type, - const char* db_name, const char* table_name, + const char* db_name, String &table_name, const char* op_name, const char *fmt, ...) { va_list args; @@ -1107,7 +1129,7 @@ static bool print_admin_msg(THD* thd, const char* msg_type, return TRUE; } - length=(uint) (strxmov(name, db_name, ".", table_name,NullS) - name); + length=(uint) (strxmov(name, db_name, ".", table_name.c_ptr_safe(), NullS) - name); /* TODO: switch from protocol to push_warning here. The main reason we didn't it yet is parallel repair. Due to following trace: @@ -1179,15 +1201,14 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, part= i * num_subparts + j; DBUG_PRINT("info", ("Optimize subpartition %u (%s)", part, sub_elem->partition_name)); - if ((error= handle_opt_part(thd, check_opt, m_file[part], flag))) + if ((error= handle_opt_part(thd, check_opt, part, flag))) { /* print a line which partition the error belongs to */ if (error != HA_ADMIN_NOT_IMPLEMENTED && error != HA_ADMIN_ALREADY_DONE && error != HA_ADMIN_TRY_ALTER) { - print_admin_msg(thd, "error", table_share->db.str, - table->alias.c_ptr(), + print_admin_msg(thd, "error", table_share->db.str, table->alias, opt_op_name[flag], "Subpartition %s returned error", sub_elem->partition_name); @@ -1206,15 +1227,14 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, { DBUG_PRINT("info", ("Optimize partition %u (%s)", i, part_elem->partition_name)); - if ((error= handle_opt_part(thd, check_opt, m_file[i], flag))) + if ((error= handle_opt_part(thd, check_opt, i, flag))) { /* print a line which partition the error belongs to */ if (error != HA_ADMIN_NOT_IMPLEMENTED && error != HA_ADMIN_ALREADY_DONE && error != HA_ADMIN_TRY_ALTER) { - print_admin_msg(thd, "error", table_share->db.str, - table->alias.c_ptr(), + print_admin_msg(thd, "error", table_share->db.str, table->alias, opt_op_name[flag], "Partition %s returned error", part_elem->partition_name); } @@ -3583,7 +3603,7 @@ exit: Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc. new_data is always record[0] - old_data is normally record[1] but may be anything + old_data is always record[1] */ int ha_partition::update_row(const uchar *old_data, uchar *new_data) @@ -3593,6 +3613,7 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) int error= 0; longlong func_value; DBUG_ENTER("ha_partition::update_row"); + m_err_rec= NULL; if ((error= get_parts_for_update(old_data, new_data, table->record[0], m_part_info, &old_part_id, &new_part_id, @@ -3601,6 +3622,25 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) m_part_info->err_value= func_value; goto exit; } + /* + The protocol for updating a row is: + 1) position the handler (cursor) on the row to be updated, + either through the last read row (rnd or index) or by rnd_pos. + 2) call update_row with both old and new full records as arguments. + + This means that m_last_part should already be set to actual partition + where the row was read from. And if that is not the same as the + calculated part_id we found a misplaced row, we return an error to + notify the user that something is broken in the row distribution + between partitions! Since we don't check all rows on read, we return an + error instead of correcting m_last_part, to make the user aware of the + problem! + */ + if (old_part_id != m_last_part) + { + m_err_rec= old_data; + DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + } m_last_part= new_part_id; start_part_bulk_insert(thd, new_part_id); @@ -3705,12 +3745,34 @@ int ha_partition::delete_row(const uchar *buf) int error; THD *thd= ha_thd(); DBUG_ENTER("ha_partition::delete_row"); + m_err_rec= NULL; if ((error= get_part_for_delete(buf, m_rec0, m_part_info, &part_id))) { DBUG_RETURN(error); } - m_last_part= part_id; + /* + The protocol for deleting a row is: + 1) position the handler (cursor) on the row to be deleted, + either through the last read row (rnd or index) or by rnd_pos. + 2) call delete_row with the full record as argument. + + This means that m_last_part should already be set to actual partition + where the row was read from. And if that is not the same as the + calculated part_id we found a misplaced row, we return an error to + notify the user that something is broken in the row distribution + between partitions! Since we don't check all rows on read, we return an + error instead of forwarding the delete to the correct (m_last_part) + partition! + TODO: change the assert in InnoDB into an error instead and make this one + an assert instead and remove the get_part_for_delete()! + */ + if (part_id != m_last_part) + { + m_err_rec= buf; + DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + } + tmp_disable_binlog(thd); error= m_file[part_id]->ha_delete_row(buf); reenable_binlog(thd); @@ -4557,7 +4619,6 @@ int ha_partition::index_init(uint inx, bool sorted) file= m_file; do { - /* TODO RONM: Change to index_init() when code is stable */ if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) if ((error= (*file)->ha_index_init(inx, sorted))) { @@ -7010,6 +7071,57 @@ enum row_type ha_partition::get_row_type() const } +void ha_partition::append_row_to_str(String &str) +{ + Field **field_ptr; + const uchar *rec; + bool is_rec0= !m_err_rec || m_err_rec == table->record[0]; + if (is_rec0) + rec= table->record[0]; + else + rec= m_err_rec; + // If PK, use full PK instead of full part field array! + if (table->s->primary_key != MAX_KEY) + { + KEY *key= table->key_info + table->s->primary_key; + KEY_PART_INFO *key_part= key->key_part; + KEY_PART_INFO *key_part_end= key_part + key->key_parts; + if (!is_rec0) + set_key_field_ptr(key, rec, table->record[0]); + for (; key_part != key_part_end; key_part++) + { + Field *field= key_part->field; + str.append(" "); + str.append(field->field_name); + str.append(":"); + field_unpack(&str, field, rec, 0, false); + } + if (!is_rec0) + set_key_field_ptr(key, table->record[0], rec); + } + else + { + if (!is_rec0) + set_field_ptr(m_part_info->full_part_field_array, rec, + table->record[0]); + /* No primary key, use full partition field array. */ + for (field_ptr= m_part_info->full_part_field_array; + *field_ptr; + field_ptr++) + { + Field *field= *field_ptr; + str.append(" "); + str.append(field->field_name); + str.append(":"); + field_unpack(&str, field, rec, 0, false); + } + if (!is_rec0) + set_field_ptr(m_part_info->full_part_field_array, table->record[0], + rec); + } +} + + void ha_partition::print_error(int error, myf errflag) { THD *thd= ha_thd(); @@ -7018,24 +7130,72 @@ void ha_partition::print_error(int error, myf errflag) /* Should probably look for my own errors first */ DBUG_PRINT("enter", ("error: %d", error)); - if ((error == HA_ERR_NO_PARTITION_FOUND) && - ! (thd->lex->alter_info.flags & ALTER_TRUNCATE_PARTITION)) - m_part_info->print_no_partition_found(table); - else + if (error == HA_ERR_NO_PARTITION_FOUND) { - /* In case m_file has not been initialized, like in bug#42438 */ - if (m_file) + switch(thd_sql_command(thd)) { - if (m_last_part >= m_tot_parts) + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + if (m_err_rec) { - DBUG_ASSERT(0); - m_last_part= 0; + uint max_length; + char buf[MAX_KEY_LENGTH]; + const char *msg= "Found a row in wrong partition ("; + String str(buf,sizeof(buf),system_charset_info); + uint32 part_id; + /* Should only happen on DELETE or UPDATE! */ + str.length(0); + str.append_ulonglong(m_last_part); + str.append(" != "); + if (!get_part_for_delete(m_err_rec, m_rec0, m_part_info, &part_id)) + { + str.append_ulonglong(part_id); + } + str.append(")"); + append_row_to_str(str); + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s' corrupted: %s%s\n" + "Please CHECK and REPAIR the table!", + table->s->table_name.str, msg, str.c_ptr_safe()); + + max_length= (MYSQL_ERRMSG_SIZE- + (uint) strlen(msg)); + if (str.length() >= max_length) + { + str.length(max_length-4); + str.append(STRING_WITH_LEN("...")); + } + my_printf_error(ER_NO_PARTITION_FOR_GIVEN_VALUE, "%s%s", MYF(0), + msg, str.c_ptr_safe()); + m_err_rec= NULL; + DBUG_VOID_RETURN; + } + default: + { + if (!(thd->lex->alter_info.flags & ALTER_TRUNCATE_PARTITION)) + { + m_part_info->print_no_partition_found(table); + DBUG_VOID_RETURN; + } } - m_file[m_last_part]->print_error(error, errflag); + /* fall through to generic error handling. */ } - else - handler::print_error(error, errflag); } + + /* In case m_file has not been initialized, like in bug#42438 */ + if (m_file) + { + if (m_last_part >= m_tot_parts) + { + DBUG_ASSERT(0); + m_last_part= 0; + } + m_file[m_last_part]->print_error(error, errflag); + } + else + handler::print_error(error, errflag); DBUG_VOID_RETURN; } @@ -7729,6 +7889,288 @@ int ha_partition::indexes_are_disabled(void) } +/** + Check/fix misplaced rows. + + @param read_part_id Partition to check/fix. + @param repair If true, move misplaced rows to correct partition. + + @return Operation status. + @retval 0 Success + @retval != 0 Error +*/ + +int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) +{ + int result= 0; + uint32 correct_part_id; + longlong func_value; + longlong num_misplaced_rows= 0; + + DBUG_ENTER("ha_partition::check_misplaced_rows"); + + DBUG_ASSERT(m_file); + + if (repair) + { + /* We must read the full row, if we need to move it! */ + bitmap_set_all(table->read_set); + bitmap_set_all(table->write_set); + } + else + { + /* Only need to read the partitioning fields. */ + bitmap_union(table->read_set, &m_part_info->full_part_field_set); + } + + if ((result= m_file[read_part_id]->ha_rnd_init(1))) + DBUG_RETURN(result); + + while (true) + { + if ((result= m_file[read_part_id]->rnd_next(m_rec0))) + { + if (result == HA_ERR_RECORD_DELETED) + continue; + if (result != HA_ERR_END_OF_FILE) + break; + + if (num_misplaced_rows > 0) + { + print_admin_msg(ha_thd(), "warning", table_share->db.str, table->alias, + opt_op_name[REPAIR_PARTS], + "Moved %lld misplaced rows", + num_misplaced_rows); + } + /* End-of-file reached, all rows are now OK, reset result and break. */ + result= 0; + break; + } + + result= m_part_info->get_partition_id(m_part_info, &correct_part_id, + &func_value); + if (result) + break; + + if (correct_part_id != read_part_id) + { + num_misplaced_rows++; + if (!repair) + { + /* Check. */ + print_admin_msg(ha_thd(), "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + "Found a misplaced row"); + /* Break on first misplaced row! */ + result= HA_ADMIN_NEEDS_UPGRADE; + break; + } + else + { + DBUG_PRINT("info", ("Moving row from partition %d to %d", + read_part_id, correct_part_id)); + + /* + Insert row into correct partition. Notice that there are no commit + for every N row, so the repair will be one large transaction! + */ + if ((result= m_file[correct_part_id]->ha_write_row(m_rec0))) + { + /* + We have failed to insert a row, it might have been a duplicate! + */ + char buf[MAX_KEY_LENGTH]; + String str(buf,sizeof(buf),system_charset_info); + str.length(0); + if (result == HA_ERR_FOUND_DUPP_KEY) + { + str.append("Duplicate key found, " + "please update or delete the record:\n"); + result= HA_ADMIN_CORRUPT; + } + m_err_rec= NULL; + append_row_to_str(str); + + /* + If the engine supports transactions, the failure will be + rollbacked. + */ + if (!m_file[correct_part_id]->has_transactions()) + { + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s' failed to move/insert a row" + " from part %d into part %d:\n%s", + table->s->table_name.str, + read_part_id, + correct_part_id, + str.c_ptr_safe()); + } + print_admin_msg(ha_thd(), "error", table_share->db.str, table->alias, + opt_op_name[REPAIR_PARTS], + "Failed to move/insert a row" + " from part %d into part %d:\n%s", + read_part_id, + correct_part_id, + str.c_ptr_safe()); + break; + } + + /* Delete row from wrong partition. */ + if ((result= m_file[read_part_id]->ha_delete_row(m_rec0))) + { + if (m_file[correct_part_id]->has_transactions()) + break; + /* + We have introduced a duplicate, since we failed to remove it + from the wrong partition. + */ + char buf[MAX_KEY_LENGTH]; + String str(buf,sizeof(buf),system_charset_info); + str.length(0); + m_err_rec= NULL; + append_row_to_str(str); + + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s': Delete from part %d failed with" + " error %d. But it was already inserted into" + " part %d, when moving the misplaced row!" + "\nPlease manually fix the duplicate row:\n%s", + table->s->table_name.str, + read_part_id, + result, + correct_part_id, + str.c_ptr_safe()); + break; + } + } + } + } + + int tmp_result= m_file[read_part_id]->ha_rnd_end(); + DBUG_RETURN(result ? result : tmp_result); +} + + +#define KEY_PARTITIONING_CHANGED_STR \ + "KEY () partitioning changed, please run:\nALTER TABLE %s.%s %s" + +int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt) +{ + int error= HA_ADMIN_NEEDS_CHECK; + DBUG_ENTER("ha_partition::check_for_upgrade"); + + /* + This is called even without FOR UPGRADE, + if the .frm version is lower than the current version. + In that case return that it needs checking! + */ + if (!(check_opt->sql_flags & TT_FOR_UPGRADE)) + DBUG_RETURN(error); + + /* + Partitions will be checked for during their ha_check! + + Check if KEY (sub)partitioning was used and any field's hash calculation + differs from 5.1, see bug#14521864. + */ + if (table->s->mysql_version < 50503 && // 5.1 table (<5.5.3) + ((m_part_info->part_type == HASH_PARTITION && // KEY partitioned + m_part_info->list_of_part_fields) || + (m_is_sub_partitioned && // KEY subpartitioned + m_part_info->list_of_subpart_fields))) + { + Field **field; + if (m_is_sub_partitioned) + { + field= m_part_info->subpart_field_array; + } + else + { + field= m_part_info->part_field_array; + } + for (; *field; field++) + { + switch ((*field)->real_type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + THD *thd= ha_thd(); + char *part_buf; + String db_name, table_name; + uint part_buf_len; + bool skip_generation= false; + partition_info::enum_key_algorithm old_algorithm; + old_algorithm= m_part_info->key_algorithm; + error= HA_ADMIN_FAILED; + append_identifier(ha_thd(), &db_name, table_share->db.str, + table_share->db.length); + append_identifier(ha_thd(), &table_name, table_share->table_name.str, + table_share->table_name.length); + if (m_part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE) + { + /* + Only possible when someone tampered with .frm files, + like during tests :) + */ + skip_generation= true; + } + m_part_info->key_algorithm= partition_info::KEY_ALGORITHM_51; + if (skip_generation || + !(part_buf= generate_partition_syntax(m_part_info, + &part_buf_len, + true, + true, + NULL, + NULL, + NULL)) || + /* Also check that the length is smaller than the output field! */ + (part_buf_len + db_name.length() + table_name.length()) >= + (SQL_ADMIN_MSG_TEXT_SIZE - + (strlen(KEY_PARTITIONING_CHANGED_STR) - 3))) + { + print_admin_msg(thd, "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + KEY_PARTITIONING_CHANGED_STR, + db_name.c_ptr_safe(), table_name.c_ptr_safe(), + "<old partition clause>, but add ALGORITHM = 1" + " between 'KEY' and '(' to change the metadata" + " without the need of a full table rebuild."); + } + else + { + print_admin_msg(thd, "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + KEY_PARTITIONING_CHANGED_STR, + db_name.c_ptr_safe(), table_name.c_ptr_safe(), + part_buf); + } + m_part_info->key_algorithm= old_algorithm; + DBUG_RETURN(error); + } + default: + /* Not affected! */ + ; + } + } + } + + DBUG_RETURN(error); +} + + struct st_mysql_storage_engine partition_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; diff --git a/sql/ha_partition.h b/sql/ha_partition.h index 55f199401f3..fd1056d7b3f 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -2,7 +2,7 @@ #define HA_PARTITION_INCLUDED /* - Copyright (c) 2005, 2012, Oracle and/or its affiliates. + Copyright (c) 2005, 2013, Oracle and/or its affiliates. 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 @@ -25,13 +25,15 @@ #include "queues.h" /* QUEUE */ enum partition_keywords -{ +{ PKW_HASH= 0, PKW_RANGE, PKW_LIST, PKW_KEY, PKW_MAXVALUE, PKW_LINEAR, - PKW_COLUMNS + PKW_COLUMNS, PKW_ALGORITHM }; #define PARTITION_BYTES_IN_POS 2 -#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | HA_REC_NOT_IN_SEQ) +#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \ + HA_REC_NOT_IN_SEQ | \ + HA_CAN_REPAIR) #define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \ HA_CAN_FULLTEXT | \ HA_DUPLICATE_POS | \ @@ -84,6 +86,7 @@ private: */ KEY *m_curr_key_info[3]; // Current index uchar *m_rec0; // table->record[0] + const uchar *m_err_rec; // record which gave error QUEUE m_queue; // Prio queue used by sorted read /* Since the partition handler is a handler on top of other handlers, it @@ -1089,9 +1092,18 @@ public: virtual bool check_and_repair(THD *thd); virtual bool auto_repair(int error) const; virtual bool is_crashed() const; + virtual int check_for_upgrade(HA_CHECK_OPT *check_opt); private: int handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, uint flags); + int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, uint part_id, + uint flag); + /** + Check if the rows are placed in the correct partition. If the given + argument is true, then move the rows to the correct partition. + */ + int check_misplaced_rows(uint read_part_id, bool repair); + void append_row_to_str(String &str); public: /* ------------------------------------------------------------------------- diff --git a/sql/handler.cc b/sql/handler.cc index 8c8089a61fb..ec30057758c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1,5 +1,5 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. - Copyright (c) 2009, 2012, Monty Program Ab. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2009, 2013, Monty Program 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 @@ -40,6 +40,7 @@ #include "probes_mysql.h" #include <mysql/psi/mysql_table.h> #include "debug_sync.h" // DEBUG_SYNC +#include "sql_audit.h" #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -275,7 +276,8 @@ handler *get_ha_partition(partition_info *part_info) } else { - my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(sizeof(ha_partition))); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast<int>(sizeof(ha_partition))); } DBUG_RETURN(((handler*) partition)); } @@ -2260,7 +2262,7 @@ handle_condition(THD *, { *cond_hdl= NULL; /* Grab the error message */ - strmake(buff, msg, sizeof(buff)-1); + strmake_buf(buff, msg); return TRUE; } @@ -3778,6 +3780,9 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) } if ((error= check(thd, check_opt))) return error; + /* Skip updating frm version if not main handler. */ + if (table->file != this) + return error; return update_frm_version(table); } @@ -4030,7 +4035,6 @@ int handler::ha_delete_table(const char *name) { mark_trx_read_write(); - return delete_table(name); } @@ -4063,8 +4067,11 @@ int handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info) { mark_trx_read_write(); - - return create(name, form, info); + int error= create(name, form, info); + if (!error && + !(info->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER))) + mysql_audit_create_table(form); + return error; } @@ -5575,7 +5582,11 @@ int handler::ha_external_lock(THD *thd, int lock_type) { error= external_lock(thd, lock_type); }) if (error == 0) + { cached_table_flags= table_flags(); + if (table_share->tmp_table == NO_TMP_TABLE) + mysql_audit_external_lock(thd, table_share, lock_type); + } if (MYSQL_HANDLER_RDLOCK_DONE_ENABLED() || MYSQL_HANDLER_WRLOCK_DONE_ENABLED() || @@ -5630,6 +5641,8 @@ int handler::ha_write_row(uchar *buf) Log_func *log_func= Write_rows_log_event::binlog_row_logging_function; DBUG_ENTER("handler::ha_write_row"); DEBUG_SYNC_C("ha_write_row_start"); + DBUG_EXECUTE_IF("inject_error_ha_write_row", + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); ); MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); @@ -5660,6 +5673,7 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data) (and the old record is in record[1]). */ DBUG_ASSERT(new_data == table->record[0]); + DBUG_ASSERT(old_data == table->record[1]); MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); @@ -5681,6 +5695,13 @@ int handler::ha_delete_row(const uchar *buf) { int error; Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function; + /* + Normally table->record[0] is used, but sometimes table->record[1] is used. + */ + DBUG_ASSERT(buf == table->record[0] || + buf == table->record[1]); + DBUG_EXECUTE_IF("inject_error_ha_delete_row", + return HA_ERR_INTERNAL_ERROR; ); MYSQL_DELETE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); diff --git a/sql/handler.h b/sql/handler.h index c5639053fe2..c67360da44d 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -287,7 +287,7 @@ (yes, the sum is deliberately inaccurate) TODO remove the limit, use dynarrays */ -#define MAX_HA 15 +#define MAX_HA 64 /* Use this instead of 0 as the initial value for the slot number of @@ -320,7 +320,7 @@ #define HA_LEX_CREATE_TMP_TABLE 1 #define HA_LEX_CREATE_IF_NOT_EXISTS 2 #define HA_LEX_CREATE_TABLE_LIKE 4 -#define HA_CREATE_TMP_ALTER 8 +#define HA_CREATE_TMP_ALTER 8 #define HA_MAX_REC_LENGTH 65535 /* Table caching type */ diff --git a/sql/item.cc b/sql/item.cc index ecc7917022e..21eacb38401 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2010, 2013, Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -377,6 +377,27 @@ my_decimal *Item::val_decimal_from_time(my_decimal *decimal_value) } +longlong Item::val_int_from_date() +{ + DBUG_ASSERT(fixed == 1); + MYSQL_TIME ltime; + if (get_date(<ime, TIME_FUZZY_DATE)) + return 0; + longlong v= TIME_to_ulonglong(<ime); + return ltime.neg ? -v : v; +} + + +double Item::val_real_from_date() +{ + DBUG_ASSERT(fixed == 1); + MYSQL_TIME ltime; + if (get_date(<ime, TIME_FUZZY_DATE)) + return 0; + return TIME_to_double(<ime); +} + + double Item::val_real_from_decimal() { /* Note that fix_fields may not be called for Item_avg_field items */ @@ -513,7 +534,7 @@ Item::Item(THD *thd, Item *item): with_field(item->with_field), fixed(item->fixed), is_autogenerated_name(item->is_autogenerated_name), - with_subselect(item->with_subselect), + with_subselect(item->has_subquery()), collation(item->collation), cmp_context(item->cmp_context) { @@ -651,9 +672,12 @@ Item_result Item::cmp_type() const case MYSQL_TYPE_GEOMETRY: return STRING_RESULT; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_NEWDATE: return TIME_RESULT; }; @@ -1269,7 +1293,7 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) String tmp(buff,sizeof(buff), &my_charset_bin),*res; if (!(res=val_str(&tmp)) || str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), - ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzydate)) goto err; break; } @@ -3218,11 +3242,7 @@ void Item_param::set_time(MYSQL_TIME *tm, timestamp_type time_type, value.time= *tm; value.time.time_type= time_type; - if (value.time.year > 9999 || value.time.month > 12 || - value.time.day > 31 || - (time_type != MYSQL_TIMESTAMP_TIME && value.time.hour > 23) || - value.time.minute > 59 || value.time.second > 59 || - value.time.second_part > TIME_MAX_SECOND_PART) + if (check_datetime_range(&value.time)) { ErrConvTime str(&value.time); make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, @@ -4416,7 +4436,7 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) if (db_name && lower_case_table_names) { /* Convert database to lower case for comparison */ - strmake(name_buff, db_name, sizeof(name_buff)-1); + strmake_buf(name_buff, db_name); my_casedn_str(files_charset_info, name_buff); db_name= name_buff; } @@ -5678,19 +5698,17 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table, bool fixed_length) break; case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_DATE: - field= new Field_newdate(0, null_ptr, 0, Field::NONE, name, &my_charset_bin); + field= new Field_newdate(0, null_ptr, 0, Field::NONE, name); break; case MYSQL_TYPE_TIME: - field= new_Field_time(0, null_ptr, 0, Field::NONE, name, - decimals, &my_charset_bin); + field= new_Field_time(0, null_ptr, 0, Field::NONE, name, decimals); break; case MYSQL_TYPE_TIMESTAMP: field= new_Field_timestamp(0, null_ptr, 0, - Field::NONE, name, 0, decimals, &my_charset_bin); + Field::NONE, name, 0, decimals); break; case MYSQL_TYPE_DATETIME: - field= new_Field_datetime(0, null_ptr, 0, Field::NONE, name, - decimals, &my_charset_bin); + field= new_Field_datetime(0, null_ptr, 0, Field::NONE, name, decimals); break; case MYSQL_TYPE_YEAR: field= new Field_year((uchar*) 0, max_length, null_ptr, 0, Field::NONE, @@ -6127,17 +6145,8 @@ inline uint char_val(char X) X-'a'+10); } -Item_hex_string::Item_hex_string() -{ - hex_string_init("", 0); -} -Item_hex_string::Item_hex_string(const char *str, uint str_length) -{ - hex_string_init(str, str_length); -} - -void Item_hex_string::hex_string_init(const char *str, uint str_length) +void Item_hex_constant::hex_string_init(const char *str, uint str_length) { max_length=(str_length+1)/2; char *ptr=(char*) sql_alloc(max_length+1); @@ -6161,7 +6170,7 @@ void Item_hex_string::hex_string_init(const char *str, uint str_length) unsigned_flag= 1; } -longlong Item_hex_string::val_int() +longlong Item_hex_hybrid::val_int() { // following assert is redundant, because fixed=1 assigned in constructor DBUG_ASSERT(fixed == 1); @@ -6175,17 +6184,7 @@ longlong Item_hex_string::val_int() } -my_decimal *Item_hex_string::val_decimal(my_decimal *decimal_value) -{ - // following assert is redundant, because fixed=1 assigned in constructor - DBUG_ASSERT(fixed == 1); - ulonglong value= (ulonglong)val_int(); - int2my_decimal(E_DEC_FATAL_ERROR, value, TRUE, decimal_value); - return (decimal_value); -} - - -int Item_hex_string::save_in_field(Field *field, bool no_conversions) +int Item_hex_hybrid::save_in_field(Field *field, bool no_conversions) { field->set_notnull(); if (field->result_type() == STRING_RESULT) @@ -6218,22 +6217,27 @@ warn: } -void Item_hex_string::print(String *str, enum_query_type query_type) +void Item_hex_hybrid::print(String *str, enum_query_type query_type) { - char *end= (char*) str_value.ptr() + str_value.length(), - *ptr= end - min(str_value.length(), sizeof(longlong)); + uint32 len= min(str_value.length(), sizeof(longlong)); + const char *ptr= str_value.ptr() + str_value.length() - len; str->append("0x"); - for (; ptr != end ; ptr++) - { - str->append(_dig_vec_lower[((uchar) *ptr) >> 4]); - str->append(_dig_vec_lower[((uchar) *ptr) & 0x0F]); - } + str->append_hex(ptr, len); } -bool Item_hex_string::eq(const Item *arg, bool binary_cmp) const +void Item_hex_string::print(String *str, enum_query_type query_type) { - if (arg->basic_const_item() && arg->type() == type()) + str->append("X'"); + str->append_hex(str_value.ptr(), str_value.length()); + str->append("'"); +} + + +bool Item_hex_constant::eq(const Item *arg, bool binary_cmp) const +{ + if (arg->basic_const_item() && arg->type() == type() && + arg->cast_to_int_type() == cast_to_int_type()) { if (binary_cmp) return !stringcmp(&str_value, &arg->str_value); @@ -6243,7 +6247,7 @@ bool Item_hex_string::eq(const Item *arg, bool binary_cmp) const } -Item *Item_hex_string::safe_charset_converter(CHARSET_INFO *tocs) +Item *Item_hex_constant::safe_charset_converter(CHARSET_INFO *tocs) { Item_string *conv; String tmp, *str= val_str(&tmp); @@ -6300,6 +6304,76 @@ Item_bin_string::Item_bin_string(const char *str, uint str_length) } +bool Item_temporal_literal::eq(const Item *item, bool binary_cmp) const +{ + return + item->basic_const_item() && type() == item->type() && + field_type() == ((Item_temporal_literal *) item)->field_type() && + !my_time_compare(&cached_time, + &((Item_temporal_literal *) item)->cached_time); +} + + +void Item_date_literal::print(String *str, enum_query_type query_type) +{ + str->append("DATE'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_date_to_str(&cached_time, buf); + str->append(buf); + str->append('\''); +} + + +bool Item_date_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + *ltime= cached_time; + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + +void Item_datetime_literal::print(String *str, enum_query_type query_type) +{ + str->append("TIMESTAMP'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_datetime_to_str(&cached_time, buf, decimals); + str->append(buf); + str->append('\''); +} + + +bool Item_datetime_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + *ltime= cached_time; + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + +void Item_time_literal::print(String *str, enum_query_type query_type) +{ + str->append("TIME'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_time_to_str(&cached_time, buf, decimals); + str->append(buf); + str->append('\''); +} + + +bool Item_time_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + *ltime= cached_time; + if (fuzzy_date & TIME_TIME_ONLY) + return (null_value= false); + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + + /** Pack data in buffer for sending. */ diff --git a/sql/item.h b/sql/item.h index b3d08011a39..a298f16a93b 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1,8 +1,8 @@ #ifndef SQL_ITEM_INCLUDED #define SQL_ITEM_INCLUDED -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2009, 2013 Monty Program Ab +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2009, 2013 Monty Program 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 @@ -384,6 +384,12 @@ struct Name_resolution_context: Sql_alloc { (*error_processor)(thd, error_processor_data); } + st_select_lex *outer_select() + { + return (outer_context ? + outer_context->select_lex : + NULL); + } }; @@ -593,7 +599,8 @@ public: SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER, PARAM_ITEM, TRIGGER_FIELD_ITEM, DECIMAL_ITEM, XPATH_NODESET, XPATH_NODESET_CMP, - VIEW_FIXER_ITEM, EXPR_CACHE_ITEM}; + VIEW_FIXER_ITEM, EXPR_CACHE_ITEM, + DATE_ITEM}; enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE }; @@ -950,7 +957,9 @@ public: my_decimal *val_decimal_from_date(my_decimal *decimal_value); my_decimal *val_decimal_from_time(my_decimal *decimal_value); longlong val_int_from_decimal(); + longlong val_int_from_date(); double val_real_from_decimal(); + double val_real_from_date(); int save_time_in_field(Field *field); int save_date_in_field(Field *field); @@ -1107,8 +1116,8 @@ public: */ virtual CHARSET_INFO *charset_for_protocol(void) const { - return result_type() == STRING_RESULT ? collation.collation : - &my_charset_bin; + return cmp_type() == STRING_RESULT ? collation.collation : + &my_charset_bin; }; virtual bool walk(Item_processor processor, bool walk_subquery, uchar *arg) @@ -1466,6 +1475,12 @@ public: Return TRUE if the item points to a column of an outer-joined table. */ virtual bool is_outer_field() const { DBUG_ASSERT(fixed); return FALSE; } + + /** + Checks if this item or any of its decendents contains a subquery. + */ + virtual bool has_subquery() const { return with_subselect; } + Item* set_expr_cache(THD *thd); virtual Item_equal *get_item_equal() { return NULL; } @@ -2754,41 +2769,221 @@ public: }; -class Item_hex_string: public Item_basic_constant +/** + Item_hex_constant -- a common class for hex literals: X'HHHH' and 0xHHHH +*/ +class Item_hex_constant: public Item_basic_constant { +private: + void hex_string_init(const char *str, uint str_length); public: - Item_hex_string(); - Item_hex_string(const char *str,uint str_length); + Item_hex_constant() + { + hex_string_init("", 0); + } + Item_hex_constant(const char *str, uint str_length) + { + hex_string_init(str, str_length); + } enum Type type() const { return VARBIN_ITEM; } + enum Item_result result_type () const { return STRING_RESULT; } + enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } + virtual Item *safe_charset_converter(CHARSET_INFO *tocs); + bool check_partition_func_processor(uchar *int_arg) {return FALSE;} + bool check_vcol_func_processor(uchar *arg) { return FALSE;} + bool basic_const_item() const { return 1; } + bool eq(const Item *item, bool binary_cmp) const; + String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; } +}; + + +/** + Item_hex_hybrid -- is a class implementing 0xHHHH literals, e.g.: + SELECT 0x3132; + They can behave as numbers and as strings depending on context. +*/ +class Item_hex_hybrid: public Item_hex_constant +{ +public: + Item_hex_hybrid(): Item_hex_constant() {} + Item_hex_hybrid(const char *str, uint str_length): + Item_hex_constant(str, str_length) {} double val_real() { DBUG_ASSERT(fixed == 1); - return (double) (ulonglong) Item_hex_string::val_int(); + return (double) (ulonglong) Item_hex_hybrid::val_int(); } longlong val_int(); - bool basic_const_item() const { return 1; } - String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; } - my_decimal *val_decimal(my_decimal *); + my_decimal *val_decimal(my_decimal *decimal_value) + { + // following assert is redundant, because fixed=1 assigned in constructor + DBUG_ASSERT(fixed == 1); + ulonglong value= (ulonglong) Item_hex_hybrid::val_int(); + int2my_decimal(E_DEC_FATAL_ERROR, value, TRUE, decimal_value); + return decimal_value; + } int save_in_field(Field *field, bool no_conversions); - enum Item_result result_type () const { return STRING_RESULT; } enum Item_result cast_to_int_type() const { return INT_RESULT; } - enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } - virtual void print(String *str, enum_query_type query_type); + void print(String *str, enum_query_type query_type); +}; + + +/** + Item_hex_string -- is a class implementing X'HHHH' literals, e.g.: + SELECT X'3132'; + Unlike Item_hex_hybrid, X'HHHH' literals behave as strings in all contexts. + X'HHHH' are also used in replication of string constants in case of + "dangerous" charsets (sjis, cp932, big5, gbk) who can have backslash (0x5C) + as the second byte of a multi-byte character, so using '\' escaping for + these charsets is not desirable. +*/ +class Item_hex_string: public Item_hex_constant +{ +public: + Item_hex_string(): Item_hex_constant() {} + Item_hex_string(const char *str, uint str_length): + Item_hex_constant(str, str_length) {} + longlong val_int() + { + DBUG_ASSERT(fixed == 1); + return longlong_from_string_with_check(str_value.charset(), + str_value.ptr(), + str_value.ptr()+ + str_value.length()); + } + double val_real() + { + DBUG_ASSERT(fixed == 1); + return double_from_string_with_check(str_value.charset(), + str_value.ptr(), + str_value.ptr() + + str_value.length()); + } + my_decimal *val_decimal(my_decimal *decimal_value) + { + return val_decimal_from_string(decimal_value); + } + int save_in_field(Field *field, bool no_conversions) + { + field->set_notnull(); + return field->store(str_value.ptr(), str_value.length(), + collation.collation); + } + enum Item_result cast_to_int_type() const { return STRING_RESULT; } + void print(String *str, enum_query_type query_type); +}; + + +class Item_bin_string: public Item_hex_hybrid +{ +public: + Item_bin_string(const char *str,uint str_length); +}; + + +class Item_temporal_literal :public Item_basic_constant +{ + //sql_mode= current_thd->variables.sql_mode & + // (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE); +protected: + MYSQL_TIME cached_time; +public: + /** + Constructor for Item_date_literal. + @param ltime DATE value. + */ + Item_temporal_literal(MYSQL_TIME *ltime) :Item_basic_constant() + { + collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); + decimals= 0; + cached_time= *ltime; + } + Item_temporal_literal(MYSQL_TIME *ltime, uint dec_arg) :Item_basic_constant() + { + collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); + decimals= dec_arg; + cached_time= *ltime; + } + bool basic_const_item() const { return true; } + bool const_item() const { return true; } + enum Type type() const { return DATE_ITEM; } bool eq(const Item *item, bool binary_cmp) const; - virtual Item *safe_charset_converter(CHARSET_INFO *tocs); + enum Item_result result_type () const { return STRING_RESULT; } + Item_result cmp_type() const { return TIME_RESULT; } + bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} -private: - void hex_string_init(const char *str, uint str_length); + + String *val_str(String *str) + { return val_string_from_date(str); } + longlong val_int() + { return val_int_from_date(); } + double val_real() + { return val_real_from_date(); } + my_decimal *val_decimal(my_decimal *decimal_value) + { return val_decimal_from_date(decimal_value); } + Field *tmp_table_field(TABLE *table) + { return tmp_table_field_from_field_type(table, 0); } + int save_in_field(Field *field, bool no_conversions) + { return save_date_in_field(field); } }; -class Item_bin_string: public Item_hex_string +/** + DATE'2010-01-01' +*/ +class Item_date_literal: public Item_temporal_literal { public: - Item_bin_string(const char *str,uint str_length); + Item_date_literal(MYSQL_TIME *ltime) + :Item_temporal_literal(ltime) + { + max_length= MAX_DATE_WIDTH; + fixed= 1; + } + enum_field_types field_type() const { return MYSQL_TYPE_DATE; } + void print(String *str, enum_query_type query_type); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); }; + +/** + TIME'10:10:10' +*/ +class Item_time_literal: public Item_temporal_literal +{ +public: + Item_time_literal(MYSQL_TIME *ltime, uint dec_arg) + :Item_temporal_literal(ltime, dec_arg) + { + max_length= MIN_TIME_WIDTH + (decimals ? decimals + 1 : 0); + fixed= 1; + } + enum_field_types field_type() const { return MYSQL_TYPE_TIME; } + void print(String *str, enum_query_type query_type); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); +}; + + +/** + TIMESTAMP'2001-01-01 10:20:30' +*/ +class Item_datetime_literal: public Item_temporal_literal +{ +public: + Item_datetime_literal(MYSQL_TIME *ltime, uint dec_arg) + :Item_temporal_literal(ltime, dec_arg) + { + max_length= MAX_DATETIME_WIDTH + (decimals ? decimals + 1 : 0); + fixed= 1; + } + enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } + void print(String *str, enum_query_type query_type); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); +}; + + + class Item_result_field :public Item /* Item with result field */ { public: @@ -2987,6 +3182,14 @@ public: DBUG_ASSERT(ref); return (*ref)->is_outer_field(); } + + /** + Checks if the item tree that ref points to contains a subquery. + */ + virtual bool has_subquery() const + { + return (*ref)->has_subquery(); + } }; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 7ca85a72cfa..057d75676dc 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -721,31 +721,31 @@ bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type, const char *warn_name, MYSQL_TIME *l_time) { bool value; - int error; - enum_mysql_timestamp_type timestamp_type; + MYSQL_TIME_STATUS status; int flags= TIME_FUZZY_DATE | MODE_INVALID_DATES; ErrConvString err(str); - if (warn_type == MYSQL_TIMESTAMP_TIME) - flags|= TIME_TIME_ONLY; - - timestamp_type= - str_to_datetime(str->charset(), str->ptr(), str->length(), - l_time, flags, &error); + DBUG_ASSERT(warn_type != MYSQL_TIMESTAMP_TIME); - if (timestamp_type > MYSQL_TIMESTAMP_ERROR) + if (!str_to_datetime(str->charset(), str->ptr(), str->length(), + l_time, flags, &status)) + { + DBUG_ASSERT(l_time->time_type == MYSQL_TIMESTAMP_DATETIME || + l_time->time_type == MYSQL_TIMESTAMP_DATE); /* Do not return yet, we may still want to throw a "trailing garbage" warning. */ value= FALSE; + } else { + DBUG_ASSERT(l_time->time_type != MYSQL_TIMESTAMP_TIME); + DBUG_ASSERT(status.warnings != 0); // Must be set by set_to_datetime() value= TRUE; - error= 1; /* force warning */ } - if (error > 0) + if (status.warnings > 0) make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, &err, warn_type, warn_name); @@ -4352,7 +4352,7 @@ Item_cond::fix_fields(THD *thd, Item **ref) with_sum_func= with_sum_func || item->with_sum_func; with_field= with_field || item->with_field; - with_subselect|= item->with_subselect; + with_subselect|= item->has_subquery(); if (item->maybe_null) maybe_null=1; } @@ -5052,7 +5052,7 @@ Item_func_regex::fix_fields(THD *thd, Item **ref) return TRUE; /* purecov: inspected */ with_sum_func=args[0]->with_sum_func || args[1]->with_sum_func; with_field= args[0]->with_field || args[1]->with_field; - with_subselect|= args[0]->with_subselect | args[1]->with_subselect; + with_subselect= args[0]->has_subquery() || args[1]->has_subquery(); max_length= 1; decimals= 0; diff --git a/sql/item_create.cc b/sql/item_create.cc index ce4dc7ced8f..c6d0f09907b 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -32,6 +32,7 @@ #include "set_var.h" #include "sp_head.h" #include "sp.h" +#include "sql_time.h" /* ============================================================================= @@ -5821,6 +5822,70 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, } +/** + Builder for datetime literals: + TIME'00:00:00', DATE'2001-01-01', TIMESTAMP'2001-01-01 00:00:00'. + @param thd The current thread + @param str Character literal + @param length Length of str + @param type Type of literal (TIME, DATE or DATETIME) + @param send_error Whether to generate an error on failure +*/ + +Item *create_temporal_literal(THD *thd, + const char *str, uint length, + CHARSET_INFO *cs, + enum_field_types type, + bool send_error) +{ + MYSQL_TIME_STATUS status; + MYSQL_TIME ltime; + Item *item= NULL; + ulonglong datetime_flags= thd->variables.sql_mode & + (MODE_NO_ZERO_IN_DATE | + MODE_NO_ZERO_DATE | + MODE_INVALID_DATES); + ulonglong flags= TIME_FUZZY_DATE | datetime_flags; + + switch(type) + { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + if (!str_to_datetime(cs, str, length, <ime, flags, &status) && + ltime.time_type == MYSQL_TIMESTAMP_DATE && !status.warnings) + item= new (thd->mem_root) Item_date_literal(<ime); + break; + case MYSQL_TYPE_DATETIME: + if (!str_to_datetime(cs, str, length, <ime, flags, &status) && + ltime.time_type == MYSQL_TIMESTAMP_DATETIME && !status.warnings) + item= new (thd->mem_root) Item_datetime_literal(<ime, + status.precision); + break; + case MYSQL_TYPE_TIME: + if (!str_to_time(cs, str, length, <ime, 0, &status) && + ltime.time_type == MYSQL_TIMESTAMP_TIME && !status.warnings) + item= new (thd->mem_root) Item_time_literal(<ime, + status.precision); + break; + default: + DBUG_ASSERT(0); + } + + if (item) + return item; + + if (send_error) + { + const char *typestr= + (type == MYSQL_TYPE_DATE) ? "DATE" : + (type == MYSQL_TYPE_TIME) ? "TIME" : "DATETIME"; + ErrConvString err(str, length, thd->variables.character_set_client); + my_error(ER_WRONG_VALUE, MYF(0), typestr, err.ptr()); + } + return NULL; +} + + static List<Item> *create_func_dyncol_prepare(THD *thd, DYNCALL_CREATE_DEF **dfs, List<DYNCALL_CREATE_DEF> &list) diff --git a/sql/item_create.h b/sql/item_create.h index 5ecb45e9eae..5f1a8c6006d 100644 --- a/sql/item_create.h +++ b/sql/item_create.h @@ -168,6 +168,11 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, const char *len, const char *dec, CHARSET_INFO *cs); +Item *create_temporal_literal(THD *thd, + const char *str, uint length, + CHARSET_INFO *cs, + enum_field_types type, + bool send_error); int item_create_init(); void item_create_cleanup(); diff --git a/sql/item_func.cc b/sql/item_func.cc index a231b245b53..7aad5ea0fd6 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -25,7 +25,7 @@ #pragma implementation // gcc: Class implementation #endif -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -52,8 +52,6 @@ #include "sp.h" #include "set_var.h" #include "debug_sync.h" -#include <mysql/plugin.h> -#include <mysql/service_thd_wait.h> #ifdef NO_EMBEDDED_ACCESS_CHECKS #define sp_restore_security_context(A,B) while (0) {} @@ -225,7 +223,7 @@ Item_func::fix_fields(THD *thd, Item **ref) with_field= with_field || item->with_field; used_tables_cache|= item->used_tables(); const_item_cache&= item->const_item(); - with_subselect|= item->with_subselect; + with_subselect|= item->has_subquery(); } } fix_length_and_dec(); @@ -5478,7 +5476,7 @@ enum Item_result Item_func_get_user_var::result_type() const void Item_func_get_user_var::print(String *str, enum_query_type query_type) { str->append(STRING_WITH_LEN("(@")); - str->append(name.str,name.length); + append_identifier(current_thd, str, name.str, name.length); str->append(')'); } diff --git a/sql/item_func.h b/sql/item_func.h index ab6ec706248..bdf91810d81 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -552,12 +552,18 @@ public: class Item_func_signed :public Item_int_func { public: - Item_func_signed(Item *a) :Item_int_func(a) {} + Item_func_signed(Item *a) :Item_int_func(a) + { + unsigned_flag= 0; + } const char *func_name() const { return "cast_as_signed"; } longlong val_int(); longlong val_int_from_str(int *error); void fix_length_and_dec() - { fix_char_length(args[0]->max_char_length()); unsigned_flag=0; } + { + fix_char_length(min(args[0]->max_char_length(), + MY_INT64_NUM_DECIMAL_DIGITS)); + } virtual void print(String *str, enum_query_type query_type); uint decimal_precision() const { return args[0]->decimal_precision(); } }; @@ -566,14 +572,11 @@ public: class Item_func_unsigned :public Item_func_signed { public: - Item_func_unsigned(Item *a) :Item_func_signed(a) {} - const char *func_name() const { return "cast_as_unsigned"; } - void fix_length_and_dec() + Item_func_unsigned(Item *a) :Item_func_signed(a) { - fix_char_length(min(args[0]->max_char_length(), - DECIMAL_MAX_PRECISION + 2)); - unsigned_flag=1; + unsigned_flag= 1; } + const char *func_name() const { return "cast_as_unsigned"; } longlong val_int(); virtual void print(String *str, enum_query_type query_type); }; diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 5dcd83681c6..1495456bda4 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -62,6 +62,8 @@ C_MODE_END #include <sql_repl.h> #include "sql_statistics.h" +size_t username_char_length= 16; + /** @todo Remove this. It is not safe to use a shared String object. */ @@ -3970,7 +3972,9 @@ bool Item_func_dyncol_create::prepare_arguments(bool force_names_arg) type= DYN_COL_NULL; break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: type= DYN_COL_DATETIME; break; case MYSQL_TYPE_DATE: @@ -3978,6 +3982,7 @@ bool Item_func_dyncol_create::prepare_arguments(bool force_names_arg) type= DYN_COL_DATE; break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: type= DYN_COL_TIME; break; case MYSQL_TYPE_VARCHAR: @@ -4720,7 +4725,7 @@ bool Item_dyncol_get::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (str_to_datetime_with_warn(&my_charset_numeric, val.x.string.value.str, val.x.string.value.length, - ltime, fuzzy_date) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzy_date)) goto null; return 0; case DYN_COL_DATETIME: diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 169da25e826..9daddf94c0b 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -25,6 +25,8 @@ #pragma interface /* gcc class implementation */ #endif +extern size_t username_char_length; + class MY_LOCALE; class Item_str_func :public Item_func @@ -510,8 +512,8 @@ public: bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec() { - max_length= (USERNAME_LENGTH + - (HOSTNAME_LENGTH + 1) * SYSTEM_CHARSET_MBMAXLEN); + max_length= (username_char_length + + HOSTNAME_LENGTH + 1) * SYSTEM_CHARSET_MBMAXLEN; } const char *func_name() const { return "user"; } const char *fully_qualified_func_name() const { return "user()"; } diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 03afd1a4365..92fff032095 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -65,7 +65,15 @@ ulonglong Item_sum::ram_limitation(THD *thd) bool Item_sum::init_sum_func_check(THD *thd) { - if (!thd->lex->allow_sum_func) + SELECT_LEX *curr_sel= thd->lex->current_select; + if (!curr_sel->name_visibility_map) + { + for (SELECT_LEX *sl= curr_sel; sl; sl= sl->context.outer_select()) + { + curr_sel->name_visibility_map|= (1 << sl-> nest_level); + } + } + if (!(thd->lex->allow_sum_func & curr_sel->name_visibility_map)) { my_message(ER_INVALID_GROUP_FUNC_USE, ER(ER_INVALID_GROUP_FUNC_USE), MYF(0)); @@ -136,8 +144,11 @@ bool Item_sum::init_sum_func_check(THD *thd) bool Item_sum::check_sum_func(THD *thd, Item **ref) { + SELECT_LEX *curr_sel= thd->lex->current_select; + nesting_map allow_sum_func= (thd->lex->allow_sum_func & + curr_sel->name_visibility_map); bool invalid= FALSE; - nesting_map allow_sum_func= thd->lex->allow_sum_func; + DBUG_ASSERT(curr_sel->name_visibility_map); // should be set already /* The value of max_arg_level is updated if an argument of the set function contains a column reference resolved against a subquery whose level is @@ -172,7 +183,7 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref) if (!invalid && aggr_level < 0) { aggr_level= nest_level; - aggr_sel= thd->lex->current_select; + aggr_sel= curr_sel; } /* By this moment we either found a subquery where the set function is @@ -309,9 +320,9 @@ bool Item_sum::register_sum_func(THD *thd, Item **ref) { SELECT_LEX *sl; nesting_map allow_sum_func= thd->lex->allow_sum_func; - for (sl= thd->lex->current_select->master_unit()->outer_select() ; + for (sl= thd->lex->current_select->context.outer_select() ; sl && sl->nest_level > max_arg_level; - sl= sl->master_unit()->outer_select() ) + sl= sl->context.outer_select()) { if (aggr_level < 0 && (allow_sum_func & ((nesting_map)1 << sl->nest_level))) @@ -1296,16 +1307,16 @@ Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table, switch (args[0]->field_type()) { case MYSQL_TYPE_DATE: field= new Field_newdate(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, collation.collation); + name); break; case MYSQL_TYPE_TIME: field= new_Field_time(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, decimals, collation.collation); + name, decimals); break; case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_DATETIME: field= new_Field_datetime(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, decimals, collation.collation); + name, decimals); break; default: return Item_sum::create_tmp_field(group, table, convert_blob_length); diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index f43a4e79431..c0e623f499c 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -1574,7 +1574,7 @@ static void set_sec_part(ulong sec_part, MYSQL_TIME *ltime, Item *item) { ltime->second_part= sec_part; if (item->decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, item->decimals); + my_time_trunc(ltime, item->decimals); } } @@ -2411,7 +2411,7 @@ bool Item_time_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (get_arg0_time(ltime)) return 1; if (decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, decimals); + my_time_trunc(ltime, decimals); /* MYSQL_TIMESTAMP_TIME value can have non-zero day part, which we should not lose. @@ -2430,17 +2430,8 @@ bool Item_date_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0; ltime->time_type= MYSQL_TIMESTAMP_DATE; - - int unused; - if (check_date(ltime, ltime->year || ltime->month || ltime->day, - fuzzy_date, &unused)) - { - ErrConvTime str(ltime); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - &str, MYSQL_TIMESTAMP_DATE, 0); - return (null_value= 1); - } - return (null_value= 0); + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_DATE)); } @@ -2450,8 +2441,7 @@ bool Item_datetime_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) return 1; if (decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, decimals); - + my_time_trunc(ltime, decimals); /* ltime is valid MYSQL_TYPE_TIME (according to fuzzy_date). diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 9b2db9e816e..3f58fe09af1 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -489,7 +489,6 @@ public: Item_temporal_func(Item *a, Item *b) :Item_func(a,b) {} Item_temporal_func(Item *a, Item *b, Item *c) :Item_func(a,b,c) {} enum Item_result result_type () const { return STRING_RESULT; } - CHARSET_INFO *charset_for_protocol(void) const { return &my_charset_bin; } enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } Item_result cmp_type() const { return TIME_RESULT; } String *val_str(String *str); diff --git a/sql/key.cc b/sql/key.cc index dd7818119c8..0d3db2d5bf5 100644 --- a/sql/key.cc +++ b/sql/key.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -335,6 +335,70 @@ bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length) return 0; } + +/** + Unpack a field and append it. + + @param[inout] to String to append the field contents to. + @param field Field to unpack. + @param rec Record which contains the field data. + @param max_length Maximum length of field to unpack + or 0 for unlimited. + @param prefix_key The field is used as a prefix key. +*/ + +void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, + bool prefix_key) +{ + String tmp; + DBUG_ENTER("field_unpack"); + if (!max_length) + max_length= field->pack_length(); + if (field) + { + if (field->is_null()) + { + to->append(STRING_WITH_LEN("NULL")); + DBUG_VOID_RETURN; + } + CHARSET_INFO *cs= field->charset(); + field->val_str(&tmp); + /* + For BINARY(N) strip trailing zeroes to make + the error message nice-looking + */ + if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length()) + { + const char *tmp_end= tmp.ptr() + tmp.length(); + while (tmp_end > tmp.ptr() && !*--tmp_end) ; + tmp.length(tmp_end - tmp.ptr() + 1); + } + if (cs->mbmaxlen > 1 && prefix_key) + { + /* + Prefix key, multi-byte charset. + For the columns of type CHAR(N), the above val_str() + call will return exactly "key_part->length" bytes, + which can break a multi-byte characters in the middle. + Align, returning not more than "char_length" characters. + */ + uint charpos, char_length= max_length / cs->mbmaxlen; + if ((charpos= my_charpos(cs, tmp.ptr(), + tmp.ptr() + tmp.length(), + char_length)) < tmp.length()) + tmp.length(charpos); + } + if (max_length < field->pack_length()) + tmp.length(min(tmp.length(),max_length)); + ErrConvString err(&tmp); + to->append(err.ptr()); + } + else + to->append(STRING_WITH_LEN("???")); + DBUG_VOID_RETURN; +} + + /* unpack key-fields from record to some buffer. @@ -352,8 +416,6 @@ bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length) void key_unpack(String *to,TABLE *table,uint idx) { KEY_PART_INFO *key_part,*key_part_end; - Field *field; - String tmp; my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); DBUG_ENTER("key_unpack"); @@ -369,47 +431,13 @@ void key_unpack(String *to,TABLE *table,uint idx) { if (table->record[0][key_part->null_offset] & key_part->null_bit) { - to->append(STRING_WITH_LEN("NULL")); - continue; - } - } - if ((field=key_part->field)) - { - CHARSET_INFO *cs= field->charset(); - field->val_str(&tmp); - /* - For BINARY(N) strip trailing zeroes to make - the error message nice-looking - */ - if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length()) - { - const char *tmp_end= tmp.ptr() + tmp.length(); - while (tmp_end > tmp.ptr() && !*--tmp_end) ; - tmp.length(tmp_end - tmp.ptr() + 1); - } - if (cs->mbmaxlen > 1 && (key_part->key_part_flag & HA_PART_KEY_SEG)) - { - /* - Prefix key, multi-byte charset. - For the columns of type CHAR(N), the above val_str() - call will return exactly "key_part->length" bytes, - which can break a multi-byte characters in the middle. - Align, returning not more than "char_length" characters. - */ - uint charpos, char_length= key_part->length / cs->mbmaxlen; - if ((charpos= my_charpos(cs, tmp.ptr(), - tmp.ptr() + tmp.length(), - char_length)) < tmp.length()) - tmp.length(charpos); + to->append(STRING_WITH_LEN("NULL")); + continue; } - if (key_part->length < field->pack_length()) - tmp.length(min(tmp.length(),key_part->length)); - ErrConvString err(&tmp); - to->append(err.ptr()); } - else - to->append(STRING_WITH_LEN("???")); - } + field_unpack(to, key_part->field, table->record[0], key_part->length, + test(key_part->key_part_flag & HA_PART_KEY_SEG)); + } dbug_tmp_restore_column_map(table->read_set, old_map); DBUG_VOID_RETURN; } diff --git a/sql/key.h b/sql/key.h index 8bf6f88fa04..0eeda58cd17 100644 --- a/sql/key.h +++ b/sql/key.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -33,6 +33,8 @@ void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, uint key_length); bool key_cmp_if_same(TABLE *form,const uchar *key,uint index,uint key_length); void key_unpack(String *to,TABLE *form,uint index); +void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, + bool prefix_key); bool is_key_used(TABLE *table, uint idx, const MY_BITMAP *fields); int key_cmp(KEY_PART_INFO *key_part, const uchar *key, uint key_length); ulong key_hashnr(KEY *key_info, uint used_key_parts, const uchar *key); diff --git a/sql/log.cc b/sql/log.cc index aba3c67583b..36940579ce5 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -189,7 +189,7 @@ Silence_log_table_errors::handle_condition(THD *, MYSQL_ERROR ** cond_hdl) { *cond_hdl= NULL; - strmake(m_message, msg, sizeof(m_message)-1); + strmake_buf(m_message, msg); return TRUE; } @@ -3557,8 +3557,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, mysql_file_sync(log_file.file, MYF(MY_WME|MY_SYNC_FILESIZE))) goto err; mysql_mutex_lock(&LOCK_commit_ordered); - strmake(last_commit_pos_file, log_file_name, - sizeof(last_commit_pos_file)-1); + strmake_buf(last_commit_pos_file, log_file_name); last_commit_pos_offset= my_b_tell(&log_file); mysql_mutex_unlock(&LOCK_commit_ordered); @@ -3646,7 +3645,7 @@ int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo) int MYSQL_BIN_LOG::raw_get_current_log(LOG_INFO* linfo) { - strmake(linfo->log_file_name, log_file_name, sizeof(linfo->log_file_name)-1); + strmake_buf(linfo->log_file_name, log_file_name); linfo->pos = my_b_tell(&log_file); return 0; } @@ -4167,8 +4166,7 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) Reset rli's coordinates to the current log. */ rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; - strmake(rli->event_relay_log_name,rli->linfo.log_file_name, - sizeof(rli->event_relay_log_name)-1); + strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name); /* If we removed the rli->group_relay_log_name file, @@ -4178,8 +4176,7 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) if (included) { rli->group_relay_log_pos = BIN_LOG_HEADER_SIZE; - strmake(rli->group_relay_log_name,rli->linfo.log_file_name, - sizeof(rli->group_relay_log_name)-1); + strmake_buf(rli->group_relay_log_name,rli->linfo.log_file_name); rli->notify_group_relay_log_name_update(); } @@ -4687,9 +4684,7 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) else { if (stat_area.st_mtime < purge_time) - strmake(to_log, - log_info.log_file_name, - sizeof(log_info.log_file_name) - 1); + strmake_buf(to_log, log_info.log_file_name); else break; } @@ -5332,8 +5327,7 @@ binlog_start_consistent_snapshot(handlerton *hton, THD *thd) binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data(); /* Server layer calls us with LOCK_commit_ordered locked, so this is safe. */ - strmake(cache_mngr->last_commit_pos_file, mysql_bin_log.last_commit_pos_file, - sizeof(cache_mngr->last_commit_pos_file)-1); + strmake_buf(cache_mngr->last_commit_pos_file, mysql_bin_log.last_commit_pos_file); cache_mngr->last_commit_pos_offset= mysql_bin_log.last_commit_pos_offset; trans_register_ha(thd, TRUE, hton); @@ -6968,8 +6962,8 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) if ((current->error= write_transaction_or_stmt(current))) current->commit_errno= errno; - strmake(cache_mngr->last_commit_pos_file, log_file_name, - sizeof(cache_mngr->last_commit_pos_file)-1); + + strmake_buf(cache_mngr->last_commit_pos_file, log_file_name); commit_offset= my_b_write_tell(&log_file); cache_mngr->last_commit_pos_offset= commit_offset; if (cache_mngr->using_xa && cache_mngr->xa_xid) @@ -8488,7 +8482,7 @@ int TC_LOG_BINLOG::open(const char *opt_name) do { - strmake(log_name, log_info.log_file_name, sizeof(log_name)-1); + strmake_buf(log_name, log_info.log_file_name); } while (!(error= find_next_log(&log_info, 1))); if (error != LOG_INFO_EOF) @@ -9221,7 +9215,7 @@ static void set_binlog_snapshot_file(const char *src) { int dir_len = dirname_length(src); - strmake(binlog_snapshot_file, src + dir_len, sizeof(binlog_snapshot_file)-1); + strmake_buf(binlog_snapshot_file, src + dir_len); } /* diff --git a/sql/log_event.cc b/sql/log_event.cc index 2968764920e..6bb96bf2f7d 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -48,6 +48,7 @@ #include <my_dir.h> #include "sql_show.h" // append_identifier #include <strfunc.h> +#include "compat56.h" #if WITH_WSREP #include "wsrep_mysqld.h" @@ -675,16 +676,18 @@ static inline int read_str(const char **buf, const char *buf_end, /** - Transforms a string into "" or its expression in 0x... form. + Transforms a string into "" or its expression in X'HHHH' form. */ char *str_to_hex(char *to, const char *from, uint len) { if (len) { - *to++= '0'; - *to++= 'x'; + *to++= 'X'; + *to++= '\''; to= octet2hex(to, from, len); + *to++= '\''; + *to= '\0'; } else to= strmov(to, "\"\""); @@ -705,7 +708,7 @@ append_query_string(THD *thd, CHARSET_INFO *csinfo, { char *beg, *ptr; uint32 const orig_len= to->length(); - if (to->reserve(orig_len + from->length()*2+3)) + if (to->reserve(orig_len + from->length() * 2 + 4)) return 1; beg= (char*) to->ptr() + to->length(); @@ -1273,7 +1276,9 @@ bool Log_event::write_header(IO_CACHE* file, ulong event_data_length) int Log_event::read_log_event(IO_CACHE* file, String* packet, mysql_mutex_t* log_lock, - uint8 checksum_alg_arg) + uint8 checksum_alg_arg, + const char *log_file_name_arg, + bool* is_binlog_active) { ulong data_len; int result=0; @@ -1283,6 +1288,10 @@ int Log_event::read_log_event(IO_CACHE* file, String* packet, if (log_lock) mysql_mutex_lock(log_lock); + + if (log_file_name_arg) + *is_binlog_active= mysql_bin_log.is_active(log_file_name_arg); + if (my_b_read(file, (uchar*) buf, sizeof(buf))) { /* @@ -2123,6 +2132,17 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, return 4; } + case MYSQL_TYPE_TIMESTAMP2: + { + char buf[MAX_DATE_STRING_REP_LENGTH]; + struct timeval tm; + my_timestamp_from_binary(&tm, ptr, meta); + int buflen= my_timeval_to_str(&tm, buf, meta); + my_b_write(file, buf, buflen); + my_snprintf(typestr, typestr_length, "TIMESTAMP(%d)", meta); + return my_timestamp_binary_length(meta); + } + case MYSQL_TYPE_DATETIME: { ulong d, t; @@ -2137,15 +2157,41 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, return 8; } + case MYSQL_TYPE_DATETIME2: + { + char buf[MAX_DATE_STRING_REP_LENGTH]; + MYSQL_TIME ltime; + longlong packed= my_datetime_packed_from_binary(ptr, meta); + TIME_from_longlong_datetime_packed(<ime, packed); + int buflen= my_datetime_to_str(<ime, buf, meta); + my_b_write_quoted(file, (uchar *) buf, buflen); + my_snprintf(typestr, typestr_length, "DATETIME(%d)", meta); + return my_datetime_binary_length(meta); + } + case MYSQL_TYPE_TIME: { - uint32 i32= uint3korr(ptr); - my_b_printf(file, "'%02d:%02d:%02d'", - i32 / 10000, (i32 % 10000) / 100, i32 % 100); + int32 tmp= sint3korr(ptr); + int32 i32= tmp >= 0 ? tmp : - tmp; + const char *sign= tmp < 0 ? "-" : ""; + my_b_printf(file, "'%s%02d:%02d:%02d'", + sign, i32 / 10000, (i32 % 10000) / 100, i32 % 100, i32); my_snprintf(typestr, typestr_length, "TIME"); return 3; } - + + case MYSQL_TYPE_TIME2: + { + char buf[MAX_DATE_STRING_REP_LENGTH]; + MYSQL_TIME ltime; + longlong packed= my_time_packed_from_binary(ptr, meta); + TIME_from_longlong_time_packed(<ime, packed); + int buflen= my_time_to_str(<ime, buf, meta); + my_b_write_quoted(file, (uchar *) buf, buflen); + my_snprintf(typestr, typestr_length, "TIME(%d)", meta); + return my_time_binary_length(meta); + } + case MYSQL_TYPE_NEWDATE: { uint32 tmp= uint3korr(ptr); @@ -7053,7 +7099,7 @@ void User_var_log_event::pack_info(THD *thd, Protocol* protocol) buf.append(" ")) return; old_len= buf.length(); - if (buf.reserve(old_len + val_len*2 + 2 + sizeof(" COLLATE ") + + if (buf.reserve(old_len + val_len * 2 + 3 + sizeof(" COLLATE ") + MY_CS_NAME_SIZE)) return; beg= const_cast<char *>(buf.ptr()) + old_len; @@ -7096,10 +7142,9 @@ User_var_log_event(const char* buf, uint event_len, /* We don't know yet is_null value, so we must assume that name_len may have the bigger value possible, is_null= True and there is no - payload for val. + payload for val, or even that name_len is 0. */ - if (0 == name_len || - !valid_buffer_range<uint>(name_len, buf_start, name, + if (!valid_buffer_range<uint>(name_len, buf_start, name, event_len - UV_VAL_IS_NULL)) { error= true; @@ -7322,7 +7367,8 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) char *hex_str; CHARSET_INFO *cs; - hex_str= (char *)my_malloc(2*val_len+1+2,MYF(MY_WME)); // 2 hex digits / byte + // 2 hex digits / byte + hex_str= (char *) my_malloc(2 * val_len + 1 + 3, MYF(MY_WME)); if (!hex_str) return; str_to_hex(hex_str, val, val_len); @@ -9868,7 +9914,7 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, { m_coltype= reinterpret_cast<uchar*>(m_memory); for (unsigned int i= 0 ; i < m_table->s->fields ; ++i) - m_coltype[i]= m_table->field[i]->type(); + m_coltype[i]= m_table->field[i]->binlog_type(); } /* @@ -10272,6 +10318,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->table_id= DBUG_EVALUATE_IF("inject_tblmap_same_id_maps_diff_table", 0, m_table_id); table_list->updating= 1; + table_list->required_type= FRMTYPE_TABLE; DBUG_PRINT("debug", ("table: %s is mapped to %u", table_list->table_name, table_list->table_id)); enum_tbl_map_status tblmap_status= check_table_map(rli, table_list); if (tblmap_status == OK_TO_PROCESS) diff --git a/sql/log_event.h b/sql/log_event.h index b5b488f320d..b54e2028ef2 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -1121,8 +1121,35 @@ public: const Format_description_log_event *description_event, my_bool crc_check); + + /** + Reads an event from a binlog or relay log. Used by the dump thread + this method reads the event into a raw buffer without parsing it. + + @Note If mutex is 0, the read will proceed without mutex. + + @Note If a log name is given than the method will check if the + given binlog is still active. + + @param[in] file log file to be read + @param[out] packet packet to hold the event + @param[in] lock the lock to be used upon read + @param[in] log_file_name_arg the log's file name + @param[out] is_binlog_active is the current log still active + + @retval 0 success + @retval LOG_READ_EOF end of file, nothing was read + @retval LOG_READ_BOGUS malformed event + @retval LOG_READ_IO io error while reading + @retval LOG_READ_MEM packet memory allocation failed + @retval LOG_READ_TRUNC only a partial event could be read + @retval LOG_READ_TOO_LARGE event too large + */ static int read_log_event(IO_CACHE* file, String* packet, - mysql_mutex_t* log_lock, uint8 checksum_alg_arg); + mysql_mutex_t* log_lock, + uint8 checksum_alg_arg, + const char *log_file_name_arg = NULL, + bool* is_binlog_active = NULL); /* init_show_field_list() prepares the column names and types for the output of SHOW BINLOG EVENTS; it is used only by SHOW BINLOG diff --git a/sql/mdl.cc b/sql/mdl.cc index e7321d35e77..91e1766bf6c 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -97,7 +97,7 @@ PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= {0, "Waiting for trigger metadata lock", 0}, {0, "Waiting for event metadata lock", 0}, {0, "Waiting for commit lock", 0}, - {0, "User lock"} /* Be compatible with old status. */ + {0, "User lock", 0} /* Be compatible with old status. */ }; #ifdef HAVE_PSI_INTERFACE diff --git a/sql/mysql_install_db.cc b/sql/mysql_install_db.cc index 6a1ca2e09d3..6f28760c055 100644 --- a/sql/mysql_install_db.cc +++ b/sql/mysql_install_db.cc @@ -54,6 +54,7 @@ static char *opt_os_password; static my_bool opt_default_user; static my_bool opt_allow_remote_root_access; static my_bool opt_skip_networking; +static my_bool opt_verbose_bootstrap; static my_bool verbose_errors; @@ -83,6 +84,8 @@ static struct my_option my_long_options[]= 0, 0}, {"silent", 's', "Print less information", &opt_silent, &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose-bootstrap", 'o', "Include mysqld bootstrap output",&opt_verbose_bootstrap, + &opt_verbose_bootstrap, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; @@ -244,11 +247,12 @@ static char *init_bootstrap_command_line(char *cmdline, size_t size) get_basedir(basedir, sizeof(basedir), mysqld_path); my_snprintf(cmdline, size-1, - "\"\"%s\" --no-defaults --bootstrap" - " \"--language=%s\\share\\english\"" + "\"\"%s\" --no-defaults %s --bootstrap" + " \"--lc-messages-dir=%s/share\"" " --basedir=. --datadir=. --default-storage-engine=myisam" " --max_allowed_packet=9M " - " --net-buffer-length=16k\"", mysqld_path, basedir); + " --net-buffer-length=16k\"", mysqld_path, + opt_verbose_bootstrap?"--console":"", basedir ); return cmdline; } @@ -377,7 +381,7 @@ static int register_service() static void clean_directory(const char *dir) { char dir2[MAX_PATH+2]; - *(strmake(dir2, dir, MAX_PATH+1)+1)= 0; + *(strmake_buf(dir2, dir)+1)= 0; SHFILEOPSTRUCT fileop; fileop.hwnd= NULL; /* no status display */ @@ -552,7 +556,9 @@ static int create_db_instance() /* Do mysqld --bootstrap. */ init_bootstrap_command_line(cmdline, sizeof(cmdline)); - /* verbose("Executing %s", cmdline); */ + + if(opt_verbose_bootstrap) + printf("Executing %s\n", cmdline); in= popen(cmdline, "wt"); if (!in) diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1b61167cf3b..4fbda3a764e 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -14,7 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_priv.h" #include "unireg.h" #include <signal.h> @@ -1062,7 +1062,8 @@ static PSI_cond_info all_server_conds[]= PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert, key_thread_handle_manager, key_thread_main, - key_thread_one_connection, key_thread_signal_hand; + key_thread_one_connection, key_thread_signal_hand, + key_thread_slave_init; static PSI_thread_info all_server_threads[]= { @@ -1087,7 +1088,8 @@ static PSI_thread_info all_server_threads[]= { &key_thread_handle_manager, "manager", PSI_FLAG_GLOBAL}, { &key_thread_main, "main", PSI_FLAG_GLOBAL}, { &key_thread_one_connection, "one_connection", 0}, - { &key_thread_signal_hand, "signal_handler", PSI_FLAG_GLOBAL} + { &key_thread_signal_hand, "signal_handler", PSI_FLAG_GLOBAL}, + { &key_thread_slave_init, "slave_init", PSI_FLAG_GLOBAL} }; #ifdef HAVE_MMAP @@ -3551,14 +3553,25 @@ sizeof(load_default_groups)/sizeof(load_default_groups[0]); #ifndef EMBEDDED_LIBRARY -static -int -check_enough_stack_size() +/** + This function is used to check for stack overrun for pathological + cases of regular expressions and 'like' expressions. + The call to current_thd is quite expensive, so we try to avoid it + for the normal cases. + The size of each stack frame for the wildcmp() routines is ~128 bytes, + so checking *every* recursive call is not necessary. + */ +extern "C" int +check_enough_stack_size(int recurse_level) { uchar stack_top; + if (recurse_level % 16 != 0) + return 0; - return check_stack_overrun(current_thd, STACK_MIN_SIZE, - &stack_top); + THD *my_thd= current_thd; + if (my_thd != NULL) + return check_stack_overrun(my_thd, STACK_MIN_SIZE * 2, &stack_top); + return 0; } #endif @@ -3916,7 +3929,7 @@ static int init_common_variables() WideCharToMultiByte(CP_UTF8,0, wtz_name, -1, system_time_zone, sizeof(system_time_zone) - 1, NULL, NULL); #else - strmake(system_time_zone, tz_name, sizeof(system_time_zone)-1); + strmake_buf(system_time_zone, tz_name); #endif /* _WIN32 */ #endif /* HAVE_TZNAME */ @@ -4192,6 +4205,7 @@ static int init_common_variables() item_init(); #ifndef EMBEDDED_LIBRARY my_regex_init(&my_charset_latin1, check_enough_stack_size); + my_string_stack_guard= check_enough_stack_size; #else my_regex_init(&my_charset_latin1, NULL); #endif @@ -4739,6 +4753,7 @@ will be ignored as the --log-bin option is not defined."); { WSREP_ERROR("Could not append %s/bin to PATH", mysql_home); } + DBUG_ASSERT(!opt_bin_log || opt_bin_logname); if (wsrep_before_SE()) { @@ -4753,7 +4768,7 @@ will be ignored as the --log-bin option is not defined."); { /* Reports an error and aborts, if the --log-bin's path is a directory.*/ - if (opt_bin_logname && + if (opt_bin_logname[0] && opt_bin_logname[strlen(opt_bin_logname) - 1] == FN_LIBCHAR) { sql_print_error("Path '%s' is a directory name, please specify \ @@ -4775,7 +4790,7 @@ a file name for --log-bin-index option", opt_binlog_index_name); char buf[FN_REFLEN]; const char *ln; ln= mysql_bin_log.generate_name(opt_bin_logname, "-bin", 1, buf); - if (!opt_bin_logname && !opt_binlog_index_name) + if (!opt_bin_logname[0] && !opt_binlog_index_name) { /* User didn't give us info to name the binlog index file. @@ -8108,8 +8123,8 @@ static int mysql_init_variables(void) /* Set directory paths */ mysql_real_data_home_len= - strmake(mysql_real_data_home, get_relative_path(MYSQL_DATADIR), - sizeof(mysql_real_data_home)-1) - mysql_real_data_home; + strmake_buf(mysql_real_data_home, + get_relative_path(MYSQL_DATADIR)) - mysql_real_data_home; /* Replication parameters */ master_info_file= (char*) "master.info", relay_log_info_file= (char*) "relay-log.info"; @@ -8218,7 +8233,7 @@ static int mysql_init_variables(void) const char *tmpenv; if (!(tmpenv = getenv("MY_BASEDIR_VERSION"))) tmpenv = DEFAULT_MYSQL_HOME; - (void) strmake(mysql_home, tmpenv, sizeof(mysql_home)-1); + strmake_buf(mysql_home, tmpenv); #endif #ifdef WITH_WSREP if (WSREP_ON && wsrep_init_vars()) @@ -8261,7 +8276,7 @@ mysqld_get_one_option(int optid, global_system_variables.tx_isolation= ISO_SERIALIZABLE; break; case 'b': - strmake(mysql_home,argument,sizeof(mysql_home)-1); + strmake_buf(mysql_home, argument); break; case 'C': if (default_collation_name == compiled_default_collation_name) @@ -8272,7 +8287,7 @@ mysqld_get_one_option(int optid, opt_log=1; break; case 'h': - strmake(mysql_real_data_home,argument, sizeof(mysql_real_data_home)-1); + strmake_buf(mysql_real_data_home, argument); /* Correct pointer set by my_getopt (for embedded library) */ mysql_real_data_home_ptr= mysql_real_data_home; break; @@ -8283,7 +8298,7 @@ mysqld_get_one_option(int optid, sql_print_warning("Ignoring user change to '%s' because the user was set to '%s' earlier on the command line\n", argument, mysqld_user); break; case 'L': - strmake(lc_messages_dir, argument, sizeof(lc_messages_dir)-1); + strmake_buf(lc_messages_dir, argument); break; case OPT_BINLOG_FORMAT: binlog_format_used= true; @@ -9103,7 +9118,7 @@ static int fix_paths(void) char *sharedir=get_relative_path(SHAREDIR); if (test_if_hard_path(sharedir)) - strmake(buff,sharedir,sizeof(buff)-1); /* purecov: tested */ + strmake_buf(buff, sharedir); /* purecov: tested */ else strxnmov(buff,sizeof(buff)-1,mysql_home,sharedir,NullS); convert_dirname(buff,buff,NullS); @@ -9111,7 +9126,7 @@ static int fix_paths(void) /* If --character-sets-dir isn't given, use shared library dir */ if (charsets_dir) - strmake(mysql_charsets_dir, charsets_dir, sizeof(mysql_charsets_dir)-1); + strmake_buf(mysql_charsets_dir, charsets_dir); else strxnmov(mysql_charsets_dir, sizeof(mysql_charsets_dir)-1, buff, CHARSET_DIR, NullS); diff --git a/sql/mysqld.h b/sql/mysqld.h index 55a631c3ccf..c1b481c94bd 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -293,7 +293,7 @@ extern PSI_cond_key key_TC_LOG_MMAP_COND_queue_busy; extern PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert, key_thread_handle_manager, key_thread_kill_server, key_thread_main, - key_thread_one_connection, key_thread_signal_hand; + key_thread_one_connection, key_thread_signal_hand, key_thread_slave_init; extern PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file, diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 37a7944bbaf..e8213ea8dd6 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -7689,6 +7689,14 @@ static SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param, param->current_table); DBUG_ENTER("get_full_func_mm_tree"); +#ifdef HAVE_SPATIAL + if (field_item->field->type() == MYSQL_TYPE_GEOMETRY) + { + /* We have to be able to store all sorts of spatial features here */ + ((Field_geom*) field_item->field)->geom_type= Field::GEOM_GEOMETRY; + } +#endif /*HAVE_SPATIAL*/ + for (uint i= 0; i < cond_func->arg_count; i++) { Item *arg= cond_func->arguments()[i]->real_item(); @@ -8006,10 +8014,10 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ if (field->result_type() == STRING_RESULT && - ((Field_str*) field)->match_collation_to_optimize_range() && + field->match_collation_to_optimize_range() && value->result_type() == STRING_RESULT && key_part->image_type == Field::itRAW && - ((Field_str*)field)->charset() != conf_func->compare_collation() && + field->charset() != conf_func->compare_collation() && !(conf_func->compare_collation()->state & MY_CS_BINSORT && (type == Item_func::EQUAL_FUNC || type == Item_func::EQ_FUNC))) goto end; @@ -11248,9 +11256,13 @@ int QUICK_ROR_INTERSECT_SELECT::get_next() do { + DBUG_EXECUTE_IF("innodb_quick_report_deadlock", + DBUG_SET("+d,innodb_report_deadlock");); if ((error= quick->get_next())) { - quick_with_last_rowid->file->unlock_row(); + /* On certain errors like deadlock, trx might be rolled back.*/ + if (!current_thd->transaction_rollback_request) + quick_with_last_rowid->file->unlock_row(); DBUG_RETURN(error); } quick->file->position(quick->record); @@ -11276,7 +11288,9 @@ int QUICK_ROR_INTERSECT_SELECT::get_next() quick->file->unlock_row(); /* row not in range; unlock */ if ((error= quick->get_next())) { - quick_with_last_rowid->file->unlock_row(); + /* On certain errors like deadlock, trx might be rolled back.*/ + if (!current_thd->transaction_rollback_request) + quick_with_last_rowid->file->unlock_row(); DBUG_RETURN(error); } } @@ -12161,13 +12175,15 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, NGA1.If in the index I there is a gap between the last GROUP attribute G_k, and the MIN/MAX attribute C, then NGA must consist of exactly the index attributes that constitute the gap. As a result there is a - permutation of NGA that coincides with the gap in the index - <B_1, ..., B_m>. + permutation of NGA, BA=<B_1,...,B_m>, that coincides with the gap + in the index. NGA2.If BA <> {}, then the WHERE clause must contain a conjunction EQ of equality conditions for all NG_i of the form (NG_i = const) or (const = NG_i), such that each NG_i is referenced in exactly one conjunct. Informally, the predicates provide constants to fill the gap in the index. + NGA3.If BA <> {}, there can only be one range. TODO: This is a code + limitation and is not strictly needed. See BUG#15947433 WA1. There are no other attributes in the WHERE clause except the ones referenced in predicates RNG, PA, PC, EQ defined above. Therefore WA is subset of (GA union NGA union C) for GA,NGA,C that pass the @@ -12869,7 +12885,7 @@ check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item, */ ((args[1]->result_type() == STRING_RESULT && image_type == Field::itRAW && - ((Field_str*) min_max_arg_item->field)->charset() != + min_max_arg_item->field->charset() != pred->compare_collation()) || /* @@ -12910,6 +12926,74 @@ check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item, /* + Get SEL_ARG tree, if any, for the keypart covering non grouping + attribute (NGA) field 'nga_field'. + + This function enforces the NGA3 test: If 'keypart_tree' contains a + condition for 'nga_field', there can only be one range. In the + opposite case, this function returns with error and 'cur_range' + should not be used. + + Note that the NGA1 and NGA2 requirements, like whether or not the + range predicate for 'nga_field' is equality, is not tested by this + function. + + @param[in] nga_field The NGA field we want the SEL_ARG tree for + @param[in] keypart_tree Root node of the SEL_ARG* tree for the index + @param[out] cur_range The SEL_ARG tree, if any, for the keypart + covering field 'keypart_field' + @retval true 'keypart_tree' contained a predicate for 'nga_field' but + multiple ranges exists. 'cur_range' should not be used. + @retval false otherwise +*/ + +static bool +get_sel_arg_for_keypart(Field *nga_field, + SEL_ARG *keypart_tree, + SEL_ARG **cur_range) +{ + if(keypart_tree == NULL) + return false; + if(keypart_tree->field->eq(nga_field)) + { + /* + Enforce NGA3: If a condition for nga_field has been found, only + a single range is allowed. + */ + if (keypart_tree->prev || keypart_tree->next) + return true; // There are multiple ranges + + *cur_range= keypart_tree; + return false; + } + + SEL_ARG *found_tree= NULL; + SEL_ARG *first_kp= keypart_tree->first(); + + for (SEL_ARG *cur_kp= first_kp; cur_kp && !found_tree; + cur_kp= cur_kp->next) + { + if (cur_kp->next_key_part) + { + if (get_sel_arg_for_keypart(nga_field, + cur_kp->next_key_part, + &found_tree)) + return true; + + } + /* + Enforce NGA3: If a condition for nga_field has been found,only + a single range is allowed. + */ + if (found_tree && first_kp->next) + return true; // There are multiple ranges + } + *cur_range= found_tree; + return false; +} + + +/* Extract a sequence of constants from a conjunction of equality predicates. SYNOPSIS @@ -12923,12 +13007,13 @@ check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item, key_infix [out] Infix of constants to be used for index lookup key_infix_len [out] Lenghth of the infix first_non_infix_part [out] The first keypart after the infix (if any) - + DESCRIPTION - Test conditions (NGA1, NGA2) from get_best_group_min_max(). Namely, - for each keypart field NGF_i not in GROUP-BY, check that there is a - constant equality predicate among conds with the form (NGF_i = const_ci) or - (const_ci = NGF_i). + Test conditions (NGA1, NGA2, NGA3) from get_best_group_min_max(). Namely, + for each keypart field NG_i not in GROUP-BY, check that there is exactly one + constant equality predicate among conds with the form (NG_i = const_ci) or + (const_ci = NG_i).. In addition, there can only be one range when there is + such a gap. Thus all the NGF_i attributes must fill the 'gap' between the last group-by attribute and the MIN/MAX attribute in the index (if present). If these conditions hold, copy each constant from its corresponding predicate into @@ -12957,17 +13042,14 @@ get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree, uchar *key_ptr= key_infix; for (cur_part= first_non_group_part; cur_part != end_part; cur_part++) { + cur_range= NULL; /* Find the range tree for the current keypart. We assume that - index_range_tree points to the leftmost keypart in the index. + index_range_tree points to the first keypart in the index. */ - for (cur_range= index_range_tree; - cur_range && cur_range->type == SEL_ARG::KEY_RANGE; - cur_range= cur_range->next_key_part) - { - if (cur_range->field->eq(cur_part->field)) - break; - } + if(get_sel_arg_for_keypart(cur_part->field, index_range_tree, &cur_range)) + return false; + if (!cur_range || cur_range->type != SEL_ARG::KEY_RANGE) { if (min_max_arg_part) @@ -12979,9 +13061,6 @@ get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree, } } - /* Check that the current range tree is a single point interval. */ - if (cur_range->prev || cur_range->next) - return FALSE; /* This is not the only range predicate for the field. */ if ((cur_range->min_flag & NO_MIN_RANGE) || (cur_range->max_flag & NO_MAX_RANGE) || (cur_range->min_flag & NEAR_MIN) || (cur_range->max_flag & NEAR_MAX)) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 33164c1ed12..1542efa7415 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -1482,7 +1482,7 @@ void check_equality(Dep_analysis_context *ctx, Dep_module_expr **eq_mod, collation of the operation differ from the field collation. */ if (field->cmp_type() == STRING_RESULT && - ((Field_str*)field)->charset() != cond->compare_collation()) + field->charset() != cond->compare_collation()) return; } } diff --git a/sql/partition_element.h b/sql/partition_element.h index 87f3d00e68c..4f03d91035a 100644 --- a/sql/partition_element.h +++ b/sql/partition_element.h @@ -1,7 +1,7 @@ #ifndef PARTITION_ELEMENT_INCLUDED #define PARTITION_ELEMENT_INCLUDED -/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -107,9 +107,8 @@ public: enum partition_state part_state; uint16 nodegroup_id; bool has_null_value; - /* signed_flag and max_value only relevant for subpartitions */ - bool signed_flag; - bool max_value; + bool signed_flag; // Range value signed + bool max_value; // MAXVALUE range partition_element() : part_max_rows(0), part_min_rows(0), range_value(0), diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 934f4e970cb..34e47331664 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -1,5 +1,4 @@ -/* - Copyright (c) 2006, 2010, Oracle and/or its affiliates. +/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. 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 @@ -2173,9 +2172,36 @@ int partition_info::fix_parser_data(THD *thd) if (!(part_type == RANGE_PARTITION || part_type == LIST_PARTITION)) { - /* Nothing to do for HASH/KEY partitioning */ + if (part_type == HASH_PARTITION && list_of_part_fields) + { + /* KEY partitioning, check ALGORITHM = N. Should not pass the parser! */ + if (key_algorithm > KEY_ALGORITHM_55) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(true); + } + /* If not set, use DEFAULT = 2 for CREATE and ALTER! */ + if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE || + thd_sql_command(thd) == SQLCOM_ALTER_TABLE) && + key_algorithm == KEY_ALGORITHM_NONE) + key_algorithm= KEY_ALGORITHM_55; + } DBUG_RETURN(FALSE); } + if (is_sub_partitioned() && list_of_subpart_fields) + { + /* KEY subpartitioning, check ALGORITHM = N. Should not pass the parser! */ + if (key_algorithm > KEY_ALGORITHM_55) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(true); + } + /* If not set, use DEFAULT = 2 for CREATE and ALTER! */ + if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE || + thd_sql_command(thd) == SQLCOM_ALTER_TABLE) && + key_algorithm == KEY_ALGORITHM_NONE) + key_algorithm= KEY_ALGORITHM_55; + } do { part_elem= it++; @@ -2224,6 +2250,263 @@ int partition_info::fix_parser_data(THD *thd) DBUG_RETURN(FALSE); } + +/** + helper function to compare strings that can also be + a NULL pointer. + + @param a char pointer (can be NULL). + @param b char pointer (can be NULL). + + @return false if equal + @retval true strings differs + @retval false strings is equal +*/ + +static bool strcmp_null(const char *a, const char *b) +{ + if (!a && !b) + return false; + if (a && b && !strcmp(a, b)) + return false; + return true; +} + + +/** + Check if the new part_info has the same partitioning. + + @param new_part_info New partition definition to compare with. + + @return True if not considered to have changed the partitioning. + @retval true Allowed change (only .frm change, compatible distribution). + @retval false Different partitioning, will need redistribution of rows. + + @note Currently only used to allow changing from non-set key_algorithm + to a specified key_algorithm, to avoid rebuild when upgrading from 5.1 of + such partitioned tables using numeric colums in the partitioning expression. + For more info see bug#14521864. + Does not check if columns etc has changed, i.e. only for + alter_info->flags == ALTER_PARTITION. +*/ + +bool partition_info::has_same_partitioning(partition_info *new_part_info) +{ + DBUG_ENTER("partition_info::has_same_partitioning"); + + DBUG_ASSERT(part_field_array && part_field_array[0]); + + /* + Only consider pre 5.5.3 .frm's to have same partitioning as + a new one with KEY ALGORITHM = 1 (). + */ + + if (part_field_array[0]->table->s->mysql_version >= 50503) + DBUG_RETURN(false); + + if (!new_part_info || + part_type != new_part_info->part_type || + num_parts != new_part_info->num_parts || + use_default_partitions != new_part_info->use_default_partitions || + new_part_info->is_sub_partitioned() != is_sub_partitioned()) + DBUG_RETURN(false); + + if (part_type != HASH_PARTITION) + { + /* + RANGE or LIST partitioning, check if KEY subpartitioned. + Also COLUMNS partitioning was added in 5.5, so treat that as different. + */ + if (!is_sub_partitioned() || + !new_part_info->is_sub_partitioned() || + column_list || + new_part_info->column_list || + !list_of_subpart_fields || + !new_part_info->list_of_subpart_fields || + new_part_info->num_subparts != num_subparts || + new_part_info->subpart_field_list.elements != + subpart_field_list.elements || + new_part_info->use_default_subpartitions != + use_default_subpartitions) + DBUG_RETURN(false); + } + else + { + /* Check if KEY partitioned. */ + if (!new_part_info->list_of_part_fields || + !list_of_part_fields || + new_part_info->part_field_list.elements != part_field_list.elements) + DBUG_RETURN(false); + } + + /* Check that it will use the same fields in KEY (fields) list. */ + List_iterator<char> old_field_name_it(part_field_list); + List_iterator<char> new_field_name_it(new_part_info->part_field_list); + char *old_name, *new_name; + while ((old_name= old_field_name_it++)) + { + new_name= new_field_name_it++; + if (!new_name || my_strcasecmp(system_charset_info, + new_name, + old_name)) + DBUG_RETURN(false); + } + + if (is_sub_partitioned()) + { + /* Check that it will use the same fields in KEY subpart fields list. */ + List_iterator<char> old_field_name_it(subpart_field_list); + List_iterator<char> new_field_name_it(new_part_info->subpart_field_list); + char *old_name, *new_name; + while ((old_name= old_field_name_it++)) + { + new_name= new_field_name_it++; + if (!new_name || my_strcasecmp(system_charset_info, + new_name, + old_name)) + DBUG_RETURN(false); + } + } + + if (!use_default_partitions) + { + /* + Loop over partitions/subpartition to verify that they are + the same, including state and name. + */ + List_iterator<partition_element> part_it(partitions); + List_iterator<partition_element> new_part_it(new_part_info->partitions); + uint i= 0; + do + { + partition_element *part_elem= part_it++; + partition_element *new_part_elem= new_part_it++; + /* + The following must match: + partition_name, tablespace_name, data_file_name, index_file_name, + engine_type, part_max_rows, part_min_rows, nodegroup_id. + (max_value, signed_flag, has_null_value only on partition level, + RANGE/LIST) + The following can differ: + - part_comment + part_state must be PART_NORMAL! + */ + if (!part_elem || !new_part_elem || + strcmp(part_elem->partition_name, + new_part_elem->partition_name) || + part_elem->part_state != PART_NORMAL || + new_part_elem->part_state != PART_NORMAL || + part_elem->max_value != new_part_elem->max_value || + part_elem->signed_flag != new_part_elem->signed_flag || + part_elem->has_null_value != new_part_elem->has_null_value) + DBUG_RETURN(false); + + /* new_part_elem may not have engine_type set! */ + if (new_part_elem->engine_type && + part_elem->engine_type != new_part_elem->engine_type) + DBUG_RETURN(false); + + if (is_sub_partitioned()) + { + /* + Check that both old and new partition has the same definition + (VALUES IN/VALUES LESS THAN) (No COLUMNS partitioning, see above) + */ + if (part_type == LIST_PARTITION) + { + List_iterator<part_elem_value> list_vals(part_elem->list_val_list); + List_iterator<part_elem_value> + new_list_vals(new_part_elem->list_val_list); + part_elem_value *val; + part_elem_value *new_val; + while ((val= list_vals++)) + { + new_val= new_list_vals++; + if (!new_val) + DBUG_RETURN(false); + if ((!val->null_value && !new_val->null_value) && + val->value != new_val->value) + DBUG_RETURN(false); + } + if (new_list_vals++) + DBUG_RETURN(false); + } + else + { + DBUG_ASSERT(part_type == RANGE_PARTITION); + if (new_part_elem->range_value != part_elem->range_value) + DBUG_RETURN(false); + } + + if (!use_default_subpartitions) + { + List_iterator<partition_element> + sub_part_it(part_elem->subpartitions); + List_iterator<partition_element> + new_sub_part_it(new_part_elem->subpartitions); + uint j= 0; + do + { + partition_element *sub_part_elem= sub_part_it++; + partition_element *new_sub_part_elem= new_sub_part_it++; + /* new_part_elem may not have engine_type set! */ + if (new_sub_part_elem->engine_type && + sub_part_elem->engine_type != new_part_elem->engine_type) + DBUG_RETURN(false); + + if (strcmp(sub_part_elem->partition_name, + new_sub_part_elem->partition_name) || + sub_part_elem->part_state != PART_NORMAL || + new_sub_part_elem->part_state != PART_NORMAL || + sub_part_elem->part_min_rows != + new_sub_part_elem->part_min_rows || + sub_part_elem->part_max_rows != + new_sub_part_elem->part_max_rows || + sub_part_elem->nodegroup_id != + new_sub_part_elem->nodegroup_id) + DBUG_RETURN(false); + + if (strcmp_null(sub_part_elem->data_file_name, + new_sub_part_elem->data_file_name) || + strcmp_null(sub_part_elem->index_file_name, + new_sub_part_elem->index_file_name) || + strcmp_null(sub_part_elem->tablespace_name, + new_sub_part_elem->tablespace_name)) + DBUG_RETURN(false); + + } while (++j < num_subparts); + } + } + else + { + if (part_elem->part_min_rows != new_part_elem->part_min_rows || + part_elem->part_max_rows != new_part_elem->part_max_rows || + part_elem->nodegroup_id != new_part_elem->nodegroup_id) + DBUG_RETURN(false); + + if (strcmp_null(part_elem->data_file_name, + new_part_elem->data_file_name) || + strcmp_null(part_elem->index_file_name, + new_part_elem->index_file_name) || + strcmp_null(part_elem->tablespace_name, + new_part_elem->tablespace_name)) + DBUG_RETURN(false); + } + } while (++i < num_parts); + } + + /* + Only if key_algorithm was not specified before and it is now set, + consider this as nothing was changed, and allow change without rebuild! + */ + if (key_algorithm != partition_info::KEY_ALGORITHM_NONE || + new_part_info->key_algorithm == partition_info::KEY_ALGORITHM_NONE) + DBUG_RETURN(false); + + DBUG_RETURN(true); +} + + void partition_info::print_debug(const char *str, uint *value) { DBUG_ENTER("print_debug"); diff --git a/sql/partition_info.h b/sql/partition_info.h index 2fbdfff6636..17c9cb383ee 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -1,7 +1,7 @@ #ifndef PARTITION_INFO_INCLUDED #define PARTITION_INFO_INCLUDED -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -192,6 +192,19 @@ public: but mainly of use to handlers supporting partitioning. */ uint16 linear_hash_mask; + /* + PARTITION BY KEY ALGORITHM=N + Which algorithm to use for hashing the fields. + N = 1 - Use 5.1 hashing (numeric fields are hashed as binary) + N = 2 - Use 5.5 hashing (numeric fields are hashed like latin1 bytes) + */ + enum enum_key_algorithm + { + KEY_ALGORITHM_NONE= 0, + KEY_ALGORITHM_51= 1, + KEY_ALGORITHM_55= 2 + }; + enum_key_algorithm key_algorithm; bool use_default_partitions; bool use_default_num_partitions; @@ -232,6 +245,7 @@ public: count_curr_subparts(0), part_error_code(0), num_list_values(0), num_part_fields(0), num_subpart_fields(0), num_full_part_fields(0), has_null_part_id(0), linear_hash_mask(0), + key_algorithm(KEY_ALGORITHM_NONE), use_default_partitions(TRUE), use_default_num_partitions(TRUE), use_default_subpartitions(TRUE), use_default_num_subpartitions(TRUE), default_partitions_setup(FALSE), defined_max_value(FALSE), @@ -298,6 +312,7 @@ public: bool add_column_list_value(THD *thd, Item *item); void set_show_version_string(String *packet); void report_part_expr_error(bool use_subpart_expr); + bool has_same_partitioning(partition_info *new_part_info); private: static int list_part_cmp(const void* a, const void* b); bool set_up_default_partitions(handler *file, HA_CREATE_INFO *info, diff --git a/sql/protocol.cc b/sql/protocol.cc index a86da94af61..d46365df0ad 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -1435,7 +1435,7 @@ bool Protocol_binary::store(MYSQL_TIME *tm, int decimals) DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); if (decimals != AUTO_SEC_PART_DIGITS) - tm->second_part= sec_part_truncate(tm->second_part, decimals); + my_time_trunc(tm, decimals); int4store(pos+7, tm->second_part); if (tm->second_part) length=11; @@ -1477,7 +1477,7 @@ bool Protocol_binary::store_time(MYSQL_TIME *tm, int decimals) DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); if (decimals != AUTO_SEC_PART_DIGITS) - tm->second_part= sec_part_truncate(tm->second_part, decimals); + my_time_trunc(tm, decimals); int4store(pos+8, tm->second_part); if (tm->second_part) length=12; diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc index b34b890060b..d5e9380296e 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -180,6 +180,22 @@ rpl_slave_state::get_element(uint32 domain_id) int +rpl_slave_state::put_back_list(uint32 domain_id, list_element *list) +{ + element *e; + if (!(e= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0))) + return 1; + while (list) + { + list_element *next= list->next; + e->add(list); + list= next; + } + return 0; +} + + +int rpl_slave_state::truncate_state_table(THD *thd) { TABLE_LIST tlist; @@ -330,7 +346,10 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, table->field[3]->store(gtid->seq_no, true); DBUG_EXECUTE_IF("inject_crash_before_write_rpl_slave_state", DBUG_SUICIDE();); if ((err= table->file->ha_write_row(table->record[0]))) - goto end; + { + table->file->print_error(err, MYF(0)); + goto end; + } lock(); if ((elem= get_element(gtid->domain_id)) == NULL) @@ -351,23 +370,42 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, bitmap_set_bit(table->read_set, table->field[1]->field_index); if ((err= table->file->ha_index_init(0, 0))) + { + table->file->print_error(err, MYF(0)); goto end; + } while (elist) { uchar key_buffer[4+8]; + DBUG_EXECUTE_IF("gtid_slave_pos_simulate_failed_delete", + { err= ENOENT; + table->file->print_error(err, MYF(0)); + /* `break' does not work in DBUG_EXECUTE_IF */ + goto dbug_break; }); + next= elist->next; table->field[1]->store(elist->sub_id, true); /* domain_id is already set in table->record[0] from write_row() above. */ key_copy(key_buffer, table->record[0], &table->key_info[0], 0, false); - if ((err= table->file->ha_index_read_map(table->record[1], key_buffer, - HA_WHOLE_KEY, HA_READ_KEY_EXACT)) || - (err= table->file->ha_delete_row(table->record[1]))) - break; + if (table->file->ha_index_read_map(table->record[1], key_buffer, + HA_WHOLE_KEY, HA_READ_KEY_EXACT)) + /* We cannot find the row, assume it is already deleted. */ + ; + else if ((err= table->file->ha_delete_row(table->record[1]))) + table->file->print_error(err, MYF(0)); + /* + In case of error, we still discard the element from the list. We do + not want to endlessly error on the same element in case of table + corruption or such. + */ my_free(elist); elist= next; + if (err) + break; } +IF_DBUG(dbug_break:, ) table->file->ha_index_end(); if(!err && opt_bin_log && @@ -382,9 +420,16 @@ end: if (err) { /* - ToDo: If error, we need to put any remaining elist back into the HASH so - we can do another delete attempt later. + If error, we need to put any remaining elist back into the HASH so we + can do another delete attempt later. */ + if (elist) + { + lock(); + put_back_list(gtid->domain_id, elist); + unlock(); + } + ha_rollback_trans(thd, FALSE); close_thread_tables(thd); } @@ -437,26 +482,10 @@ rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid, bool *first) } -/* - Prepare the current slave state as a string, suitable for sending to the - master to request to receive binlog events starting from that GTID state. - - The state consists of the most recently applied GTID for each domain_id, - ie. the one with the highest sub_id within each domain_id. - - Optinally, extra_gtids is a list of GTIDs from the binlog. This is used when - a server was previously a master and now needs to connect to a new master as - a slave. For each domain_id, if the GTID in the binlog was logged with our - own server_id _and_ has a higher seq_no than what is in the slave state, - then this should be used as the position to start replicating at. This - allows to promote a slave as new master, and connect the old master as a - slave with MASTER_GTID_POS=AUTO. -*/ - int -rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra) +rpl_slave_state::iterate(int (*cb)(rpl_gtid *, void *), void *data, + rpl_gtid *extra_gtids, uint32 num_extra) { - bool first= true; uint32 i; HASH gtid_hash; uchar *rec; @@ -510,7 +539,7 @@ rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra) } } - if (rpl_slave_state_tostring_helper(dest, &best_gtid, &first)) + if ((res= (*cb)(&best_gtid, data))) { unlock(); goto err; @@ -523,7 +552,7 @@ rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra) for (i= 0; i < gtid_hash.records; ++i) { gtid= (rpl_gtid *)my_hash_element(>id_hash, i); - if (rpl_slave_state_tostring_helper(dest, gtid, &first)) + if ((res= (*cb)(gtid, data))) goto err; } @@ -536,6 +565,44 @@ err: } +struct rpl_slave_state_tostring_data { + String *dest; + bool first; +}; +static int +rpl_slave_state_tostring_cb(rpl_gtid *gtid, void *data) +{ + rpl_slave_state_tostring_data *p= (rpl_slave_state_tostring_data *)data; + return rpl_slave_state_tostring_helper(p->dest, gtid, &p->first); +} + + +/* + Prepare the current slave state as a string, suitable for sending to the + master to request to receive binlog events starting from that GTID state. + + The state consists of the most recently applied GTID for each domain_id, + ie. the one with the highest sub_id within each domain_id. + + Optinally, extra_gtids is a list of GTIDs from the binlog. This is used when + a server was previously a master and now needs to connect to a new master as + a slave. For each domain_id, if the GTID in the binlog was logged with our + own server_id _and_ has a higher seq_no than what is in the slave state, + then this should be used as the position to start replicating at. This + allows to promote a slave as new master, and connect the old master as a + slave with MASTER_GTID_POS=AUTO. +*/ +int +rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra) +{ + struct rpl_slave_state_tostring_data data; + data.first= true; + data.dest= dest; + + return iterate(rpl_slave_state_tostring_cb, &data, extra_gtids, num_extra); +} + + /* Lookup a domain_id in the current replication slave state. @@ -581,9 +648,6 @@ rpl_slave_state::domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid) Parse a GTID at the start of a string, and update the pointer to point at the first character after the parsed GTID. - GTID can be in short form with domain_id=0 implied, SERVERID-SEQNO. - Or long form, DOMAINID-SERVERID-SEQNO. - Returns 0 on ok, non-zero on parse error. */ static int @@ -1172,7 +1236,7 @@ slave_connection_state::load(char *slave_request, size_t len) rpl_gtid *gtid; const rpl_gtid *gtid2; - my_hash_reset(&hash); + reset(); p= slave_request; end= slave_request + len; if (p == end) @@ -1225,7 +1289,7 @@ slave_connection_state::load(const rpl_gtid *gtid_list, uint32 count) { uint32 i; - my_hash_reset(&hash); + reset(); for (i= 0; i < count; ++i) if (update(>id_list[i])) return 1; @@ -1233,6 +1297,28 @@ slave_connection_state::load(const rpl_gtid *gtid_list, uint32 count) } +static int +slave_connection_state_load_cb(rpl_gtid *gtid, void *data) +{ + slave_connection_state *state= (slave_connection_state *)data; + return state->update(gtid); +} + + +/* + Same as rpl_slave_state::tostring(), but populates a slave_connection_state + instead. +*/ +int +slave_connection_state::load(rpl_slave_state *state, + rpl_gtid *extra_gtids, uint32 num_extra) +{ + reset(); + return state->iterate(slave_connection_state_load_cb, this, + extra_gtids, num_extra); +} + + rpl_gtid * slave_connection_state::find(uint32 domain_id) { diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index fefce684c2c..4d5302020bf 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -93,6 +93,8 @@ struct rpl_slave_state int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, bool in_transaction, bool in_statement); uint64 next_subid(uint32 domain_id); + int iterate(int (*cb)(rpl_gtid *, void *), void *data, + rpl_gtid *extra_gtids, uint32 num_extra); int tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra); bool domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid); int load(THD *thd, char *state_from_master, size_t len, bool reset, @@ -103,6 +105,7 @@ struct rpl_slave_state void unlock() { DBUG_ASSERT(inited); mysql_mutex_unlock(&LOCK_slave_state); } element *get_element(uint32 domain_id); + int put_back_list(uint32 domain_id, list_element *list); void update_state_hash(uint64 sub_id, rpl_gtid *gtid); int record_and_update_gtid(THD *thd, Relay_log_info *rli); @@ -177,8 +180,10 @@ struct slave_connection_state slave_connection_state(); ~slave_connection_state(); + void reset() { my_hash_reset(&hash); } int load(char *slave_request, size_t len); int load(const rpl_gtid *gtid_list, uint32 count); + int load(rpl_slave_state *state, rpl_gtid *extra_gtids, uint32 num_extra); rpl_gtid *find(uint32 domain_id); int update(const rpl_gtid *in_gtid); void remove(const rpl_gtid *gtid); diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc index a68fd055d74..12bdf722bec 100644 --- a/sql/rpl_mi.cc +++ b/sql/rpl_mi.cc @@ -38,7 +38,8 @@ Master_info::Master_info(LEX_STRING *connection_name_arg, connect_retry(DEFAULT_CONNECT_RETRY), inited(0), abort_slave(0), slave_running(0), slave_run_id(0), sync_counter(0), heartbeat_period(0), received_heartbeats(0), master_id(0), - using_gtid(USE_GTID_NO) + using_gtid(USE_GTID_NO), events_queued_since_last_gtid(0), + gtid_reconnect_event_skip_count(0), gtid_event_seen(false) { host[0] = 0; user[0] = 0; password[0] = 0; ssl_ca[0]= 0; ssl_capath[0]= 0; ssl_cert[0]= 0; @@ -148,6 +149,23 @@ void Master_info::clear_in_memory_info(bool all) } } + +const char * +Master_info::using_gtid_astext(enum enum_using_gtid arg) +{ + switch (arg) + { + case USE_GTID_NO: + return "No"; + case USE_GTID_SLAVE_POS: + return "Slave_Pos"; + default: + DBUG_ASSERT(arg == USE_GTID_CURRENT_POS); + return "Current_Pos"; + } +} + + void init_master_log_pos(Master_info* mi) { DBUG_ENTER("init_master_log_pos"); @@ -155,6 +173,10 @@ void init_master_log_pos(Master_info* mi) mi->master_log_name[0] = 0; mi->master_log_pos = BIN_LOG_HEADER_SIZE; // skip magic number mi->using_gtid= Master_info::USE_GTID_NO; + mi->gtid_current_pos.reset(); + mi->events_queued_since_last_gtid= 0; + mi->gtid_reconnect_event_skip_count= 0; + mi->gtid_event_seen= false; /* Intentionally init ssl_verify_server_cert to 0, no option available */ mi->ssl_verify_server_cert= 0; diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index 7e3709993ed..ad6c57e21c4 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -61,6 +61,10 @@ typedef struct st_mysql MYSQL; class Master_info : public Slave_reporting_capability { public: + enum enum_using_gtid { + USE_GTID_NO= 0, USE_GTID_CURRENT_POS= 1, USE_GTID_SLAVE_POS= 2 + }; + Master_info(LEX_STRING *connection_name, bool is_slave_recovery); ~Master_info(); bool shall_ignore_server_id(ulong s_id); @@ -70,11 +74,12 @@ class Master_info : public Slave_reporting_capability /* If malloc() in initialization failed */ return connection_name.str == 0; } + static const char *using_gtid_astext(enum enum_using_gtid arg); /* the variables below are needed because we can change masters on the fly */ char master_log_name[FN_REFLEN+6]; /* Room for multi-*/ char host[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; - char user[USERNAME_LENGTH*+1]; + char user[USERNAME_LENGTH+1]; char password[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; LEX_STRING connection_name; /* User supplied connection name */ LEX_STRING cmp_connection_name; /* Connection name in lower case */ @@ -136,9 +141,35 @@ class Master_info : public Slave_reporting_capability Note that you can not change the numeric values of these, they are used in master.info. */ - enum { - USE_GTID_NO= 0, USE_GTID_CURRENT_POS= 1, USE_GTID_SLAVE_POS= 2 - } using_gtid; + enum enum_using_gtid using_gtid; + + /* + This GTID position records how far we have fetched into the relay logs. + This is used to continue fetching when the IO thread reconnects to the + master. + + (Full slave stop/start does not use it, as it resets the relay logs). + */ + slave_connection_state gtid_current_pos; + /* + If events_queued_since_last_gtid is non-zero, it is the number of events + queued so far in the relaylog of a GTID-prefixed event group. + It is zero when no partial event group has been queued at the moment. + */ + uint64 events_queued_since_last_gtid; + /* + The GTID of the partially-queued event group, when + events_queued_since_last_gtid is non-zero. + */ + rpl_gtid last_queued_gtid; + /* + When slave IO thread needs to reconnect, gtid_reconnect_event_skip_count + counts number of events to skip from the first GTID-prefixed event group, + to avoid duplicating events in the relay log. + */ + uint64 gtid_reconnect_event_skip_count; + /* gtid_event_seen is false until we receive first GTID event from master. */ + bool gtid_event_seen; }; int init_master_info(Master_info* mi, const char* master_info_fname, const char* slave_info_fname, diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 543c6d9fffe..7c8396ceaae 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -327,8 +327,7 @@ Failed to open the existing relay log info file '%s' (errno %d)", msg="Error reading slave log configuration"; goto err; } - strmake(rli->event_relay_log_name,rli->group_relay_log_name, - sizeof(rli->event_relay_log_name)-1); + strmake_buf(rli->event_relay_log_name,rli->group_relay_log_name); rli->group_relay_log_pos= rli->event_relay_log_pos= relay_log_pos; rli->group_master_log_pos= master_log_pos; @@ -546,10 +545,8 @@ int init_relay_log_pos(Relay_log_info* rli,const char* log, *errmsg="Could not find target log during relay log initialization"; goto err; } - strmake(rli->group_relay_log_name,rli->linfo.log_file_name, - sizeof(rli->group_relay_log_name)-1); - strmake(rli->event_relay_log_name,rli->linfo.log_file_name, - sizeof(rli->event_relay_log_name)-1); + strmake_buf(rli->group_relay_log_name,rli->linfo.log_file_name); + strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name); if (rli->relay_log.is_active(rli->linfo.log_file_name)) { /* @@ -889,8 +886,7 @@ void Relay_log_info::inc_group_relay_log_pos(ulonglong log_pos, mysql_mutex_lock(&data_lock); inc_event_relay_log_pos(); group_relay_log_pos= event_relay_log_pos; - strmake(group_relay_log_name,event_relay_log_name, - sizeof(group_relay_log_name)-1); + strmake_buf(group_relay_log_name,event_relay_log_name); notify_group_relay_log_name_update(); @@ -1025,10 +1021,8 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, if (!just_reset) { /* Save name of used relay log file */ - strmake(rli->group_relay_log_name, rli->relay_log.get_log_fname(), - sizeof(rli->group_relay_log_name)-1); - strmake(rli->event_relay_log_name, rli->relay_log.get_log_fname(), - sizeof(rli->event_relay_log_name)-1); + strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname()); + strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname()); rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; rli->log_space_total= 0; @@ -1426,7 +1420,10 @@ rpl_load_gtid_slave_state(THD *thd) bitmap_set_all(table->read_set); if ((err= table->file->ha_rnd_init_with_error(1))) + { + table->file->print_error(err, MYF(0)); goto end; + } table_scanned= true; for (;;) { @@ -1441,7 +1438,10 @@ rpl_load_gtid_slave_state(THD *thd) else if (err == HA_ERR_END_OF_FILE) break; else + { + table->file->print_error(err, MYF(0)); goto end; + } } domain_id= (ulonglong)table->field[0]->val_int(); sub_id= (ulonglong)table->field[1]->val_int(); @@ -1466,6 +1466,7 @@ rpl_load_gtid_slave_state(THD *thd) if (!(entry= (struct local_element *)my_malloc(sizeof(*entry), MYF(MY_WME)))) { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*entry)); err= 1; goto end; } @@ -1476,12 +1477,18 @@ rpl_load_gtid_slave_state(THD *thd) if ((err= my_hash_insert(&hash, (uchar *)entry))) { my_free(entry); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto end; } } } rpl_global_gtid_slave_state.lock(); + if (rpl_global_gtid_slave_state.loaded) + { + rpl_global_gtid_slave_state.unlock(); + goto end; + } for (i= 0; i < hash.records; ++i) { entry= (struct local_element *)my_hash_element(&hash, i); @@ -1491,14 +1498,15 @@ rpl_load_gtid_slave_state(THD *thd) entry->gtid.seq_no))) { rpl_global_gtid_slave_state.unlock(); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto end; } if (opt_bin_log && mysql_bin_log.bump_seq_no_counter_if_needed(entry->gtid.domain_id, entry->gtid.seq_no)) { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); rpl_global_gtid_slave_state.unlock(); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto end; } } diff --git a/sql/rpl_utility.cc b/sql/rpl_utility.cc index 1be97b34204..ac8a8fe356b 100644 --- a/sql/rpl_utility.cc +++ b/sql/rpl_utility.cc @@ -109,12 +109,15 @@ max_display_length_for_field(enum_field_types sql_type, unsigned int metadata) case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: return 3; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: return 4; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: return 8; case MYSQL_TYPE_BIT: @@ -262,12 +265,21 @@ uint32 table_def::calc_field_size(uint col, uchar *master_data) const case MYSQL_TYPE_TIME: length= 3; break; + case MYSQL_TYPE_TIME2: + length= my_time_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_TIMESTAMP: length= 4; break; + case MYSQL_TYPE_TIMESTAMP2: + length= my_timestamp_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_DATETIME: length= 8; break; + case MYSQL_TYPE_DATETIME2: + length= my_datetime_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_BIT: { /* @@ -376,6 +388,7 @@ void show_sql_type(enum_field_types type, uint16 metadata, String *str, CHARSET_ break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: str->set_ascii(STRING_WITH_LEN("timestamp")); break; @@ -393,10 +406,12 @@ void show_sql_type(enum_field_types type, uint16 metadata, String *str, CHARSET_ break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: str->set_ascii(STRING_WITH_LEN("time")); break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: str->set_ascii(STRING_WITH_LEN("datetime")); break; @@ -615,6 +630,23 @@ can_convert_field_to(Field *field, else DBUG_RETURN(false); } + else if (metadata == 0 && + ((field->real_type() == MYSQL_TYPE_TIMESTAMP2 && + source_type == MYSQL_TYPE_TIMESTAMP) || + (field->real_type() == MYSQL_TYPE_TIME2 && + source_type == MYSQL_TYPE_TIME) || + (field->real_type() == MYSQL_TYPE_DATETIME2 && + source_type == MYSQL_TYPE_DATETIME))) + { + /* + TS-TODO: conversion from FSP1>FSP2. + Can do non-lossy conversion + from old TIME, TIMESTAMP, DATETIME + to MySQL56 TIME(0), TIMESTAMP(0), DATETIME(0). + */ + *order_var= -1; + DBUG_RETURN(true); + } else if (!slave_type_conversions_options) DBUG_RETURN(false); @@ -739,6 +771,9 @@ can_convert_field_to(Field *field, case MYSQL_TYPE_NULL: case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIME2: DBUG_RETURN(false); } DBUG_RETURN(false); // To keep GCC happy @@ -939,7 +974,7 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * DBUG_PRINT("debug", ("sql_type: %d, target_field: '%s', max_length: %d, decimals: %d," " maybe_null: %d, unsigned_flag: %d, pack_length: %u", - type(col), target_table->field[col]->field_name, + binlog_type(col), target_table->field[col]->field_name, max_length, decimals, TRUE, FALSE, pack_length)); field_def->init_for_tmp_table(type(col), max_length, @@ -993,7 +1028,7 @@ table_def::table_def(unsigned char *types, ulong size, int index= 0; for (unsigned int i= 0; i < m_size; i++) { - switch (m_type[i]) { + switch (binlog_type(i)) { case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: @@ -1042,6 +1077,11 @@ table_def::table_def(unsigned char *types, ulong size, m_field_metadata[i]= x; break; } + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + m_field_metadata[i]= field_metadata[index++]; + break; default: m_field_metadata[i]= 0; break; @@ -1173,6 +1213,7 @@ void Deferred_log_events::rewind() Log_event *ev= *(Log_event **) dynamic_array_ptr(&array, i); delete ev; } + last_added= NULL; if (array.elements > array.max_element) freeze_size(&array); reset_dynamic(&array); diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index 79f4517c492..b08721aa8c2 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -65,6 +65,14 @@ public: ulong size() const { return m_size; } + /** + Returns internal binlog type code for one field, + without translation to real types. + */ + enum_field_types binlog_type(ulong index) const + { + return static_cast<enum_field_types>(m_type[index]); + } /* Return a representation of the type data for one field. @@ -82,7 +90,7 @@ public: either MYSQL_TYPE_STRING, MYSQL_TYPE_ENUM, or MYSQL_TYPE_SET, so we might need to modify the type to get the real type. */ - enum_field_types source_type= static_cast<enum_field_types>(m_type[index]); + enum_field_types source_type= binlog_type(index); uint16 source_metadata= m_field_metadata[index]; switch (source_type) { diff --git a/sql/set_var.cc b/sql/set_var.cc index 363f03579ef..d9741ca3481 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1,5 +1,5 @@ -/* Copyright (c) 2002, 2011, Oracle and/or its affiliates. - Copyright (c) 2008, 2012, Monty Program Ab +/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. + Copyright (c) 2008, 2013, Monty Program 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 @@ -16,7 +16,7 @@ /* variable declarations are in sys_vars.cc now !!! */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_class.h" // set_var.h: session_var_ptr #include "set_var.h" #include "sql_priv.h" @@ -251,16 +251,14 @@ uchar *sys_var::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base) return session_value_ptr(thd, base); } -bool sys_var::set_default(THD *thd, enum_var_type type) +bool sys_var::set_default(THD *thd, set_var* var) { - set_var var(type, this, &null_lex_str, 0); - - if (type == OPT_GLOBAL || scope() == GLOBAL) - global_save_default(thd, &var); + if (var->type == OPT_GLOBAL || scope() == GLOBAL) + global_save_default(thd, var); else - session_save_default(thd, &var); + session_save_default(thd, var); - return check(thd, &var) || update(thd, &var); + return check(thd, var) || update(thd, var); } @@ -771,7 +769,7 @@ int set_var::light_check(THD *thd) */ int set_var::update(THD *thd) { - return value ? var->update(thd, this) : var->set_default(thd, type); + return value ? var->update(thd, this) : var->set_default(thd, this); } diff --git a/sql/set_var.h b/sql/set_var.h index 52964a1f29d..4e13dfb2987 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -1,6 +1,6 @@ #ifndef SET_VAR_INCLUDED #define SET_VAR_INCLUDED -/* Copyright (c) 2002, 2011, Oracle and/or its affiliates. +/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. 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 @@ -105,7 +105,13 @@ public: bool check(THD *thd, set_var *var); uchar *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); - bool set_default(THD *thd, enum_var_type type); + + /** + Update the system variable with the default value from either + session or global scope. The default value is stored in the + 'var' argument. Return false when successful. + */ + bool set_default(THD *thd, set_var *var); bool update(THD *thd, set_var *var); longlong val_int(bool *is_null, THD *thd, enum_var_type type, LEX_STRING *base); diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index ad055273155..5c60de14742 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6760,7 +6760,7 @@ ER_GTID_OPEN_TABLE_FAILED ER_GTID_POSITION_NOT_FOUND_IN_BINLOG eng "Connecting slave requested to start from GTID %u-%u-%llu, which is not in the master's binlog" ER_CANNOT_LOAD_SLAVE_GTID_STATE - eng "Failed to load replication slave GTID state from table %s.%s" + eng "Failed to load replication slave GTID position from table %s.%s" ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG eng "Specified GTID %u-%u-%llu conflicts with the binary log which contains a more recent GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos." ER_MASTER_GTID_POS_MISSING_DOMAIN @@ -6771,3 +6771,5 @@ ER_GTID_STRICT_OUT_OF_ORDER eng "An attempt was made to binlog GTID %u-%u-%llu which would create an out-of-order sequence number with existing GTID %u-%u-%llu, and gtid strict mode is enabled." ER_GTID_START_FROM_BINLOG_HOLE eng "The binlog on the master is missing the GTID %u-%u-%llu requested by the slave (even though both a prior and a subsequent sequence number does exist), and GTID strict mode is enabled" +ER_SLAVE_UNEXPECTED_MASTER_SWITCH + eng "Unexpected GTID received from master after reconnect. This normally indicates that the master server was replaced without restarting the slave threads. %s" diff --git a/sql/slave.cc b/sql/slave.cc index 327f1b763a3..fe250103fb3 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -117,7 +117,7 @@ static const char *reconnect_messages[SLAVE_RECON_ACT_MAX][SLAVE_RECON_MSG_MAX]= registration on master", "Reconnecting after a failed registration on master", "failed registering on master, reconnecting to try again, \ -log '%s' at position %s", +log '%s' at position %s%s", "COM_REGISTER_SLAVE", "Slave I/O thread killed during or after reconnect" }, @@ -125,7 +125,7 @@ log '%s' at position %s", "Waiting to reconnect after a failed binlog dump request", "Slave I/O thread killed while retrying master dump", "Reconnecting after a failed binlog dump request", - "failed dump request, reconnecting to try again, log '%s' at position %s", + "failed dump request, reconnecting to try again, log '%s' at position %s%s", "COM_BINLOG_DUMP", "Slave I/O thread killed during or after reconnect" }, @@ -134,7 +134,7 @@ log '%s' at position %s", "Slave I/O thread killed while waiting to reconnect after a failed read", "Reconnecting after a failed master event read", "Slave I/O thread: Failed reading log event, reconnecting to retry, \ -log '%s' at position %s", +log '%s' at position %s%s", "", "Slave I/O thread killed during or after a reconnect done to recover from \ failed read" @@ -256,6 +256,66 @@ static void init_slave_psi_keys(void) } #endif /* HAVE_PSI_INTERFACE */ + +static bool slave_init_thread_running; + + +pthread_handler_t +handle_slave_init(void *arg __attribute__((unused))) +{ + THD *thd; + + my_thread_init(); + thd= new THD; + thd->thread_stack= (char*) &thd; /* Set approximate stack start */ + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id= thread_id++; + mysql_mutex_unlock(&LOCK_thread_count); + thd->store_globals(); + + thd_proc_info(thd, "Loading slave GTID position from table"); + if (rpl_load_gtid_slave_state(thd)) + sql_print_warning("Failed to load slave replication state from table " + "%s.%s: %u: %s", "mysql", + rpl_gtid_slave_state_table_name.str, + thd->stmt_da->sql_errno(), thd->stmt_da->message()); + + mysql_mutex_lock(&LOCK_thread_count); + delete thd; + mysql_mutex_unlock(&LOCK_thread_count); + my_thread_end(); + + mysql_mutex_lock(&LOCK_thread_count); + slave_init_thread_running= false; + mysql_cond_signal(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + + return 0; +} + + +static int +run_slave_init_thread() +{ + pthread_t th; + + slave_init_thread_running= true; + if (mysql_thread_create(key_thread_slave_init, &th, NULL, + handle_slave_init, NULL)) + { + sql_print_error("Failed to create thread while initialising slave"); + return 1; + } + + mysql_mutex_lock(&LOCK_thread_count); + while (slave_init_thread_running) + mysql_cond_wait(&COND_thread_count, &LOCK_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + + return 0; +} + + /* Initialize slave structures */ int init_slave() @@ -267,6 +327,9 @@ int init_slave() init_slave_psi_keys(); #endif + if (run_slave_init_thread()) + return 1; + /* This is called when mysqld starts. Before client connections are accepted. However bootstrap may conflict with us if it does START SLAVE. @@ -384,16 +447,13 @@ int init_recovery(Master_info* mi, const char** errmsg) { mi->master_log_pos= max(BIN_LOG_HEADER_SIZE, rli->group_master_log_pos); - strmake(mi->master_log_name, rli->group_master_log_name, - sizeof(mi->master_log_name)-1); + strmake_buf(mi->master_log_name, rli->group_master_log_name); sql_print_warning("Recovery from master pos %ld and file %s.", (ulong) mi->master_log_pos, mi->master_log_name); - strmake(rli->group_relay_log_name, rli->relay_log.get_log_fname(), - sizeof(rli->group_relay_log_name)-1); - strmake(rli->event_relay_log_name, rli->relay_log.get_log_fname(), - sizeof(mi->rli.event_relay_log_name)-1); + strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname()); + strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname()); rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; } @@ -833,9 +893,13 @@ int start_slave_threads(bool need_slave_mutex, bool wait_for_start, purge_relay_logs(&mi->rli, NULL, 0, &errmsg); mi->master_log_name[0]= 0; mi->master_log_pos= 0; + error= rpl_load_gtid_state(&mi->gtid_current_pos, mi->using_gtid == + Master_info::USE_GTID_CURRENT_POS); + mi->events_queued_since_last_gtid= 0; + mi->gtid_reconnect_event_skip_count= 0; } - if (thread_mask & SLAVE_IO) + if (!error && (thread_mask & SLAVE_IO)) error= start_slave_thread( #ifdef HAVE_PSI_INTERFACE key_thread_slave_io, @@ -1827,18 +1891,9 @@ past_checksum: after_set_capability: #endif - /* - Request dump start from slave replication GTID state. - - Only request GTID position the first time we connect after CHANGE MASTER - or after starting both IO or SQL thread. - - Otherwise, if the IO thread was ahead of the SQL thread before the - restart or reconnect, we might end up re-fetching and hence re-applying - the same event(s) again. - */ - if (mi->using_gtid != Master_info::USE_GTID_NO && !mi->master_log_name[0]) + if (mi->using_gtid != Master_info::USE_GTID_NO) { + /* Request dump to start from slave replication GTID state. */ int rc; char str_buf[256]; String query_str(str_buf, sizeof(str_buf), system_charset_info); @@ -1867,9 +1922,7 @@ after_set_capability: query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"), system_charset_info); - if (rpl_append_gtid_state(&query_str, - mi->using_gtid == - Master_info::USE_GTID_CURRENT_POS)) + if (mi->gtid_current_pos.append_to_string(&query_str)) { err_code= ER_OUTOFMEMORY; errmsg= "The slave I/O thread stops because a fatal out-of-memory " @@ -1970,7 +2023,7 @@ after_set_capability: } } } - if (mi->using_gtid == Master_info::USE_GTID_NO) + else { /* If we are not using GTID to connect this time, then instead request @@ -2551,10 +2604,7 @@ static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, protocol->store(mi->ssl_ca, &my_charset_bin); // Master_Ssl_Crlpath protocol->store(mi->ssl_capath, &my_charset_bin); - protocol->store((mi->using_gtid==Master_info::USE_GTID_NO ? "No" : - (mi->using_gtid==Master_info::USE_GTID_SLAVE_POS ? - "Slave_Pos" : "Current_Pos")), - &my_charset_bin); + protocol->store(mi->using_gtid_astext(mi->using_gtid), &my_charset_bin); if (full) { protocol->store((uint32) mi->rli.retried_trans); @@ -3403,8 +3453,22 @@ static int try_to_reconnect(THD *thd, MYSQL *mysql, Master_info *mi, if (!suppress_warnings) { char buf[256], llbuff[22]; + String tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + mi->gtid_current_pos.append_to_string(&tmp); + if (mi->events_queued_since_last_gtid == 0) + tmp.append(STRING_WITH_LEN("'")); + else + { + tmp.append(STRING_WITH_LEN("', GTID event skip ")); + tmp.append_ulonglong((ulonglong)mi->events_queued_since_last_gtid); + } + } my_snprintf(buf, sizeof(buf), messages[SLAVE_RECON_MSG_FAILED], - IO_RPL_LOG_NAME, llstr(mi->master_log_pos, llbuff)); + IO_RPL_LOG_NAME, llstr(mi->master_log_pos, llbuff), + tmp.c_ptr_safe()); /* Raise a warining during registering on master/requesting dump. Log a message reading event. @@ -3496,6 +3560,16 @@ pthread_handler_t handle_slave_io(void *arg) /* This must be called before run any binlog_relay_io hooks */ my_pthread_setspecific_ptr(RPL_MASTER_INFO, mi); + /* Load the set of seen GTIDs, if we did not already. */ + if (rpl_load_gtid_slave_state(thd)) + { + mi->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), + "Unable to load replication GTID slave state from mysql.%s: %s", + rpl_gtid_slave_state_table_name.str, thd->stmt_da->message()); + goto err; + } + + if (RUN_HOOK(binlog_relay_io, thread_start, (thd, mi))) { mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, @@ -3514,11 +3588,21 @@ pthread_handler_t handle_slave_io(void *arg) // we can get killed during safe_connect if (!safe_connect(thd, mysql, mi)) { - sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," - "replication started in log '%s' at position %s", - mi->user, mi->host, mi->port, - IO_RPL_LOG_NAME, - llstr(mi->master_log_pos,llbuff)); + if (mi->using_gtid == Master_info::USE_GTID_NO) + sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," + "replication started in log '%s' at position %s", + mi->user, mi->host, mi->port, + IO_RPL_LOG_NAME, + llstr(mi->master_log_pos,llbuff)); + else + { + String tmp; + mi->gtid_current_pos.to_string(&tmp); + sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," + "replication starts at GTID position '%s'", + mi->user, mi->host, mi->port, tmp.c_ptr_safe()); + } + /* Adding MAX_LOG_EVENT_HEADER_LEN to the max_packet_size on the I/O thread, since a replication event can become this much larger than @@ -3535,6 +3619,25 @@ pthread_handler_t handle_slave_io(void *arg) connected: + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + /* + When the IO thread (re)connects to the master using GTID, it will + connect at the start of an event group. But the IO thread may have + previously logged part of the following event group to the relay + log. + + When the IO and SQL thread are started together, we erase any previous + relay logs, but this is not possible/desirable while the SQL thread is + running. To avoid duplicating partial event groups in the relay logs in + this case, we remember the count of events in any partially logged event + group before the reconnect, and then here at connect we set up a counter + to skip the already-logged part of the group. + */ + mi->gtid_reconnect_event_skip_count= mi->events_queued_since_last_gtid; + mi->gtid_event_seen= false; + } + #ifdef ENABLED_DEBUG_SYNC DBUG_EXECUTE_IF("dbug.before_get_running_status_yes", { @@ -3760,8 +3863,19 @@ log space"); // error = 0; err: // print the current replication position - sql_print_information("Slave I/O thread exiting, read up to log '%s', position %s", - IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff)); + if (mi->using_gtid == Master_info::USE_GTID_NO) + sql_print_information("Slave I/O thread exiting, read up to log '%s', " + "position %s", + IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff)); + else + { + String tmp; + mi->gtid_current_pos.to_string(&tmp); + sql_print_information("Slave I/O thread exiting, read up to log '%s', " + "position %s; GTID position %s", + IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff), + tmp.c_ptr_safe()); + } RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi)); thd->reset_query(); thd->reset_db(NULL, 0); @@ -4033,10 +4147,20 @@ pthread_handler_t handle_slave_sql(void *arg) rli->group_master_log_name, llstr(rli->group_master_log_pos,llbuff))); if (global_system_variables.log_warnings) + { + String tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, + mi->using_gtid==Master_info::USE_GTID_CURRENT_POS); + tmp.append(STRING_WITH_LEN("'")); + } sql_print_information("Slave SQL thread initialized, starting replication in \ -log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, +log '%s' at position %s, relay log '%s' position: %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos,llbuff),rli->group_relay_log_name, - llstr(rli->group_relay_log_pos,llbuff1)); + llstr(rli->group_relay_log_pos,llbuff1), tmp.c_ptr_safe()); + } if (check_temp_dir(rli->slave_patternload_file)) { @@ -4074,8 +4198,8 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, mysql_mutex_lock(&rli->data_lock); if (rli->slave_skip_counter) { - strmake(saved_log_name, rli->group_relay_log_name, FN_REFLEN - 1); - strmake(saved_master_log_name, rli->group_master_log_name, FN_REFLEN - 1); + strmake_buf(saved_log_name, rli->group_relay_log_name); + strmake_buf(saved_master_log_name, rli->group_master_log_name); saved_log_pos= rli->group_relay_log_pos; saved_master_log_pos= rli->group_master_log_pos; saved_skip= rli->slave_skip_counter; @@ -4170,16 +4294,35 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, sql_print_warning("Slave: %s Error_code: %d", err->get_message_text(), err->get_sql_errno()); } if (udf_error) + { + String tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } sql_print_error("Error loading user-defined library, slave SQL " "thread aborted. Install the missing library, and restart the " "slave SQL thread with \"SLAVE START\". We stopped at log '%s' " - "position %s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, - llbuff)); + "position %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, + llbuff), tmp.c_ptr_safe()); + } else + { + String tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } sql_print_error("\ Error running query, slave SQL thread aborted. Fix the problem, and restart \ the slave SQL thread with \"SLAVE START\". We stopped at log \ -'%s' position %s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff)); +'%s' position %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff), + tmp.c_ptr_safe()); + } } goto err; } @@ -4187,9 +4330,20 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ } /* Thread stopped. Print the current replication position to the log */ - sql_print_information("Slave SQL thread exiting, replication stopped in log " - "'%s' at position %s", - RPL_LOG_NAME, llstr(rli->group_master_log_pos,llbuff)); + { + String tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } + sql_print_information("Slave SQL thread exiting, replication stopped in " + "log '%s' at position %s%s", + RPL_LOG_NAME, + llstr(rli->group_master_log_pos,llbuff), + tmp.c_ptr_safe()); + } err: @@ -4664,6 +4818,8 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) mysql_mutex_t *log_lock= rli->relay_log.get_log_lock(); ulong s_id; bool unlock_data_lock= TRUE; + bool gtid_skip_enqueue= false; + /* FD_q must have been prepared for the first R_a event inside get_master_version_and_clock() @@ -4851,6 +5007,19 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) mi->rli.relay_log.relay_log_checksum_alg= tmp->checksum_alg; /* + Do not queue any format description event that we receive after a + reconnect where we are skipping over a partial event group received + before the reconnect. + + (If we queued such an event, and it was the first format_description + event after master restart, the slave SQL thread would think that + the partial event group before it in the relay log was from a + previous master crash and should be rolled back). + */ + if (unlikely(mi->gtid_reconnect_event_skip_count && !mi->gtid_event_seen)) + gtid_skip_enqueue= true; + + /* Though this does some conversion to the slave's format, this will preserve the master's binlog format version, and number of event types. */ @@ -4945,18 +5114,113 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) " UNTIL master_gtid_pos %s", str.c_ptr_safe()); mi->abort_slave= true; } + event_pos= glev->log_pos; delete glev; /* - Do not update position for fake Gtid_list event (which has a zero - end_log_pos). + We use fake Gtid_list events to update the old-style position (among + other things). + + Early code created fake Gtid_list events with zero log_pos, those should + not modify old-style position. */ - inc_pos= uint4korr(buf+LOG_POS_OFFSET) ? event_len : 0; + if (event_pos == 0 || event_pos <= mi->master_log_pos) + inc_pos= 0; + else + inc_pos= event_pos - mi->master_log_pos; + } + break; + + case GTID_EVENT: + { + uchar dummy_flag; + + if (mi->using_gtid == Master_info::USE_GTID_NO) + goto default_action; + if (unlikely(!mi->gtid_event_seen)) + { + mi->gtid_event_seen= true; + if (mi->gtid_reconnect_event_skip_count) + { + rpl_gtid gtid; + + /* + If we are reconnecting, and we need to skip a partial event group + already queued to the relay log before the reconnect, then we check + that we actually get the same event group (same GTID) as before, so + we do not end up with half of one group and half another. + + The only way we should be able to receive a different GTID than what + we expect is if the binlog on the master (or more likely the whole + master server) was replaced with a different one, one the same IP + address, _and_ the new master happens to have domains in a different + order so we get the GTID from a different domain first. Still, it is + best to protect against this case. + */ + if (Gtid_log_event::peek(buf, event_len, checksum_alg, + >id.domain_id, >id.server_id, + >id.seq_no, &dummy_flag)) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + if (gtid.domain_id != mi->last_queued_gtid.domain_id || + gtid.server_id != mi->last_queued_gtid.server_id || + gtid.seq_no != mi->last_queued_gtid.seq_no) + { + bool first; + error= ER_SLAVE_UNEXPECTED_MASTER_SWITCH; + error_msg.append(STRING_WITH_LEN("Expected: ")); + first= true; + rpl_slave_state_tostring_helper(&error_msg, &mi->last_queued_gtid, + &first); + error_msg.append(STRING_WITH_LEN(", received: ")); + first= true; + rpl_slave_state_tostring_helper(&error_msg, >id, &first); + goto err; + } + } + } + + if (unlikely(mi->gtid_reconnect_event_skip_count)) + { + goto default_action; + } + + /* + We have successfully queued to relay log everything before this GTID, so + in case of reconnect we can start from after any previous GTID. + */ + if (mi->events_queued_since_last_gtid) + { + mi->gtid_current_pos.update(&mi->last_queued_gtid); + mi->events_queued_since_last_gtid= 0; + } + if (Gtid_log_event::peek(buf, event_len, checksum_alg, + &mi->last_queued_gtid.domain_id, + &mi->last_queued_gtid.server_id, + &mi->last_queued_gtid.seq_no, &dummy_flag)) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + ++mi->events_queued_since_last_gtid; } break; default: default_action: + if (mi->using_gtid != Master_info::USE_GTID_NO && mi->gtid_event_seen) + { + if (unlikely(mi->gtid_reconnect_event_skip_count)) + { + --mi->gtid_reconnect_event_skip_count; + gtid_skip_enqueue= true; + } + else if (mi->events_queued_since_last_gtid) + ++mi->events_queued_since_last_gtid; + } + inc_pos= event_len; break; } @@ -5041,8 +5305,16 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) } else { - /* write the event to the relay log */ - if (likely(!(rli->relay_log.appendv(buf,event_len,0)))) + /* + Write the event to the relay log, unless we reconnected in the middle + of an event group and now need to skip the initial part of the group that + we already wrote before reconnecting. + */ + if (unlikely(gtid_skip_enqueue)) + { + mi->master_log_pos+= inc_pos; + } + else if (likely(!(rli->relay_log.appendv(buf,event_len,0)))) { mi->master_log_pos+= inc_pos; DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); @@ -5780,8 +6052,7 @@ static Log_event* next_event(Relay_log_info* rli) goto err; } rli->event_relay_log_pos = BIN_LOG_HEADER_SIZE; - strmake(rli->event_relay_log_name,rli->linfo.log_file_name, - sizeof(rli->event_relay_log_name)-1); + strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name); flush_relay_log_info(rli); } diff --git a/sql/sp.cc b/sql/sp.cc index b60411a9121..0f32f7a305b 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -114,7 +114,7 @@ TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] = }, { { C_STRING_WITH_LEN("definer") }, - { C_STRING_WITH_LEN("char(77)") }, + { C_STRING_WITH_LEN("char(") }, { C_STRING_WITH_LEN("utf8") } }, { diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 37f5e97110a..98e807722a3 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1159,8 +1159,9 @@ find_handler_after_execution(THD *thd, sp_rcontext *ctx) MYSQL_ERROR *err; while ((err= it++)) { - if (err->get_level() != MYSQL_ERROR::WARN_LEVEL_WARN && - err->get_level() != MYSQL_ERROR::WARN_LEVEL_NOTE) + if ((err->get_level() != MYSQL_ERROR::WARN_LEVEL_WARN && + err->get_level() != MYSQL_ERROR::WARN_LEVEL_NOTE) || + err->handled()) continue; if (ctx->find_handler(thd, @@ -1169,6 +1170,7 @@ find_handler_after_execution(THD *thd, sp_rcontext *ctx) err->get_level(), err->get_message_text())) { + err->mark_handled(); break; } } @@ -1418,6 +1420,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) Will write this SP statement into binlog separately. TODO: consider changing the condition to "not inside event union". */ + MEM_ROOT *user_var_events_alloc_saved= thd->user_var_events_alloc; if (thd->locked_tables_mode <= LTM_LOCK_TABLES) thd->user_var_events_alloc= thd->mem_root; @@ -1433,7 +1436,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { reset_dynamic(&thd->user_var_events); - thd->user_var_events_alloc= NULL;//DEBUG + thd->user_var_events_alloc= user_var_events_alloc_saved; } /* we should cleanup free_list and memroot, used by instruction */ diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index d056727c2fd..7e261cc755c 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -70,7 +70,7 @@ TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { }, { { C_STRING_WITH_LEN("User") }, - { C_STRING_WITH_LEN("char(16)") }, + { C_STRING_WITH_LEN("char(") }, {NULL, 0} }, { @@ -829,6 +829,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) goto end; table->use_all_columns(); (void) my_init_dynamic_array(&acl_users,sizeof(ACL_USER), 50, 100, MYF(0)); + username_char_length= min(table->field[1]->char_length(), USERNAME_CHAR_LENGTH); password_length= table->field[2]->field_length / table->field[2]->charset()->mbmaxlen; if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323) @@ -1436,12 +1437,12 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, sctx->master_access= acl_user->access; if (acl_user->user) - strmake(sctx->priv_user, user, USERNAME_LENGTH); + strmake_buf(sctx->priv_user, user); else *sctx->priv_user= 0; if (acl_user->host.hostname) - strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); + strmake_buf(sctx->priv_host, acl_user->host.hostname); else *sctx->priv_host= 0; } @@ -8193,6 +8194,12 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) cs->coll->hash_sort(cs, (uchar*) sctx->user, strlen(sctx->user), &nr1, &nr2); mysql_mutex_lock(&acl_cache->lock); + if (!acl_users.elements) + { + mysql_mutex_unlock(&acl_cache->lock); + login_failed_error(mpvio->thd); + DBUG_RETURN(1); + } uint i= nr1 % acl_users.elements; ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); mpvio->acl_user= acl_user_tmp->copy(mpvio->thd->mem_root); @@ -8218,10 +8225,9 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) mpvio->auth_info.user_name= sctx->user; mpvio->auth_info.user_name_length= strlen(sctx->user); mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; - mpvio->auth_info.auth_string_length= - (unsigned long) mpvio->acl_user->auth_string.length; - strmake(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? - mpvio->acl_user->user : "", USERNAME_LENGTH); + mpvio->auth_info.auth_string_length= (unsigned long) mpvio->acl_user->auth_string.length; + strmake_buf(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? + mpvio->acl_user->user : ""); DBUG_PRINT("info", ("exit: user=%s, auth_string=%s, authenticated as=%s" "plugin=%s", @@ -8305,7 +8311,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) /* Clear variables that are allocated */ thd->user_connect= 0; - strmake(sctx->priv_user, sctx->user, USERNAME_LENGTH); + strmake_buf(sctx->priv_user, sctx->user); if (thd->make_lex_string(&mpvio->db, db_buff, db_len) == 0) DBUG_RETURN(1); /* The error is set by make_lex_string(). */ @@ -8520,14 +8526,15 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, /* Clip username to allowed length in characters (not bytes). This is - mostly for backward compatibility. + mostly for backward compatibility (to truncate long usernames, as + old 5.1 did) */ { CHARSET_INFO *cs= system_charset_info; int err; user_len= (uint) cs->cset->well_formed_len(cs, user, user + user_len, - USERNAME_CHAR_LENGTH, &err); + username_char_length, &err); user[user_len]= '\0'; } @@ -9122,12 +9129,12 @@ bool acl_authenticate(THD *thd, uint connect_errors, sctx->master_access= acl_user->access; if (acl_user->user) - strmake(sctx->priv_user, acl_user->user, USERNAME_LENGTH - 1); + strmake_buf(sctx->priv_user, acl_user->user); else *sctx->priv_user= 0; if (acl_user->host.hostname) - strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); + strmake_buf(sctx->priv_host, acl_user->host.hostname); else *sctx->priv_host= 0; diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index a9b7624af3c..d08cd7915ab 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -329,7 +329,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Msg_type", 10)); item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_text", 255)); + field_list.push_back(item = new Item_empty_string("Msg_text", + SQL_ADMIN_MSG_TEXT_SIZE)); item->maybe_null = 1; if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) diff --git a/sql/sql_admin.h b/sql/sql_admin.h index f7ec76efd5e..5398e3019f1 100644 --- a/sql/sql_admin.h +++ b/sql/sql_admin.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 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 @@ -16,6 +16,8 @@ #ifndef SQL_TABLE_MAINTENANCE_H #define SQL_TABLE_MAINTENANCE_H +/* Must be able to hold ALTER TABLE t PARTITION BY ... KEY ALGORITHM = 1 ... */ +#define SQL_ADMIN_MSG_TEXT_SIZE 128 * 1024 bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, LEX_STRING *key_cache_name); diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc index 793eead9869..07a5243e836 100644 --- a/sql/sql_audit.cc +++ b/sql/sql_audit.cc @@ -31,8 +31,7 @@ unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; static mysql_mutex_t LOCK_audit_mask; -static void event_class_dispatch(THD *thd, unsigned int event_class, - const void *event); +static void event_class_dispatch(THD *, unsigned int, const void *); static inline @@ -111,9 +110,36 @@ static void connection_class_handler(THD *thd, uint event_subclass, va_list ap) } +static void table_class_handler(THD *thd, uint event_subclass, va_list ap) +{ + mysql_event_table event; + event.event_subclass= event_subclass; + event.read_only= va_arg(ap, int); + event.thread_id= va_arg(ap, unsigned long); + event.user= va_arg(ap, const char *); + event.priv_user= va_arg(ap, const char *); + event.priv_host= va_arg(ap, const char *); + event.external_user= va_arg(ap, const char *); + event.proxy_user= va_arg(ap, const char *); + event.host= va_arg(ap, const char *); + event.ip= va_arg(ap, const char *); + event.database= va_arg(ap, const char *); + event.database_length= va_arg(ap, unsigned int); + event.table= va_arg(ap, const char *); + event.table_length= va_arg(ap, unsigned int); + event.new_database= va_arg(ap, const char *); + event.new_database_length= va_arg(ap, unsigned int); + event.new_table= va_arg(ap, const char *); + event.new_table_length= va_arg(ap, unsigned int); + event_class_dispatch(thd, MYSQL_AUDIT_TABLE_CLASS, &event); +} + + static audit_handler_t audit_handlers[] = { - general_class_handler, connection_class_handler + general_class_handler, connection_class_handler, + 0,0,0,0,0,0,0,0,0,0,0,0,0, /* placeholders */ + table_class_handler }; static const uint audit_handlers_count= diff --git a/sql/sql_audit.h b/sql/sql_audit.h index a651ce61374..22fdd221e62 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -48,18 +48,24 @@ static inline bool mysql_audit_connection_enabled() return mysql_global_audit_mask[0] & MYSQL_AUDIT_CONNECTION_CLASSMASK; } +static inline bool mysql_audit_table_enabled() +{ + return mysql_global_audit_mask[0] & MYSQL_AUDIT_TABLE_CLASSMASK; +} + #else static inline void mysql_audit_notify(THD *thd, uint event_class, uint event_subtype, ...) { } #define mysql_audit_general_enabled() 0 #define mysql_audit_connection_enabled() 0 +#define mysql_audit_table_enabled() 0 #endif extern void mysql_audit_release(THD *thd); #define MAX_USER_HOST_SIZE 512 static inline uint make_user_name(THD *thd, char *buf) { - Security_context *sctx= thd->security_ctx; + const Security_context *sctx= thd->security_ctx; return strxnmov(buf, MAX_USER_HOST_SIZE, sctx->priv_user[0] ? sctx->priv_user : "", "[", sctx->user ? sctx->user : "", "] @ ", @@ -197,4 +203,87 @@ void mysql_audit_notify_connection_change_user(THD *thd) } } +static inline +void mysql_audit_external_lock(THD *thd, TABLE_SHARE *share, int lock) +{ + if (lock != F_UNLCK && mysql_audit_table_enabled()) + { + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_LOCK, + (int)(lock == F_RDLCK), (ulong)thd->thread_id, + sctx->user, sctx->priv_user, sctx->priv_host, + sctx->external_user, sctx->proxy_user, sctx->host, + sctx->ip, share->db.str, (uint)share->db.length, + share->table_name.str, (uint)share->table_name.length, + 0,0,0,0); + } +} + +static inline +void mysql_audit_create_table(TABLE *table) +{ + if (mysql_audit_table_enabled()) + { + THD *thd= table->in_use; + const TABLE_SHARE *share= table->s; + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_CREATE, + 0, (ulong)thd->thread_id, + sctx->user, sctx->priv_user, sctx->priv_host, + sctx->external_user, sctx->proxy_user, sctx->host, + sctx->ip, share->db.str, (uint)share->db.length, + share->table_name.str, (uint)share->table_name.length, + 0,0,0,0); + } +} + +static inline +void mysql_audit_drop_table(THD *thd, TABLE_LIST *table) +{ + if (mysql_audit_table_enabled()) + { + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_DROP, + 0, (ulong)thd->thread_id, + sctx->user, sctx->priv_user, sctx->priv_host, + sctx->external_user, sctx->proxy_user, sctx->host, + sctx->ip, table->db, (uint)table->db_length, + table->table_name, (uint)table->table_name_length, + 0,0,0,0); + } +} + +static inline +void mysql_audit_rename_table(THD *thd, const char *old_db, const char *old_tb, + const char *new_db, const char *new_tb) +{ + if (mysql_audit_table_enabled()) + { + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_RENAME, + 0, (ulong)thd->thread_id, + sctx->user, sctx->priv_user, sctx->priv_host, + sctx->external_user, sctx->proxy_user, sctx->host, + sctx->ip, + old_db, (uint)strlen(old_db), old_tb, (uint)strlen(old_tb), + new_db, (uint)strlen(new_db), new_tb, (uint)strlen(new_tb)); + } +} + +static inline +void mysql_audit_alter_table(THD *thd, TABLE_LIST *table) +{ + if (mysql_audit_table_enabled()) + { + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_TABLE_CLASS, MYSQL_AUDIT_TABLE_ALTER, + 0, (ulong)thd->thread_id, + sctx->user, sctx->priv_user, sctx->priv_host, + sctx->external_user, sctx->proxy_user, sctx->host, + sctx->ip, table->db, (uint)table->db_length, + table->table_name, (uint)table->table_name_length, + 0,0,0,0); + } +} + #endif /* SQL_AUDIT_INCLUDED */ diff --git a/sql/sql_base.cc b/sql/sql_base.cc index c9630aef126..931fb07f70c 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -6864,7 +6864,7 @@ find_field_in_tables(THD *thd, Item_ident *item, We can't do this in Item_field as this would change the 'name' of the item which may be used in the select list */ - strmake(name_buff, db, sizeof(name_buff)-1); + strmake_buf(name_buff, db); my_casedn_str(files_charset_info, name_buff); db= name_buff; } @@ -8437,7 +8437,7 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, We can't do this in Item_field as this would change the 'name' of the item which may be used in the select list */ - strmake(name_buff, db_name, sizeof(name_buff)-1); + strmake_buf(name_buff, db_name); my_casedn_str(files_charset_info, name_buff); db_name= name_buff; } diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index a4664da2bf6..3bb5deab406 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -107,7 +107,7 @@ void mysql_client_binlog_statement(THD* thd) rli->relay_log.description_event_for_exec && buf)) { - my_error(ER_OUTOFMEMORY, MYF(0), 1); /* needed 1 bytes */ + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 1); /* needed 1 bytes */ goto end; } diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index ba204f48978..436f7043c49 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -472,6 +472,8 @@ static void make_base_query(String *new_query, /* The following is guaranteed by the query_cache interface */ DBUG_ASSERT(query[query_length] == 0); DBUG_ASSERT(!is_white_space(query[0])); + /* We do not support UCS2, UTF16, UTF32 as a client character set */ + DBUG_ASSERT(current_thd->variables.character_set_client->mbminlen == 1); new_query->length(0); // Don't copy anything from old buffer if (new_query->realloc(query_length + additional_length)) @@ -2439,7 +2441,28 @@ void Query_cache::init() m_cache_status= Query_cache::OK; m_requests_in_progress= 0; initialized = 1; - query_state_map= default_charset_info->state_map; + /* + Using state_map from latin1 should be fine in all cases: + 1. We do not support UCS2, UTF16, UTF32 as a client character set. + 2. The other character sets are compatible on the lower ASCII-range + 0x00-0x20, and have the following characters marked as spaces: + + 0x09 TAB + 0x0A LINE FEED + 0x0B VERTICAL TAB + 0x0C FORM FEED + 0x0D CARRIAGE RETUR + 0x20 SPACE + + Additionally, only some of the ASCII-compatible character sets + (including latin1) can have 0xA0 mapped to "NON-BREAK SPACE" + and thus marked as space. + That should not be a problem for those charsets that map 0xA0 + to something else: the parser will just return syntax error + if this character appears straight in the query + (i.e. not inside a string literal or comment). + */ + query_state_map= my_charset_latin1.state_map; /* If we explicitly turn off query cache from the command line query cache will be disabled for the reminder of the server life @@ -3972,6 +3995,18 @@ Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used, /* +In non-embedded QC intercepts result in net_real_write +but if we have no net.vio then net_real_write +will not be called, so QC can't get results of the query +*/ +#ifdef EMBEDDED_LIBRARY +#define qc_is_able_to_intercept_result(T) 1 +#else +#define qc_is_able_to_intercept_result(T) ((T)->net.vio) +#endif + + +/* If query is cacheable return number tables in query (query without tables are not cached) */ @@ -3986,7 +4021,8 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, if (thd->lex->safe_to_cache_query && (thd->variables.query_cache_type == 1 || (thd->variables.query_cache_type == 2 && (lex->select_lex.options & - OPTION_TO_QUERY_CACHE)))) + OPTION_TO_QUERY_CACHE))) && + qc_is_able_to_intercept_result(thd)) { DBUG_PRINT("qcache", ("options: %lx %lx type: %u", (long) OPTION_TO_QUERY_CACHE, @@ -4008,11 +4044,12 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, } DBUG_PRINT("qcache", - ("not interesting query: %d or not cacheable, options %lx %lx type: %u", + ("not interesting query: %d or not cacheable, options %lx %lx type: %u net->vio present: %u", (int) lex->sql_command, (long) OPTION_TO_QUERY_CACHE, (long) lex->select_lex.options, - (int) thd->variables.query_cache_type)); + (int) thd->variables.query_cache_type, + (uint) test(qc_is_able_to_intercept_result(thd)))); DBUG_RETURN(0); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 71df97dd964..fcf707f46b7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -35,6 +35,7 @@ #include "sql_cache.h" // query_cache_abort #include "sql_base.h" // close_thread_tables #include "sql_time.h" // date_time_format_copy +#include "tztime.h" // MYSQL_TIME <-> my_time_t #include "sql_acl.h" // NO_ACCESS, // acl_getroot_no_password #include "sql_base.h" // close_temporary_tables @@ -1412,10 +1413,16 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno, query_cache_abort(&query_cache_tls); - /* When simulating OOM, skip writing to error log to avoid mtr errors */ - DBUG_EXECUTE_IF("simulate_out_of_memory", DBUG_RETURN(NULL);); - - cond= warning_info->push_warning(this, sql_errno, sqlstate, level, msg); + /* + Avoid pushing a condition for fatal out of memory errors as this will + require memory allocation and therefore might fail. Non fatal out of + memory errors can occur if raised by SIGNAL/RESIGNAL statement. + */ + if (!(is_fatal_error && (sql_errno == EE_OUTOFMEMORY || + sql_errno == ER_OUTOFMEMORY))) + { + cond= warning_info->push_warning(this, sql_errno, sqlstate, level, msg); + } DBUG_RETURN(cond); } @@ -1464,6 +1471,26 @@ void thd_get_xid(const MYSQL_THD thd, MYSQL_XID *xid) *xid = *(MYSQL_XID *) &thd->transaction.xid_state.xid; } + +extern "C" +my_time_t thd_TIME_to_gmt_sec(MYSQL_THD thd, const MYSQL_TIME *ltime, + unsigned int *errcode) +{ + Time_zone *tz= thd ? thd->variables.time_zone : + global_system_variables.time_zone; + return tz->TIME_to_gmt_sec(ltime, errcode); +} + + +extern "C" +void thd_gmt_sec_to_TIME(MYSQL_THD thd, MYSQL_TIME *ltime, my_time_t t) +{ + Time_zone *tz= thd ? thd->variables.time_zone : + global_system_variables.time_zone; + tz->gmt_sec_to_TIME(ltime, t); +} + + #ifdef _WIN32 extern "C" THD *_current_thd_noinline(void) { @@ -2358,7 +2385,7 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) key_length + 1); if (!new_table) { - my_error(EE_OUTOFMEMORY, MYF(ME_BELL), + my_error(EE_OUTOFMEMORY, MYF(ME_BELL+ME_FATALERROR), ALIGN_SIZE(sizeof(TABLE_LIST)) + key_length + 1); killed= KILL_CONNECTION; return 0; @@ -2831,7 +2858,7 @@ select_export::prepare(List<Item> &list, SELECT_LEX_UNIT *u) bool string_results= FALSE, non_string_results= FALSE; unit= u; if ((uint) strlen(exchange->file_name) + NAME_LEN >= FN_REFLEN) - strmake(path,exchange->file_name,FN_REFLEN-1); + strmake_buf(path,exchange->file_name); write_cs= exchange->cs ? exchange->cs : &my_charset_bin; @@ -2969,7 +2996,7 @@ int select_export::send_data(List<Item> &items) set_if_smaller(estimated_bytes, UINT_MAX32); if (cvt_str.realloc((uint32) estimated_bytes)) { - my_error(ER_OUTOFMEMORY, MYF(0), (uint32) estimated_bytes); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), (uint32) estimated_bytes); goto err; } diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index b3af783c566..acf1ba489ad 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -438,7 +438,7 @@ void init_user_stats(USER_STATS *user_stats, memcpy(user_stats->user, user, user_length); user_stats->user[user_length]= 0; user_stats->user_name_length= user_length; - strmake(user_stats->priv_user, priv_user, sizeof(user_stats->priv_user)-1); + strmake_buf(user_stats->priv_user, priv_user); user_stats->total_connections= total_connections; user_stats->concurrent_connections= concurrent_connections; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 1fed2a37e15..1d52be4272a 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -686,7 +686,7 @@ multi_delete::initialize_tables(JOIN *join) tab; tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS)) { - if (tab->table->map & tables_to_delete_from) + if (!tab->bush_children && tab->table->map & tables_to_delete_from) { /* We are going to delete from this table */ TABLE *tbl=walk->table=tab->table; diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 23a60267737..8cfc304e0e7 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -185,6 +185,7 @@ MYSQL_ERROR::MYSQL_ERROR() m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), + m_handled(0), m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), m_mem_root(NULL) { @@ -212,6 +213,7 @@ void MYSQL_ERROR::clear() m_cursor_name.length(0); m_message_text.length(0); m_sql_errno= 0; + m_handled= 0; m_level= MYSQL_ERROR::WARN_LEVEL_ERROR; } @@ -229,6 +231,7 @@ MYSQL_ERROR::MYSQL_ERROR(MEM_ROOT *mem_root) m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), + m_handled(0), m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), m_mem_root(mem_root) { @@ -267,6 +270,7 @@ MYSQL_ERROR::copy_opt_attributes(const MYSQL_ERROR *cond) copy_string(m_mem_root, & m_table_name, & cond->m_table_name); copy_string(m_mem_root, & m_column_name, & cond->m_column_name); copy_string(m_mem_root, & m_cursor_name, & cond->m_cursor_name); + m_handled= cond->m_handled; } void @@ -367,7 +371,7 @@ Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, m_affected_rows= affected_rows_arg; m_last_insert_id= last_insert_id_arg; if (message_arg) - strmake(m_message, message_arg, sizeof(m_message) - 1); + strmake_buf(m_message, message_arg); else m_message[0]= '\0'; m_status= DA_OK; @@ -435,7 +439,7 @@ Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, m_sql_errno= sql_errno_arg; memcpy(m_sqlstate, sqlstate, SQLSTATE_LENGTH); m_sqlstate[SQLSTATE_LENGTH]= '\0'; - strmake(m_message, message_arg, sizeof(m_message)-1); + strmake_buf(m_message, message_arg); m_status= DA_ERROR; DBUG_VOID_RETURN; diff --git a/sql/sql_error.h b/sql/sql_error.h index fadd3b51ec6..ae9ef9f8d47 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -197,6 +197,17 @@ public: ~MYSQL_ERROR() {} + /** check if condition was handled by a condition handler */ + bool handled() const + { + return m_handled; + } + /** mark that condition was handled */ + void mark_handled() + { + m_handled= 1; + } + private: /* The interface of MYSQL_ERROR is mostly private, by design, @@ -310,6 +321,9 @@ private: /** MySQL extension, MYSQL_ERRNO condition item. */ uint m_sql_errno; + /** Marker if error/warning was handled by a continue handler */ + bool m_handled; + /** SQL RETURNED_SQLSTATE condition item. This member is always NUL terminated. diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index d1e5d731183..06efaea8a5b 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -1,5 +1,5 @@ -/* Copyright (c) 2001, 2011, Oracle and/or its affiliates. - Copyright (c) 2011 Monty Program Ab +/* Copyright (c) 2001, 2013, Oracle and/or its affiliates. + Copyright (c) 2011, 2013, Monty Program 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 @@ -1010,11 +1010,13 @@ static SQL_HANDLER *mysql_ha_find_match(THD *thd, TABLE_LIST *tables) for (tables= first; tables; tables= tables->next_local) { + if (tables->is_anonymous_derived_table()) + continue; if ((! *tables->db || ! my_strcasecmp(&my_charset_latin1, hash_tables->db.str, - tables->db)) && + tables->get_db_name())) && ! my_strcasecmp(&my_charset_latin1, hash_tables->table_name.str, - tables->table_name)) + tables->get_table_name())) { /* Link into hash_tables list */ hash_tables->next= head; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 34b4385ce94..b3d77300576 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1999,8 +1999,7 @@ public: DBUG_ENTER("Delayed_insert constructor"); thd.security_ctx->user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; - strmake(thd.security_ctx->priv_user, thd.security_ctx->user, - USERNAME_LENGTH); + strmake_buf(thd.security_ctx->priv_user, thd.security_ctx->user); thd.current_tablenr=0; thd.set_command(COM_DELAYED_INSERT); thd.lex->current_select= 0; // for my_message_sql diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1ba132b534b..3e53f8e0211 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1423,7 +1423,7 @@ int lex_one_token(void *arg, void *yythd) yylval->lex_str=get_token(lip, 2, // skip x' length-3); // don't count x' and last ' - return (HEX_NUM); + return HEX_STRING; case MY_LEX_BIN_NUMBER: // Found b'bin-string' lip->yySkip(); // Accept opening ' @@ -1950,6 +1950,7 @@ void st_select_lex::init_select() merged_into= 0; m_non_agg_field_used= false; m_agg_func_used= false; + name_visibility_map= 0; } /* diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 9cdc8d7d0b6..58cd6316066 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -831,6 +831,9 @@ public: */ List<String> *prev_join_using; + /* namp of nesting SELECT visibility (for aggregate functions check) */ + nesting_map name_visibility_map; + void init_query(); void init_select(); st_select_lex_unit* master_unit(); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 888aebd52b2..8dc07ee36e8 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1933,7 +1933,8 @@ void log_slow_statement(THD *thd) /* Follow the slow log filter configuration. */ if (!thd->enable_slow_log || - !(thd->variables.log_slow_filter & thd->query_plan_flags)) + (thd->variables.log_slow_filter + && !(thd->variables.log_slow_filter & thd->query_plan_flags))) DBUG_VOID_RETURN; if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || @@ -3275,13 +3276,34 @@ end_with_restore_list: { LEX_MASTER_INFO* lex_mi= &thd->lex->mi; Master_info *mi; + int load_error; + + load_error= rpl_load_gtid_slave_state(thd); + mysql_mutex_lock(&LOCK_active_mi); if ((mi= (master_info_index-> get_master_info(&lex_mi->connection_name, MYSQL_ERROR::WARN_LEVEL_ERROR)))) + { + if (load_error) + { + /* + We cannot start a slave using GTID if we cannot load the GTID position + from the mysql.gtid_slave_pos table. But we can allow non-GTID + replication (useful eg. during upgrade). + */ + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + mysql_mutex_unlock(&LOCK_active_mi); + break; + } + else + thd->clear_error(); + } if (!start_slave(thd, mi, 1 /* net report*/)) my_ok(thd); + } mysql_mutex_unlock(&LOCK_active_mi); break; } diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index b2e2016b47d..5bac6643772 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2005, 2011, Oracle and/or its affiliates. +/* Copyright (c) 2005, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -87,7 +87,9 @@ const LEX_STRING partition_keywords[]= { C_STRING_WITH_LEN("KEY") }, { C_STRING_WITH_LEN("MAXVALUE") }, { C_STRING_WITH_LEN("LINEAR ") }, - { C_STRING_WITH_LEN(" COLUMNS") } + { C_STRING_WITH_LEN(" COLUMNS") }, + { C_STRING_WITH_LEN("ALGORITHM") } + }; static const char *part_str= "PARTITION"; static const char *sub_str= "SUB"; @@ -368,7 +370,7 @@ int get_parts_for_update(const uchar *old_data, uchar *new_data, longlong old_func_value; DBUG_ENTER("get_parts_for_update"); - DBUG_ASSERT(new_data == rec0); + DBUG_ASSERT(new_data == rec0); // table->record[0] set_field_ptr(part_field_array, old_data, rec0); error= part_info->get_partition_id(part_info, old_part_id, &old_func_value); @@ -526,12 +528,12 @@ static bool set_up_field_array(TABLE *table, } if (num_fields > MAX_REF_PARTS) { - char *ptr; + char *err_str; if (is_sub_part) - ptr= (char*)"subpartition function"; + err_str= (char*)"subpartition function"; else - ptr= (char*)"partition function"; - my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), ptr); + err_str= (char*)"partition function"; + my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), err_str); DBUG_RETURN(TRUE); } if (num_fields == 0) @@ -1561,7 +1563,7 @@ bool field_is_partition_charset(Field *field) !(field->type() == MYSQL_TYPE_VARCHAR)) return FALSE; { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); if (!(field->type() == MYSQL_TYPE_STRING) || !(cs->state & MY_CS_BINSORT)) return TRUE; @@ -1604,7 +1606,7 @@ bool check_part_func_fields(Field **ptr, bool ok_with_charsets) */ if (field_is_partition_charset(field)) { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); if (!ok_with_charsets || cs->mbmaxlen > 1 || cs->strxfrm_multiply > 1) @@ -2088,6 +2090,8 @@ static int check_part_field(enum_field_types sql_type, case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: *result_type= STRING_RESULT; *need_cs_check= TRUE; return FALSE; @@ -2100,6 +2104,7 @@ static int check_part_field(enum_field_types sql_type, case MYSQL_TYPE_NEWDECIMAL: case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_NULL: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: @@ -2339,6 +2344,58 @@ end: return err; } + +/** + Add 'KEY' word, with optional 'ALGORTIHM = N'. + + @param fptr File to write to. + @param part_info partition_info holding the used key_algorithm + @param current_comment_start NULL, or comment string encapsulating the + PARTITION BY clause. + + @return Operation status. + @retval 0 Success + @retval != 0 Failure +*/ + +static int add_key_with_algorithm(File fptr, partition_info *part_info, + const char *current_comment_start) +{ + int err= 0; + err+= add_part_key_word(fptr, partition_keywords[PKW_KEY].str); + + /* + current_comment_start is given when called from SHOW CREATE TABLE, + Then only add ALGORITHM = 1, not the default 2 or non-set 0! + For .frm current_comment_start is NULL, then add ALGORITHM if != 0. + */ + if (part_info->key_algorithm == partition_info::KEY_ALGORITHM_51 || // SHOW + (!current_comment_start && // .frm + (part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE))) + { + /* If we already are within a comment, end that comment first. */ + if (current_comment_start) + err+= add_string(fptr, "*/ "); + err+= add_string(fptr, "/*!50531 "); + err+= add_part_key_word(fptr, partition_keywords[PKW_ALGORITHM].str); + err+= add_equal(fptr); + err+= add_space(fptr); + err+= add_int(fptr, part_info->key_algorithm); + err+= add_space(fptr); + err+= add_string(fptr, "*/ "); + if (current_comment_start) + { + /* Skip new line. */ + if (current_comment_start[0] == '\n') + current_comment_start++; + err+= add_string(fptr, current_comment_start); + err+= add_space(fptr); + } + } + return err; +} + + /* Generate the partition syntax from the partition data structure. Useful for support of generating defaults, SHOW CREATE TABLES @@ -2383,7 +2440,8 @@ char *generate_partition_syntax(partition_info *part_info, bool use_sql_alloc, bool show_partition_options, HA_CREATE_INFO *create_info, - Alter_info *alter_info) + Alter_info *alter_info, + const char *current_comment_start) { uint i,j, tot_num_parts, num_subparts; partition_element *part_elem; @@ -2417,7 +2475,8 @@ char *generate_partition_syntax(partition_info *part_info, err+= add_string(fptr, partition_keywords[PKW_LINEAR].str); if (part_info->list_of_part_fields) { - err+= add_part_key_word(fptr, partition_keywords[PKW_KEY].str); + err+= add_key_with_algorithm(fptr, part_info, + current_comment_start); err+= add_part_field_list(fptr, part_info->part_field_list); } else @@ -2457,8 +2516,9 @@ char *generate_partition_syntax(partition_info *part_info, err+= add_string(fptr, partition_keywords[PKW_LINEAR].str); if (part_info->list_of_subpart_fields) { - add_part_key_word(fptr, partition_keywords[PKW_KEY].str); - add_part_field_list(fptr, part_info->subpart_field_list); + err+= add_key_with_algorithm(fptr, part_info, + current_comment_start); + err+= add_part_field_list(fptr, part_info->subpart_field_list); } else err+= add_part_key_word(fptr, partition_keywords[PKW_HASH].str); @@ -2667,10 +2727,82 @@ static uint32 calculate_key_value(Field **field_array) { ulong nr1= 1; ulong nr2= 4; + bool use_51_hash; + use_51_hash= test((*field_array)->table->part_info->key_algorithm == + partition_info::KEY_ALGORITHM_51); do { Field *field= *field_array; + if (use_51_hash) + { + switch (field->real_type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_bin.coll->hash_sort(&my_charset_bin, field->ptr, len, + &nr1, &nr2); + /* Done with this field, continue with next one. */ + continue; + } + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_BIT: + /* Not affected, same in 5.1 and 5.5 */ + break; + /* + ENUM/SET uses my_hash_sort_simple in 5.1 (i.e. my_charset_latin1) + and my_hash_sort_bin in 5.5! + */ + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_latin1.coll->hash_sort(&my_charset_latin1, field->ptr, + len, &nr1, &nr2); + continue; + } + /* These types should not be allowed for partitioning! */ + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_GEOMETRY: + /* fall through. */ + default: + DBUG_ASSERT(0); // New type? + /* Fall through for default hashing (5.5). */ + } + /* fall through, use collation based hashing. */ + } field->hash(&nr1, &nr2); } while (*(++field_array)); return (uint32) nr1; @@ -2845,7 +2977,7 @@ static void copy_to_part_field_buffers(Field **ptr, restore_ptr++; if (!field->maybe_null() || !field->is_null()) { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); uint max_len= field->pack_length(); uint data_len= field->data_length(); uchar *field_buf= *field_bufs; @@ -5411,14 +5543,25 @@ the generated partition syntax in a correct manner. Need to cater for engine types that can handle partition without using the partition handler. */ - if (thd->work_part_info != table->part_info) + if (part_info != table->part_info) { - DBUG_PRINT("info", ("partition changed")); - *partition_changed= TRUE; - if (thd->work_part_info->fix_parser_data(thd)) + if (part_info->fix_parser_data(thd)) { goto err; } + /* + Compare the old and new part_info. If only key_algorithm + change is done, don't consider it as changed partitioning (to avoid + rebuild). This is to handle KEY (numeric_cols) partitioned tables + created in 5.1. For more info, see bug#14521864. + */ + if (alter_info->flags != ALTER_PARTITION || + !table->part_info || + !table->part_info->has_same_partitioning(part_info)) + { + DBUG_PRINT("info", ("partition changed")); + *partition_changed= true; + } } /* Set up partition default_engine_type either from the create_info @@ -6970,7 +7113,8 @@ void set_key_field_ptr(KEY *key_info, const uchar *new_buf, void mem_alloc_error(size_t size) { - my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(size)); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast<int>(size)); } #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -7057,12 +7201,12 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) definition) IMPLEMENTATION - There are two available interval analyzer functions: - (1) get_part_iter_for_interval_via_mapping + There are three available interval analyzer functions: + (1) get_part_iter_for_interval_via_mapping (2) get_part_iter_for_interval_cols_via_map (3) get_part_iter_for_interval_via_walking - They both have limited applicability: + They all have limited applicability: (1) is applicable for "PARTITION BY <RANGE|LIST>(func(t.field))", where func is a monotonic function. @@ -7435,6 +7579,9 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, get_endpoint_func UNINIT_VAR(get_endpoint); bool can_match_multiple_values; /* is not '=' */ uint field_len= field->pack_length_in_rec(); + MYSQL_TIME start_date; + bool check_zero_dates= false; + bool zero_in_start_date= true; DBUG_ENTER("get_part_iter_for_interval_via_mapping"); DBUG_ASSERT(!is_subpart); (void) store_length_array; @@ -7491,6 +7638,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, { /* col is NOT NULL, but F(col) can return NULL, add NULL partition */ part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE; + check_zero_dates= true; } } @@ -7534,6 +7682,19 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, DBUG_RETURN(1); } part_iter->part_nums.cur= part_iter->part_nums.start; + if (check_zero_dates && !part_info->part_expr->null_value) + { + if (!(flags & NO_MAX_RANGE) && + (field->type() == MYSQL_TYPE_DATE || + field->type() == MYSQL_TYPE_DATETIME)) + { + /* Monotonic, but return NULL for dates with zeros in month/day. */ + zero_in_start_date= field->get_date(&start_date, 0); + DBUG_PRINT("info", ("zero start %u %04d-%02d-%02d", + zero_in_start_date, start_date.year, + start_date.month, start_date.day)); + } + } if (part_iter->part_nums.start == max_endpoint_val) DBUG_RETURN(0); /* No partitions */ } @@ -7547,6 +7708,29 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, store_key_image_to_rec(field, max_value, field_len); bool include_endp= !test(flags & NEAR_MAX); part_iter->part_nums.end= get_endpoint(part_info, 0, include_endp); + if (check_zero_dates && + !zero_in_start_date && + !part_info->part_expr->null_value) + { + MYSQL_TIME end_date; + bool zero_in_end_date= field->get_date(&end_date, 0); + /* + This is an optimization for TO_DAYS()/TO_SECONDS() to avoid scanning + the NULL partition for ranges that cannot include a date with 0 as + month/day. + */ + DBUG_PRINT("info", ("zero end %u %04d-%02d-%02d", + zero_in_end_date, + end_date.year, end_date.month, end_date.day)); + DBUG_ASSERT(!memcmp(((Item_func*) part_info->part_expr)->func_name(), + "to_days", 7) || + !memcmp(((Item_func*) part_info->part_expr)->func_name(), + "to_seconds", 10)); + if (!zero_in_end_date && + start_date.month == end_date.month && + start_date.year == end_date.year) + part_iter->ret_null_part= part_iter->ret_null_part_orig= false; + } if (part_iter->part_nums.start >= part_iter->part_nums.end && !part_iter->ret_null_part) DBUG_RETURN(0); /* No partitions */ diff --git a/sql/sql_partition.h b/sql/sql_partition.h index 8db07f836b0..cf532c45c66 100644 --- a/sql/sql_partition.h +++ b/sql/sql_partition.h @@ -1,8 +1,7 @@ #ifndef SQL_PARTITION_INCLUDED #define SQL_PARTITION_INCLUDED -/* - Copyright (c) 2006, 2010, Oracle and/or its affiliates. +/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. 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 @@ -267,7 +266,8 @@ char *generate_partition_syntax(partition_info *part_info, uint *buf_length, bool use_sql_alloc, bool show_partition_options, HA_CREATE_INFO *create_info, - Alter_info *alter_info); + Alter_info *alter_info, + const char *current_comment_start); bool partition_key_modified(TABLE *table, const MY_BITMAP *fields); #else #define partition_key_modified(X,Y) 0 diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index 4edd47de855..b9bf3dbc217 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -141,7 +141,9 @@ bool Alter_table_truncate_partition_statement::execute(THD *thd) TODO: Add support for TRUNCATE PARTITION for NDB and other engines supporting native partitioning. */ - if (first_table->table->s->db_type() != partition_hton) + + if (!first_table->table || first_table->view || + first_table->table->s->db_type() != partition_hton) { my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); DBUG_RETURN(TRUE); diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index c0a547c46d8..51a0e58d8ab 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -15,9 +15,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "sql_plugin.h" #include "sql_priv.h" // SHOW_MY_BOOL #include "unireg.h" -#include "my_global.h" // REQUIRED by m_string.h #include "sql_class.h" // set_var.h: THD #include "sys_vars_shared.h" #include "sql_locale.h" diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index 906ff20ddb7..5327b27e97c 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -17,7 +17,6 @@ #ifndef _sql_plugin_h #define _sql_plugin_h -#include <my_global.h> /* the following #define adds server-only members to enum_mysql_show_type, @@ -27,7 +26,7 @@ SHOW_LONG_STATUS, SHOW_DOUBLE_STATUS, \ SHOW_HAVE, SHOW_MY_BOOL, SHOW_HA_ROWS, SHOW_SYS, \ SHOW_LONG_NOFLUSH, SHOW_LONGLONG_STATUS, SHOW_LEX_STRING -#include <mysql/plugin.h> +#include <my_global.h> #undef SHOW_always_last #include "m_string.h" /* LEX_STRING */ diff --git a/sql/sql_plugin_compat.h b/sql/sql_plugin_compat.h index 8c6014f8dc6..5c7bb620575 100644 --- a/sql/sql_plugin_compat.h +++ b/sql/sql_plugin_compat.h @@ -16,9 +16,9 @@ /* old plugin api structures, used for backward compatibility */ #define upgrade_var(X) latest->X= X -#define upgrade_str(X) strmake(latest->X, X, sizeof(X)) +#define upgrade_str(X) strmake_buf(latest->X, X) #define downgrade_var(X) X= latest->X -#define downgrade_str(X) strmake(X, latest->X, sizeof(X)-1) +#define downgrade_str(X) strmake_buf(X, latest->X) /**************************************************************/ /* Authentication API, version 0x0100 *************************/ diff --git a/sql/sql_plugin_services.h b/sql/sql_plugin_services.h index d2f60a6e08c..838b994b6e8 100644 --- a/sql/sql_plugin_services.h +++ b/sql/sql_plugin_services.h @@ -54,6 +54,11 @@ static struct kill_statement_service_st thd_kill_statement_handler= { thd_kill_level }; +static struct thd_timezone_service_st thd_timezone_handler= { + thd_TIME_to_gmt_sec, + thd_gmt_sec_to_TIME +}; + static struct st_service_ref list_of_services[]= { { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler }, @@ -61,6 +66,7 @@ static struct st_service_ref list_of_services[]= { "thd_wait_service", VERSION_thd_wait, &thd_wait_handler }, { "progress_report_service", VERSION_progress_report, &progress_report_handler }, { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init() - { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler } + { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler }, + { "thd_timezone_service", VERSION_thd_timezone, &thd_timezone_handler }, }; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 7ccc6aaad39..001526b5b3b 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -879,7 +879,7 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, if (param->state == Item_param::NO_VALUE) DBUG_RETURN(1); - if (param->limit_clause_param && param->item_type != Item::INT_ITEM) + if (param->limit_clause_param) { param->set_int(param->val_int(), MY_INT64_NUM_DECIMAL_DIGITS); param->item_type= Item::INT_ITEM; @@ -1503,7 +1503,8 @@ static int mysql_test_select(Prepared_statement *stmt, if (!lex->result && !(lex->result= new (stmt->mem_root) select_send)) { - my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(sizeof(select_send))); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast<int>(sizeof(select_send))); goto error; } @@ -1877,7 +1878,7 @@ static bool mysql_test_multidelete(Prepared_statement *stmt, stmt->thd->lex->current_select= &stmt->thd->lex->select_lex; if (add_item_to_list(stmt->thd, new Item_null())) { - my_error(ER_OUTOFMEMORY, MYF(0), 0); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0); goto error; } @@ -3885,7 +3886,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) alloc_query(thd, (char*) expanded_query->ptr(), expanded_query->length())) { - my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), expanded_query->length()); goto error; } /* @@ -4426,7 +4427,7 @@ bool Protocol_local::store(const char *str, size_t length, bool Protocol_local::store(MYSQL_TIME *time, int decimals) { if (decimals != AUTO_SEC_PART_DIGITS) - time->second_part= sec_part_truncate(time->second_part, decimals); + my_time_trunc(time, decimals); return store_column(time, sizeof(MYSQL_TIME)); } @@ -4444,7 +4445,7 @@ bool Protocol_local::store_date(MYSQL_TIME *time) bool Protocol_local::store_time(MYSQL_TIME *time, int decimals) { if (decimals != AUTO_SEC_PART_DIGITS) - time->second_part= sec_part_truncate(time->second_part, decimals); + my_time_trunc(time, decimals); return store_column(time, sizeof(MYSQL_TIME)); } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 9e3da1ce641..0d6c97842d4 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2008, 2013, Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -50,7 +50,7 @@ extern TYPELIB binlog_checksum_typelib; static int fake_event_header(String* packet, Log_event_type event_type, ulong extra_len, my_bool *do_checksum, ha_checksum *crc, const char** errmsg, - uint8 checksum_alg_arg) + uint8 checksum_alg_arg, uint32 end_pos) { char header[LOG_EVENT_HEADER_LEN]; ulong event_len; @@ -70,7 +70,7 @@ fake_event_header(String* packet, Log_event_type event_type, ulong extra_len, int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F); // TODO: check what problems this may cause and fix them - int4store(header + LOG_POS_OFFSET, 0); + int4store(header + LOG_POS_OFFSET, end_pos); if (packet->append(header, sizeof(header))) { *errmsg= "Failed due to out-of-memory writing event"; @@ -146,7 +146,7 @@ static int fake_rotate_event(NET* net, String* packet, char* log_file_name, if ((err= fake_event_header(packet, ROTATE_EVENT, ident_len + ROTATE_HEADER_LEN, &do_checksum, &crc, - errmsg, checksum_alg_arg))) + errmsg, checksum_alg_arg, 0))) DBUG_RETURN(err); int8store(buf+R_POS_OFFSET,position); @@ -169,7 +169,7 @@ static int fake_rotate_event(NET* net, String* packet, char* log_file_name, static int fake_gtid_list_event(NET* net, String* packet, Gtid_list_log_event *glev, const char** errmsg, - uint8 checksum_alg_arg) + uint8 checksum_alg_arg, uint32 current_pos) { my_bool do_checksum; int err; @@ -185,7 +185,7 @@ static int fake_gtid_list_event(NET* net, String* packet, } if ((err= fake_event_header(packet, GTID_LIST_EVENT, str.length(), &do_checksum, &crc, - errmsg, checksum_alg_arg))) + errmsg, checksum_alg_arg, current_pos))) return err; packet->append(str); @@ -901,8 +901,6 @@ check_slave_start_position(THD *thd, slave_connection_state *st, rpl_gtid **delete_list= NULL; uint32 delete_idx= 0; bool slave_state_loaded= false; - uint32 missing_domains= 0; - rpl_gtid missing_domain_gtid; for (i= 0; i < st->hash.records; ++i) { @@ -943,14 +941,7 @@ check_slave_start_position(THD *thd, slave_connection_state *st, We do not have anything in this domain, neither in the binlog nor in the slave state. So we are probably one master in a multi-master setup, and this domain is served by a different master. - - This is not an error, however if we are missing _all_ domains - requested by the slave, then we still give error (below, after - the loop). */ - if (!missing_domains) - missing_domain_gtid= *slave_gtid; - ++missing_domains; continue; } @@ -1043,14 +1034,6 @@ check_slave_start_position(THD *thd, slave_connection_state *st, } } - if (missing_domains == st->hash.records && missing_domains > 0) - { - *errormsg= "Requested slave GTID state not found in binlog"; - *error_gtid= missing_domain_gtid; - err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; - goto end; - } - /* Do any delayed deletes from the hash. */ if (delete_list) { @@ -1423,7 +1406,7 @@ is_until_reached(THD *thd, NET *net, String *packet, ulong *ev_offset, enum_gtid_until_state gtid_until_group, Log_event_type event_type, uint8 current_checksum_alg, ushort flags, const char **errmsg, - rpl_binlog_state *until_binlog_state) + rpl_binlog_state *until_binlog_state, uint32 current_pos) { switch (gtid_until_group) { @@ -1454,7 +1437,8 @@ is_until_reached(THD *thd, NET *net, String *packet, ulong *ev_offset, return true; Gtid_list_log_event glev(until_binlog_state, Gtid_list_log_event::FLAG_UNTIL_REACHED); - if (fake_gtid_list_event(net, packet, &glev, errmsg, current_checksum_alg)) + if (fake_gtid_list_event(net, packet, &glev, errmsg, current_checksum_alg, + current_pos)) return true; *errmsg= NULL; return true; @@ -1525,6 +1509,19 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, return "Failed to read Gtid_log_event: corrupt binlog"; } + DBUG_EXECUTE_IF("gtid_force_reconnect_at_10_1_100", + { + rpl_gtid *dbug_gtid; + if ((dbug_gtid= until_binlog_state->find(10,1)) && + dbug_gtid->seq_no == 100) + { + DBUG_SET("-d,gtid_force_reconnect_at_10_1_100"); + DBUG_SET_INITIAL("-d,gtid_force_reconnect_at_10_1_100"); + my_errno= ER_UNKNOWN_ERROR; + return "DBUG-injected forced reconnect"; + } + }); + if (until_binlog_state->update(&event_gtid, false)) { my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; @@ -1544,19 +1541,31 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, if (event_gtid.server_id == gtid->server_id && event_gtid.seq_no >= gtid->seq_no) { - /* - In strict mode, it is an error if the slave requests to start in - a "hole" in the master's binlog: a GTID that does not exist, even - though both the prior and subsequent seq_no exists for same - domain_id and server_id. - */ - if (slave_gtid_strict_mode && event_gtid.seq_no > gtid->seq_no) + if (event_gtid.seq_no > gtid->seq_no) { - my_errno= ER_GTID_START_FROM_BINLOG_HOLE; - *error_gtid= *gtid; - return "The binlog on the master is missing the GTID requested " - "by the slave (even though both a prior and a subsequent " - "sequence number does exist), and GTID strict mode is enabled."; + /* + In strict mode, it is an error if the slave requests to start + in a "hole" in the master's binlog: a GTID that does not + exist, even though both the prior and subsequent seq_no exists + for same domain_id and server_id. + */ + if (slave_gtid_strict_mode) + { + my_errno= ER_GTID_START_FROM_BINLOG_HOLE; + *error_gtid= *gtid; + return "The binlog on the master is missing the GTID requested " + "by the slave (even though both a prior and a subsequent " + "sequence number does exist), and GTID strict mode is enabled."; + } + } + else + { + /* + Send a fake Gtid_list event to the slave. + This allows the slave to update its current binlog position + so MASTER_POS_WAIT() and MASTER_GTID_WAIT() can work. + */ +// send_fake_gtid_list_event(until_binlog_state); } /* Delete this entry if we have reached slave start position (so we @@ -1812,8 +1821,10 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; int old_max_allowed_packet= thd->variables.max_allowed_packet; + #ifndef DBUG_OFF int left_events = max_binlog_dump_events; + uint dbug_reconnect_counter= 0; #endif DBUG_ENTER("mysql_binlog_send"); DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos)); @@ -1847,6 +1858,13 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, until_gtid_state= &until_gtid_state_obj; } + DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events", + { + DBUG_SET("-d,binlog_force_reconnect_after_22_events"); + DBUG_SET_INITIAL("-d,binlog_force_reconnect_after_22_events"); + dbug_reconnect_counter= 22; + }); + /* We want to corrupt the first event, in Log_event::read_log_event(). But we do not want the corruption to happen early, eg. when client does @@ -2130,9 +2148,12 @@ impossible position"; if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) goto err; + bool is_active_binlog= false; while (!(killed= thd->killed) && !(error = Log_event::read_log_event(&log, packet, log_lock, - current_checksum_alg))) + current_checksum_alg, + log_file_name, + &is_active_binlog))) { #ifndef DBUG_OFF if (max_binlog_dump_events && !left_events--) @@ -2193,6 +2214,19 @@ impossible position"; (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; } +#ifndef DBUG_OFF + if (dbug_reconnect_counter > 0) + { + --dbug_reconnect_counter; + if (dbug_reconnect_counter == 0) + { + errmsg= "DBUG-injected forced reconnect"; + my_errno= ER_UNKNOWN_ERROR; + goto err; + } + } +#endif + if ((tmp_msg= send_event_to_slave(thd, net, packet, flags, event_type, log_file_name, &log, mariadb_slave_capability, ev_offset, @@ -2208,7 +2242,7 @@ impossible position"; if (until_gtid_state && is_until_reached(thd, net, packet, &ev_offset, gtid_until_group, event_type, current_checksum_alg, flags, &errmsg, - &until_binlog_state)) + &until_binlog_state, my_b_tell(&log))) { if (errmsg) { @@ -2233,6 +2267,13 @@ impossible position"; if (killed) goto end; + DBUG_EXECUTE_IF("wait_after_binlog_EOF", + { + const char act[]= "now wait_for signal.rotate_finished"; + DBUG_ASSERT(!debug_sync_set_action(current_thd, + STRING_WITH_LEN(act))); + };); + /* TODO: now that we are logging the offset, check to make sure the recorded offset and the actual match. @@ -2243,8 +2284,11 @@ impossible position"; if (test_for_non_eof_log_read_errors(error, &errmsg)) goto err; - if (!(flags & BINLOG_DUMP_NON_BLOCK) && - mysql_bin_log.is_active(log_file_name)) + /* + We should only move to the next binlog when the last read event + came from a already deactivated binlog. + */ + if (!(flags & BINLOG_DUMP_NON_BLOCK) && is_active_binlog) { /* Block until there is more data in the log @@ -2298,7 +2342,8 @@ impossible position"; mysql_mutex_unlock(log_lock); read_packet = 1; p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET); - event_type= (Log_event_type)((*packet)[LOG_EVENT_OFFSET+ev_offset]); + event_type= + (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); break; case LOG_READ_EOF: @@ -2390,7 +2435,7 @@ impossible position"; until_gtid_state && is_until_reached(thd, net, packet, &ev_offset, gtid_until_group, event_type, current_checksum_alg, flags, &errmsg, - &until_binlog_state)) + &until_binlog_state, my_b_tell(&log))) { if (errmsg) { @@ -2628,8 +2673,7 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) We don't check thd->lex->mi.log_file_name for NULL here since it is checked in sql_yacc.yy */ - strmake(mi->rli.until_log_name, thd->lex->mi.log_file_name, - sizeof(mi->rli.until_log_name)-1); + strmake_buf(mi->rli.until_log_name, thd->lex->mi.log_file_name); } else if (thd->lex->mi.relay_log_pos) { @@ -2637,8 +2681,7 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) slave_errno=ER_BAD_SLAVE_UNTIL_COND; mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS; mi->rli.until_log_pos= thd->lex->mi.relay_log_pos; - strmake(mi->rli.until_log_name, thd->lex->mi.relay_log_name, - sizeof(mi->rli.until_log_name)-1); + strmake_buf(mi->rli.until_log_name, thd->lex->mi.relay_log_name); } else if (thd->lex->mi.gtid_pos_str.str) { @@ -2945,14 +2988,15 @@ void kill_zombie_dump_threads(uint32 slave_server_id) */ static bool get_string_parameter(char *to, const char *from, size_t length, - const char *name) + const char *name, CHARSET_INFO *cs) { if (from) // Empty paramaters allowed { - size_t from_length; - if ((from_length= strlen(from)) > length) + size_t from_length= strlen(from); + uint from_numchars= cs->cset->numchars(cs, from, from + from_length); + if (from_numchars > length / cs->mbmaxlen) { - my_error(ER_WRONG_STRING_LENGTH, MYF(0), from, name, (int) length); + my_error(ER_WRONG_STRING_LENGTH, MYF(0), from, name, length / cs->mbmaxlen); return 1; } memcpy(to, from, from_length+1); @@ -2986,6 +3030,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) char saved_host[HOSTNAME_LENGTH + 1]; uint saved_port; char saved_log_name[FN_REFLEN]; + Master_info::enum_using_gtid saved_using_gtid; char master_info_file_tmp[FN_REFLEN]; char relay_log_info_file_tmp[FN_REFLEN]; my_off_t saved_log_pos; @@ -3071,10 +3116,11 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) /* Before processing the command, save the previous state. */ - strmake(saved_host, mi->host, HOSTNAME_LENGTH); + strmake_buf(saved_host, mi->host); saved_port= mi->port; - strmake(saved_log_name, mi->master_log_name, FN_REFLEN - 1); + strmake_buf(saved_log_name, mi->master_log_name); saved_log_pos= mi->master_log_pos; + saved_using_gtid= mi->using_gtid; /* If the user specified host or port without binlog or position, @@ -3088,8 +3134,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) } if (lex_mi->log_file_name) - strmake(mi->master_log_name, lex_mi->log_file_name, - sizeof(mi->master_log_name)-1); + strmake_buf(mi->master_log_name, lex_mi->log_file_name); if (lex_mi->pos) { mi->master_log_pos= lex_mi->pos; @@ -3097,11 +3142,12 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); if (get_string_parameter(mi->host, lex_mi->host, sizeof(mi->host)-1, - "MASTER_HOST") || + "MASTER_HOST", system_charset_info) || get_string_parameter(mi->user, lex_mi->user, sizeof(mi->user)-1, - "MASTER_USER") || + "MASTER_USER", system_charset_info) || get_string_parameter(mi->password, lex_mi->password, - sizeof(mi->password)-1, "MASTER_PASSWORD")) + sizeof(mi->password)-1, "MASTER_PASSWORD", + &my_charset_bin)) { ret= TRUE; goto err; @@ -3153,19 +3199,19 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) (lex_mi->ssl_verify_server_cert == LEX_MASTER_INFO::LEX_MI_ENABLE); if (lex_mi->ssl_ca) - strmake(mi->ssl_ca, lex_mi->ssl_ca, sizeof(mi->ssl_ca)-1); + strmake_buf(mi->ssl_ca, lex_mi->ssl_ca); if (lex_mi->ssl_capath) - strmake(mi->ssl_capath, lex_mi->ssl_capath, sizeof(mi->ssl_capath)-1); + strmake_buf(mi->ssl_capath, lex_mi->ssl_capath); if (lex_mi->ssl_cert) - strmake(mi->ssl_cert, lex_mi->ssl_cert, sizeof(mi->ssl_cert)-1); + strmake_buf(mi->ssl_cert, lex_mi->ssl_cert); if (lex_mi->ssl_cipher) - strmake(mi->ssl_cipher, lex_mi->ssl_cipher, sizeof(mi->ssl_cipher)-1); + strmake_buf(mi->ssl_cipher, lex_mi->ssl_cipher); if (lex_mi->ssl_key) - strmake(mi->ssl_key, lex_mi->ssl_key, sizeof(mi->ssl_key)-1); + strmake_buf(mi->ssl_key, lex_mi->ssl_key); if (lex_mi->ssl_crl) - strmake(mi->ssl_crl, lex_mi->ssl_crl, sizeof(mi->ssl_crl)-1); + strmake_buf(mi->ssl_crl, lex_mi->ssl_crl); if (lex_mi->ssl_crlpath) - strmake(mi->ssl_crlpath, lex_mi->ssl_crlpath, sizeof(mi->ssl_crlpath)-1); + strmake_buf(mi->ssl_crlpath, lex_mi->ssl_crlpath); #ifndef HAVE_OPENSSL if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath || @@ -3180,10 +3226,8 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) need_relay_log_purge= 0; char relay_log_name[FN_REFLEN]; mi->rli.relay_log.make_log_name(relay_log_name, lex_mi->relay_log_name); - strmake(mi->rli.group_relay_log_name, relay_log_name, - sizeof(mi->rli.group_relay_log_name)-1); - strmake(mi->rli.event_relay_log_name, relay_log_name, - sizeof(mi->rli.event_relay_log_name)-1); + strmake_buf(mi->rli.group_relay_log_name, relay_log_name); + strmake_buf(mi->rli.event_relay_log_name, relay_log_name); } if (lex_mi->relay_log_pos) @@ -3228,8 +3272,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) */ mi->master_log_pos = max(BIN_LOG_HEADER_SIZE, mi->rli.group_master_log_pos); - strmake(mi->master_log_name, mi->rli.group_master_log_name, - sizeof(mi->master_log_name)-1); + strmake_buf(mi->master_log_name, mi->rli.group_master_log_name); } /* @@ -3293,8 +3336,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) */ mi->rli.group_master_log_pos= mi->master_log_pos; DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); - strmake(mi->rli.group_master_log_name,mi->master_log_name, - sizeof(mi->rli.group_master_log_name)-1); + strmake_buf(mi->rli.group_master_log_name,mi->master_log_name); if (!mi->rli.group_master_log_name[0]) // uninitialized case mi->rli.group_master_log_pos=0; @@ -3312,6 +3354,11 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) "master_log_pos='%ld'.", saved_host, saved_port, saved_log_name, (ulong) saved_log_pos, mi->host, mi->port, mi->master_log_name, (ulong) mi->master_log_pos); + if (saved_using_gtid != Master_info::USE_GTID_NO || + mi->using_gtid != Master_info::USE_GTID_NO) + sql_print_information("Previous Using_Gtid=%s. New Using_Gtid=%s", + mi->using_gtid_astext(saved_using_gtid), + mi->using_gtid_astext(mi->using_gtid)); /* If we don't write new coordinates to disk now, then old will remain in @@ -3773,11 +3820,11 @@ rpl_deinit_gtid_slave_state() /* - Format the current GTID state as a string, for use when connecting to a - master server with GTID, or for returning the value of @@global.gtid_state. + Format the current GTID state as a string, for returning the value of + @@global.gtid_slave_pos. If the flag use_binlog is true, then the contents of the binary log (if - enabled) is merged into the current GTID state. + enabled) is merged into the current GTID state (@@global.gtid_current_pos). */ int rpl_append_gtid_state(String *dest, bool use_binlog) @@ -3790,10 +3837,35 @@ rpl_append_gtid_state(String *dest, bool use_binlog) (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) return err; - rpl_global_gtid_slave_state.tostring(dest, gtid_list, num_gtids); + err= rpl_global_gtid_slave_state.tostring(dest, gtid_list, num_gtids); my_free(gtid_list); - return 0; + return err; +} + + +/* + Load the current GITD position into a slave_connection_state, for use when + connecting to a master server with GTID. + + If the flag use_binlog is true, then the contents of the binary log (if + enabled) is merged into the current GTID state (master_use_gtid=current_pos). +*/ +int +rpl_load_gtid_state(slave_connection_state *state, bool use_binlog) +{ + int err; + rpl_gtid *gtid_list= NULL; + uint32 num_gtids= 0; + + if (use_binlog && opt_bin_log && + (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) + return err; + + err= state->load(&rpl_global_gtid_slave_state, gtid_list, num_gtids); + my_free(gtid_list); + + return err; } diff --git a/sql/sql_repl.h b/sql/sql_repl.h index 820ffed0928..917da9b598e 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -32,6 +32,8 @@ typedef struct st_slave_info THD* thd; } SLAVE_INFO; +struct slave_connection_state; + extern my_bool opt_show_slave_auth_info; extern char *master_host, *master_info_file; extern bool server_id_supplied; @@ -70,6 +72,7 @@ void rpl_init_gtid_slave_state(); void rpl_deinit_gtid_slave_state(); int gtid_state_from_binlog_pos(const char *name, uint32 pos, String *out_str); int rpl_append_gtid_state(String *dest, bool use_binlog); +int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog); bool rpl_gtid_pos_check(THD *thd, char *str, size_t len); bool rpl_gtid_pos_update(THD *thd, char *str, size_t len); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c19c7aa95a7..737f568fd62 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2495,7 +2495,12 @@ void JOIN::exec_inner() List<Item> *curr_all_fields= &all_fields; List<Item> *curr_fields_list= &fields_list; TABLE *curr_tmp_table= 0; - bool tmp_having_used_tables_updated= FALSE; + /* + curr_join->join_free() will call JOIN::cleanup(full=TRUE). It will not + be safe to call update_used_tables() after that. + */ + if (curr_join->tmp_having) + curr_join->tmp_having->update_used_tables(); /* Initialize examined rows here because the values from all join parts @@ -2751,16 +2756,6 @@ void JOIN::exec_inner() curr_join->select_distinct=0; /* Each row is unique */ - /* - curr_join->join_free() will call JOIN::cleanup(full=TRUE). It will not - be safe to call update_used_tables() after that. - */ - if (curr_join->tmp_having) - { - curr_join->tmp_having->update_used_tables(); - tmp_having_used_tables_updated= TRUE; - } - curr_join->join_free(); /* Free quick selects */ if (curr_join->select_distinct && ! curr_join->group_list) @@ -2841,9 +2836,6 @@ void JOIN::exec_inner() if (curr_join->tmp_having && ! curr_join->group_list && ! curr_join->sort_and_group) { - // Some tables may have been const - if (!tmp_having_used_tables_updated) - curr_join->tmp_having->update_used_tables(); JOIN_TAB *curr_table= &curr_join->join_tab[curr_join->const_tables]; table_map used_tables= (curr_join->const_table_map | curr_table->table->map); @@ -3692,9 +3684,16 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, !table->fulltext_searched && (!embedding || (embedding->sj_on_expr && !embedding->embedding))) { + key_map base_part, base_const_ref, base_eq_part; + base_part.set_prefix(keyinfo->key_parts); + base_const_ref= const_ref; + base_const_ref.intersect(base_part); + base_eq_part= eq_part; + base_eq_part.intersect(base_part); if (table->actual_key_flags(keyinfo) & HA_NOSAME) { - if (const_ref == eq_part && + + if (base_const_ref == base_eq_part && !has_expensive_keyparts && !((outer_join & table->map) && (*s->on_expr_ref)->is_expensive())) @@ -3720,7 +3719,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, else found_ref|= refs; // Table is const if all refs are const } - else if (const_ref == eq_part) + else if (base_const_ref == base_eq_part) s->const_keys.set_bit(key); } } @@ -4308,7 +4307,7 @@ add_key_field(JOIN *join, { if ((*value)->cmp_type() != STRING_RESULT) return; - if (((Field_str*)field)->charset() != cond->compare_collation()) + if (field->charset() != cond->compare_collation()) return; } } @@ -11414,7 +11413,7 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, *simple_order=0; // Must do a temp table to sort else if (!(order_tables & not_const_tables)) { - if (order->item[0]->with_subselect) + if (order->item[0]->has_subquery()) { /* Delay the evaluation of constant ORDER and/or GROUP expressions that @@ -11858,7 +11857,7 @@ static bool check_simple_equality(Item *left_item, Item *right_item, if (field_item->cmp_type() == STRING_RESULT) { - CHARSET_INFO *cs= ((Field_str*) field_item->field)->charset(); + CHARSET_INFO *cs= field_item->field->charset(); if (!item) { Item_func_eq *eq_item; @@ -13734,13 +13733,13 @@ static void restore_prev_nj_state(JOIN_TAB *last) bool was_fully_covered= nest->is_fully_covered(); + join->cur_embedding_map|= nest->nj_map; + if (--nest->counter == 0) join->cur_embedding_map&= ~nest->nj_map; if (!was_fully_covered) break; - - join->cur_embedding_map|= nest->nj_map; } } } @@ -13960,7 +13959,27 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) if (new_item_and_list->is_empty()) li.remove(); else + { + Item *list_item; + Item *new_list_item; + uint cnt= new_item_and_list->elements; + List_iterator<Item> it(*new_item_and_list); + while ((list_item= it++)) + { + uchar* is_subst_valid= (uchar *) Item::ANY_SUBST; + new_list_item= + list_item->compile(&Item::subst_argument_checker, + &is_subst_valid, + &Item::equal_fields_propagator, + (uchar *) &cond_and->cond_equal); + if (new_list_item != list_item) + it.replace(new_list_item); + new_list_item->update_used_tables(); + } li.replace(*new_item_and_list); + for (cnt--; cnt; cnt--) + item= li++; + } cond_and_list->concat((List<Item>*) cond_equal_items); } else if (new_item->type() == Item::FUNC_ITEM && @@ -13980,7 +13999,13 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) if (new_item->type() == Item::COND_ITEM && ((Item_cond*) new_item)->functype() == ((Item_cond*) cond)->functype()) - li.replace(*((Item_cond*) new_item)->argument_list()); + { + List<Item> *arg_list= ((Item_cond*) new_item)->argument_list(); + uint cnt= arg_list->elements; + li.replace(*arg_list); + for ( cnt--; cnt; cnt--) + item= li++; + } else li.replace(new_item); } @@ -14671,6 +14696,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::REAL_ITEM: case Item::DECIMAL_ITEM: case Item::STRING_ITEM: + case Item::DATE_ITEM: case Item::REF_ITEM: case Item::NULL_ITEM: case Item::VARBIN_ITEM: @@ -19326,7 +19352,8 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, !(table->file->index_flags(best_key, 0, 1) & HA_CLUSTERED_INDEX))) goto use_filesort; - if (table->quick_keys.is_set(best_key) && best_key != ref_key) + if (select && + table->quick_keys.is_set(best_key) && best_key != ref_key) { key_map map; map.clear_all(); // Force the creation of quick select @@ -19904,7 +19931,7 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field, } if (copy_blobs(first_field)) { - my_message(ER_OUTOFMEMORY, ER(ER_OUTOFMEMORY), MYF(0)); + my_message(ER_OUTOFMEMORY, ER(ER_OUTOFMEMORY), MYF(ME_FATALERROR)); error=0; goto err; } @@ -19937,7 +19964,8 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field, if (!found) break; // End of file /* Restart search on saved row */ - error=file->restart_rnd_next(record); + if ((error= file->restart_rnd_next(record))) + goto err; } file->extra(HA_EXTRA_NO_CACHE); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index feba387d3b4..e6f0be69bc6 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2013, Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -17,7 +17,7 @@ /* Function with list databases, tables or fields */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_priv.h" #include "unireg.h" #include "sql_acl.h" // fill_schema_*_privileges @@ -1561,6 +1561,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, MODE_MYSQL323 | MODE_MYSQL40)) != 0; my_bitmap_map *old_map; + int error= 0; DBUG_ENTER("store_create_info"); DBUG_PRINT("enter",("table: %s", table->s->table_name.str)); @@ -1924,28 +1925,35 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, } #ifdef WITH_PARTITION_STORAGE_ENGINE { - /* - Partition syntax for CREATE TABLE is at the end of the syntax. - */ - uint part_syntax_len; - char *part_syntax; if (table->part_info && - (!table->part_info->is_auto_partitioned) && - ((part_syntax= generate_partition_syntax(table->part_info, + !((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) && + table->part_info->is_auto_partitioned)) + { + /* + Partition syntax for CREATE TABLE is at the end of the syntax. + */ + uint part_syntax_len; + char *part_syntax; + String comment_start; + table->part_info->set_show_version_string(&comment_start); + if ((part_syntax= generate_partition_syntax(table->part_info, &part_syntax_len, FALSE, show_table_options, - NULL, NULL)))) - { - table->part_info->set_show_version_string(packet); - packet->append(part_syntax, part_syntax_len); - packet->append(STRING_WITH_LEN(" */")); - my_free(part_syntax); + NULL, NULL, + comment_start.c_ptr()))) + { + packet->append(comment_start); + if (packet->append(part_syntax, part_syntax_len) || + packet->append(STRING_WITH_LEN(" */"))) + error= 1; + my_free(part_syntax); + } } } #endif tmp_restore_column_map(table->read_set, old_map); - DBUG_RETURN(0); + DBUG_RETURN(error); } @@ -2185,7 +2193,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) DBUG_ENTER("mysqld_list_processes"); field_list.push_back(new Item_int("Id", 0, MY_INT32_NUM_DECIMAL_DIGITS)); - field_list.push_back(new Item_empty_string("User",16)); + field_list.push_back(new Item_empty_string("User", USERNAME_CHAR_LENGTH)); field_list.push_back(new Item_empty_string("Host",LIST_PROCESS_HOST_LEN)); field_list.push_back(field=new Item_empty_string("db",NAME_CHAR_LEN)); field->maybe_null=1; @@ -7251,7 +7259,7 @@ struct schema_table_ref ST_FIELD_INFO user_stats_fields_info[]= { - {"USER", USERNAME_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User", SKIP_OPEN_TABLE}, + {"USER", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User", SKIP_OPEN_TABLE}, {"TOTAL_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Total_connections",SKIP_OPEN_TABLE}, {"CONCURRENT_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Concurrent_connections",SKIP_OPEN_TABLE}, {"CONNECTED_TIME", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Connected_time",SKIP_OPEN_TABLE}, @@ -8623,7 +8631,8 @@ ST_FIELD_INFO variables_fields_info[]= ST_FIELD_INFO processlist_fields_info[]= { {"ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, "Id", SKIP_OPEN_TABLE}, - {"USER", 16, MYSQL_TYPE_STRING, 0, 0, "User", SKIP_OPEN_TABLE}, + {"USER", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User", + SKIP_OPEN_TABLE}, {"HOST", LIST_PROCESS_HOST_LEN, MYSQL_TYPE_STRING, 0, 0, "Host", SKIP_OPEN_TABLE}, {"DB", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, "Db", SKIP_OPEN_TABLE}, diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 21748861204..f1cb5e07eca 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -758,7 +758,7 @@ String *copy_if_not_alloced(String *to,String *from,uint32 from_length) { if (from->Alloced_length >= from_length) return from; - if (from->alloced || !to || from == to) + if ((from->alloced && (from->Alloced_length != 0)) || !to || from == to) { (void) from->realloc(from_length); return from; diff --git a/sql/sql_string.h b/sql/sql_string.h index 2a5f43b35f2..4ce08a5d9e6 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -378,6 +378,16 @@ public: } return 0; } + bool append_hex(const char *src, uint32 srclen) + { + for (const char *end= src + srclen ; src != end ; src++) + { + if (append(_dig_vec_lower[((uchar) *src) >> 4]) || + append(_dig_vec_lower[((uchar) *src) & 0x0F])) + return true; + } + return false; + } bool fill(uint32 max_length,char fill); void strip_sp(); friend int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 6bb650f55db..9ce1b863086 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2010, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -53,6 +53,7 @@ #include "sql_parse.h" #include "sql_show.h" #include "transaction.h" +#include "sql_audit.h" #ifdef __WIN__ #include <io.h> @@ -1690,7 +1691,8 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) &syntax_len, TRUE, TRUE, lpt->create_info, - lpt->alter_info))) + lpt->alter_info, + NULL))) { DBUG_RETURN(TRUE); } @@ -1793,7 +1795,8 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) &syntax_len, TRUE, TRUE, lpt->create_info, - lpt->alter_info))) + lpt->alter_info, + NULL))) { error= 1; goto err; @@ -2379,6 +2382,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, PSI_CALL_drop_table_share(table->internal_tmp_table, table->db, table->db_length, table->table_name, table->table_name_length); + mysql_audit_drop_table(thd, table); } DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table, @@ -2786,6 +2790,8 @@ int prepare_create_field(Create_field *sql_field, case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_NULL: sql_field->pack_flag=f_settype((uint) sql_field->sql_type); break; @@ -2804,6 +2810,7 @@ int prepare_create_field(Create_field *sql_field, (sql_field->decimals << FIELDFLAG_DEC_SHIFT)); break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: /* fall-through */ default: sql_field->pack_flag=(FIELDFLAG_NUMBER | @@ -2889,7 +2896,7 @@ void promote_first_timestamp_column(List<Create_field> *column_definitions) while ((column_definition= it++) != NULL) { - if (column_definition->sql_type == MYSQL_TYPE_TIMESTAMP || // TIMESTAMP + if (is_timestamp_type(column_definition->sql_type) || // TIMESTAMP column_definition->unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy { if ((column_definition->flags & NOT_NULL_FLAG) != 0 && // NOT NULL, @@ -3850,7 +3857,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, if (thd->variables.sql_mode & MODE_NO_ZERO_DATE && !sql_field->def && - sql_field->sql_type == MYSQL_TYPE_TIMESTAMP && + is_timestamp_type(sql_field->sql_type) && (sql_field->flags & NOT_NULL_FLAG) && (type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD)) { @@ -4206,7 +4213,8 @@ handler *mysql_create_frm_image(THD *thd, &syntax_len, TRUE, TRUE, create_info, - alter_info))) + alter_info, + NULL))) goto err; part_info->part_info_string= part_syntax_buf; part_info->part_info_len= syntax_len; @@ -4688,6 +4696,8 @@ mysql_rename_table(handlerton *base, const char *old_db, my_error(ER_NOT_SUPPORTED_YET, MYF(0), "ALTER TABLE"); else if (error) my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error); + else if (!(flags & FN_IS_TMP)) + mysql_audit_rename_table(thd, old_db, old_name, new_db, new_name); /* Remove the old table share from the pfs table share array. The new table @@ -5943,7 +5953,8 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, */ if ((def->sql_type == MYSQL_TYPE_DATE || def->sql_type == MYSQL_TYPE_NEWDATE || - def->sql_type == MYSQL_TYPE_DATETIME) && + def->sql_type == MYSQL_TYPE_DATETIME || + def->sql_type == MYSQL_TYPE_DATETIME2) && !alter_info->datetime_field && !(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) && thd->variables.sql_mode & MODE_NO_ZERO_DATE) @@ -6355,6 +6366,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, mysql_ha_rm_tables(thd, table_list); + mysql_audit_alter_table(thd, table_list); + /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ if (alter_info->tablespace_op != NO_TABLESPACE_OP) /* Conditionally writes to binlog. */ @@ -7579,6 +7592,7 @@ err: t_type= MYSQL_TIMESTAMP_DATE; break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: f_val= "0000-00-00 00:00:00"; t_type= MYSQL_TIMESTAMP_DATETIME; break; diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 5b3286d77b0..93b35b4918f 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -629,7 +629,7 @@ Max used alarms: %u\n\ Next alarm time: %lu\n", alarm_info.active_alarms, alarm_info.max_used_alarms, - alarm_info.next_alarm_time); + (ulong)alarm_info.next_alarm_time); #endif display_table_locks(); fflush(stdout); diff --git a/sql/sql_time.cc b/sql/sql_time.cc index 89c2e3b7086..ff2ec62f815 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -214,6 +214,22 @@ ulong convert_month_to_period(ulong month) } +bool +check_date_with_warn(const MYSQL_TIME *ltime, ulonglong fuzzy_date, + timestamp_type ts_type) +{ + int unused; + if (check_date(ltime, fuzzy_date, &unused)) + { + ErrConvTime str(ltime); + make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + &str, ts_type, 0); + return true; + } + return false; +} + + /* Convert a string to 8-bit representation, for use in str_to_time/str_to_date/str_to_date. @@ -249,9 +265,9 @@ to_ascii(CHARSET_INFO *cs, /* Character set-aware version of str_to_time() */ -timestamp_type +bool str_to_time(CHARSET_INFO *cs, const char *str,uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate, int *warning) + MYSQL_TIME *l_time, ulonglong fuzzydate, MYSQL_TIME_STATUS *status) { char cnv[32]; if ((cs->state & MY_CS_NONASCII) != 0) @@ -259,14 +275,14 @@ str_to_time(CHARSET_INFO *cs, const char *str,uint length, length= to_ascii(cs, str, length, cnv, sizeof(cnv)); str= cnv; } - return str_to_time(str, length, l_time, fuzzydate, warning); + return str_to_time(str, length, l_time, fuzzydate, status); } /* Character set-aware version of str_to_datetime() */ -timestamp_type str_to_datetime(CHARSET_INFO *cs, - const char *str, uint length, - MYSQL_TIME *l_time, ulonglong flags, int *was_cut) +bool str_to_datetime(CHARSET_INFO *cs, const char *str, uint length, + MYSQL_TIME *l_time, ulonglong flags, + MYSQL_TIME_STATUS *status) { char cnv[32]; if ((cs->state & MY_CS_NONASCII) != 0) @@ -274,7 +290,7 @@ timestamp_type str_to_datetime(CHARSET_INFO *cs, length= to_ascii(cs, str, length, cnv, sizeof(cnv)); str= cnv; } - return str_to_datetime(str, length, l_time, flags, was_cut); + return str_to_datetime(str, length, l_time, flags, status); } @@ -286,26 +302,24 @@ timestamp_type str_to_datetime(CHARSET_INFO *cs, See description of str_to_datetime() for more information. */ -timestamp_type +bool str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, uint length, MYSQL_TIME *l_time, ulonglong flags) { - int was_cut; + MYSQL_TIME_STATUS status; THD *thd= current_thd; - timestamp_type ts_type; - - ts_type= str_to_datetime(cs, str, length, l_time, + bool ret_val= str_to_datetime(cs, str, length, l_time, (flags | (sql_mode_for_dates(thd))), - &was_cut); - if (was_cut || ts_type <= MYSQL_TIMESTAMP_ERROR) + &status); + if (ret_val || status.warnings) make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, str, length, flags & TIME_TIME_ONLY ? - MYSQL_TIMESTAMP_TIME : ts_type, NullS); + MYSQL_TIMESTAMP_TIME : l_time->time_type, NullS); DBUG_EXECUTE_IF("str_to_datetime_warn", push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_YES, str);); - return ts_type; + return ret_val; } @@ -1055,7 +1069,7 @@ calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *s */ -int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b) +int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b) { ulonglong a_t= pack_time(a); ulonglong b_t= pack_time(b); diff --git a/sql/sql_time.h b/sql/sql_time.h index c1a75bb2ad3..cf029f143b3 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -35,11 +35,9 @@ ulong convert_period_to_month(ulong period); ulong convert_month_to_period(ulong month); bool get_date_from_daynr(long daynr,uint *year, uint *month, uint *day); my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code); -bool str_to_time_with_warn(CHARSET_INFO *cs, const char *str, uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate); -timestamp_type str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, - uint length, MYSQL_TIME *l_time, - ulonglong flags); +bool str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, + uint length, MYSQL_TIME *l_time, + ulonglong flags); bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *name); @@ -76,7 +74,7 @@ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, INTERVAL interval); bool calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *seconds_out, long *microseconds_out); -int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b); +int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b); void localtime_to_TIME(MYSQL_TIME *to, struct tm *from); void calc_time_from_sec(MYSQL_TIME *to, long seconds, long microseconds); uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year); @@ -86,12 +84,14 @@ bool parse_date_time_format(timestamp_type format_type, const char *format, uint format_length, DATE_TIME_FORMAT *date_time_format); /* Character set-aware version of str_to_time() */ -timestamp_type str_to_time(CHARSET_INFO *cs, const char *str,uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate, int *warning); +bool str_to_time(CHARSET_INFO *cs, const char *str,uint length, + MYSQL_TIME *l_time, ulonglong fuzzydate, + MYSQL_TIME_STATUS *status); /* Character set-aware version of str_to_datetime() */ -timestamp_type str_to_datetime(CHARSET_INFO *cs, - const char *str, uint length, - MYSQL_TIME *l_time, ulonglong flags, int *was_cut); +bool str_to_datetime(CHARSET_INFO *cs, + const char *str, uint length, + MYSQL_TIME *l_time, ulonglong flags, + MYSQL_TIME_STATUS *status); /* convenience wrapper */ inline bool parse_date_time_format(timestamp_type format_type, @@ -110,4 +110,18 @@ extern DATE_TIME_FORMAT global_time_format; extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[]; extern LEX_STRING interval_type_to_name[]; + +static inline bool +non_zero_date(const MYSQL_TIME *ltime) +{ + return ltime->year || ltime->month || ltime->day; +} +static inline bool +check_date(const MYSQL_TIME *ltime, ulonglong flags, int *was_cut) +{ + return check_date(ltime, non_zero_date(ltime), flags, was_cut); +} +bool check_date_with_warn(const MYSQL_TIME *ltime, ulonglong fuzzy_date, + timestamp_type ts_type); + #endif /* SQL_TIME_INCLUDED */ diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 5b5bd0ed2fc..63df46c7094 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1152,9 +1152,10 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, TODO: when VIEWs will be stored in cache, table mem_root should be used here */ - if (parser->parse((uchar*)table, thd->mem_root, view_parameters, - required_view_parameters, &file_parser_dummy_hook)) - goto err; + if ((result= parser->parse((uchar*)table, thd->mem_root, + view_parameters, required_view_parameters, + &file_parser_dummy_hook))) + goto end; /* check old format view .frm @@ -1217,6 +1218,11 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, now Lex placed in statement memory */ table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local; + if (!table->view) + { + result= true; + goto end; + } { char old_db_buf[SAFE_NAME_LEN+1]; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6be7c23fb0d..0dc8ed7037e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1,6 +1,6 @@ /* - Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2010, 2011 Monty Program Ab + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2011, Monty Program 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 @@ -32,6 +32,7 @@ #define YYTHD ((THD *)yythd) #define YYLIP (& YYTHD->m_parser_state->m_lip) #define YYPS (& YYTHD->m_parser_state->m_yacc) +#define YYCSCL YYTHD->variables.character_set_client #define MYSQL_YACC #define YYINITDEPTH 100 @@ -899,10 +900,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %pure_parser /* We have threads */ /* - Currently there are 170 shift/reduce conflicts. + Currently there are 167 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 170 +%expect 167 /* Comments for TOKENS. @@ -1133,6 +1134,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token HAVING /* SQL-2003-R */ %token HELP_SYM %token HEX_NUM +%token HEX_STRING %token HIGH_PRIORITY %token HOST_SYM %token HOSTS_SYM @@ -1566,7 +1568,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %left INTERVAL_SYM %type <lex_str> - IDENT IDENT_QUOTED TEXT_STRING DECIMAL_NUM FLOAT_NUM NUM LONG_NUM HEX_NUM + IDENT IDENT_QUOTED TEXT_STRING DECIMAL_NUM FLOAT_NUM NUM LONG_NUM + HEX_NUM HEX_STRING hex_num_or_string LEX_HOSTNAME ULONGLONG_NUM field_ident select_alias ident ident_or_text IDENT_sys TEXT_STRING_sys TEXT_STRING_literal NCHAR_STRING opt_component key_cache_name @@ -1626,7 +1629,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); replace_lock_option opt_low_priority insert_lock_option load_data_lock %type <item> - literal text_literal insert_ident order_ident + literal text_literal insert_ident order_ident temporal_literal simple_ident expr opt_expr opt_else sum_expr in_sum_expr variable variable_aux bool_pri predicate bit_expr @@ -4591,7 +4594,7 @@ partition: ; part_type_def: - opt_linear KEY_SYM '(' part_field_list ')' + opt_linear KEY_SYM opt_key_algo '(' part_field_list ')' { partition_info *part_info= Lex->part_info; part_info->list_of_part_fields= TRUE; @@ -4617,6 +4620,25 @@ opt_linear: { Lex->part_info->linear_hash_ind= TRUE;} ; +opt_key_algo: + /* empty */ + { Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_NONE;} + | ALGORITHM_SYM EQ real_ulong_num + { + switch ($3) { + case 1: + Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_51; + break; + case 2: + Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_55; + break; + default: + my_parse_error(ER(ER_SYNTAX_ERROR)); + MYSQL_YYABORT; + } + } + ; + part_field_list: /* empty */ {} | part_field_item_list {} @@ -4698,7 +4720,7 @@ opt_sub_part: | SUBPARTITION_SYM BY opt_linear HASH_SYM sub_part_func { Lex->part_info->subpart_type= HASH_PARTITION; } opt_num_subparts {} - | SUBPARTITION_SYM BY opt_linear KEY_SYM + | SUBPARTITION_SYM BY opt_linear KEY_SYM opt_key_algo '(' sub_part_field_list ')' { partition_info *part_info= Lex->part_info; @@ -6159,6 +6181,11 @@ now_or_signed_literal: { $$=$1; } ; +hex_num_or_string: + HEX_NUM {} + | HEX_STRING {} + ; + charset: CHAR_SYM SET {} | CHARSET {} @@ -8715,7 +8742,48 @@ simple_expr: MYSQL_YYABORT; } | '{' ident expr '}' - { $$= $3; } + { + Item_string *item; + $$= NULL; + /* + If "expr" is reasonably short pure ASCII string literal, + try to parse known ODBC style date, time or timestamp literals, + e.g: + SELECT {d'2001-01-01'}; + SELECT {t'10:20:30'}; + SELECT {ts'2001-01-01 10:20:30'}; + */ + if ($3->type() == Item::STRING_ITEM && + (item= (Item_string *) $3) && + item->collation.repertoire == MY_REPERTOIRE_ASCII && + item->str_value.length() < MAX_DATE_STRING_REP_LENGTH * 4) + { + enum_field_types type= MYSQL_TYPE_STRING; + LEX_STRING *ls= &$2; + if (ls->length == 1) + { + if (ls->str[0] == 'd') /* {d'2001-01-01'} */ + type= MYSQL_TYPE_DATE; + else if (ls->str[0] == 't') /* {t'10:20:30'} */ + type= MYSQL_TYPE_TIME; + } + else if (ls->length == 2) /* {ts'2001-01-01 10:20:30'} */ + { + if (ls->str[0] == 't' && ls->str[1] == 's') + type= MYSQL_TYPE_DATETIME; + } + if (type != MYSQL_TYPE_STRING) + { + $$= create_temporal_literal(YYTHD, + item->str_value.ptr(), + item->str_value.length(), + item->str_value.charset(), + type, false); + } + } + if ($$ == NULL) + $$= $3; + } | MATCH ident_list_arg AGAINST '(' bit_expr fulltext_options ')' { $2->push_front($5); @@ -12638,7 +12706,7 @@ text_string: } | HEX_NUM { - Item *tmp= new (YYTHD->mem_root) Item_hex_string($1.str, $1.length); + Item *tmp= new (YYTHD->mem_root) Item_hex_hybrid($1.str, $1.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -12648,6 +12716,14 @@ text_string: tmp->quick_fix_field(); $$= tmp->val_str((String*) 0); } + | HEX_STRING + { + Item *tmp= new (YYTHD->mem_root) Item_hex_string($1.str, $1.length); + if (tmp == NULL) + MYSQL_YYABORT; + tmp->quick_fix_field(); + $$= tmp->val_str((String*) 0); + } | BIN_NUM { Item *tmp= new (YYTHD->mem_root) Item_bin_string($1.str, $1.length); @@ -12696,6 +12772,7 @@ signed_literal: literal: text_literal { $$ = $1; } | NUM_literal { $$ = $1; } + | temporal_literal { $$= $1; } | NULL_SYM { $$ = new (YYTHD->mem_root) Item_null(); @@ -12717,6 +12794,12 @@ literal: } | HEX_NUM { + $$ = new (YYTHD->mem_root) Item_hex_hybrid($1.str, $1.length); + if ($$ == NULL) + MYSQL_YYABORT; + } + | HEX_STRING + { $$ = new (YYTHD->mem_root) Item_hex_string($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; @@ -12727,7 +12810,7 @@ literal: if ($$ == NULL) MYSQL_YYABORT; } - | UNDERSCORE_CHARSET HEX_NUM + | UNDERSCORE_CHARSET hex_num_or_string { Item *tmp= new (YYTHD->mem_root) Item_hex_string($2.str, $2.length); if (tmp == NULL) @@ -12784,9 +12867,6 @@ literal: $$= item_str; } - | DATE_SYM text_literal { $$ = $2; } - | TIME_SYM text_literal { $$ = $2; } - | TIMESTAMP text_literal { $$ = $2; } ; NUM_literal: @@ -12835,6 +12915,31 @@ NUM_literal: } ; + +temporal_literal: + DATE_SYM TEXT_STRING + { + if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_DATE, true))) + MYSQL_YYABORT; + } + | TIME_SYM TEXT_STRING + { + if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_TIME, true))) + MYSQL_YYABORT; + } + | TIMESTAMP TEXT_STRING + { + if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_DATETIME, true))) + MYSQL_YYABORT; + } + ; + + + + /********************************************************************** ** Creating different items. **********************************************************************/ @@ -13283,7 +13388,7 @@ user: $$->auth= empty_lex_str; if (check_string_char_length(&$$->user, ER(ER_USERNAME), - USERNAME_CHAR_LENGTH, + username_char_length, system_charset_info, 0)) MYSQL_YYABORT; } @@ -13298,7 +13403,7 @@ user: $$->auth= empty_lex_str; if (check_string_char_length(&$$->user, ER(ER_USERNAME), - USERNAME_CHAR_LENGTH, + username_char_length, system_charset_info, 0) || check_host_name(&$$->host)) MYSQL_YYABORT; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 9ca504fd275..565b5cafe3e 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -31,7 +31,7 @@ (for example in storage/myisam/ha_myisam.cc) ! */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_priv.h" #include "sql_class.h" // set_var.h: THD #include "sys_vars.h" @@ -1392,6 +1392,12 @@ Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, LEX_STRING *base) String str(buf, sizeof(buf), system_charset_info); char *p; + if (!rpl_global_gtid_slave_state.loaded) + { + my_error(ER_CANNOT_LOAD_SLAVE_GTID_STATE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + return NULL; + } str.length(0); if ((opt_bin_log && mysql_bin_log.append_state_pos(&str)) || !(p= thd->strmake(str.ptr(), str.length()))) @@ -1438,6 +1444,14 @@ Sys_var_gtid_slave_pos::do_check(THD *thd, set_var *var) bool running; DBUG_ASSERT(var->type == OPT_GLOBAL); + + if (!rpl_global_gtid_slave_state.loaded) + { + my_error(ER_CANNOT_LOAD_SLAVE_GTID_STATE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + return true; + } + mysql_mutex_lock(&LOCK_active_mi); running= master_info_index->give_error_if_slave_running(); mysql_mutex_unlock(&LOCK_active_mi); @@ -1496,6 +1510,13 @@ Sys_var_gtid_slave_pos::global_value_ptr(THD *thd, LEX_STRING *base) String str; char *p; + if (!rpl_global_gtid_slave_state.loaded) + { + my_error(ER_CANNOT_LOAD_SLAVE_GTID_STATE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + return NULL; + } + str.length(0); if (rpl_append_gtid_state(&str, false) || !(p= thd->strmake(str.ptr(), str.length()))) diff --git a/sql/table.cc b/sql/table.cc index 2f867eb7dc4..32a841ffd46 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3281,7 +3281,7 @@ bool get_field(MEM_ROOT *mem, Field *field, String *res) } if (!(to= strmake_root(mem, str.ptr(), length))) length= 0; // Safety fix - res->set(to, length, ((Field_str*)field)->charset()); + res->set(to, length, field->charset()); return 0; } diff --git a/sql/threadpool_unix.cc b/sql/threadpool_unix.cc index 69df00a2c9a..658a04d877e 100644 --- a/sql/threadpool_unix.cc +++ b/sql/threadpool_unix.cc @@ -286,6 +286,20 @@ static void *native_event_get_userdata(native_event *event) } #elif defined(HAVE_KQUEUE) + +/* + NetBSD is incompatible with other BSDs , last parameter in EV_SET macro + (udata, user data) needs to be intptr_t, whereas it needs to be void* + everywhere else. +*/ + +#ifdef __NetBSD__ +#define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, (intptr_t)g) +#else +#define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, g) +#endif + + int io_poll_create() { return kqueue(); @@ -294,7 +308,7 @@ int io_poll_create() int io_poll_start_read(int pollfd, int fd, void *data) { struct kevent ke; - EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT, + MY_EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT, 0, 0, data); return kevent(pollfd, &ke, 1, 0, 0, 0); } @@ -303,7 +317,7 @@ int io_poll_start_read(int pollfd, int fd, void *data) int io_poll_associate_fd(int pollfd, int fd, void *data) { struct kevent ke; - EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT, + MY_EV_SET(&ke, fd, EVFILT_READ, EV_ADD|EV_ONESHOT, 0, 0, data); return io_poll_start_read(pollfd,fd, data); } @@ -312,7 +326,7 @@ int io_poll_associate_fd(int pollfd, int fd, void *data) int io_poll_disassociate_fd(int pollfd, int fd) { struct kevent ke; - EV_SET(&ke,fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + MY_EV_SET(&ke,fd, EVFILT_READ, EV_DELETE, 0, 0, 0); return kevent(pollfd, &ke, 1, 0, 0, 0); } @@ -337,7 +351,7 @@ int io_poll_wait(int pollfd, struct kevent *events, int maxevents, int timeout_m static void* native_event_get_userdata(native_event *event) { - return event->udata; + return (void *)event->udata; } #elif defined (__sun) diff --git a/sql/tztime.cc b/sql/tztime.cc index 5e1e8bec7b3..f5e9182522e 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -2559,7 +2559,7 @@ main(int argc, char **argv) if (argc == 2) { - root_name_end= strmake(fullname, argv[1], FN_REFLEN); + root_name_end= strmake_buf(fullname, argv[1]); printf("TRUNCATE TABLE time_zone;\n"); printf("TRUNCATE TABLE time_zone_name;\n"); @@ -2713,7 +2713,7 @@ main(int argc, char **argv) (int)t, (int)t1); /* Let us load time zone description */ - str_end= strmake(fullname, TZDIR, FN_REFLEN); + str_end= strmake_buf(fullname, TZDIR); strmake(str_end, "/MET", FN_REFLEN - (str_end - fullname)); if (tz_load(fullname, &tz_info, &tz_storage)) |