Commit dc11d3cf authored by konstantin@mysql.com's avatar konstantin@mysql.com

Proposed fix for Bug#4026 "Microseconds part of TIME/DATETIME types

is broken (prepared statements)": fixed date handling in many places 
of prepared statements code.
parent dabc0e77
......@@ -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,7 +2222,7 @@ 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
......@@ -2189,7 +2234,7 @@ static void store_param_time(NET *net, MYSQL_BIND *param)
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)
{
/* 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 this queries
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:
......@@ -2861,15 +2929,15 @@ static uint read_binary_time(MYSQL_TIME *tm, uchar **pos)
}
to= *pos;
tm->second_part= (length > 8 ) ? (ulong) sint4korr(to+7): 0;
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;
}
......@@ -2886,7 +2954,11 @@ static uint read_binary_datetime(MYSQL_TIME *tm, uchar **pos)
}
to= *pos;
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];
if (length > 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;
}
......
......@@ -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) {
......
......@@ -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();
......
......@@ -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
......
......@@ -18,7 +18,6 @@
#pragma interface /* gcc class implementation */
#endif
#define PACKET_BUFFET_EXTRA_ALLOC 1024
class i_string;
class THD;
......
......@@ -333,19 +333,18 @@ 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);
......@@ -371,8 +369,10 @@ static void set_param_datetime(Item_param *param, uchar **pos, ulong len)
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
......@@ -387,10 +387,7 @@ 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
......
......@@ -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.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment