From 9aefc403f9f707edf0f84ce401d32929067aea30 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 16 Oct 2004 00:12:59 +0400 Subject: [PATCH 1/3] A fix and test case for Bug#6049 "Loss of sign when using prepared statements and negative time/date values". The bug was in wrong sprintf format used in the client library. The fix moves TIME -> string conversion functions to sql-common and utilized them in the client library. include/my_time.h: Declarations for new functions shared between the client and server. libmysql/libmysql.c: Fix for Bug#6049 "Loss of sign when using prepared statements and negative time/date values": use the same function as the server to convert date/time/datetime values to strings. sql-common/my_time.c: Implementation of my_{time,datetime,date,TIME}_to_str: it's needed by the client library, so it should be shared. sql/field.cc: Don't create String object if it's not needed. sql/item.cc: Don't create String object if it's not needed: TIME_to_string was moved to my_TIME_to_str, with different arguments. sql/item_timefunc.cc: Don't create String object if it's not needed. sql/mysql_priv.h: TIME_to_string and MAX_DATE_REP_LENGTH moved to the client library. MAX_DATE_REP_LENGTH was renamed to MAX_DATE_STRING_REP_LENGTH to not conflict with the same name in libmysql.c sql/protocol.cc: Don't create String object if it's not needed. sql/time.cc: Implementation of my_{time,date,datetime,TIME}_to_str moved to my_time.c shared between the client and the server. tests/client_test.c: A test case for Bug#6049. --- include/my_time.h | 14 +++++++++ libmysql/libmysql.c | 24 ++------------- sql-common/my_time.c | 72 ++++++++++++++++++++++++++++++++++++++++++++ sql/field.cc | 8 ++--- sql/item.cc | 14 +++------ sql/item_timefunc.cc | 11 +++---- sql/mysql_priv.h | 9 ------ sql/protocol.cc | 7 ++--- sql/time.cc | 59 ++---------------------------------- tests/client_test.c | 47 +++++++++++++++++++++++++++++ 10 files changed, 153 insertions(+), 112 deletions(-) diff --git a/include/my_time.h b/include/my_time.h index d4dbe459c3b..dab17904b2d 100644 --- a/include/my_time.h +++ b/include/my_time.h @@ -60,6 +60,20 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap) void set_zero_time(MYSQL_TIME *tm); +/* + Required buffer length for my_time_to_str, my_date_to_str, + my_datetime_to_str and TIME_to_string functions. Note, that the + caller is still responsible to check that given TIME structure + has values in valid ranges, otherwise size of the buffer could + be not enough. +*/ +#define MAX_DATE_STRING_REP_LENGTH 30 + +int my_time_to_str(const MYSQL_TIME *l_time, char *to); +int my_date_to_str(const MYSQL_TIME *l_time, char *to); +int my_datetime_to_str(const MYSQL_TIME *l_time, char *to); +int my_TIME_to_str(const MYSQL_TIME *l_time, char *to); + C_MODE_END #endif /* _my_time_h_ */ diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index ef926e2f93d..fe008d24e63 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -3556,28 +3556,8 @@ static void fetch_datetime_with_conversion(MYSQL_BIND *param, Convert time value to string and delegate the rest to fetch_string_with_conversion: */ - char buff[25]; - uint length; - - switch (time->time_type) { - case MYSQL_TIMESTAMP_DATE: - length= my_sprintf(buff,(buff, "%04d-%02d-%02d", - time->year, time->month, time->day)); - break; - case MYSQL_TIMESTAMP_DATETIME: - length= my_sprintf(buff,(buff, "%04d-%02d-%02d %02d:%02d:%02d", - time->year, time->month, time->day, - time->hour, time->minute, time->second)); - break; - case MYSQL_TIMESTAMP_TIME: - length= my_sprintf(buff, (buff, "%02d:%02d:%02d", - time->hour, time->minute, time->second)); - break; - default: - length= 0; - buff[0]='\0'; - break; - } + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= my_TIME_to_str(time, buff); /* Resort to string conversion */ fetch_string_with_conversion(param, (char *)buff, length); break; diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 4b5daf53bea..93549f7340c 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -726,3 +726,75 @@ void set_zero_time(MYSQL_TIME *tm) tm->time_type= MYSQL_TIMESTAMP_NONE; } + +/* + Functions to convert time/date/datetime value to a string, + using default format. + This functions don't check that given TIME structure members are + in valid range. If they are not, return value won't reflect any + valid date either. Additionally, make_time doesn't take into + account time->day member: it's assumed that days have been converted + to hours already. + + RETURN + number of characters written to 'to' +*/ + +int my_time_to_str(const MYSQL_TIME *l_time, char *to) +{ + return my_sprintf(to, (to, "%s%02d:%02d:%02d", + (l_time->neg ? "-" : ""), + l_time->hour, + l_time->minute, + l_time->second)); +} + +int my_date_to_str(const MYSQL_TIME *l_time, char *to) +{ + return my_sprintf(to, (to, "%04d-%02d-%02d", + l_time->year, + l_time->month, + l_time->day)); +} + +int my_datetime_to_str(const MYSQL_TIME *l_time, char *to) +{ + return my_sprintf(to, (to, "%04d-%02d-%02d %02d:%02d:%02d", + l_time->year, + l_time->month, + l_time->day, + l_time->hour, + l_time->minute, + l_time->second)); +} + + +/* + Convert struct DATE/TIME/DATETIME value to string using built-in + MySQL time conversion formats. + + SYNOPSIS + my_TIME_to_string() + + NOTE + The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved. +*/ + +int my_TIME_to_str(const MYSQL_TIME *l_time, char *to) +{ + switch (l_time->time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return my_datetime_to_str(l_time, to); + case MYSQL_TIMESTAMP_DATE: + return my_date_to_str(l_time, to); + case MYSQL_TIMESTAMP_TIME: + return my_time_to_str(l_time, to); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + to[0]='\0'; + return 0; + default: + DBUG_ASSERT(0); + return 0; + } +} diff --git a/sql/field.cc b/sql/field.cc index 3dc1375dff3..c98276b961d 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -455,11 +455,9 @@ bool Field::get_time(TIME *ltime) void Field::store_time(TIME *ltime,timestamp_type type) { - char buff[MAX_DATE_REP_LENGTH]; - String tmp; - tmp.set(buff, sizeof(buff), &my_charset_bin); - TIME_to_string(ltime, &tmp); - store(buff, tmp.length(), &my_charset_bin); + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= (uint) my_TIME_to_str(ltime, buff); + store(buff, length, &my_charset_bin); } diff --git a/sql/item.cc b/sql/item.cc index 0366ea29485..cff7b739893 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -988,9 +988,10 @@ String *Item_param::val_str(String* str) return str; case TIME_VALUE: { - if (str->reserve(MAX_DATE_REP_LENGTH)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH)) break; - TIME_to_string(&value.time, str); + str->length((uint) my_TIME_to_str(&value.time, (char*) str->ptr())); + str->set_charset(&my_charset_bin); return str; } case NULL_VALUE: @@ -1020,24 +1021,19 @@ const String *Item_param::query_val_str(String* str) const case TIME_VALUE: { char *buf, *ptr; - String tmp; str->length(0); /* TODO: in case of error we need to notify replication that binary log contains wrong statement */ - if (str->reserve(MAX_DATE_REP_LENGTH+3)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3)) break; /* Create date string inplace */ buf= str->c_ptr_quick(); ptr= buf; *ptr++= '\''; - tmp.set(ptr, MAX_DATE_REP_LENGTH, &my_charset_bin); - tmp.length(0); - TIME_to_string(&value.time, &tmp); - - ptr+= tmp.length(); + ptr+= (uint) my_TIME_to_str(&value.time, ptr); *ptr++= '\''; str->length((uint32) (ptr - buf)); break; diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 863b041044e..48c1f2c5443 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -1292,14 +1292,13 @@ String *Item_func_curtime::val_str(String *str) void Item_func_curtime::fix_length_and_dec() { TIME ltime; - String tmp((char*) buff,sizeof(buff), &my_charset_bin); decimals=0; collation.set(&my_charset_bin); store_now_in_TIME(<ime); value= TIME_to_ulonglong_time(<ime); - make_time((DATE_TIME_FORMAT *) 0, <ime, &tmp); - max_length= buff_length= tmp.length(); + buff_length= (uint) my_time_to_str(<ime, buff); + max_length= buff_length; } @@ -1341,16 +1340,14 @@ String *Item_func_now::val_str(String *str) void Item_func_now::fix_length_and_dec() { - String tmp((char*) buff,sizeof(buff),&my_charset_bin); - decimals=0; collation.set(&my_charset_bin); store_now_in_TIME(<ime); value= (longlong) TIME_to_ulonglong_datetime(<ime); - make_datetime((DATE_TIME_FORMAT *) 0, <ime, &tmp); - max_length= buff_length= tmp.length(); + buff_length= (uint) my_datetime_to_str(<ime, buff); + max_length= buff_length; } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 1a0879c6347..15bdfdaacfc 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -284,14 +284,6 @@ void debug_sync_point(const char* lock_name, uint lock_timeout); #define WEEK_MONDAY_FIRST 1 #define WEEK_YEAR 2 #define WEEK_FIRST_WEEKDAY 4 -/* - Required buffer length for make_date, make_time, make_datetime - and TIME_to_string functions. Note, that the caller is still - responsible to check that given TIME structure has values - in valid ranges, otherwise size of the buffer could be not - enough. -*/ -#define MAX_DATE_REP_LENGTH 30 enum enum_parsing_place { @@ -1046,7 +1038,6 @@ void make_date(const DATE_TIME_FORMAT *format, const TIME *l_time, String *str); void make_time(const DATE_TIME_FORMAT *format, const TIME *l_time, String *str); -void TIME_to_string(const TIME *time, String *str); ulonglong TIME_to_ulonglong_datetime(const TIME *time); ulonglong TIME_to_ulonglong_date(const TIME *time); ulonglong TIME_to_ulonglong_time(const TIME *time); diff --git a/sql/protocol.cc b/sql/protocol.cc index c2d9117b062..060dc14be10 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -880,10 +880,9 @@ bool Protocol_simple::store_date(TIME *tm) field_types[field_pos] == MYSQL_TYPE_DATE); field_pos++; #endif - char buff[40]; - String tmp((char*) buff,sizeof(buff),&my_charset_bin); - make_date((DATE_TIME_FORMAT *) 0, tm, &tmp); - return net_store_data((char*) tmp.ptr(), tmp.length()); + char buff[MAX_DATE_STRING_REP_LENGTH]; + int length= my_date_to_str(tm, buff); + return net_store_data(buff, (uint) length); } diff --git a/sql/time.cc b/sql/time.cc index 4421b6aa00f..e76b169b336 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -747,13 +747,7 @@ const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format, void make_time(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%s%02d:%02d:%02d", - (l_time->neg ? "-" : ""), - l_time->hour, - l_time->minute, - l_time->second)); + uint length= (uint) my_time_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -762,12 +756,7 @@ void make_time(const DATE_TIME_FORMAT *format __attribute__((unused)), void make_date(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%04d-%02d-%02d", - l_time->year, - l_time->month, - l_time->day)); + uint length= (uint) my_date_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -776,15 +765,7 @@ void make_date(const DATE_TIME_FORMAT *format __attribute__((unused)), void make_datetime(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%04d-%02d-%02d %02d:%02d:%02d", - l_time->year, - l_time->month, - l_time->day, - l_time->hour, - l_time->minute, - l_time->second)); + uint length= (uint) my_datetime_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -894,38 +875,4 @@ ulonglong TIME_to_ulonglong(const TIME *time) return 0; } - -/* - Convert struct DATE/TIME/DATETIME value to string using built-in - MySQL time conversion formats. - - SYNOPSIS - TIME_to_string() - - NOTE - The string must have at least MAX_DATE_REP_LENGTH bytes reserved. -*/ - -void TIME_to_string(const TIME *time, String *str) -{ - switch (time->time_type) { - case MYSQL_TIMESTAMP_DATETIME: - make_datetime((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_DATE: - make_date((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_TIME: - make_time((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_NONE: - case MYSQL_TIMESTAMP_ERROR: - str->length(0); - str->set_charset(&my_charset_bin); - break; - default: - DBUG_ASSERT(0); - } -} - #endif diff --git a/tests/client_test.c b/tests/client_test.c index 0b30cc3386d..5cafd2e033e 100644 --- a/tests/client_test.c +++ b/tests/client_test.c @@ -10541,6 +10541,52 @@ static void test_bug5315() } +static void test_bug6049() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[1]; + MYSQL_RES *res; + MYSQL_ROW row; + const char *stmt_text; + char *buffer[30]; + ulong length; + int rc; + + myheader("test_bug6049"); + + stmt_text= "SELECT MAKETIME(-25, 12, 12)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bzero(bind, sizeof(bind)); + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = &buffer; + bind[0].buffer_length = sizeof(buffer); + bind[0].length = &length; + + mysql_stmt_bind_result(stmt, bind); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc == 0); + + printf("Result from query: %s\n", row[0]); + printf("Result from prepared statement: %s\n", buffer); + + DIE_UNLESS(strcmp(row[0], buffer) == 0); + + mysql_free_result(res); + mysql_stmt_close(stmt); +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -10851,6 +10897,7 @@ int main(int argc, char **argv) test_bug5194(); /* bulk inserts in prepared mode */ test_bug5315(); /* check that mysql_change_user closes all prepared statements */ + test_bug6049(); /* check support for negative TIME values */ /* XXX: PLEASE RUN THIS PROGRAM UNDER VALGRIND AND VERIFY THAT YOUR TEST DOESN'T CONTAIN WARNINGS/ERRORS BEFORE YOU PUSH. From 8fe8912f4c1d01b9bb8c5c1aefe4ac6405a87976 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Oct 2004 16:43:36 +0400 Subject: [PATCH 2/3] A fix and test case for bug#6058 "Prepared statements return '0000-00-00' (date) as empty string": preserve time type (date, time, or datetime) for zero dates, times, and datetimes. libmysql/libmysql.c: A fix for bug#6058 "Prepared statements return '0000-00-00' (date) as empty string": preserve time type (date, time, or datetime) for zero dates, times, and datetimes. tests/client_test.c: A test case for Bug#6058, the existing tests required some adjustments too. --- libmysql/libmysql.c | 6 ++--- tests/client_test.c | 56 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index fe008d24e63..249e23a8300 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -3221,12 +3221,12 @@ static void read_binary_time(MYSQL_TIME *tm, uchar **pos) tm->second_part= (length > 8) ? (ulong) sint4korr(to+8) : 0; tm->year= tm->month= 0; - tm->time_type= MYSQL_TIMESTAMP_TIME; *pos+= length; } else set_zero_time(tm); + tm->time_type= MYSQL_TIMESTAMP_TIME; } static void read_binary_datetime(MYSQL_TIME *tm, uchar **pos) @@ -3251,12 +3251,12 @@ static void read_binary_datetime(MYSQL_TIME *tm, uchar **pos) else tm->hour= tm->minute= tm->second= 0; tm->second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0; - tm->time_type= MYSQL_TIMESTAMP_DATETIME; *pos+= length; } else set_zero_time(tm); + tm->time_type= MYSQL_TIMESTAMP_DATETIME; } static void read_binary_date(MYSQL_TIME *tm, uchar **pos) @@ -3273,12 +3273,12 @@ static void read_binary_date(MYSQL_TIME *tm, uchar **pos) tm->hour= tm->minute= tm->second= 0; tm->second_part= 0; tm->neg= 0; - tm->time_type= MYSQL_TIMESTAMP_DATE; *pos+= length; } else set_zero_time(tm); + tm->time_type= MYSQL_TIMESTAMP_DATE; } diff --git a/tests/client_test.c b/tests/client_test.c index 5cafd2e033e..587515d0ba8 100644 --- a/tests/client_test.c +++ b/tests/client_test.c @@ -3439,7 +3439,7 @@ static void test_fetch_date() MYSQL_STMT *stmt; uint i; int rc, year; - char date[25], time[25], ts[25], ts_4[15], ts_6[20], dt[20]; + char date[25], time[25], ts[25], ts_4[25], ts_6[20], dt[20]; ulong d_length, t_length, ts_length, ts4_length, ts6_length, dt_length, y_length; MYSQL_BIND bind[8]; @@ -3549,8 +3549,8 @@ static void test_fetch_date() DIE_UNLESS(strcmp(dt, "2010-07-10 00:00:00") == 0); DIE_UNLESS(dt_length == 19); - DIE_UNLESS(ts_4[0] == '\0'); - DIE_UNLESS(ts4_length == 0); + DIE_UNLESS(strcmp(ts_4, "0000-00-00 00:00:00") == 0); + DIE_UNLESS(ts4_length == strlen("0000-00-00 00:00:00")); DIE_UNLESS(strcmp(ts_6, "1999-12-29 00:00:00") == 0); DIE_UNLESS(ts6_length == 19); @@ -10548,7 +10548,7 @@ static void test_bug6049() MYSQL_RES *res; MYSQL_ROW row; const char *stmt_text; - char *buffer[30]; + char buffer[30]; ulong length; int rc; @@ -10577,6 +10577,52 @@ static void test_bug6049() rc= mysql_stmt_fetch(stmt); DIE_UNLESS(rc == 0); + printf("Result from query: %s\n", row[0]); + printf("Result from prepared statement: %s\n", (char*) buffer); + + DIE_UNLESS(strcmp(row[0], (char*) buffer) == 0); + + mysql_free_result(res); + mysql_stmt_close(stmt); +} + + +static void test_bug6058() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[1]; + MYSQL_RES *res; + MYSQL_ROW row; + const char *stmt_text; + char buffer[30]; + ulong length; + int rc; + + myheader("test_bug6058"); + + stmt_text= "SELECT CAST('0000-00-00' AS DATE)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bzero(bind, sizeof(bind)); + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = &buffer; + bind[0].buffer_length = sizeof(buffer); + bind[0].length = &length; + + mysql_stmt_bind_result(stmt, bind); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc == 0); + printf("Result from query: %s\n", row[0]); printf("Result from prepared statement: %s\n", buffer); @@ -10587,6 +10633,7 @@ static void test_bug6049() } + /* Read and parse arguments and MySQL options from my.cnf */ @@ -10898,6 +10945,7 @@ int main(int argc, char **argv) test_bug5315(); /* check that mysql_change_user closes all prepared statements */ test_bug6049(); /* check support for negative TIME values */ + test_bug6058(); /* check support for 0000-00-00 dates */ /* XXX: PLEASE RUN THIS PROGRAM UNDER VALGRIND AND VERIFY THAT YOUR TEST DOESN'T CONTAIN WARNINGS/ERRORS BEFORE YOU PUSH. From 59cbec3d4e181d675a8d38eedf8f2aa1868d830d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Oct 2004 15:14:50 +0200 Subject: [PATCH 3/3] NdbOperationSearch.cpp: fix C coding error found by tomas on sol9x86 ndb/src/ndbapi/NdbOperationSearch.cpp: fix C coding error found by tomas on sol9x86 --- ndb/src/ndbapi/NdbOperationSearch.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndb/src/ndbapi/NdbOperationSearch.cpp b/ndb/src/ndbapi/NdbOperationSearch.cpp index e5166fc4a82..0d3130fffd0 100644 --- a/ndb/src/ndbapi/NdbOperationSearch.cpp +++ b/ndb/src/ndbapi/NdbOperationSearch.cpp @@ -543,7 +543,8 @@ NdbOperation::getKeyFromTCREQ(Uint32* data, unsigned size) assert(m_accessTable->m_sizeOfKeysInWords == size); unsigned pos = 0; while (pos < 8 && pos < size) { - data[pos++] = theKEYINFOptr[pos]; + data[pos] = theKEYINFOptr[pos]; + pos++; } NdbApiSignal* tSignal = theFirstKEYINFO; unsigned n = 0;