diff options
author | unknown <konstantin@mysql.com> | 2004-05-25 02:03:49 +0400 |
---|---|---|
committer | unknown <konstantin@mysql.com> | 2004-05-25 02:03:49 +0400 |
commit | 093d62922b326cec1a05bd8baba2d9ed96137488 (patch) | |
tree | 39de3845fc8340f9a27f30803b9104e416c7ab62 /sql | |
parent | 88da3ae5f9e69a3bf6ced379908d01da5b3bfbb0 (diff) | |
download | mariadb-git-093d62922b326cec1a05bd8baba2d9ed96137488.tar.gz |
Support for character set conversion in binary protocol: another go
after Monty's review.
- Item_param was rewritten.
- it turns out that we can't convert string data to character set of
connection on the fly, because they first should be written to the binary
log.
To support efficient conversion we need to rewrite prepared statements
binlogging code first.
include/my_global.h:
Macro swap(a, b, c) was renamed to resolve name conflict with
String::swap() method.
include/my_sys.h:
Added declaration of escape_string_for_mysql()
include/mysql_com.h:
Removed and moved back: a macro which is visible to libmysql user but
has sence only in prepared statement protocol implementation.
isam/_search.c:
swap -> swap_variables
isam/test2.c:
swap -> swap_variables
libmysql/libmysql.c:
- sub_escape_string moved to mysys/charset.c to be visible in sql/
- few cleanups
myisam/mi_test2.c:
swap -> swap_variables
mysys/charset.c:
sub_escape_string was moved from libmysql.c to be able to use it in sql/
code.
mysys/my_chsize.c:
rename: swap -> swap_variables
mysys/my_compress.c:
swap -> swap_variables
mysys/my_handler.c:
swap -> swap_variables
sql/field.cc:
Field::store_time refactored to use TIME_to_string function from time.cc
sql/item.cc:
New implementation of Item_param class:
added support for character sets conversion.
sql/item.h:
Item_param:
- 'state' member introduced instead of many boolean variables.
- put ltime, int_value and real_value into union to save space.
- remove unimplemented members
- set_value renamed to set_str
sql/item_timefunc.cc:
Refactored to use functions from time.cc
sql/lock.cc:
rename: swap -> swap_variables
sql/mysql_priv.h:
- added declarations for TIME_to_ulonglong_*, TIME_to_string functions
- const specifiers for make_date, make_time, make_datetime arguments
sql/opt_range.cc:
rename: swap -> swap_variables
sql/protocol.cc:
- added character set conversion support to binary protocol.
- Protocol::convert changed to point at shared buffer in THD.
This lets us use one convert buffer for binary and simple protocol.
The same buffer is used for client->server conversions in prepared
statements code.
- string conversion code refactored to Protocol::store_string_aux function.
- few more comments
sql/protocol.h:
- Protocol::convert now points at THD::convert_buffer: we want to share one
buffer between all protocol implementations.
sql/sql_class.cc:
- implementation of THD::convert_string using THD::convert_buffer
(conversion of strings allocated in the system heap).
sql/sql_class.h:
- THD::convert_buffer is shared between THD and network Protocols and
used for character set conversion of strings.
- new function to convert String object from one charset to another using
THD::convert_buffer
sql/sql_insert.cc:
A little fix in a comment.
sql/sql_parse.cc:
Shrink convert buffer in the end of each statement.
sql/sql_prepare.cc:
Many changes:
- static specifier for set_param_* family of functions.
- FIELD_TYPE -> MYSQL_TYPE
- added set_param_binary as handler for BLOB types.
- added character set support
- added support for param typecode in mysql_stmt_get_longdata
(mysql_stmt_send_long_data handler)
- changes in Item_param deployed
- few cleanups
sql/sql_select.cc:
rename: swap -> swap_variables
sql/sql_string.cc:
- String::append rewritten to support character set conversion for
single-byte encodings.
- added String::swap method to efficiently exchange two string objects.
sql/sql_string.h:
Declraration for String::swap().
sql/time.cc:
- function TIME_to_string to convert TIME to String in default MySQL format
- family of functions TIME_to_ulonglong_*
tests/client_test.c:
Test for support for character set conversions in prepared statements
(binary and text data).
Diffstat (limited to 'sql')
-rw-r--r-- | sql/field.cc | 29 | ||||
-rw-r--r-- | sql/item.cc | 421 | ||||
-rw-r--r-- | sql/item.h | 72 | ||||
-rw-r--r-- | sql/item_timefunc.cc | 77 | ||||
-rw-r--r-- | sql/lock.cc | 4 | ||||
-rw-r--r-- | sql/mysql_priv.h | 22 | ||||
-rw-r--r-- | sql/opt_range.cc | 6 | ||||
-rw-r--r-- | sql/protocol.cc | 58 | ||||
-rw-r--r-- | sql/protocol.h | 4 | ||||
-rw-r--r-- | sql/sql_class.cc | 33 | ||||
-rw-r--r-- | sql/sql_class.h | 4 | ||||
-rw-r--r-- | sql/sql_insert.cc | 2 | ||||
-rw-r--r-- | sql/sql_parse.cc | 5 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 272 | ||||
-rw-r--r-- | sql/sql_select.cc | 4 | ||||
-rw-r--r-- | sql/sql_string.cc | 45 | ||||
-rw-r--r-- | sql/sql_string.h | 3 | ||||
-rw-r--r-- | sql/time.cc | 122 |
18 files changed, 757 insertions, 426 deletions
diff --git a/sql/field.cc b/sql/field.cc index 944f18080f6..df9b4f84ae7 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -423,30 +423,11 @@ bool Field::get_time(TIME *ltime) void Field::store_time(TIME *ltime,timestamp_type type) { - char buff[25]; - switch (type) { - case TIMESTAMP_NONE: - case TIMESTAMP_DATETIME_ERROR: - store("",0,&my_charset_bin); // Probably an error - break; - case TIMESTAMP_DATE: - sprintf(buff,"%04d-%02d-%02d", ltime->year,ltime->month,ltime->day); - store(buff,10,&my_charset_bin); - break; - case TIMESTAMP_DATETIME: - sprintf(buff,"%04d-%02d-%02d %02d:%02d:%02d", - ltime->year,ltime->month,ltime->day, - ltime->hour,ltime->minute,ltime->second); - store(buff,19,&my_charset_bin); - break; - case TIMESTAMP_TIME: - { - ulong length= my_sprintf(buff, (buff, "%02d:%02d:%02d", - ltime->hour,ltime->minute,ltime->second)); - store(buff,(uint) length, &my_charset_bin); - break; - } - } + 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); } diff --git a/sql/item.cc b/sql/item.cc index 700d9482815..f3a13411fe3 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -27,6 +27,8 @@ static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, Item_ident *item); +const String my_null_string("NULL", 4, default_charset_info); + /***************************************************************************** ** Item functions *****************************************************************************/ @@ -623,13 +625,11 @@ default_set_param_func(Item_param *param, param->set_null(); } -Item_param::Item_param(unsigned position) : - value_is_set(FALSE), +Item_param::Item_param(unsigned pos_in_query_arg) : + state(NO_VALUE), item_result_type(STRING_RESULT), item_type(STRING_ITEM), - item_is_time(FALSE), - long_data_supplied(FALSE), - pos_in_query(position), + pos_in_query(pos_in_query_arg), set_param_func(default_set_param_func) { name= (char*) "?"; @@ -645,93 +645,93 @@ void Item_param::set_null() { DBUG_ENTER("Item_param::set_null"); /* These are cleared after each execution by reset() method */ - null_value= value_is_set= 1; max_length= 0; + null_value= 1; + /* + Because of NULL and string values we need to set max_length for each new + placeholder value: user can submit NULL for any placeholder type, and + string length can be different in each execution. + */ + max_length= 0; + decimals= 0; + state= NULL_VALUE; DBUG_VOID_RETURN; } -void Item_param::set_int(longlong i) +void Item_param::set_int(longlong i, uint32 max_length_arg) { DBUG_ENTER("Item_param::set_int"); - int_value= (longlong)i; - item_type= INT_ITEM; - value_is_set= 1; + value.integer= (longlong) i; + state= INT_VALUE; + max_length= max_length_arg; + decimals= 0; maybe_null= 0; - max_length= 11; - DBUG_PRINT("info", ("integer: %lld", int_value)); DBUG_VOID_RETURN; } -void Item_param::set_double(double value) +void Item_param::set_double(double d) { DBUG_ENTER("Item_param::set_double"); - real_value=value; - item_type= REAL_ITEM; - value_is_set= 1; - maybe_null= 0; + value.real= d; + state= REAL_VALUE; + max_length= DBL_DIG + 8; decimals= NOT_FIXED_DEC; - max_length= DBL_DIG + 8;; - DBUG_PRINT("info", ("double: %lg", real_value)); - DBUG_VOID_RETURN; -} - - -void Item_param::set_value(const char *str, uint length) -{ - DBUG_ENTER("Item_param::set_value"); - str_value.copy(str,length,default_charset()); - item_type= STRING_ITEM; - value_is_set= 1; maybe_null= 0; - max_length= length; - DBUG_PRINT("info", ("string: %s", str_value.ptr())); DBUG_VOID_RETURN; } -void Item_param::set_time(TIME *tm, timestamp_type type) +void Item_param::set_time(TIME *tm, timestamp_type type, uint32 max_length_arg) { - ltime.year= tm->year; - ltime.month= tm->month; - ltime.day= tm->day; - - ltime.hour= tm->hour; - ltime.minute= tm->minute; - ltime.second= tm->second; + DBUG_ENTER("Item_param::set_time"); - ltime.second_part= tm->second_part; + value.time= *tm; + value.time.time_type= type; - ltime.neg= tm->neg; + state= TIME_VALUE; + maybe_null= 0; + max_length= max_length_arg; + decimals= 0; + DBUG_VOID_RETURN; +} - ltime.time_type= type; - - item_is_time= TRUE; - item_type= STRING_ITEM; - value_is_set= 1; + +bool Item_param::set_str(const char *str, ulong length) +{ + DBUG_ENTER("Item_param::set_str"); + /* + Assign string with no conversion: data is converted only after it's + been written to the binary log. + */ + if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin)) + DBUG_RETURN(TRUE); + state= STRING_VALUE; maybe_null= 0; - switch(type) - { - case TIMESTAMP_DATE: - max_length= 10; - break; - case TIMESTAMP_DATETIME: - max_length= 19; - break; - case TIMESTAMP_TIME: - max_length= 8; - break; - default: - DBUG_ASSERT(0); // it should be impossible - } + /* max_length and decimals are set after charset conversion */ + /* sic: str may be not null-terminated, don't add DBUG_PRINT here */ + DBUG_RETURN(FALSE); } -void Item_param::set_longdata(const char *str, ulong length) -{ - str_value.append(str,length); - long_data_supplied= 1; - value_is_set= 1; +bool Item_param::set_longdata(const char *str, ulong length) +{ + DBUG_ENTER("Item_param::set_longdata"); + + /* + If client character set is multibyte, end of long data packet + may hit at the middle of a multibyte character. Additionally, + if binary log is open we must write long data value to the + binary log in character set of client. This is why we can't + convert long data to connection character set as it comes + (here), and first have to concatenate all pieces together, + write query to the binary log and only then perform conversion. + */ + if (str_value.append(str, length, &my_charset_bin)) + DBUG_RETURN(TRUE); + state= LONG_DATA_VALUE; maybe_null= 0; + + DBUG_RETURN(FALSE); } @@ -747,9 +747,18 @@ void Item_param::set_longdata(const char *str, ulong length) */ void Item_param::reset() -{ - str_value.set("", 0, &my_charset_bin); - value_is_set= long_data_supplied= 0; +{ + /* Shrink string buffer if it's bigger than max possible CHAR column */ + if (str_value.alloced_length() > MAX_CHAR_WIDTH) + str_value.free(); + else + str_value.length(0); + /* + We must prevent all charset conversions unless data of str_value + has been written to the binary log. + */ + str_value.set_charset(&my_charset_bin); + state= NO_VALUE; maybe_null= 1; null_value= 0; } @@ -758,155 +767,223 @@ void Item_param::reset() int Item_param::save_in_field(Field *field, bool no_conversions) { DBUG_ASSERT(current_thd->command == COM_EXECUTE); - - if (null_value) - return (int) set_field_to_null(field); - + field->set_notnull(); - if (item_result_type == INT_RESULT) - { - longlong nr=val_int(); - return field->store(nr); - } - if (item_result_type == REAL_RESULT) - { - double nr=val(); - return field->store(nr); - } - if (item_is_time) - { - field->store_time(<ime, ltime.time_type); + + switch (state) { + case INT_VALUE: + return field->store(value.integer); + case REAL_VALUE: + return field->store(value.real); + case TIME_VALUE: + field->store_time(&value.time, value.time.time_type); return 0; + case STRING_VALUE: + case LONG_DATA_VALUE: + return field->store(str_value.ptr(), str_value.length(), + str_value.charset()); + case NULL_VALUE: + return set_field_to_null(field); + case NO_VALUE: + default: + DBUG_ASSERT(0); } - String *result=val_str(&str_value); - return field->store(result->ptr(),result->length(),field->charset()); + return 1; } + bool Item_param::get_time(TIME *res) { - *res=ltime; - return 0; + if (state == TIME_VALUE) + { + *res= value.time; + return 0; + } + /* + If parameter value isn't supplied assertion will fire in val_str() + which is called from Item::get_time(). + */ + return Item::get_time(res); } + double Item_param::val() { - DBUG_ASSERT(value_is_set == 1); - int err; - if (null_value) + switch (state) { + case REAL_VALUE: + return value.real; + case INT_VALUE: + return (double) value.integer; + case STRING_VALUE: + case LONG_DATA_VALUE: + { + int dummy_err; + return my_strntod(str_value.charset(), (char*) str_value.ptr(), + str_value.length(), (char**) 0, &dummy_err); + } + case TIME_VALUE: + /* + This works for example when user says SELECT ?+0.0 and supplies + time value for the placeholder. + */ + return (double) TIME_to_ulonglong(&value.time); + case NULL_VALUE: return 0.0; - switch (item_result_type) { - case STRING_RESULT: - return (double) my_strntod(str_value.charset(), (char*) str_value.ptr(), - str_value.length(), (char**) 0, &err); - case INT_RESULT: - return (double)int_value; default: - return real_value; + DBUG_ASSERT(0); } + return 0.0; } longlong Item_param::val_int() { - DBUG_ASSERT(value_is_set == 1); - int err; - if (null_value) - return 0; - switch (item_result_type) { - case STRING_RESULT: - return my_strntoll(str_value.charset(), - str_value.ptr(),str_value.length(),10, - (char**) 0,&err); - case REAL_RESULT: - return (longlong) (real_value+(real_value > 0 ? 0.5 : -0.5)); + switch (state) { + case REAL_VALUE: + return (longlong) (value.real + (value.real > 0 ? 0.5 : -0.5)); + case INT_VALUE: + return value.integer; + case STRING_VALUE: + case LONG_DATA_VALUE: + { + int dummy_err; + return my_strntoll(str_value.charset(), str_value.ptr(), + str_value.length(), 10, (char**) 0, &dummy_err); + } + case TIME_VALUE: + return (longlong) TIME_to_ulonglong(&value.time); + case NULL_VALUE: + return 0; default: - return int_value; + DBUG_ASSERT(0); } + return 0; } String *Item_param::val_str(String* str) { - DBUG_ASSERT(value_is_set == 1); - if (null_value) - return NULL; - switch (item_result_type) { - case INT_RESULT: - str->set(int_value, &my_charset_bin); + switch (state) { + case STRING_VALUE: + case LONG_DATA_VALUE: + return &str_value; + case REAL_VALUE: + str->set(value.real, NOT_FIXED_DEC, &my_charset_bin); return str; - case REAL_RESULT: - str->set(real_value, 2, &my_charset_bin); + case INT_VALUE: + str->set(value.integer, &my_charset_bin); return str; + case TIME_VALUE: + { + if (str->reserve(MAX_DATE_REP_LENGTH)) + break; + TIME_to_string(&value.time, str); + return str; + } + case NULL_VALUE: + return NULL; default: - return (String*) &str_value; + DBUG_ASSERT(0); } + return str; } /* Return Param item values in string format, for generating the dynamic query used in update/binary logs + TODO: change interface and implementation to fill log data in place + and avoid one more memcpy/alloc between str and log string. */ -String *Item_param::query_val_str(String* str) +const String *Item_param::query_val_str(String* str) const { - DBUG_ASSERT(value_is_set == 1); - switch (item_result_type) { - case INT_RESULT: - case REAL_RESULT: - return val_str(str); - default: - str->set("'", 1, default_charset()); - - if (!item_is_time) + switch (state) { + case INT_VALUE: + str->set(value.integer, &my_charset_bin); + break; + case REAL_VALUE: + str->set(value.real, NOT_FIXED_DEC, &my_charset_bin); + break; + case TIME_VALUE: { - str->append(str_value); - const char *from= str->ptr(); - uint32 length= 1; - - // Escape misc cases - char *to= (char *)from, *end= (char *)to+str->length(); - for (to++; to != end ; length++, to++) - { - switch(*to) { - case '\'': - case '"': - case '\r': - case '\n': - case '\\': // TODO: Add remaining .. - str->replace(length,0,"\\",1); - to++; end++; length++; - break; - default: - break; - } - } + 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)) + 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++= '\''; + str->length((uint32) (ptr - buf)); + break; } - else + case STRING_VALUE: + case LONG_DATA_VALUE: { - char buff[40]; - String tmp(buff,sizeof(buff), &my_charset_bin); - - switch (ltime.time_type) { - case TIMESTAMP_NONE: - case TIMESTAMP_DATETIME_ERROR: - tmp.length(0); // Should never happen - break; - case TIMESTAMP_DATE: - make_date((DATE_TIME_FORMAT*) 0, <ime, &tmp); - break; - case TIMESTAMP_DATETIME: - make_datetime((DATE_TIME_FORMAT*) 0, <ime, &tmp); - break; - case TIMESTAMP_TIME: - make_time((DATE_TIME_FORMAT*) 0, <ime, &tmp); - break; - } - str->append(tmp); + char *buf, *ptr; + str->length(0); + if (str->reserve(str_value.length()*2+3)) + break; + + buf= str->c_ptr_quick(); + ptr= buf; + *ptr++= '\''; + ptr+= escape_string_for_mysql(str_value.charset(), ptr, + str_value.ptr(), str_value.length()); + *ptr++= '\''; + str->length(ptr - buf); + break; } - str->append('\''); + case NULL_VALUE: + return &my_null_string; + default: + DBUG_ASSERT(0); } return str; } + + +/* + Convert string from client character set to the character set of + connection. +*/ + +bool Item_param::convert_str_value(THD *thd) +{ + bool rc= FALSE; + if (state == STRING_VALUE || state == LONG_DATA_VALUE) + { + /* + Check is so simple because all charsets were set up properly + in setup_one_conversion_function, where typecode of + placeholder was also taken into account: the variables are different + here only if conversion is really necessary. + */ + if (value.cs_info.final_character_set_of_str_value != + value.cs_info.character_set_client) + { + rc= thd->convert_string(&str_value, + value.cs_info.character_set_client, + value.cs_info.final_character_set_of_str_value); + } + max_length= str_value.length(); + decimals= 0; + } + return rc; +} + /* End of Item_param related */ diff --git a/sql/item.h b/sql/item.h index 99a0516e439..ccf8e8685d0 100644 --- a/sql/item.h +++ b/sql/item.h @@ -393,33 +393,63 @@ public: class Item_param :public Item { public: - bool value_is_set; - longlong int_value; - double real_value; - TIME ltime; + enum enum_item_param_state + { + NO_VALUE, NULL_VALUE, INT_VALUE, REAL_VALUE, + STRING_VALUE, TIME_VALUE, LONG_DATA_VALUE + } state; + + union + { + longlong integer; + double real; + /* + Character sets conversion info for string values. + Character sets of client and connection defined at bind time are used + for all conversions, even if one of them is later changed (i.e. + between subsequent calls to mysql_stmt_execute). + */ + struct CONVERSION_INFO + { + CHARSET_INFO *character_set_client; + /* + This points at character set of connection if conversion + to it is required (i. e. if placeholder typecode is not BLOB). + Otherwise it's equal to character_set_client (to simplify + check in convert_str_value()). + */ + CHARSET_INFO *final_character_set_of_str_value; + } cs_info; + TIME time; + } value; + + /* Cached values for virtual methods to save us one switch. */ enum Item_result item_result_type; enum Type item_type; - enum enum_field_types buffer_type; - bool item_is_time; - bool long_data_supplied; + /* + Offset of placeholder inside statement text. Used to create + no-placeholders version of this statement for the binary log. + */ uint pos_in_query; - Item_param(uint position); + Item_param(uint pos_in_query_arg); + + 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; } + double val(); longlong val_int(); String *val_str(String*); + bool get_time(TIME *tm); int save_in_field(Field *field, bool no_conversions); + void set_null(); - void set_int(longlong i); + void set_int(longlong i, uint32 max_length_arg); void set_double(double i); - void set_value(const char *str, uint length); - void set_long_str(const char *str, ulong length); - void set_long_binary(const char *str, ulong length); - void set_longdata(const char *str, ulong length); - void set_long_end(); - void set_time(TIME *tm, timestamp_type type); - bool get_time(TIME *tm); + bool set_str(const char *str, ulong length); + bool set_longdata(const char *str, ulong length); + void set_time(TIME *tm, timestamp_type type, uint32 max_length_arg); void reset(); /* Assign placeholder value from bind data. @@ -429,10 +459,10 @@ public: */ void (*set_param_func)(Item_param *param, uchar **pos, ulong len); - enum Item_result result_type () const - { return item_result_type; } - String *query_val_str(String *str); - enum_field_types field_type() const { return MYSQL_TYPE_STRING; } + const String *query_val_str(String *str) const; + + bool convert_str_value(THD *thd); + Item *new_item() { return new Item_param(pos_in_query); } /* If value for parameter was not set we treat it as non-const @@ -440,7 +470,7 @@ public: parameter is constant during execution. */ virtual table_map used_tables() const - { return value_is_set ? (table_map)0 : PARAM_TABLE_BIT; } + { return state != NO_VALUE ? (table_map)0 : PARAM_TABLE_BIT; } void print(String *str) { str->append('?'); } }; diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index e8848243812..45d66addc9f 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -1134,9 +1134,6 @@ void Item_func_curdate::fix_length_and_dec() store_now_in_tm(current_thd->query_start(),&start); - value=(longlong) ((ulong) ((uint) start.tm_year+1900)*10000L+ - ((uint) start.tm_mon+1)*100+ - (uint) start.tm_mday); /* For getdate */ ltime.year= start.tm_year+1900; ltime.month= start.tm_mon+1; @@ -1147,6 +1144,7 @@ void Item_func_curdate::fix_length_and_dec() ltime.second_part=0; ltime.neg=0; ltime.time_type=TIMESTAMP_DATE; + value= (longlong) TIME_to_ulonglong_date(<ime); } String *Item_func_curdate::val_str(String *str) @@ -1205,15 +1203,12 @@ void Item_func_curtime::fix_length_and_dec() decimals=0; store_now_in_tm(current_thd->query_start(),&start); - value=(longlong) ((ulong) ((uint) start.tm_hour)*10000L+ - (ulong) (((uint) start.tm_min)*100L+ - (uint) start.tm_sec)); - ltime.day= 0; ltime.hour= start.tm_hour; ltime.minute= start.tm_min; ltime.second= start.tm_sec; ltime.second_part= 0; ltime.neg= 0; + value= TIME_to_ulonglong_time(<ime); make_time((DATE_TIME_FORMAT *) 0, <ime, &tmp); max_length= buff_length= tmp.length(); } @@ -1256,23 +1251,12 @@ void Item_func_now::fix_length_and_dec() collation.set(&my_charset_bin); store_now_in_tm(current_thd->query_start(),&start); - value=((longlong) ((ulong) ((uint) start.tm_year+1900)*10000L+ - (((uint) start.tm_mon+1)*100+ - (uint) start.tm_mday))*(longlong) 1000000L+ - (longlong) ((ulong) ((uint) start.tm_hour)*10000L+ - (ulong) (((uint) start.tm_min)*100L+ - (uint) start.tm_sec))); /* For getdate */ - ltime.year= start.tm_year+1900; - ltime.month= start.tm_mon+1; - ltime.day= start.tm_mday; - ltime.hour= start.tm_hour; - ltime.minute= start.tm_min; - ltime.second= start.tm_sec; - ltime.second_part= 0; - ltime.neg= 0; + localtime_to_TIME(<ime, &start); ltime.time_type= TIMESTAMP_DATETIME; + + value= (longlong) TIME_to_ulonglong_datetime(<ime); make_datetime((DATE_TIME_FORMAT *) 0, <ime, &tmp); max_length= buff_length= tmp.length(); @@ -1457,10 +1441,10 @@ uint Item_func_date_format::format_length(const String *format) String *Item_func_date_format::val_str(String *str) { - DBUG_ASSERT(fixed == 1); String *format; TIME l_time; uint size; + DBUG_ASSERT(fixed == 1); if (!is_time_format) { @@ -1507,25 +1491,18 @@ null_date: String *Item_func_from_unixtime::val_str(String *str) { - DBUG_ASSERT(fixed == 1); - struct tm tm_tmp,*start; - time_t tmp=(time_t) args[0]->val_int(); + struct tm tm_tmp; + time_t tmp; TIME ltime; + DBUG_ASSERT(fixed == 1); + tmp= (time_t) args[0]->val_int(); if ((null_value=args[0]->null_value)) goto null_date; localtime_r(&tmp,&tm_tmp); - start=&tm_tmp; - - ltime.year= start->tm_year+1900; - ltime.month= start->tm_mon+1; - ltime.day= start->tm_mday; - ltime.hour= start->tm_hour; - ltime.minute= start->tm_min; - ltime.second= start->tm_sec; - ltime.second_part= 0; - ltime.neg=0; + + localtime_to_TIME(<ime, &tm_tmp); if (str->alloc(20*MY_CHARSET_BIN_MB_MAXLEN)) goto null_date; @@ -1540,19 +1517,17 @@ null_date: longlong Item_func_from_unixtime::val_int() { + TIME ltime; + struct tm tm_tmp; + time_t tmp; DBUG_ASSERT(fixed == 1); - time_t tmp=(time_t) (ulong) args[0]->val_int(); + + tmp= (time_t) (ulong) args[0]->val_int(); if ((null_value=args[0]->null_value)) return 0; - struct tm tm_tmp,*start; localtime_r(&tmp,&tm_tmp); - start= &tm_tmp; - return ((longlong) ((ulong) ((uint) start->tm_year+1900)*10000L+ - (((uint) start->tm_mon+1)*100+ - (uint) start->tm_mday))*LL(1000000)+ - (longlong) ((ulong) ((uint) start->tm_hour)*10000L+ - (ulong) (((uint) start->tm_min)*100L+ - (uint) start->tm_sec))); + localtime_to_TIME(<ime, &tm_tmp); + return (longlong) TIME_to_ulonglong_datetime(<ime); } bool Item_func_from_unixtime::get_date(TIME *ltime, @@ -1561,17 +1536,9 @@ bool Item_func_from_unixtime::get_date(TIME *ltime, time_t tmp=(time_t) (ulong) args[0]->val_int(); if ((null_value=args[0]->null_value)) return 1; - struct tm tm_tmp,*start; + struct tm tm_tmp; localtime_r(&tmp,&tm_tmp); - start= &tm_tmp; - ltime->year= start->tm_year+1900; - ltime->month= start->tm_mon+1; - ltime->day= start->tm_mday; - ltime->hour= start->tm_hour; - ltime->minute=start->tm_min; - ltime->second=start->tm_sec; - ltime->second_part=0; - ltime->neg=0; + localtime_to_TIME(ltime, &tm_tmp); return 0; } @@ -2035,7 +2002,7 @@ String *Item_date_typecast::val_str(String *str) if (!get_arg0_date(<ime,1) && !str->alloc(11)) { - make_date((DATE_TIME_FORMAT *) 0,<ime, str); + make_date((DATE_TIME_FORMAT *) 0, <ime, str); return str; } diff --git a/sql/lock.cc b/sql/lock.cc index 923932a768a..ac689495ca3 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -240,7 +240,7 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) { if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_READ) { - swap(THR_LOCK_DATA *,*lock,sql_lock->locks[i]); + swap_variables(THR_LOCK_DATA *, *lock, sql_lock->locks[i]); lock++; found++; } @@ -259,7 +259,7 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) { if ((uint) sql_lock->table[i]->reginfo.lock_type >= TL_WRITE_ALLOW_READ) { - swap(TABLE *,*table,sql_lock->table[i]); + swap_variables(TABLE *, *table, sql_lock->table[i]); table++; found++; } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index cf609a7da0a..fb9ff5be771 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -284,6 +284,14 @@ 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 struct st_table; class THD; @@ -995,9 +1003,17 @@ const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format, timestamp_type type); extern bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time, timestamp_type type, String *str); -extern void make_time(DATE_TIME_FORMAT *format, TIME *l_time, String *str); -void make_date(DATE_TIME_FORMAT *format, TIME *l_time, String *str); -void make_datetime(DATE_TIME_FORMAT *format, TIME *l_time, String *str); +void make_datetime(const DATE_TIME_FORMAT *format, const TIME *l_time, + String *str); +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); +ulonglong TIME_to_ulonglong(const TIME *time); int test_if_number(char *str,int *res,bool allow_wildcards); void change_byte(byte *,uint,char,char); diff --git a/sql/opt_range.cc b/sql/opt_range.cc index ddedf4ca3f0..a5d2450e551 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -1401,7 +1401,7 @@ key_and(SEL_ARG *key1,SEL_ARG *key2,uint clone_flag) { if (key1->part > key2->part) { - swap(SEL_ARG *,key1,key2); + swap_variables(SEL_ARG *, key1, key2); clone_flag=swap_clone_flag(clone_flag); } // key1->part < key2->part @@ -1417,7 +1417,7 @@ key_and(SEL_ARG *key1,SEL_ARG *key2,uint clone_flag) key2->type != SEL_ARG::MAYBE_KEY) || key1->type == SEL_ARG::MAYBE_KEY) { // Put simple key in key2 - swap(SEL_ARG *,key1,key2); + swap_variables(SEL_ARG *, key1, key2); clone_flag=swap_clone_flag(clone_flag); } @@ -1559,7 +1559,7 @@ key_or(SEL_ARG *key1,SEL_ARG *key2) { if (key2->use_count == 0 || key1->elements > key2->elements) { - swap(SEL_ARG *,key1,key2); + swap_variables(SEL_ARG *,key1,key2); } else if (!(key1=key1->clone_tree())) return 0; // OOM diff --git a/sql/protocol.cc b/sql/protocol.cc index a5944af829d..44fc4eff9ad 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -455,6 +455,7 @@ void Protocol::init(THD *thd_arg) { thd=thd_arg; packet= &thd->packet; + convert= &thd->convert_buffer; #ifndef DEBUG_OFF field_types= 0; #endif @@ -691,6 +692,26 @@ bool Protocol_simple::store_null() #endif +/* + Auxilary function to convert string to the given character set + and store in network buffer. +*/ + +bool Protocol::store_string_aux(const char *from, uint length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs) +{ + /* 'tocs' is set 0 when client issues SET character_set_results=NULL */ + if (tocs && !my_charset_same(fromcs, tocs) && + fromcs != &my_charset_bin && + tocs != &my_charset_bin) + { + return convert->copy(from, length, fromcs, tocs) || + net_store_data(convert->ptr(), convert->length()); + } + return net_store_data(from, length); +} + + bool Protocol_simple::store(const char *from, uint length, CHARSET_INFO *fromcs, CHARSET_INFO *tocs) { @@ -701,15 +722,7 @@ bool Protocol_simple::store(const char *from, uint length, field_types[field_pos] <= MYSQL_TYPE_GEOMETRY)); field_pos++; #endif - if (tocs && !my_charset_same(fromcs, tocs) && - (fromcs != &my_charset_bin) && - (tocs != &my_charset_bin)) - { - convert.copy(from, length, fromcs, tocs); - return net_store_data(convert.ptr(), convert.length()); - } - else - return net_store_data(from, length); + return store_string_aux(from, length, fromcs, tocs); } @@ -724,15 +737,7 @@ bool Protocol_simple::store(const char *from, uint length, field_types[field_pos] <= MYSQL_TYPE_GEOMETRY)); field_pos++; #endif - if (tocs && !my_charset_same(fromcs, tocs) && - (fromcs != &my_charset_bin) && - (tocs != &my_charset_bin)) - { - convert.copy(from, length, fromcs, tocs); - return net_store_data(convert.ptr(), convert.length()); - } - else - return net_store_data(from, length); + return store_string_aux(from, length, fromcs, tocs); } @@ -826,15 +831,7 @@ bool Protocol_simple::store(Field *field) CHARSET_INFO *tocs= this->thd->variables.character_set_results; field->val_str(&str); - if (tocs && !my_charset_same(field->charset(), tocs) && - (field->charset() != &my_charset_bin) && - (tocs != &my_charset_bin)) - { - convert.copy(str.ptr(), str.length(), str.charset(), tocs); - return net_store_data(convert.ptr(), convert.length()); - } - else - return net_store_data(str.ptr(), str.length()); + return store_string_aux(str.ptr(), str.length(), str.charset(), tocs); } @@ -947,8 +944,9 @@ void Protocol_prep::prepare_for_resend() } -bool Protocol_prep::store(const char *from,uint length, CHARSET_INFO *cs) +bool Protocol_prep::store(const char *from, uint length, CHARSET_INFO *fromcs) { + CHARSET_INFO *tocs= thd->variables.character_set_results; #ifndef DEBUG_OFF DBUG_ASSERT(field_types == 0 || field_types[field_pos] == MYSQL_TYPE_DECIMAL || @@ -956,7 +954,7 @@ bool Protocol_prep::store(const char *from,uint length, CHARSET_INFO *cs) field_types[field_pos] <= MYSQL_TYPE_GEOMETRY)); #endif field_pos++; - return net_store_data(from, length); + return store_string_aux(from, length, fromcs, tocs); } bool Protocol_prep::store(const char *from,uint length, @@ -969,7 +967,7 @@ bool Protocol_prep::store(const char *from,uint length, field_types[field_pos] <= MYSQL_TYPE_GEOMETRY)); #endif field_pos++; - return net_store_data(from, length); + return store_string_aux(from, length, fromcs, tocs); } bool Protocol_prep::store_null() diff --git a/sql/protocol.h b/sql/protocol.h index 17c8f0d321d..41885ec9f1f 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -30,7 +30,7 @@ class Protocol protected: THD *thd; String *packet; - String convert; + String *convert; uint field_pos; #ifndef DEBUG_OFF enum enum_field_types *field_types; @@ -42,6 +42,8 @@ protected: MYSQL_FIELD *next_mysql_field; MEM_ROOT *alloc; #endif + bool store_string_aux(const char *from, uint length, + CHARSET_INFO *fromcs, CHARSET_INFO *tocs); public: Protocol() {} Protocol(THD *thd_arg) { init(thd_arg); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index f7992c3db9e..9d368db0229 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -106,13 +106,13 @@ bool foreign_key_prefix(Key *a, Key *b) if (a->generated) { if (b->generated && a->columns.elements > b->columns.elements) - swap(Key*, a, b); // Put shorter key in 'a' + swap_variables(Key*, a, b); // Put shorter key in 'a' } else { if (!b->generated) return TRUE; // No foreign key - swap(Key*, a, b); // Put generated key in 'a' + swap_variables(Key*, a, b); // Put generated key in 'a' } /* Test if 'a' is a prefix of 'b' */ @@ -504,6 +504,35 @@ bool THD::convert_string(LEX_STRING *to, CHARSET_INFO *to_cs, /* + Convert string from source character set to target character set inplace. + + SYNOPSIS + THD::convert_string + + DESCRIPTION + Convert string using convert_buffer - buffer for character set + conversion shared between all protocols. + + RETURN + 0 ok + !0 out of memory +*/ + +bool THD::convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs) +{ + if (convert_buffer.copy(s->ptr(), s->length(), from_cs, to_cs)) + return TRUE; + /* If convert_buffer >> s copying is more efficient long term */ + if (convert_buffer.alloced_length() >= convert_buffer.length() * 2 || + !s->is_alloced()) + { + return s->copy(convert_buffer); + } + s->swap(convert_buffer); + return FALSE; +} + +/* Update some cache variables when character set changes */ diff --git a/sql/sql_class.h b/sql/sql_class.h index e74aca1434d..7894cf5fb2c 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -594,6 +594,7 @@ public: Protocol_prep protocol_prep; // Binary protocol HASH user_vars; // hash for user variables String packet; // dynamic buffer for network I/O + String convert_buffer; // buffer for charset conversions struct sockaddr_in remote; // client socket address struct rand_struct rand; // used for authentication struct system_variables variables; // Changeable local variables @@ -917,6 +918,9 @@ public: bool convert_string(LEX_STRING *to, CHARSET_INFO *to_cs, const char *from, uint from_length, CHARSET_INFO *from_cs); + + bool convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs); + void add_changed_table(TABLE *table); void add_changed_table(const char *key, long key_length); CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 3aa0e9511a7..5032e9c33f0 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -433,7 +433,7 @@ abort: Prepare items in INSERT statement SYNOPSIS - mysql_prepare_update() + mysql_prepare_insert() thd - thread handler table_list - global table list insert_table_list - local table list of INSERT SELECT_LEX diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 8ddac68bcdb..7596e37de93 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1816,7 +1816,10 @@ bool alloc_query(THD *thd, char *packet, ulong packet_length) return 1; thd->query[packet_length]=0; thd->query_length= packet_length; - thd->packet.shrink(thd->variables.net_buffer_length);// Reclaim some memory + + /* Reclaim some memory */ + thd->packet.shrink(thd->variables.net_buffer_length); + thd->convert_buffer.shrink(thd->variables.net_buffer_length); if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),QUERY_PRIOR); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index a8e2cabe44b..18437265e0e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -76,8 +76,6 @@ Long data handling: #include <mysql.h> #endif -const String my_null_string("NULL", 4, default_charset_info); - /****************************************************************************** Prepared_statement: statement which can contain placeholders ******************************************************************************/ @@ -91,7 +89,6 @@ public: uint last_errno; char last_error[MYSQL_ERRMSG_SIZE]; bool get_longdata_error; - bool log_full_query; #ifndef EMBEDDED_LIBRARY bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end, uchar *read_pos); @@ -239,7 +236,7 @@ static ulong get_param_length(uchar **packet, ulong len) none */ -void set_param_tiny(Item_param *param, uchar **pos, ulong len) +static void set_param_tiny(Item_param *param, uchar **pos, ulong len) { #ifndef EMBEDDED_LIBRARY if (len < 1) @@ -247,55 +244,55 @@ void set_param_tiny(Item_param *param, uchar **pos, ulong len) #endif int8 value= (int8) **pos; param->set_int(param->unsigned_flag ? (longlong) ((uint8) value) : - (longlong) value); + (longlong) value, 4); *pos+= 1; } -void set_param_short(Item_param *param, uchar **pos, ulong len) +static void set_param_short(Item_param *param, uchar **pos, ulong len) { + int16 value; #ifndef EMBEDDED_LIBRARY if (len < 2) return; - int16 value= sint2korr(*pos); + value= sint2korr(*pos); #else - int16 value; shortget(value, *pos); #endif param->set_int(param->unsigned_flag ? (longlong) ((uint16) value) : - (longlong) value); + (longlong) value, 6); *pos+= 2; } -void set_param_int32(Item_param *param, uchar **pos, ulong len) +static void set_param_int32(Item_param *param, uchar **pos, ulong len) { + int32 value; #ifndef EMBEDDED_LIBRARY if (len < 4) return; - int32 value= sint4korr(*pos); + value= sint4korr(*pos); #else - int32 value; longget(value, *pos); #endif param->set_int(param->unsigned_flag ? (longlong) ((uint32) value) : - (longlong) value); + (longlong) value, 11); *pos+= 4; } -void set_param_int64(Item_param *param, uchar **pos, ulong len) +static void set_param_int64(Item_param *param, uchar **pos, ulong len) { + longlong value; #ifndef EMBEDDED_LIBRARY if (len < 8) return; - param->set_int((longlong)sint8korr(*pos)); - *pos+= 8; + value= (longlong) sint8korr(*pos); #else - longlong value; longlongget(value, *pos); - param->set_int(value); #endif + param->set_int(value, 21); + *pos+= 8; } -void set_param_float(Item_param *param, uchar **pos, ulong len) +static void set_param_float(Item_param *param, uchar **pos, ulong len) { #ifndef EMBEDDED_LIBRARY if (len < 4) @@ -307,7 +304,7 @@ void set_param_float(Item_param *param, uchar **pos, ulong len) *pos+= 4; } -void set_param_double(Item_param *param, uchar **pos, ulong len) +static void set_param_double(Item_param *param, uchar **pos, ulong len) { #ifndef EMBEDDED_LIBRARY if (len < 8) @@ -320,9 +317,10 @@ void set_param_double(Item_param *param, uchar **pos, ulong len) } #ifndef EMBEDDED_LIBRARY -void set_param_time(Item_param *param, uchar **pos, ulong len) +static void set_param_time(Item_param *param, uchar **pos, ulong len) { ulong length; + uint day; if ((length= get_param_length(pos, len)) >= 8) { @@ -332,20 +330,33 @@ void set_param_time(Item_param *param, uchar **pos, ulong len) /* TODO: why length is compared with 8 here? */ tm.second_part= (length > 8 ) ? (ulong) sint4korr(to+7): 0; - tm.day= (ulong) sint4korr(to+1); - tm.hour= (uint) to[5]; + /* + 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.year= tm.month= 0; + if (tm.hour > 838) + { + /* TODO: add warning 'Data truncated' here */ + tm.hour= 838; + tm.minute= 59; + tm.second= 59; + } + tm.day= tm.year= tm.month= 0; tm.neg= (bool)to[0]; - param->set_time(&tm, TIMESTAMP_TIME); + param->set_time(&tm, TIMESTAMP_TIME, + MAX_TIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } *pos+= length; } -void set_param_datetime(Item_param *param, uchar **pos, ulong len) +static void set_param_datetime(Item_param *param, uchar **pos, ulong len) { uint length; @@ -356,6 +367,11 @@ void set_param_datetime(Item_param *param, uchar **pos, ulong len) tm.second_part= (length > 7 ) ? (ulong) sint4korr(to+7): 0; + /* + 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. + */ if (length > 4) { tm.hour= (uint) to[4]; @@ -370,12 +386,13 @@ void set_param_datetime(Item_param *param, uchar **pos, ulong len) tm.day= (uint) to[3]; tm.neg= 0; - param->set_time(&tm, TIMESTAMP_DATETIME); + param->set_time(&tm, TIMESTAMP_DATETIME, + MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } *pos+= length; } -void set_param_date(Item_param *param, uchar **pos, ulong len) +static void set_param_date(Item_param *param, uchar **pos, ulong len) { ulong length; @@ -383,7 +400,11 @@ void set_param_date(Item_param *param, uchar **pos, ulong len) { uchar *to= *pos; TIME tm; - + /* + 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. + */ tm.year= (uint) sint2korr(to); tm.month= (uint) to[2]; tm.day= (uint) to[3]; @@ -392,7 +413,8 @@ void set_param_date(Item_param *param, uchar **pos, ulong len) tm.second_part= 0; tm.neg= 0; - param->set_time(&tm, TIMESTAMP_DATE); + param->set_time(&tm, TIMESTAMP_DATE, + MAX_DATE_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } *pos+= length; } @@ -412,8 +434,9 @@ void set_param_time(Item_param *param, uchar **pos, ulong len) tm.year= tm.month= 0; tm.neg= to->neg; + param->set_time(&tm, TIMESTAMP_TIME, + MAX_TIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); - param->set_time(&tm, TIMESTAMP_TIME); } void set_param_datetime(Item_param *param, uchar **pos, ulong len) @@ -431,7 +454,8 @@ void set_param_datetime(Item_param *param, uchar **pos, ulong len) tm.month= to->month; tm.neg= 0; - param->set_time(&tm, TIMESTAMP_DATETIME); + param->set_time(&tm, TIMESTAMP_DATETIME, + MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } void set_param_date(Item_param *param, uchar **pos, ulong len) @@ -449,60 +473,111 @@ void set_param_date(Item_param *param, uchar **pos, ulong len) tm.second_part= 0; tm.neg= 0; - param->set_time(&tm, TIMESTAMP_DATE); + param->set_time(&tm, TIMESTAMP_DATE, + MAX_DATE_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } #endif /*!EMBEDDED_LIBRARY*/ -void set_param_str(Item_param *param, uchar **pos, ulong len) + +static void set_param_str(Item_param *param, uchar **pos, ulong len) { ulong length= get_param_length(pos, len); - param->set_value((const char *)*pos, length); + param->set_str((const char *)*pos, length); *pos+= length; } -static void setup_one_conversion_function(Item_param *param, uchar param_type) + +#undef get_param_length + +static void setup_one_conversion_function(THD *thd, Item_param *param, + uchar param_type) { switch (param_type) { - case FIELD_TYPE_TINY: + case MYSQL_TYPE_TINY: param->set_param_func= set_param_tiny; + param->item_type= Item::INT_ITEM; param->item_result_type= INT_RESULT; break; - case FIELD_TYPE_SHORT: + case MYSQL_TYPE_SHORT: param->set_param_func= set_param_short; + param->item_type= Item::INT_ITEM; param->item_result_type= INT_RESULT; break; - case FIELD_TYPE_LONG: + case MYSQL_TYPE_LONG: param->set_param_func= set_param_int32; + param->item_type= Item::INT_ITEM; param->item_result_type= INT_RESULT; break; - case FIELD_TYPE_LONGLONG: + case MYSQL_TYPE_LONGLONG: param->set_param_func= set_param_int64; + param->item_type= Item::INT_ITEM; param->item_result_type= INT_RESULT; break; - case FIELD_TYPE_FLOAT: + case MYSQL_TYPE_FLOAT: param->set_param_func= set_param_float; + param->item_type= Item::REAL_ITEM; param->item_result_type= REAL_RESULT; break; - case FIELD_TYPE_DOUBLE: + case MYSQL_TYPE_DOUBLE: param->set_param_func= set_param_double; + param->item_type= Item::REAL_ITEM; param->item_result_type= REAL_RESULT; break; - case FIELD_TYPE_TIME: + case MYSQL_TYPE_TIME: param->set_param_func= set_param_time; + param->item_type= Item::STRING_ITEM; param->item_result_type= STRING_RESULT; break; - case FIELD_TYPE_DATE: + case MYSQL_TYPE_DATE: param->set_param_func= set_param_date; + param->item_type= Item::STRING_ITEM; param->item_result_type= STRING_RESULT; break; case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_TIMESTAMP: param->set_param_func= set_param_datetime; + param->item_type= Item::STRING_ITEM; param->item_result_type= STRING_RESULT; break; - default: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: param->set_param_func= set_param_str; + param->value.cs_info.character_set_client= &my_charset_bin; + param->value.cs_info.final_character_set_of_str_value= &my_charset_bin; + param->item_type= Item::STRING_ITEM; param->item_result_type= STRING_RESULT; + break; + default: + /* + The client library ensures that we won't get any other typecodes + except typecodes above and typecodes for string types. Marking + label as 'default' lets us to handle malformed packets as well. + */ + { + CHARSET_INFO *fromcs= thd->variables.character_set_client; + CHARSET_INFO *tocs= thd->variables.collation_connection; + uint32 dummy_offset; + + param->value.cs_info.character_set_client= fromcs; + + /* + Setup source and destination character sets so that they + are different only if conversion is necessary: this will + make later checks easier. + */ + param->value.cs_info.final_character_set_of_str_value= + String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? + tocs : fromcs; + param->set_param_func= set_param_str; + /* + Exact value of max_length is not known unless data is converted to + charset of connection, so we have to set it later. + */ + param->item_type= Item::STRING_ITEM; + param->item_result_type= STRING_RESULT; + } } } @@ -531,23 +606,21 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; - if (param->long_data_supplied) - res= param->query_val_str(&str); - else + if (param->state != Item_param::LONG_DATA_VALUE) { if (is_param_null(null_array, it - begin)) - { param->set_null(); - res= &my_null_string; - } else { if (read_pos >= data_end) DBUG_RETURN(1); param->set_param_func(param, &read_pos, data_end - read_pos); - res= param->query_val_str(&str); } } + res= param->query_val_str(&str); + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ + if (query.replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); @@ -571,7 +644,7 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array, for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; - if (!param->long_data_supplied) + if (param->state != Item_param::LONG_DATA_VALUE) { if (is_param_null(null_array, it - begin)) param->set_null(); @@ -582,6 +655,8 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array, param->set_param_func(param, &read_pos, data_end - read_pos); } } + if (param->convert_str_value(stmt->thd)) + DBUG_RETURN(1); /* out of memory */ } DBUG_RETURN(0); } @@ -603,6 +678,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, */ Item_param **it= stmt->param_array; Item_param **end= it + stmt->param_count; + THD *thd= stmt->thd; for (; it < end; ++it) { ushort typecode; @@ -614,7 +690,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, typecode= sint2korr(read_pos); read_pos+= 2; (**it).unsigned_flag= test(typecode & signed_bit); - setup_one_conversion_function(*it, (uchar) (typecode & ~signed_bit)); + setup_one_conversion_function(thd, *it, (uchar) (typecode & ~signed_bit)); } } *data= read_pos; @@ -625,6 +701,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, static bool emb_insert_params(Prepared_statement *stmt) { + THD *thd= stmt->thd; Item_param **it= stmt->param_array; Item_param **end= it + stmt->param_count; MYSQL_BIND *client_param= stmt->thd->client_params; @@ -634,21 +711,22 @@ static bool emb_insert_params(Prepared_statement *stmt) for (; it < end; ++it, ++client_param) { Item_param *param= *it; - setup_one_conversion_function(param, client_param->buffer_type); - param->unsigned_flag= client_param->is_unsigned; - if (!param->long_data_supplied) + setup_one_conversion_function(thd, param, client_param->buffer_type); + if (param->state != Item_param::LONG_DATA_VALUE) { if (*client_param->is_null) param->set_null(); else { - uchar *buff= (uchar*)client_param->buffer; + uchar *buff= (uchar*) client_param->buffer; param->set_param_func(param, &buff, client_param->length ? *client_param->length : client_param->buffer_length); } } + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ } DBUG_RETURN(0); } @@ -673,25 +751,22 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt) for (; it < end; ++it, ++client_param) { Item_param *param= *it; - setup_one_conversion_function(param, client_param->buffer_type); - if (param->long_data_supplied) - res= param->query_val_str(&str); - else + setup_one_conversion_function(thd, param, client_param->buffer_type); + if (param->state != Item_param::LONG_DATA_VALUE) { if (*client_param->is_null) - { param->set_null(); - res= &my_null_string; - } else { - uchar *buff= (uchar*)client_param->buffer; + uchar *buff= (uchar*)client_param->buffer; param->set_param_func(param, &buff, client_param->length ? *client_param->length : client_param->buffer_length); - res= param->query_val_str(&str); } + res= param->query_val_str(&str); + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ } if (query.replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); @@ -736,7 +811,7 @@ static int mysql_test_insert(Prepared_statement *stmt, TABLE_LIST *insert_table_list= (TABLE_LIST*) lex->select_lex.table_list.first; my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); - DBUG_ENTER("mysql_test_insert_fields"); + DBUG_ENTER("mysql_test_insert"); if ((res= insert_precheck(thd, table_list, update))) DBUG_RETURN(res); @@ -792,7 +867,7 @@ error: Validate UPDATE statement SYNOPSIS - mysql_test_delete() + mysql_test_update() stmt prepared statemen handler tables list of tables queries @@ -1105,7 +1180,7 @@ end: /* - Validate and prepare for execution CRETE TABLE statement + Validate and prepare for execution CREATE TABLE statement SYNOPSIS mysql_test_create_table() @@ -1142,7 +1217,7 @@ static int mysql_test_create_table(Prepared_statement *stmt, /* - Validate and prepare for execution multy update statement + Validate and prepare for execution multi update statement SYNOPSIS mysql_test_multiupdate() @@ -1165,7 +1240,7 @@ static int mysql_test_multiupdate(Prepared_statement *stmt, /* - Validate and prepare for execution multy delete statement + Validate and prepare for execution multi delete statement SYNOPSIS mysql_test_multidelete() @@ -1337,8 +1412,8 @@ error: } /* - Initialize array of parametes in statement from LEX. - (We need to have quick access to items by number in mysql_send_longdata). + Initialize array of parameters in statement from LEX. + (We need to have quick access to items by number in mysql_stmt_get_longdata). This is to avoid using malloc/realloc in the parser. */ @@ -1540,7 +1615,6 @@ static void reset_stmt_params(Prepared_statement *stmt) mysql_stmt_execute() */ - void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) { ulong stmt_id= uint4korr(packet); @@ -1552,7 +1626,8 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) packet+= 9; /* stmt_id + 5 bytes of flags */ - if (!(stmt= find_prepared_statement(thd, stmt_id, "execute", SEND_ERROR))) + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute", + SEND_ERROR))) DBUG_VOID_RETURN; DBUG_PRINT("exec_query:", ("%s", stmt->query)); @@ -1606,7 +1681,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) reset_stmt_params(stmt); close_thread_tables(thd); // to close derived tables thd->set_statement(&thd->stmt_backup); - /* + /* Free Items that were created during this execution of the PS by query optimizer. */ @@ -1616,7 +1691,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) set_params_data_err: reset_stmt_params(stmt); thd->set_statement(&thd->stmt_backup); - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); + my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute"); send_error(thd); DBUG_VOID_RETURN; } @@ -1647,7 +1722,8 @@ void mysql_stmt_reset(THD *thd, char *packet) DBUG_ENTER("mysql_stmt_reset"); - if (!(stmt= find_prepared_statement(thd, stmt_id, "reset", SEND_ERROR))) + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset", + SEND_ERROR))) DBUG_VOID_RETURN; stmt->get_longdata_error= 0; @@ -1677,7 +1753,8 @@ void mysql_stmt_free(THD *thd, char *packet) DBUG_ENTER("mysql_stmt_free"); - if (!(stmt= find_prepared_statement(thd, stmt_id, "close", DONT_SEND_ERROR))) + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_close", + DONT_SEND_ERROR))) DBUG_VOID_RETURN; /* Statement map deletes statement on erase */ @@ -1705,43 +1782,50 @@ void mysql_stmt_free(THD *thd, char *packet) to the server. (No checking that we get a 'end of column' in the server) */ -void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length) +void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) { + ulong stmt_id; + uint param_number; Prepared_statement *stmt; + Item_param *param; + char *packet_end= packet + packet_length - 1; DBUG_ENTER("mysql_stmt_get_longdata"); #ifndef EMBEDDED_LIBRARY - /* The following should never happen */ - if (packet_length < MYSQL_LONG_DATA_HEADER+1) + /* Minimal size of long data packet is 6 bytes */ + if ((ulong) (packet_end - packet) < MYSQL_LONG_DATA_HEADER) { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "get_longdata"); + my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_send_long_data"); DBUG_VOID_RETURN; } #endif - ulong stmt_id= uint4korr(pos); - uint param_number= uint2korr(pos+4); + stmt_id= uint4korr(packet); + packet+= 4; - if (!(stmt=find_prepared_statement(thd, stmt_id, "get_longdata", + if (!(stmt=find_prepared_statement(thd, stmt_id, "mysql_stmt_send_long_data", DONT_SEND_ERROR))) DBUG_VOID_RETURN; + param_number= uint2korr(packet); + packet+= 2; #ifndef EMBEDDED_LIBRARY if (param_number >= stmt->param_count) { /* Error will be sent in execute call */ stmt->get_longdata_error= 1; stmt->last_errno= ER_WRONG_ARGUMENTS; - sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS), "get_longdata"); + sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS), + "mysql_stmt_send_long_data"); DBUG_VOID_RETURN; } - pos+= MYSQL_LONG_DATA_HEADER; // Point to data #endif - Item_param *param= stmt->param_array[param_number]; + param= stmt->param_array[param_number]; + #ifndef EMBEDDED_LIBRARY - param->set_longdata(pos, packet_length-MYSQL_LONG_DATA_HEADER-1); + param->set_longdata(packet, (ulong) (packet_end - packet)); #else param->set_longdata(thd->extra_data, thd->extra_length); #endif @@ -1755,13 +1839,11 @@ Prepared_statement::Prepared_statement(THD *thd_arg) param_array(0), param_count(0), last_errno(0), - get_longdata_error(0), - log_full_query(0) + get_longdata_error(0) { *last_error= '\0'; if (mysql_bin_log.is_open()) { - log_full_query= 1; #ifndef EMBEDDED_LIBRARY set_params= insert_params_withlog; #else diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6da778f6196..062fcbd1976 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3044,12 +3044,12 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count, best_record_count=current_record_count; best_read_time=current_read_time; } - swap(JOIN_TAB*,join->best_ref[idx],*pos); + swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); find_best(join,rest_tables & ~real_table_bit,idx+1, current_record_count,current_read_time); if (thd->killed) return; - swap(JOIN_TAB*,join->best_ref[idx],*pos); + swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); } if (join->select_options & SELECT_STRAIGHT_JOIN) break; // Don't test all combinations diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 8a093738e2b..fb2d1661357 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -450,22 +450,25 @@ bool String::append(const char *s,uint32 arg_length) bool String::append(const char *s,uint32 arg_length, CHARSET_INFO *cs) { - if (!arg_length) // Default argument - if (!(arg_length= (uint32) strlen(s))) + uint32 dummy_offset; + uint32 add_length; + + if (!arg_length && !(arg_length= (uint32)strlen(s))) return FALSE; - if (cs != str_charset && str_charset->mbmaxlen > 1) + + add_length= arg_length * str_charset->mbmaxlen; + if (realloc(str_length + add_length)) + return TRUE; + if (needs_conversion(arg_length, cs, str_charset, &dummy_offset)) { - uint32 add_length=arg_length * str_charset->mbmaxlen; - if (realloc(str_length+ add_length)) - return TRUE; str_length+= copy_and_convert(Ptr+str_length, add_length, str_charset, s, arg_length, cs); - return FALSE; } - if (realloc(str_length+arg_length)) - return TRUE; - memcpy(Ptr+str_length,s,arg_length); - str_length+=arg_length; + else + { + memcpy(Ptr + str_length, s, arg_length); + str_length+= arg_length; + } return FALSE; } @@ -858,3 +861,23 @@ void String::print(String *str) } } } + + +/* + Exchange state of this object and argument. + + SYNOPSIS + String::swap() + + RETURN + Target string will contain state of this object and vice versa. +*/ + +void String::swap(String &s) +{ + swap_variables(char *, Ptr, s.Ptr); + swap_variables(uint32, str_length, s.str_length); + swap_variables(uint32, Alloced_length, s.Alloced_length); + swap_variables(bool, alloced, s.alloced); + swap_variables(CHARSET_INFO*, str_charset, s.str_charset); +} diff --git a/sql/sql_string.h b/sql/sql_string.h index 32333b3b381..c24511a9f74 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -299,4 +299,7 @@ public: return FALSE; } void print(String *print); + + /* Swap two string objects. Efficient way to exchange data without memcpy. */ + void swap(String &s); }; diff --git a/sql/time.cc b/sql/time.cc index db05d606292..6d15fa184a1 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -1255,9 +1255,15 @@ const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format, MySQL doesn't support comparing of date/time/datetime strings that are not in arbutary order as dates are compared as strings in some context) + 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. ****************************************************************************/ -void make_time(DATE_TIME_FORMAT *format, TIME *l_time, String *str) +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(), @@ -1271,7 +1277,8 @@ void make_time(DATE_TIME_FORMAT *format, TIME *l_time, String *str) } -void make_date(DATE_TIME_FORMAT *format, TIME *l_time, String *str) +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(), @@ -1284,7 +1291,8 @@ void make_date(DATE_TIME_FORMAT *format, TIME *l_time, String *str) } -void make_datetime(DATE_TIME_FORMAT *format, TIME *l_time, String *str) +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(), @@ -1330,3 +1338,111 @@ void make_truncated_value_warning(THD *thd, const char *str_val, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TRUNCATED_WRONG_VALUE, warn_buff); } + + +/* Convert time value to integer in YYYYMMDDHHMMSS format */ + +ulonglong TIME_to_ulonglong_datetime(const TIME *time) +{ + return ((ulonglong) (time->year * 10000UL + + time->month * 100UL + + time->day) * ULL(1000000) + + (ulonglong) (time->hour * 10000UL + + time->minute * 100UL + + time->second)); +} + + +/* Convert TIME value to integer in YYYYMMDD format */ + +ulonglong TIME_to_ulonglong_date(const TIME *time) +{ + return (ulonglong) (time->year * 10000UL + time->month * 100UL + time->day); +} + + +/* + Convert TIME value to integer in HHMMSS format. + This function doesn't take into account time->day member: + it's assumed that days have been converted to hours already. +*/ + +ulonglong TIME_to_ulonglong_time(const TIME *time) +{ + return (ulonglong) (time->hour * 10000UL + + time->minute * 100UL + + time->second); +} + + +/* + Convert struct TIME (date and time split into year/month/day/hour/... + to a number in format YYYYMMDDHHMMSS (DATETIME), + YYYYMMDD (DATE) or HHMMSS (TIME). + + SYNOPSIS + TIME_to_ulonglong() + + DESCRIPTION + The function is used when we need to convert value of time item + to a number if it's used in numeric context, i. e.: + SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0; + SELECT ?+1; + + NOTE + This function doesn't check that given TIME structure members are + in valid range. If they are not, return value won't reflect any + valid date either. +*/ + +ulonglong TIME_to_ulonglong(const TIME *time) +{ + switch (time->time_type) { + case TIMESTAMP_DATETIME: + return TIME_to_ulonglong_datetime(time); + case TIMESTAMP_DATE: + return TIME_to_ulonglong_date(time); + case TIMESTAMP_TIME: + return TIME_to_ulonglong_time(time); + case TIMESTAMP_NONE: + case TIMESTAMP_DATETIME_ERROR: + return ULL(0); + default: + DBUG_ASSERT(0); + } + 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 TIMESTAMP_DATETIME: + make_datetime((DATE_TIME_FORMAT*) 0, time, str); + break; + case TIMESTAMP_DATE: + make_date((DATE_TIME_FORMAT*) 0, time, str); + break; + case TIMESTAMP_TIME: + make_time((DATE_TIME_FORMAT*) 0, time, str); + break; + case TIMESTAMP_NONE: + case TIMESTAMP_DATETIME_ERROR: + str->length(0); + str->set_charset(&my_charset_bin); + break; + default: + DBUG_ASSERT(0); + } +} |