diff options
-rw-r--r-- | libmysql/libmysql.c | 126 | ||||
-rw-r--r-- | sql/item.cc | 12 | ||||
-rw-r--r-- | sql/item.h | 13 | ||||
-rw-r--r-- | sql/protocol.cc | 24 | ||||
-rw-r--r-- | sql/protocol.h | 1 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 26 | ||||
-rw-r--r-- | tests/client_test.c | 68 |
7 files changed, 214 insertions, 56 deletions
diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index a68837df114..b77fc5fd6fd 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -1667,6 +1667,27 @@ static int stmt_read_row_buffered(MYSQL_STMT *stmt, unsigned char **row); static int stmt_read_row_no_data(MYSQL_STMT *stmt, unsigned char **row); +/* + Maximum sizes of MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME + values stored in network buffer. +*/ + +/* 1 (length) + 2 (year) + 1 (month) + 1 (day) */ +static const unsigned MAX_DATE_REP_LENGTH= 5; + +/* + 1 (length) + 1 (is negative) + 4 (day count) + 1 (hour) + + 1 (minute) + 1 (seconds) + 4 (microseconds) +*/ +static const unsigned MAX_TIME_REP_LENGTH= 13; + +/* + 1 (length) + 2 (year) + 1 (month) + 1 (day) + + 1 (hour) + 1 (minute) + 1 (second) + 4 (microseconds) +*/ +static const unsigned MAX_DATETIME_REP_LENGTH= 12; + + /**************** Misc utility functions ****************************/ /* @@ -2030,6 +2051,30 @@ static unsigned int alloc_stmt_fields(MYSQL_STMT *stmt) return stmt->field_count; } + +/* + Update result set columns metadata if it was sent again in + reply to COM_EXECUTE. +*/ + +static void update_stmt_fields(MYSQL_STMT *stmt) +{ + MYSQL_FIELD *field= stmt->mysql->fields; + MYSQL_FIELD *field_end= field + stmt->field_count; + MYSQL_FIELD *stmt_field= stmt->fields; + + DBUG_ASSERT(stmt->field_count == stmt->mysql->field_count); + + for (; field < field_end; ++field, ++stmt_field) + { + stmt_field->charsetnr= field->charsetnr; + stmt_field->length = field->length; + stmt_field->type = field->type; + stmt_field->flags = field->flags; + stmt_field->decimals = field->decimals; + } +} + /* Returns prepared statement metadata in the form of a result set. @@ -2166,7 +2211,7 @@ static void store_param_double(NET *net, MYSQL_BIND *param) static void store_param_time(NET *net, MYSQL_BIND *param) { MYSQL_TIME *tm= (MYSQL_TIME *) param->buffer; - char buff[15], *pos; + char buff[MAX_TIME_REP_LENGTH], *pos; uint length; pos= buff+1; @@ -2177,19 +2222,19 @@ static void store_param_time(NET *net, MYSQL_BIND *param) pos[7]= (uchar) tm->second; int4store(pos+8, tm->second_part); if (tm->second_part) - length= 11; + length= 12; else if (tm->hour || tm->minute || tm->second || tm->day) length= 8; else length= 0; - buff[0]= (char) length++; + buff[0]= (char) length++; memcpy((char *)net->write_pos, buff, length); net->write_pos+= length; } static void net_store_datetime(NET *net, MYSQL_TIME *tm) { - char buff[12], *pos; + char buff[MAX_DATETIME_REP_LENGTH], *pos; uint length; pos= buff+1; @@ -2280,7 +2325,7 @@ static my_bool store_param(MYSQL_STMT *stmt, MYSQL_BIND *param) Param->length should ALWAYS point to the correct length for the type Either to the length pointer given by the user or param->buffer_length */ - if ((my_realloc_str(net, 9 + *param->length))) + if ((my_realloc_str(net, *param->length))) { set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); DBUG_RETURN(1); @@ -2557,16 +2602,37 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt) */ if (mysql->methods->stmt_execute(stmt)) DBUG_RETURN(1); - if (!stmt->field_count && mysql->field_count) + if (mysql->field_count) { - /* - This is 'SHOW'/'EXPLAIN'-like query. Current implementation of - prepared statements can't send result set metadata for this queries - on prepare stage. Read it now. - */ - alloc_stmt_fields(stmt); + /* Server has sent result set metadata */ + if (stmt->field_count == 0) + { + /* + This is 'SHOW'/'EXPLAIN'-like query. Current implementation of + prepared statements can't send result set metadata for these queries + on prepare stage. Read it now. + */ + alloc_stmt_fields(stmt); + } + else + { + /* + Update result set metadata if it for some reason changed between + prepare and execute, i.e.: + - in case of 'SELECT ?' we don't know column type unless data was + supplied to mysql_stmt_execute, so updated column type is sent + now. + - if data dictionary changed between prepare and execute, for + example a table used in the query was altered. + Note, that now (4.1.3) we always send metadata in reply to + COM_EXECUTE (even if it is not necessary), so either this or + previous always branch works. + TODO: send metadata only when it's really necessary and add a warning + 'Metadata changed' when it's sent twice. + */ + update_stmt_fields(stmt); + } } - stmt->state= MYSQL_STMT_EXECUTE_DONE; if (stmt->field_count) { @@ -2693,15 +2759,17 @@ my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) param->store_param_func= store_param_double; break; case MYSQL_TYPE_TIME: - /* Buffer length ignored for DATE, TIME and DATETIME */ param->store_param_func= store_param_time; + param->buffer_length= MAX_TIME_REP_LENGTH; break; case MYSQL_TYPE_DATE: param->store_param_func= store_param_date; + param->buffer_length= MAX_DATE_REP_LENGTH; break; case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_TIMESTAMP: param->store_param_func= store_param_datetime; + param->buffer_length= MAX_DATETIME_REP_LENGTH; break; case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: @@ -2859,17 +2927,17 @@ static uint read_binary_time(MYSQL_TIME *tm, uchar **pos) set_zero_time(tm); return 0; } - - to= *pos; - tm->second_part= (length > 8 ) ? (ulong) sint4korr(to+7): 0; + + to= *pos; + tm->neg= (bool) to[0]; tm->day= (ulong) sint4korr(to+1); tm->hour= (uint) to[5]; tm->minute= (uint) to[6]; tm->second= (uint) to[7]; + tm->second_part= (length > 8) ? (ulong) sint4korr(to+8) : 0; tm->year= tm->month= 0; - tm->neg= (bool)to[0]; return length; } @@ -2878,16 +2946,20 @@ static uint read_binary_datetime(MYSQL_TIME *tm, uchar **pos) { uchar *to; uint length; - + if (!(length= net_field_length(pos))) { set_zero_time(tm); return 0; } - - to= *pos; - tm->second_part= (length > 7 ) ? (ulong) sint4korr(to+7): 0; - + + to= *pos; + + tm->neg= 0; + tm->year= (uint) sint2korr(to); + tm->month= (uint) to[2]; + tm->day= (uint) to[3]; + if (length > 4) { tm->hour= (uint) to[4]; @@ -2896,11 +2968,7 @@ static uint read_binary_datetime(MYSQL_TIME *tm, uchar **pos) } else tm->hour= tm->minute= tm->second= 0; - - tm->year= (uint) sint2korr(to); - tm->month= (uint) to[2]; - tm->day= (uint) to[3]; - tm->neg= 0; + tm->second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0; return length; } @@ -3159,7 +3227,7 @@ static void send_data_time(MYSQL_BIND *param, MYSQL_TIME ltime, } } } - + /* Fetch data to buffers */ diff --git a/sql/item.cc b/sql/item.cc index 7db1a448e55..d85d70c69ab 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -629,6 +629,7 @@ Item_param::Item_param(unsigned pos_in_query_arg) : state(NO_VALUE), item_result_type(STRING_RESULT), item_type(STRING_ITEM), + param_type(MYSQL_TYPE_STRING), pos_in_query(pos_in_query_arg), set_param_func(default_set_param_func) { @@ -808,6 +809,17 @@ bool Item_param::get_time(TIME *res) } +bool Item_param::get_date(TIME *res, uint fuzzydate) +{ + if (state == TIME_VALUE) + { + *res= value.time; + return 0; + } + return Item::get_date(res, fuzzydate); +} + + double Item_param::val() { switch (state) { diff --git a/sql/item.h b/sql/item.h index 885a34dce81..e4c134842b9 100644 --- a/sql/item.h +++ b/sql/item.h @@ -465,6 +465,16 @@ public: /* Cached values for virtual methods to save us one switch. */ enum Item_result item_result_type; enum Type item_type; + + /* + Used when this item is used in a temporary table. + This is NOT placeholder metadata sent to client, as this value + is assigned after sending metadata (in setup_one_conversion_function). + For example in case of 'SELECT ?' you'll get MYSQL_TYPE_STRING both + in result set and placeholders metadata, no matter what type you will + supply for this placeholder in mysql_stmt_execute. + */ + enum enum_field_types param_type; /* Offset of placeholder inside statement text. Used to create no-placeholders version of this statement for the binary log. @@ -475,12 +485,13 @@ public: enum Item_result result_type () const { return item_result_type; } enum Type type() const { return item_type; } - enum_field_types field_type() const { return MYSQL_TYPE_STRING; } + enum_field_types field_type() const { return param_type; } double val(); longlong val_int(); String *val_str(String*); bool get_time(TIME *tm); + bool get_date(TIME *tm, uint fuzzydate); int save_in_field(Field *field, bool no_conversions); void set_null(); diff --git a/sql/protocol.cc b/sql/protocol.cc index 44fc4eff9ad..7738349c742 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -26,6 +26,8 @@ #include "mysql_priv.h" #include <stdarg.h> +static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024; + #ifndef EMBEDDED_LIBRARY bool Protocol::net_store_data(const char *from, uint length) #else @@ -687,7 +689,7 @@ bool Protocol_simple::store_null() #endif char buff[1]; buff[0]= (char)251; - return packet->append(buff, sizeof(buff), PACKET_BUFFET_EXTRA_ALLOC); + return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC); } #endif @@ -990,7 +992,7 @@ bool Protocol_prep::store_tiny(longlong from) char buff[1]; field_pos++; buff[0]= (uchar) from; - return packet->append(buff, sizeof(buff), PACKET_BUFFET_EXTRA_ALLOC); + return packet->append(buff, sizeof(buff), PACKET_BUFFER_EXTRA_ALLOC); } @@ -1002,7 +1004,7 @@ bool Protocol_prep::store_short(longlong from) field_types[field_pos] == MYSQL_TYPE_YEAR); #endif field_pos++; - char *to= packet->prep_append(2, PACKET_BUFFET_EXTRA_ALLOC); + char *to= packet->prep_append(2, PACKET_BUFFER_EXTRA_ALLOC); if (!to) return 1; int2store(to, (int) from); @@ -1018,7 +1020,7 @@ bool Protocol_prep::store_long(longlong from) field_types[field_pos] == MYSQL_TYPE_LONG); #endif field_pos++; - char *to= packet->prep_append(4, PACKET_BUFFET_EXTRA_ALLOC); + char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); if (!to) return 1; int4store(to, from); @@ -1033,7 +1035,7 @@ bool Protocol_prep::store_longlong(longlong from, bool unsigned_flag) field_types[field_pos] == MYSQL_TYPE_LONGLONG); #endif field_pos++; - char *to= packet->prep_append(8, PACKET_BUFFET_EXTRA_ALLOC); + char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); if (!to) return 1; int8store(to, from); @@ -1048,7 +1050,7 @@ bool Protocol_prep::store(float from, uint32 decimals, String *buffer) field_types[field_pos] == MYSQL_TYPE_FLOAT); #endif field_pos++; - char *to= packet->prep_append(4, PACKET_BUFFET_EXTRA_ALLOC); + char *to= packet->prep_append(4, PACKET_BUFFER_EXTRA_ALLOC); if (!to) return 1; float4store(to, from); @@ -1063,7 +1065,7 @@ bool Protocol_prep::store(double from, uint32 decimals, String *buffer) field_types[field_pos] == MYSQL_TYPE_DOUBLE); #endif field_pos++; - char *to= packet->prep_append(8, PACKET_BUFFET_EXTRA_ALLOC); + char *to= packet->prep_append(8, PACKET_BUFFER_EXTRA_ALLOC); if (!to) return 1; float8store(to, from); @@ -1112,7 +1114,7 @@ bool Protocol_prep::store(TIME *tm) else length=0; buff[0]=(char) length; // Length is stored first - return packet->append(buff, length+1, PACKET_BUFFET_EXTRA_ALLOC); + return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC); } bool Protocol_prep::store_date(TIME *tm) @@ -1129,7 +1131,7 @@ bool Protocol_prep::store_time(TIME *tm) DBUG_ASSERT(field_types == 0 || field_types[field_pos] == MYSQL_TYPE_TIME); #endif - char buff[15],*pos; + char buff[13], *pos; uint length; field_pos++; pos= buff+1; @@ -1140,13 +1142,13 @@ bool Protocol_prep::store_time(TIME *tm) pos[7]= (uchar) tm->second; int4store(pos+8, tm->second_part); if (tm->second_part) - length=11; + length=12; else if (tm->hour || tm->minute || tm->second || tm->day) length=8; else length=0; buff[0]=(char) length; // Length is stored first - return packet->append(buff, length+1, PACKET_BUFFET_EXTRA_ALLOC); + return packet->append(buff, length+1, PACKET_BUFFER_EXTRA_ALLOC); } #ifdef EMBEDDED_LIBRARY diff --git a/sql/protocol.h b/sql/protocol.h index 41885ec9f1f..43230983db7 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -18,7 +18,6 @@ #pragma interface /* gcc class implementation */ #endif -#define PACKET_BUFFET_EXTRA_ALLOC 1024 class i_string; class THD; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 1713c81a096..ab181e02735 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -332,20 +332,19 @@ static void set_param_time(Item_param *param, uchar **pos, ulong len) { uchar *to= *pos; TIME tm; - - /* TODO: why length is compared with 8 here? */ - tm.second_part= (length > 8 ) ? (ulong) sint4korr(to+7): 0; + tm.neg= (bool) to[0]; + day= (uint) sint4korr(to+1); /* Note, that though ranges of hour, minute and second are not checked here we rely on them being < 256: otherwise we'll get buffer overflow in make_{date,time} functions, which are called when time value is converted to string. */ - day= (uint) sint4korr(to+1); tm.hour= (uint) to[5] + day * 24; tm.minute= (uint) to[6]; tm.second= (uint) to[7]; + tm.second_part= (length > 8) ? (ulong) sint4korr(to+8) : 0; if (tm.hour > 838) { /* TODO: add warning 'Data truncated' here */ @@ -354,7 +353,6 @@ static void set_param_time(Item_param *param, uchar **pos, ulong len) tm.second= 59; } tm.day= tm.year= tm.month= 0; - tm.neg= (bool)to[0]; param->set_time(&tm, TIMESTAMP_TIME, MAX_TIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); @@ -365,14 +363,16 @@ static void set_param_time(Item_param *param, uchar **pos, ulong len) static void set_param_datetime(Item_param *param, uchar **pos, ulong len) { uint length; - + if ((length= get_param_length(pos, len)) >= 4) { uchar *to= *pos; TIME tm; - - tm.second_part= (length > 7 ) ? (ulong) sint4korr(to+7): 0; - + + tm.neg= 0; + tm.year= (uint) sint2korr(to); + tm.month= (uint) to[2]; + tm.day= (uint) to[3]; /* Note, that though ranges of hour, minute and second are not checked here we rely on them being < 256: otherwise @@ -386,11 +386,8 @@ static void set_param_datetime(Item_param *param, uchar **pos, ulong len) } else tm.hour= tm.minute= tm.second= 0; - - tm.year= (uint) sint2korr(to); - tm.month= (uint) to[2]; - tm.day= (uint) to[3]; - tm.neg= 0; + + tm.second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0; param->set_time(&tm, TIMESTAMP_DATETIME, MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); @@ -585,6 +582,7 @@ static void setup_one_conversion_function(THD *thd, Item_param *param, param->item_result_type= STRING_RESULT; } } + param->param_type= (enum enum_field_types) param_type; } #ifndef EMBEDDED_LIBRARY diff --git a/tests/client_test.c b/tests/client_test.c index 04549f3355c..a2703478036 100644 --- a/tests/client_test.c +++ b/tests/client_test.c @@ -9802,6 +9802,73 @@ static void test_bug3796() myquery(rc); } + +static void test_bug4026() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[2]; + MYSQL_TIME time_in, time_out; + MYSQL_TIME datetime_in, datetime_out; + const char *stmt_text; + int rc; + + myheader("test_bug4026"); + + /* Check that microseconds are inserted and selected successfully */ + + /* Create a statement handle and prepare it with select */ + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT ?, ?"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + /* Bind input buffers */ + bzero(bind, sizeof(bind)); + bzero(&time_in, sizeof(time_in)); + bzero(&time_out, sizeof(time_out)); + bzero(&datetime_in, sizeof(datetime_in)); + bzero(&datetime_out, sizeof(datetime_out)); + + bind[0].buffer_type= MYSQL_TYPE_TIME; + bind[0].buffer= (char*) &time_in; + bind[1].buffer_type= MYSQL_TYPE_DATETIME; + bind[1].buffer= (char*) &datetime_in; + + time_in.hour= 23; + time_in.minute= 59; + time_in.second= 59; + time_in.second_part= 123456; + + datetime_in= time_in; + datetime_in.year= 2003; + datetime_in.month= 12; + datetime_in.day= 31; + + mysql_stmt_bind_param(stmt, bind); + + /* Execute the select statement */ + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bind[0].buffer= (char*) &time_out; + bind[1].buffer= (char*) &datetime_out; + + mysql_stmt_bind_result(stmt, bind); + + rc= mysql_stmt_fetch(stmt); + assert(rc == 0); + printf("%d:%d:%d.%lu\n", time_out.hour, time_out.minute, time_out.second, + time_out.second_part); + printf("%d-%d-%d %d:%d:%d.%lu\n", datetime_out.year, datetime_out.month, + datetime_out.day, datetime_out.hour, + datetime_out.minute, datetime_out.second, + datetime_out.second_part); + assert(memcmp(&time_in, &time_out, sizeof(time_in)) == 0); + assert(memcmp(&datetime_in, &datetime_out, sizeof(datetime_in)) == 0); + mysql_stmt_close(stmt); +} + /* Read and parse arguments and MySQL options from my.cnf */ @@ -10094,6 +10161,7 @@ int main(int argc, char **argv) (Bug #3686 */ test_ps_i18n(); /* test for i18n support in binary protocol */ test_bug3796(); /* test for select concat(?, <string>) */ + test_bug4026(); /* test microseconds precision of time types */ /* XXX: PLEASE RUN THIS PROGRAM UNDER VALGRIND AND VERIFY THAT YOUR TEST DOESN'T CONTAIN WARNINGS/ERRORS BEFORE YOU PUSH. |