summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libmysql/libmysql.c126
-rw-r--r--sql/item.cc12
-rw-r--r--sql/item.h13
-rw-r--r--sql/protocol.cc24
-rw-r--r--sql/protocol.h1
-rw-r--r--sql/sql_prepare.cc26
-rw-r--r--tests/client_test.c68
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.