diff options
-rw-r--r-- | include/my_global.h | 2 | ||||
-rw-r--r-- | include/my_sys.h | 2 | ||||
-rw-r--r-- | include/mysql_com.h | 2 | ||||
-rw-r--r-- | isam/_search.c | 8 | ||||
-rw-r--r-- | isam/test2.c | 2 | ||||
-rw-r--r-- | libmysql/libmysql.c | 109 | ||||
-rw-r--r-- | myisam/mi_test2.c | 2 | ||||
-rw-r--r-- | mysys/charset.c | 58 | ||||
-rw-r--r-- | mysys/my_chsize.c | 2 | ||||
-rw-r--r-- | mysys/my_compress.c | 2 | ||||
-rw-r--r-- | mysys/my_handler.c | 8 | ||||
-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 | ||||
-rw-r--r-- | tests/client_test.c | 209 |
30 files changed, 1060 insertions, 529 deletions
diff --git a/include/my_global.h b/include/my_global.h index e7d924eb0ca..6916ea61e19 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -382,7 +382,7 @@ typedef unsigned short ushort; #define CMP_NUM(a,b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1) #define sgn(a) (((a) < 0) ? -1 : ((a) > 0) ? 1 : 0) -#define swap(t,a,b) { register t dummy; dummy = a; a = b; b = dummy; } +#define swap_variables(t, a, b) { register t dummy; dummy= a; a= b; b= dummy; } #define test(a) ((a) ? 1 : 0) #define set_if_bigger(a,b) do { if ((a) < (b)) (a)=(b); } while(0) #define set_if_smaller(a,b) do { if ((a) > (b)) (a)=(b); } while(0) diff --git a/include/my_sys.h b/include/my_sys.h index 8beaa00eb16..922e6d8cff4 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -762,6 +762,8 @@ extern char *get_charsets_dir(char *buf); extern my_bool my_charset_same(CHARSET_INFO *cs1, CHARSET_INFO *cs2); extern my_bool init_compiled_charsets(myf flags); extern void add_compiled_collation(CHARSET_INFO *cs); +extern ulong escape_string_for_mysql(CHARSET_INFO *charset_info, char *to, + const char *from, ulong length); #ifdef __WIN__ extern my_bool have_tcpip; /* Is set if tcpip is used */ diff --git a/include/mysql_com.h b/include/mysql_com.h index ada2bd1f679..a1da896af38 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -359,6 +359,6 @@ char *net_store_length(char *pkg, ulonglong length); #define NULL_LENGTH ((unsigned long) ~0) /* For net_store_length */ #define MYSQL_STMT_HEADER 4 -#define MYSQL_LONG_DATA_HEADER 6 +#define MYSQL_LONG_DATA_HEADER 6 #endif diff --git a/isam/_search.c b/isam/_search.c index 7b61abfb46b..fbffd6786e1 100644 --- a/isam/_search.c +++ b/isam/_search.c @@ -489,7 +489,7 @@ int _nisam_key_cmp(register N_KEYSEG *keyseg, register uchar *a, register uchar int alength,blength; if (swap_flag) - swap(uchar*,a,b); + swap_variables(uchar*, a, b); alength= *a++; blength= *b++; if ((flag=(int) (keyseg->base.length-key_length)) < 0) flag=0; @@ -504,7 +504,7 @@ int _nisam_key_cmp(register N_KEYSEG *keyseg, register uchar *a, register uchar if (*a == '-' && *b == '-') { swap_flag=1; - swap(uchar*,a,b); + swap_variables(uchar*, a, b); } end=a+alength; while (a < end) @@ -531,7 +531,7 @@ int _nisam_key_cmp(register N_KEYSEG *keyseg, register uchar *a, register uchar if (swap_flag) { end=b+(int) (end-a); - swap(uchar*,a,b); + swap_variables(uchar*, a, b); } while (a < end) if (*a++ != *b++) @@ -550,7 +550,7 @@ int _nisam_key_cmp(register N_KEYSEG *keyseg, register uchar *a, register uchar } } if (swap_flag) - swap(uchar*,a,b); + swap_variables(uchar*, a, b); break; } #ifdef HAVE_LONG_LONG diff --git a/isam/test2.c b/isam/test2.c index 14a40f39865..4b22f2d679c 100644 --- a/isam/test2.c +++ b/isam/test2.c @@ -526,7 +526,7 @@ int main(int argc, char *argv[]) if (j != 0 && k != 0) { if (j > k) - swap(int,j,k); + swap_variables(int, j, k); sprintf(key,"%6d",j); sprintf(key2,"%6d",k); range_records=nisam_records_in_range(file,0,key,0,HA_READ_AFTER_KEY, diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 2214ca68c81..b8ede73a0c5 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -88,8 +88,6 @@ my_bool net_flush(NET *net); static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data); static void append_wild(char *to,char *end,const char *wild); sig_handler pipe_sig_handler(int sig); -static ulong mysql_sub_escape_string(CHARSET_INFO *charset_info, char *to, - const char *from, ulong length); static my_bool mysql_client_init= 0; static my_bool org_my_init_done= 0; @@ -1570,73 +1568,14 @@ void my_net_local_init(NET *net) ulong STDCALL mysql_escape_string(char *to,const char *from,ulong length) { - return mysql_sub_escape_string(default_charset_info,to,from,length); + return escape_string_for_mysql(default_charset_info, to, from, length); } ulong STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, ulong length) { - return mysql_sub_escape_string(mysql->charset,to,from,length); -} - - -static ulong -mysql_sub_escape_string(CHARSET_INFO *charset_info, char *to, - const char *from, ulong length) -{ - const char *to_start=to; - const char *end; -#ifdef USE_MB - my_bool use_mb_flag=use_mb(charset_info); -#endif - for (end=from+length; from != end ; from++) - { -#ifdef USE_MB - int l; - if (use_mb_flag && (l = my_ismbchar(charset_info, from, end))) - { - while (l--) - *to++ = *from++; - from--; - continue; - } -#endif - switch (*from) { - case 0: /* Must be escaped for 'mysql' */ - *to++= '\\'; - *to++= '0'; - break; - case '\n': /* Must be escaped for logs */ - *to++= '\\'; - *to++= 'n'; - break; - case '\r': - *to++= '\\'; - *to++= 'r'; - break; - case '\\': - *to++= '\\'; - *to++= '\\'; - break; - case '\'': - *to++= '\\'; - *to++= '\''; - break; - case '"': /* Better safe than sorry */ - *to++= '\\'; - *to++= '"'; - break; - case '\032': /* This gives problems on Win32 */ - *to++= '\\'; - *to++= 'Z'; - break; - default: - *to++= *from; - } - } - *to=0; - return (ulong) (to-to_start); + return escape_string_for_mysql(mysql->charset, to, from, length); } @@ -1978,7 +1917,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) { /* This is second prepare with another statement */ - char buff[4]; + char buff[MYSQL_STMT_HEADER]; /* 4 bytes - stmt id */ mysql_stmt_free_result(stmt); /* @@ -2353,6 +2292,14 @@ static my_bool execute(MYSQL_STMT * stmt, char *packet, ulong length) stmt->insert_id= mysql->insert_id; DBUG_RETURN(0); } + + +static void store_param_type(char **pos, MYSQL_BIND *param) +{ + uint typecode= param->buffer_type | (param->is_unsigned ? 32768 : 0); + int2store(*pos, typecode); + *pos+= 2; +} int cli_stmt_execute(MYSQL_STMT *stmt) @@ -2390,11 +2337,7 @@ int cli_stmt_execute(MYSQL_STMT *stmt) that is sent to the server. */ for (param= stmt->params; param < param_end ; param++) - { - uint typecode= param->buffer_type | (param->is_unsigned ? 32768 : 0); - int2store(net->write_pos, typecode); - net->write_pos+= 2; - } + store_param_type((char**) &net->write_pos, param); } for (param= stmt->params; param < param_end; param++) @@ -2804,25 +2747,27 @@ mysql_stmt_send_long_data(MYSQL_STMT *stmt, uint param_number, param->param_number); DBUG_RETURN(1); } - /* Mark for execute that the result is already sent */ + + /* + Send long data packet if there is data or we're sending long data + for the first time. + */ if (length || param->long_data_used == 0) { MYSQL *mysql= stmt->mysql; - char *packet, extra_data[MYSQL_LONG_DATA_HEADER]; - + /* Packet header: stmt id (4 bytes), param no (2 bytes) */ + char buff[MYSQL_LONG_DATA_HEADER]; + + int4store(buff, stmt->stmt_id); + int2store(buff + 4, param_number); param->long_data_used= 1; - packet= extra_data; - int4store(packet, stmt->stmt_id); packet+=4; - int2store(packet, param_number); packet+=2; - /* Note that we don't get any ok packet from the server in this case This is intentional to save bandwidth. */ - if ((*mysql->methods->advanced_command)(mysql, COM_LONG_DATA, extra_data, - MYSQL_LONG_DATA_HEADER, data, - length, 1)) + if ((*mysql->methods->advanced_command)(mysql, COM_LONG_DATA, buff, + sizeof(buff), data, length, 1)) { set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, mysql->net.sqlstate); @@ -3962,7 +3907,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) mysql->stmts= list_delete(mysql->stmts, &stmt->list); if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) { - char buff[4]; + char buff[MYSQL_STMT_HEADER]; /* 4 bytes - stmt id */ if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) mysql->unbuffered_fetch_owner= 0; @@ -3997,7 +3942,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) { - char buff[MYSQL_STMT_HEADER]; + char buff[MYSQL_STMT_HEADER]; /* packet header: 4 bytes for stmt id */ MYSQL *mysql; MYSQL_BIND *param, *param_end; DBUG_ENTER("mysql_stmt_reset"); @@ -4010,7 +3955,7 @@ my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) mysql= stmt->mysql->last_used_con; int4store(buff, stmt->stmt_id); /* Send stmt id to server */ if ((*mysql->methods->advanced_command)(mysql, COM_RESET_STMT, buff, - MYSQL_STMT_HEADER,0,0,0)) + sizeof(buff), 0, 0, 0)) { set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, mysql->net.sqlstate); diff --git a/myisam/mi_test2.c b/myisam/mi_test2.c index 56d8357536a..95c8ce56a13 100644 --- a/myisam/mi_test2.c +++ b/myisam/mi_test2.c @@ -643,7 +643,7 @@ int main(int argc, char *argv[]) { key_range min_key, max_key; if (j > k) - swap(int,j,k); + swap_variables(int, j, k); sprintf(key,"%6d",j); sprintf(key2,"%6d",k); diff --git a/mysys/charset.c b/mysys/charset.c index e58c851cf7c..c80b00db397 100644 --- a/mysys/charset.c +++ b/mysys/charset.c @@ -622,3 +622,61 @@ CHARSET_INFO *get_charset_by_csname(const char *cs_name, DBUG_RETURN(cs); } + + +ulong escape_string_for_mysql(CHARSET_INFO *charset_info, char *to, + const char *from, ulong length) +{ + const char *to_start= to; + const char *end; +#ifdef USE_MB + my_bool use_mb_flag= use_mb(charset_info); +#endif + for (end= from + length; from != end; from++) + { +#ifdef USE_MB + int l; + if (use_mb_flag && (l= my_ismbchar(charset_info, from, end))) + { + while (l--) + *to++= *from++; + from--; + continue; + } +#endif + switch (*from) { + case 0: /* Must be escaped for 'mysql' */ + *to++= '\\'; + *to++= '0'; + break; + case '\n': /* Must be escaped for logs */ + *to++= '\\'; + *to++= 'n'; + break; + case '\r': + *to++= '\\'; + *to++= 'r'; + break; + case '\\': + *to++= '\\'; + *to++= '\\'; + break; + case '\'': + *to++= '\\'; + *to++= '\''; + break; + case '"': /* Better safe than sorry */ + *to++= '\\'; + *to++= '"'; + break; + case '\032': /* This gives problems on Win32 */ + *to++= '\\'; + *to++= 'Z'; + break; + default: + *to++= *from; + } + } + *to= 0; + return (ulong) (to - to_start); +} diff --git a/mysys/my_chsize.c b/mysys/my_chsize.c index 653ea569172..cf26428d65f 100644 --- a/mysys/my_chsize.c +++ b/mysys/my_chsize.c @@ -89,7 +89,7 @@ int my_chsize(File fd, my_off_t newlength, int filler, myf MyFlags) We should never come here on any modern machine */ VOID(my_seek(fd, newlength, MY_SEEK_SET, MYF(MY_WME+MY_FAE))); - swap(my_off_t, newlength, oldsize); + swap_variables(my_off_t, newlength, oldsize); } #endif diff --git a/mysys/my_compress.c b/mysys/my_compress.c index dd076311188..0e37d2fef9b 100644 --- a/mysys/my_compress.c +++ b/mysys/my_compress.c @@ -68,7 +68,7 @@ byte *my_compress_alloc(const byte *packet, ulong *len, ulong *complen) DBUG_PRINT("note",("Packet got longer on compression; Not compressed")); return 0; } - swap(ulong, *len, *complen); /* *len is now packet length */ + swap_variables(ulong, *len, *complen); /* *len is now packet length */ return compbuf; } diff --git a/mysys/my_handler.c b/mysys/my_handler.c index 35f620ccbcb..de0fba56d21 100644 --- a/mysys/my_handler.c +++ b/mysys/my_handler.c @@ -331,7 +331,7 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, if (keyseg->flag & HA_REVERSE_SORT) { - swap(uchar*,a,b); + swap_variables(uchar*, a, b); swap_flag=1; /* Remember swap of a & b */ end= a+ (int) (end-b); } @@ -356,8 +356,8 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, if (*b != '-') return -1; a++; b++; - swap(uchar*,a,b); - swap(int,alength,blength); + swap_variables(uchar*, a, b); + swap_variables(int, alength, blength); swap_flag=1-swap_flag; alength--; blength--; end=a+alength; @@ -385,7 +385,7 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, } if (swap_flag) /* Restore pointers */ - swap(uchar*,a,b); + swap_variables(uchar*, a, b); break; } #ifdef HAVE_LONG_LONG 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); + } +} diff --git a/tests/client_test.c b/tests/client_test.c index 8225eb17707..725d794c4a5 100644 --- a/tests/client_test.c +++ b/tests/client_test.c @@ -5622,12 +5622,22 @@ static void test_bind_date_conv(uint row_count) { tm[i].neg= 0; tm[i].second_part= second_part+count; - tm[i].year= year+count; - tm[i].month= month+count; - tm[i].day= day+count; - tm[i].hour= hour+count; - tm[i].minute= minute+count; - tm[i].second= sec+count; + if (bind[i].buffer_type != MYSQL_TYPE_TIME) + { + tm[i].year= year+count; + tm[i].month= month+count; + tm[i].day= day+count; + } + else + tm[i].year= tm[i].month= tm[i].day= 0; + if (bind[i].buffer_type != MYSQL_TYPE_DATE) + { + tm[i].hour= hour+count; + tm[i].minute= minute+count; + tm[i].second= sec+count; + } + else + tm[i].hour= tm[i].minute= tm[i].second = 0; } rc = mysql_execute(stmt); check_execute(stmt, rc); @@ -9455,6 +9465,7 @@ select col1 FROM t1 where col1=2"); myquery(rc); } + /* This tests for various mysql_send_long_data bugs described in #1664 */ @@ -9681,6 +9692,189 @@ static void test_union_param() mysql_stmt_close(stmt); } + + +static void test_ps_i18n() +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text; + MYSQL_BIND bind_array[2]; + + const char *koi8= "îÕ, ÚÁ ÒÙÂÁÌËÕ"; + const char *cp1251= "Íó, çà ðûáàëêó"; + char buf1[16], buf2[16]; + ulong buf1_len, buf2_len; + + + myheader("test_ps_i18n"); + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + + /* + Create table with binary columns, set session character set to cp1251, + client character set to koi8, and make sure that there is conversion + on insert and no conversion on select + */ + + stmt_text= "CREATE TABLE t1 (c1 VARBINARY(255), c2 VARBINARY(255))"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + + stmt_text= "SET CHARACTER_SET_CLIENT=koi8r, " + "CHARACTER_SET_CONNECTION=cp1251, " + "CHARACTER_SET_RESULTS=koi8r"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + + bzero(bind_array, sizeof(bind_array)); + + bind_array[0].buffer_type= MYSQL_TYPE_STRING; + bind_array[0].buffer= (char*) koi8; + bind_array[0].buffer_length= strlen(koi8); + + bind_array[1].buffer_type= MYSQL_TYPE_STRING; + bind_array[1].buffer= (char*) koi8; + bind_array[1].buffer_length= strlen(koi8); + + stmt= mysql_stmt_init(mysql); + check_stmt(stmt); + + stmt_text= "INSERT INTO t1 (c1, c2) VALUES (?, ?)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + mysql_stmt_bind_param(stmt, bind_array); + + mysql_stmt_send_long_data(stmt, 0, koi8, strlen(koi8)); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + stmt_text= "SELECT c1, c2 FROM t1"; + + /* c1 and c2 are binary so no conversion will be done on select */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bind_array[0].buffer= buf1; + bind_array[0].buffer_length= sizeof(buf1); + bind_array[0].length= &buf1_len; + + bind_array[1].buffer= buf2; + bind_array[1].buffer_length= sizeof(buf2); + bind_array[1].length= &buf2_len; + + mysql_stmt_bind_result(stmt, bind_array); + + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + + assert(buf1_len == strlen(cp1251)); + assert(buf2_len == strlen(cp1251)); + assert(!memcmp(buf1, cp1251, buf1_len)); + assert(!memcmp(buf2, cp1251, buf1_len)); + + rc= mysql_stmt_fetch(stmt); + assert(rc == MYSQL_NO_DATA); + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + + /* + Now create table with two cp1251 columns, set client character + set to koi8 and supply columns of one row as string and another as + binary data. Binary data must not be converted on insert, and both + columns must be converted to client character set on select. + */ + + stmt_text= "CREATE TABLE t1 (c1 VARCHAR(255) CHARACTER SET cp1251, " + "c2 VARCHAR(255) CHARACTER SET cp1251)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + myquery(rc); + + stmt_text= "INSERT INTO t1 (c1, c2) VALUES (?, ?)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + /* this data must be converted */ + bind_array[0].buffer_type= MYSQL_TYPE_STRING; + bind_array[0].buffer= (char*) koi8; + bind_array[0].buffer_length= strlen(koi8); + + bind_array[1].buffer_type= MYSQL_TYPE_STRING; + bind_array[1].buffer= (char*) koi8; + bind_array[1].buffer_length= strlen(koi8); + + mysql_stmt_bind_param(stmt, bind_array); + + mysql_stmt_send_long_data(stmt, 0, koi8, strlen(koi8)); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + /* this data must not be converted */ + bind_array[0].buffer_type= MYSQL_TYPE_BLOB; + bind_array[0].buffer= (char*) cp1251; + bind_array[0].buffer_length= strlen(cp1251); + + bind_array[1].buffer_type= MYSQL_TYPE_BLOB; + bind_array[1].buffer= (char*) cp1251; + bind_array[1].buffer_length= strlen(cp1251); + + mysql_stmt_bind_param(stmt, bind_array); + + mysql_stmt_send_long_data(stmt, 0, cp1251, strlen(cp1251)); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + /* Fetch data and verify that rows are in koi8 */ + + stmt_text= "SELECT c1, c2 FROM t1"; + + /* c1 and c2 are binary so no conversion will be done on select */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bind_array[0].buffer= buf1; + bind_array[0].buffer_length= sizeof(buf1); + bind_array[0].length= &buf1_len; + + bind_array[1].buffer= buf2; + bind_array[1].buffer_length= sizeof(buf2); + bind_array[1].length= &buf2_len; + + mysql_stmt_bind_result(stmt, bind_array); + + while ((rc= mysql_stmt_fetch(stmt)) == 0) + { + assert(buf1_len == strlen(koi8)); + assert(buf2_len == strlen(koi8)); + assert(!memcmp(buf1, koi8, buf1_len)); + assert(!memcmp(buf2, koi8, buf1_len)); + } + assert(rc == MYSQL_NO_DATA); + mysql_stmt_close(stmt); + + stmt_text= "DROP TABLE t1"; + mysql_real_query(mysql, stmt_text, strlen(stmt_text)); +} + /* Read and parse arguments and MySQL options from my.cnf */ @@ -9825,7 +10019,6 @@ int main(int argc, char **argv) start_time= time((time_t *)0); - test_union_param(); client_query(); /* simple client query test */ #if NOT_YET_WORKING /* Used for internal new development debugging */ @@ -9967,8 +10160,10 @@ int main(int argc, char **argv) test_union2(); /* repeatable execution of union (Bug #3577) */ test_bug1664(); /* test for bugs in mysql_stmt_send_long_data() call (Bug #1664) */ + test_union_param(); test_order_param(); /* ORDER BY with parameters in select list (Bug #3686 */ + test_ps_i18n(); /* test for i18n support in binary protocol */ end_time= time((time_t *)0); total_time+= difftime(end_time, start_time); |