diff options
Diffstat (limited to 'sql/item.cc')
-rw-r--r-- | sql/item.cc | 707 |
1 files changed, 603 insertions, 104 deletions
diff --git a/sql/item.cc b/sql/item.cc index 357cc6d7fe4..e930143edfa 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1,4 +1,4 @@ -/* Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc. +/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,7 +17,9 @@ #ifdef USE_PRAGMA_IMPLEMENTATION #pragma implementation // gcc: Class implementation #endif -#include "mysql_priv.h" +#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_priv.h" +#include "unireg.h" // REQUIRED: for other includes #include <mysql.h> #include <m_ctype.h> #include "my_dir.h" @@ -25,6 +27,19 @@ #include "sp_head.h" #include "sql_trigger.h" #include "sql_select.h" +#include "sql_show.h" // append_identifier +#include "sql_view.h" // VIEW_ANY_SQL +#include "sql_time.h" // str_to_datetime_with_warn, + // make_truncated_value_warning +#include "sql_acl.h" // get_column_grant, + // SELECT_ACL, UPDATE_ACL, + // INSERT_ACL, + // check_grant_column +#include "sql_base.h" // enum_resolution_type, + // REPORT_EXCEPT_NOT_FOUND, + // find_item_in_list, + // RESOLVED_AGAINST_ALIAS, ... +#include "log_event.h" // append_query_string const String my_null_string("NULL", 4, default_charset_info); @@ -201,6 +216,35 @@ bool Item::val_bool() } +/* + For the items which don't have its own fast val_str_ascii() + implementation we provide a generic slower version, + which converts from the Item character set to ASCII. + For better performance conversion happens only in + case of a "tricky" Item character set (e.g. UCS2). + Normally conversion does not happen. +*/ +String *Item::val_str_ascii(String *str) +{ + if (!(collation.collation->state & MY_CS_NONASCII)) + return val_str(str); + + DBUG_ASSERT(str != &str_value); + + uint errors; + String *res= val_str(&str_value); + if (!res) + return 0; + + if ((null_value= str->copy(res->ptr(), res->length(), + collation.collation, &my_charset_latin1, + &errors))) + return 0; + + return str; +} + + String *Item::val_string_from_real(String *str) { double nr= val_real(); @@ -263,10 +307,11 @@ my_decimal *Item::val_decimal_from_string(my_decimal *decimal_value) res->ptr(), res->length(), res->charset(), decimal_value) & E_DEC_BAD_NUM) { + ErrConvString err(res); push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, ER(ER_TRUNCATED_WRONG_VALUE), "DECIMAL", - str_value.c_ptr()); + err.ptr()); } return decimal_value; } @@ -441,10 +486,11 @@ uint Item::decimal_precision() const if ((restype == DECIMAL_RESULT) || (restype == INT_RESULT)) { uint prec= - my_decimal_length_to_precision(max_length, decimals, unsigned_flag); + my_decimal_length_to_precision(max_char_length(), decimals, + unsigned_flag); return min(prec, DECIMAL_MAX_PRECISION); } - return min(max_length, DECIMAL_MAX_PRECISION); + return min(max_char_length(), DECIMAL_MAX_PRECISION); } @@ -793,15 +839,40 @@ Item *Item::safe_charset_converter(CHARSET_INFO *tocs) */ Item *Item_num::safe_charset_converter(CHARSET_INFO *tocs) { + /* + Item_num returns pure ASCII result, + so conversion is needed only in case of "tricky" character + sets like UCS2. If tocs is not "tricky", return the item itself. + */ + if (!(tocs->state & MY_CS_NONASCII)) + return this; + Item_string *conv; - char buf[64]; - String *s, tmp(buf, sizeof(buf), &my_charset_bin); - s= val_str(&tmp); - if ((conv= new Item_string(s->ptr(), s->length(), s->charset()))) + uint conv_errors; + char buf[64], buf2[64]; + String tmp(buf, sizeof(buf), &my_charset_bin); + String cstr(buf2, sizeof(buf2), &my_charset_bin); + String *ostr= val_str(&tmp); + char *ptr; + cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); + if (conv_errors || !(conv= new Item_string(cstr.ptr(), cstr.length(), + cstr.charset(), + collation.derivation))) { - conv->str_value.copy(); - conv->str_value.mark_as_const(); + /* + Safe conversion is not possible (or EOM). + We could not convert a string into the requested character set + without data loss. The target charset does not cover all the + characters from the string. Operation cannot be done correctly. + */ + return NULL; } + if (!(ptr= current_thd->strmake(cstr.ptr(), cstr.length()))) + return NULL; + conv->str_value.set(ptr, cstr.length(), cstr.charset()); + /* Ensure that no one is going to change the result string */ + conv->str_value.mark_as_const(); + conv->fix_char_length(max_char_length()); return conv; } @@ -864,7 +935,7 @@ Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs) cnvitem->max_length= cnvitem->str_value.numchars() * tocs->mbmaxlen; return cnvitem; } - return NULL; + return Item::safe_charset_converter(tocs); } @@ -920,7 +991,7 @@ bool Item::get_date(MYSQL_TIME *ltime,uint fuzzydate) char buff[40]; String tmp(buff,sizeof(buff), &my_charset_bin),*res; if (!(res=val_str(&tmp)) || - str_to_datetime_with_warn(res->ptr(), res->length(), + str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) goto err; } @@ -955,8 +1026,8 @@ bool Item::get_time(MYSQL_TIME *ltime) { char buff[40]; String tmp(buff,sizeof(buff),&my_charset_bin),*res; - if (!(res=val_str(&tmp)) || - str_to_time_with_warn(res->ptr(), res->length(), ltime)) + if (!(res=val_str_ascii(&tmp)) || + str_to_time_with_warn(res->charset(), res->ptr(), res->length(), ltime)) { bzero((char*) ltime,sizeof(*ltime)); return 1; @@ -985,7 +1056,7 @@ int Item::save_in_field_no_warnings(Field *field, bool no_conversions) THD *thd= table->in_use; enum_check_fields tmp= thd->count_cuted_fields; my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->write_set); - ulong sql_mode= thd->variables.sql_mode; + ulonglong sql_mode= thd->variables.sql_mode; thd->variables.sql_mode&= ~(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE); thd->count_cuted_fields= CHECK_FIELD_IGNORE; res= save_in_field(field, no_conversions); @@ -1112,7 +1183,9 @@ Item_splocal::Item_splocal(const LEX_STRING &sp_var_name, enum_field_types sp_var_type, uint pos_in_q, uint len_in_q) :Item_sp_variable(sp_var_name.str, sp_var_name.length), - m_var_idx(sp_var_idx), pos_in_query(pos_in_q), len_in_query(len_in_q) + m_var_idx(sp_var_idx), + limit_clause_param(FALSE), + pos_in_query(pos_in_q), len_in_query(len_in_q) { maybe_null= TRUE; @@ -1206,7 +1279,7 @@ void Item_case_expr::print(String *str, enum_query_type) { if (str->reserve(MAX_INT_WIDTH + sizeof("case_expr@"))) return; /* purecov: inspected */ - VOID(str->append(STRING_WITH_LEN("case_expr@"))); + (void) str->append(STRING_WITH_LEN("case_expr@")); str->qs_append(m_case_expr_id); } @@ -1446,7 +1519,12 @@ left_is_superset(DTCollation *left, DTCollation *right) if (left->collation->state & MY_CS_UNICODE && (left->derivation < right->derivation || (left->derivation == right->derivation && - !(right->collation->state & MY_CS_UNICODE)))) + (!(right->collation->state & MY_CS_UNICODE) || + /* The code below makes 4-byte utf8 a superset over 3-byte utf8 */ + (left->collation->state & MY_CS_UNICODE_SUPPLEMENT && + !(right->collation->state & MY_CS_UNICODE_SUPPLEMENT) && + left->collation->mbmaxlen > right->collation->mbmaxlen && + left->collation->mbminlen == right->collation->mbminlen))))) return TRUE; /* Allow convert from ASCII */ if (right->repertoire == MY_REPERTOIRE_ASCII && @@ -1660,6 +1738,11 @@ bool agg_item_collations(DTCollation &c, const char *fname, my_coll_agg_error(av, count, fname, item_sep); return TRUE; } + + /* If all arguments where numbers, reset to @@collation_connection */ + if (c.derivation == DERIVATION_NUMERIC) + c.set(Item::default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_NUMERIC); + return FALSE; } @@ -1698,18 +1781,35 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, In case we're in statement prepare, create conversion item in its memory: it will be reused on each execute. */ - arena= thd->is_stmt_prepare() ? thd->activate_stmt_arena_if_needed(&backup) - : NULL; + arena= thd->activate_stmt_arena_if_needed(&backup); for (i= 0, arg= args; i < nargs; i++, arg+= item_sep) { Item* conv; uint32 dummy_offset; - if (!String::needs_conversion(0, (*arg)->collation.collation, + if (!String::needs_conversion(1, (*arg)->collation.collation, coll.collation, &dummy_offset)) continue; + /* + No needs to add converter if an "arg" is NUMERIC or DATETIME + value (which is pure ASCII) and at the same time target DTCollation + is ASCII-compatible. For example, no needs to rewrite: + SELECT * FROM t1 WHERE datetime_field = '2010-01-01'; + to + SELECT * FROM t1 WHERE CONVERT(datetime_field USING cs) = '2010-01-01'; + + TODO: avoid conversion of any values with + repertoire ASCII and 7bit-ASCII-compatible, + not only numeric/datetime origin. + */ + if ((*arg)->collation.derivation == DERIVATION_NUMERIC && + (*arg)->collation.repertoire == MY_REPERTOIRE_ASCII && + !((*arg)->collation.collation->state & MY_CS_NONASCII) && + !(coll.collation->state & MY_CS_NONASCII)) + continue; + if (!(conv= (*arg)->safe_charset_converter(coll.collation)) && ((*arg)->collation.repertoire == MY_REPERTOIRE_ASCII)) conv= new Item_func_conv_charset(*arg, coll.collation, 1); @@ -1742,11 +1842,12 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, *arg= conv; else thd->change_item_tree(arg, conv); - /* - We do not check conv->fixed, because Item_func_conv_charset which can - be return by safe_charset_converter can't be fixed at creation - */ - conv->fix_fields(thd, arg); + + if (conv->fix_fields(thd, arg)) + { + res= TRUE; + break; // we cannot return here, we need to restore "arena". + } } if (arena) thd->restore_active_arena(arena, &backup); @@ -1905,13 +2006,14 @@ void Item_field::set_field(Field *field_par) field=result_field=field_par; // for easy coding with fields maybe_null=field->maybe_null(); decimals= field->decimals(); - max_length= field_par->max_display_length(); table_name= *field_par->table_name; field_name= field_par->field_name; db_name= field_par->table->s->db.str; alias_name_used= field_par->table->alias_name_used; unsigned_flag=test(field_par->flags & UNSIGNED_FLAG); - collation.set(field_par->charset(), field_par->derivation()); + collation.set(field_par->charset(), field_par->derivation(), + field_par->repertoire()); + fix_char_length(field_par->char_length()); fixed= 1; if (field->table->s->tmp_table == SYSTEM_TMP_TABLE) any_privileges= 0; @@ -2220,7 +2322,7 @@ String *Item_int::val_str(String *str) { // following assert is redundant, because fixed=1 assigned in constructor DBUG_ASSERT(fixed == 1); - str->set_int(value, unsigned_flag, &my_charset_bin); + str->set_int(value, unsigned_flag, collation.collation); return str; } @@ -2250,7 +2352,7 @@ String *Item_uint::val_str(String *str) { // following assert is redundant, because fixed=1 assigned in constructor DBUG_ASSERT(fixed == 1); - str->set((ulonglong) value, &my_charset_bin); + str->set((ulonglong) value, collation.collation); return str; } @@ -2350,7 +2452,7 @@ double Item_decimal::val_real() String *Item_decimal::val_str(String *result) { - result->set_charset(&my_charset_bin); + result->set_charset(&my_charset_numeric); my_decimal2string(E_DEC_FATAL_ERROR, &decimal_value, 0, 0, 0, result); return result; } @@ -2412,7 +2514,9 @@ my_decimal *Item_float::val_decimal(my_decimal *decimal_value) void Item_string::print(String *str, enum_query_type query_type) { - if (query_type == QT_ORDINARY && is_cs_specified()) + const bool print_introducer= + !(query_type & QT_WITHOUT_INTRODUCERS) && is_cs_specified(); + if (print_introducer) { str->append('_'); str->append(collation.collation->csname); @@ -2420,27 +2524,52 @@ void Item_string::print(String *str, enum_query_type query_type) str->append('\''); - if (query_type == QT_ORDINARY || - my_charset_same(str_value.charset(), system_charset_info)) + if (query_type & QT_TO_SYSTEM_CHARSET) { - str_value.print(str); - } - else - { - THD *thd= current_thd; - LEX_STRING utf8_lex_str; + if (print_introducer) + { + /* + Because we wrote an introducer, we must print str_value in its + charset, and the resulting bytes must not be changed until they + reach the end client. + But the caller is asking for system_charset_info, and may later + convert into character_set_results. That means two conversions: we + must ensure that they don't change our printed bytes. + So we print str_value in the least common denominator of the three + charsets involved: ASCII. Non-ASCII characters are printed as \xFF + sequences (which is ASCII too). This way, our bytes will not be + changed. + */ + ErrConvString tmp(str_value.ptr(), str_value.length(), &my_charset_bin); + str->append(tmp.ptr()); + } + else + { + if (my_charset_same(str_value.charset(), system_charset_info)) + str_value.print(str); // already in system_charset_info + else // need to convert + { + THD *thd= current_thd; + LEX_STRING utf8_lex_str; - thd->convert_string(&utf8_lex_str, - system_charset_info, - str_value.c_ptr_safe(), - str_value.length(), - str_value.charset()); + thd->convert_string(&utf8_lex_str, + system_charset_info, + str_value.c_ptr_safe(), + str_value.length(), + str_value.charset()); - String utf8_str(utf8_lex_str.str, - utf8_lex_str.length, - system_charset_info); + String utf8_str(utf8_lex_str.str, + utf8_lex_str.length, + system_charset_info); - utf8_str.print(str); + utf8_str.print(str); + } + } + } + else + { + // Caller wants a result in the charset of str_value. + str_value.print(str); } str->append('\''); @@ -2458,6 +2587,7 @@ double_from_string_with_check (CHARSET_INFO *cs, const char *cptr, char *end) tmp= my_strntod(cs, (char*) cptr, end - cptr, &end, &error); if (error || (end != org_end && !check_if_only_end_space(cs, end, org_end))) { + ErrConvString err(cptr, cs); /* We can use str_value.ptr() here as Item_string is gurantee to put an end \0 here. @@ -2465,7 +2595,7 @@ double_from_string_with_check (CHARSET_INFO *cs, const char *cptr, char *end) push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, ER(ER_TRUNCATED_WRONG_VALUE), "DOUBLE", - cptr); + err.ptr()); } return tmp; } @@ -2495,10 +2625,11 @@ longlong_from_string_with_check (CHARSET_INFO *cs, const char *cptr, char *end) (err > 0 || (end != org_end && !check_if_only_end_space(cs, end, org_end)))) { + ErrConvString err(cptr, cs); push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER", - cptr); + err.ptr()); } return tmp; } @@ -2585,7 +2716,8 @@ Item_param::Item_param(uint pos_in_query_arg) : param_type(MYSQL_TYPE_VARCHAR), pos_in_query(pos_in_query_arg), set_param_func(default_set_param_func), - limit_clause_param(FALSE) + limit_clause_param(FALSE), + m_out_param_info(NULL) { name= (char*) "?"; /* @@ -2667,6 +2799,17 @@ void Item_param::set_decimal(const char *str, ulong length) DBUG_VOID_RETURN; } +void Item_param::set_decimal(const my_decimal *dv) +{ + state= DECIMAL_VALUE; + + my_decimal2decimal(dv, &decimal_value); + + decimals= (uint8) decimal_value.frac; + unsigned_flag= !decimal_value.sign(); + max_length= my_decimal_precision_to_length(decimal_value.intg + decimals, + decimals, unsigned_flag); +} /** Set parameter value from MYSQL_TIME value. @@ -3299,6 +3442,155 @@ Item_param::set_param_type_and_swap_value(Item_param *src) str_value_ptr.swap(src->str_value_ptr); } + +/** + This operation is intended to store some item value in Item_param to be + used later. + + @param thd thread context + @param ctx stored procedure runtime context + @param it a pointer to an item in the tree + + @return Error status + @retval TRUE on error + @retval FALSE on success +*/ + +bool +Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it) +{ + Item *arg= *it; + + if (arg->is_null()) + { + set_null(); + return FALSE; + } + + null_value= FALSE; + + switch (arg->result_type()) { + case STRING_RESULT: + { + char str_buffer[STRING_BUFFER_USUAL_SIZE]; + String sv_buffer(str_buffer, sizeof(str_buffer), &my_charset_bin); + String *sv= arg->val_str(&sv_buffer); + + if (!sv) + return TRUE; + + set_str(sv->c_ptr_safe(), sv->length()); + str_value_ptr.set(str_value.ptr(), + str_value.length(), + str_value.charset()); + collation.set(str_value.charset(), DERIVATION_COERCIBLE); + decimals= 0; + + break; + } + + case REAL_RESULT: + set_double(arg->val_real()); + break; + + case INT_RESULT: + set_int(arg->val_int(), arg->max_length); + break; + + case DECIMAL_RESULT: + { + my_decimal dv_buf; + my_decimal *dv= arg->val_decimal(&dv_buf); + + if (!dv) + return TRUE; + + set_decimal(dv); + break; + } + + default: + /* That can not happen. */ + + DBUG_ASSERT(TRUE); // Abort in debug mode. + + set_null(); // Set to NULL in release mode. + return FALSE; + } + + item_result_type= arg->result_type(); + item_type= arg->type(); + return FALSE; +} + + +/** + Setter of Item_param::m_out_param_info. + + m_out_param_info is used to store information about store routine + OUT-parameters, such as stored routine name, database, stored routine + variable name. It is supposed to be set in sp_head::execute() after + Item_param::set_value() is called. +*/ + +void +Item_param::set_out_param_info(Send_field *info) +{ + m_out_param_info= info; + param_type= m_out_param_info->type; +} + + +/** + Getter of Item_param::m_out_param_info. + + m_out_param_info is used to store information about store routine + OUT-parameters, such as stored routine name, database, stored routine + variable name. It is supposed to be retrieved in + Protocol_binary::send_out_parameters() during creation of OUT-parameter + result set. +*/ + +const Send_field * +Item_param::get_out_param_info() const +{ + return m_out_param_info; +} + + +/** + Fill meta-data information for the corresponding column in a result set. + If this is an OUT-parameter of a stored procedure, preserve meta-data of + stored-routine variable. + + @param field container for meta-data to be filled +*/ + +void Item_param::make_field(Send_field *field) +{ + Item::make_field(field); + + if (!m_out_param_info) + return; + + /* + This is an OUT-parameter of stored procedure. We should use + OUT-parameter info to fill out the names. + */ + + field->db_name= m_out_param_info->db_name; + field->table_name= m_out_param_info->table_name; + field->org_table_name= m_out_param_info->org_table_name; + field->col_name= m_out_param_info->col_name; + field->org_col_name= m_out_param_info->org_col_name; + + field->length= m_out_param_info->length; + field->charsetnr= m_out_param_info->charsetnr; + field->flags= m_out_param_info->flags; + field->decimals= m_out_param_info->decimals; + field->type= m_out_param_info->type; +} + /**************************************************************************** Item_copy ****************************************************************************/ @@ -3530,7 +3822,7 @@ void Item_copy_decimal::copy() /* - Functions to convert item to field (for send_fields) + Functions to convert item to field (for send_result_set_metadata) */ /* ARGSUSED */ @@ -4644,11 +4936,8 @@ Item *Item_field::equal_fields_propagator(uchar *arg) e.g. <bin_col> = <int_col> AND <bin_col> = <hex_string>) since Items don't know the context they are in and there are functions like IF (<hex_string>, 'yes', 'no'). - The same problem occurs when comparing a DATE/TIME field with a - DATE/TIME represented as an int and as a string. */ - if (!item || - (cmp_context != (Item_result)-1 && item->cmp_context != cmp_context)) + if (!item || !has_compatible_context(item)) item= this; else if (field && (field->flags & ZEROFILL_FLAG) && IS_NUM(field->type())) { @@ -4712,8 +5001,7 @@ Item *Item_field::replace_equal_field(uchar *arg) Item *const_item= item_equal->get_const(); if (const_item) { - if (cmp_context != (Item_result)-1 && - const_item->cmp_context != cmp_context) + if (!has_compatible_context(const_item)) return this; return const_item; } @@ -4736,7 +5024,7 @@ void Item::init_make_field(Send_field *tmp_field, tmp_field->col_name= name; tmp_field->charsetnr= collation.collation->number; tmp_field->flags= (maybe_null ? 0 : NOT_NULL_FLAG) | - (my_binary_compare(collation.collation) ? + (my_binary_compare(charset_for_protocol()) ? BINARY_FLAG : 0); tmp_field->type= field_type_arg; tmp_field->length=max_length; @@ -4783,21 +5071,6 @@ enum_field_types Item::field_type() const } -bool Item::is_datetime() -{ - switch (field_type()) - { - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - return TRUE; - default: - break; - } - return FALSE; -} - - String *Item::check_well_formed_result(String *str, bool send_error) { /* Check whether we got a well-formed string */ @@ -4810,7 +5083,6 @@ String *Item::check_well_formed_result(String *str, bool send_error) { THD *thd= current_thd; char hexbuf[7]; - enum MYSQL_ERROR::enum_warning_level level; uint diff= str->length() - wlen; set_if_smaller(diff, 3); octet2hex(hexbuf, str->ptr() + wlen, diff); @@ -4823,16 +5095,14 @@ String *Item::check_well_formed_result(String *str, bool send_error) if ((thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) { - level= MYSQL_ERROR::WARN_LEVEL_ERROR; null_value= 1; str= 0; } else { - level= MYSQL_ERROR::WARN_LEVEL_WARN; str->length(wlen); } - push_warning_printf(thd, level, ER_INVALID_CHARACTER_STRING, + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_INVALID_CHARACTER_STRING, ER(ER_INVALID_CHARACTER_STRING), cs->csname, hexbuf); } return str; @@ -4898,7 +5168,7 @@ Field *Item::make_string_field(TABLE *table) DBUG_ASSERT(collation.collation); if (max_length/collation.collation->mbmaxlen > CONVERT_IF_BIGGER_TO_BLOB) field= new Field_blob(max_length, maybe_null, name, - collation.collation); + collation.collation, TRUE); /* Item_type_holder holds the exact type, do not change it */ else if (max_length > 0 && (type() != Item::TYPE_HOLDER || field_type() != MYSQL_TYPE_STRING)) @@ -5140,9 +5410,7 @@ int Item_null::save_safe_in_field(Field *field) int Item::save_in_field(Field *field, bool no_conversions) { int error; - if (result_type() == STRING_RESULT || - (result_type() == REAL_RESULT && - field->result_type() == STRING_RESULT)) + if (result_type() == STRING_RESULT) { String *result; CHARSET_INFO *cs= collation.collation; @@ -5161,11 +5429,20 @@ int Item::save_in_field(Field *field, bool no_conversions) error=field->store(result->ptr(),result->length(),cs); str_value.set_quick(0, 0, cs); } + else if (result_type() == REAL_RESULT && + field->result_type() == STRING_RESULT) + { + double nr= val_real(); + if (null_value) + return set_field_to_null_with_conversions(field, no_conversions); + field->set_notnull(); + error= field->store(nr); + } else if (result_type() == REAL_RESULT) { double nr= val_real(); if (null_value) - return set_field_to_null(field); + return set_field_to_null_with_conversions(field, no_conversions); field->set_notnull(); error=field->store(nr); } @@ -5278,10 +5555,27 @@ static uint nr_of_decimals(const char *str, const char *end) break; } decimal_point= str; - for (; my_isdigit(system_charset_info, *str) ; str++) + for ( ; str < end && my_isdigit(system_charset_info, *str) ; str++) ; - if (*str == 'e' || *str == 'E') + if (str < end && (*str == 'e' || *str == 'E')) return NOT_FIXED_DEC; + /* + QQ: + The number of decimal digist in fact should be (str - decimal_point - 1). + But it seems the result of nr_of_decimals() is never used! + + In case of 'e' and 'E' nr_of_decimals returns NOT_FIXED_DEC. + In case if there is no 'e' or 'E' parser code in sql_yacc.yy + never calls Item_float::Item_float() - it creates Item_decimal instead. + + The only piece of code where we call Item_float::Item_float(str, len) + without having 'e' or 'E' is item_xmlfunc.cc, but this Item_float + never appears in metadata itself. Changing the code to return + (str - decimal_point - 1) does not make any changes in the test results. + + This should be addressed somehow. + Looks like a reminder from before real DECIMAL times. + */ return (uint) (str - decimal_point); } @@ -5667,6 +5961,68 @@ bool Item::send(Protocol *protocol, String *buffer) } +/** + Check if an item is a constant one and can be cached. + + @param arg [out] TRUE <=> Cache this item. + + @return TRUE Go deeper in item tree. + @return FALSE Don't go deeper in item tree. +*/ + +bool Item::cache_const_expr_analyzer(uchar **arg) +{ + bool *cache_flag= (bool*)*arg; + if (!*cache_flag) + { + Item *item= real_item(); + /* + Cache constant items unless it's a basic constant, constant field or + a subselect (they use their own cache). + */ + if (const_item() && + !(basic_const_item() || item->basic_const_item() || + item->type() == Item::FIELD_ITEM || + item->type() == SUBSELECT_ITEM || + /* + Do not cache GET_USER_VAR() function as its const_item() may + return TRUE for the current thread but it still may change + during the execution. + */ + (item->type() == Item::FUNC_ITEM && + ((Item_func*)item)->functype() == Item_func::GUSERVAR_FUNC))) + *cache_flag= TRUE; + return TRUE; + } + return FALSE; +} + + +/** + Cache item if needed. + + @param arg TRUE <=> Cache this item. + + @return cache if cache needed. + @return this otherwise. +*/ + +Item* Item::cache_const_expr_transformer(uchar *arg) +{ + if (*(bool*)arg) + { + *((bool*)arg)= FALSE; + Item_cache *cache= Item_cache::get_cache(this); + if (!cache) + return NULL; + cache->setup(this); + cache->store(this); + return cache; + } + return this; +} + + bool Item_field::send(Protocol *protocol, String *buffer) { return protocol->store(result_field); @@ -7050,6 +7406,11 @@ Item_cache* Item_cache::get_cache(const Item *item, const Item_result type) case DECIMAL_RESULT: return new Item_cache_decimal(); case STRING_RESULT: + /* Not all functions that return DATE/TIME are actually DATE/TIME funcs. */ + if ((item->is_datetime() || + item->field_type() == MYSQL_TYPE_TIME) && + (const_cast<Item*>(item))->result_as_longlong()) + return new Item_cache_datetime(item->field_type()); return new Item_cache_str(item); case ROW_RESULT: return new Item_cache_row(); @@ -7103,7 +7464,7 @@ void Item_cache_int::store(Item *item, longlong val_arg) String *Item_cache_int::val_str(String *str) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; str->set(value, default_charset()); return str; @@ -7113,7 +7474,7 @@ String *Item_cache_int::val_str(String *str) my_decimal *Item_cache_int::val_decimal(my_decimal *decimal_val) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; int2my_decimal(E_DEC_FATAL_ERROR, value, unsigned_flag, decimal_val); return decimal_val; @@ -7122,7 +7483,7 @@ my_decimal *Item_cache_int::val_decimal(my_decimal *decimal_val) double Item_cache_int::val_real() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return 0.0; return (double) value; } @@ -7130,11 +7491,149 @@ double Item_cache_int::val_real() longlong Item_cache_int::val_int() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return 0; return value; } +bool Item_cache_datetime::cache_value_int() +{ + if (!example) + return FALSE; + + value_cached= TRUE; + // Mark cached string value obsolete + str_value_cached= FALSE; + /* Assume here that the underlying item will do correct conversion.*/ + int_value= example->val_int_result(); + null_value= example->null_value; + unsigned_flag= example->unsigned_flag; + return TRUE; +} + + +bool Item_cache_datetime::cache_value() +{ + if (!example) + return FALSE; + + if (cmp_context == INT_RESULT) + return cache_value_int(); + + str_value_cached= TRUE; + // Mark cached int value obsolete + value_cached= FALSE; + /* Assume here that the underlying item will do correct conversion.*/ + String *res= example->str_result(&str_value); + if (res && res != &str_value) + str_value.copy(*res); + null_value= example->null_value; + unsigned_flag= example->unsigned_flag; + return TRUE; +} + + +void Item_cache_datetime::store(Item *item, longlong val_arg) +{ + /* An explicit values is given, save it. */ + value_cached= TRUE; + int_value= val_arg; + null_value= item->null_value; + unsigned_flag= item->unsigned_flag; +} + + +void Item_cache_datetime::store(Item *item) +{ + Item_cache::store(item); + str_value_cached= FALSE; +} + +String *Item_cache_datetime::val_str(String *str) +{ + DBUG_ASSERT(fixed == 1); + + if ((value_cached || str_value_cached) && null_value) + return NULL; + + if (!str_value_cached) + { + /* + When it's possible the Item_cache_datetime uses INT datetime + representation due to speed reasons. But still, it always has the STRING + result type and thus it can be asked to return a string value. + It is possible that at this time cached item doesn't contain correct + string value, thus we have to convert cached int value to string and + return it. + */ + if (value_cached) + { + MYSQL_TIME ltime; + /* Return NULL in case of OOM/conversion error. */ + null_value= TRUE; + if (str_value.alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + if (cached_field_type == MYSQL_TYPE_TIME) + { + longlong time= int_value; + set_zero_time(<ime, MYSQL_TIMESTAMP_TIME); + if (time < 0) + { + time= -time; + ltime.neg= TRUE; + } + DBUG_ASSERT(time <= TIME_MAX_VALUE); + ltime.second= time % 100; + time/= 100; + ltime.minute= time % 100; + time/= 100; + ltime.hour= time; + } + else + { + int was_cut; + longlong res; + res= number_to_datetime(int_value, <ime, TIME_FUZZY_DATE, &was_cut); + if (res == -1) + return NULL; + } + str_value.length(my_TIME_to_str(<ime, + const_cast<char*>(str_value.ptr()))); + str_value_cached= TRUE; + null_value= FALSE; + } + else if (!cache_value()) + return NULL; + } + return &str_value; +} + + +my_decimal *Item_cache_datetime::val_decimal(my_decimal *decimal_val) +{ + DBUG_ASSERT(fixed == 1); + if (!has_value()) + return NULL; + int2my_decimal(E_DEC_FATAL_ERROR, int_value, unsigned_flag, decimal_val); + return decimal_val; +} + +double Item_cache_datetime::val_real() +{ + DBUG_ASSERT(fixed == 1); + if ((!value_cached && !cache_value_int()) || null_value) + return 0.0; + return (double) int_value; +} + +longlong Item_cache_datetime::val_int() +{ + DBUG_ASSERT(fixed == 1); + if ((!value_cached && !cache_value_int()) || null_value) + return 0; + return int_value; +} + bool Item_cache_real::cache_value() { if (!example) @@ -7149,7 +7648,7 @@ bool Item_cache_real::cache_value() double Item_cache_real::val_real() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return 0.0; return value; } @@ -7157,7 +7656,7 @@ double Item_cache_real::val_real() longlong Item_cache_real::val_int() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return 0; return (longlong) rint(value); } @@ -7166,7 +7665,7 @@ longlong Item_cache_real::val_int() String* Item_cache_real::val_str(String *str) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; str->set_real(value, decimals, default_charset()); return str; @@ -7176,7 +7675,7 @@ String* Item_cache_real::val_str(String *str) my_decimal *Item_cache_real::val_decimal(my_decimal *decimal_val) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; double2my_decimal(E_DEC_FATAL_ERROR, value, decimal_val); return decimal_val; @@ -7198,7 +7697,7 @@ double Item_cache_decimal::val_real() { DBUG_ASSERT(fixed); double res; - if (!value_cached && !cache_value()) + if (!has_value()) return 0.0; my_decimal2double(E_DEC_FATAL_ERROR, &decimal_value, &res); return res; @@ -7208,7 +7707,7 @@ longlong Item_cache_decimal::val_int() { DBUG_ASSERT(fixed); longlong res; - if (!value_cached && !cache_value()) + if (!has_value()) return 0; my_decimal2int(E_DEC_FATAL_ERROR, &decimal_value, unsigned_flag, &res); return res; @@ -7217,7 +7716,7 @@ longlong Item_cache_decimal::val_int() String* Item_cache_decimal::val_str(String *str) { DBUG_ASSERT(fixed); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; my_decimal_round(E_DEC_FATAL_ERROR, &decimal_value, decimals, FALSE, &decimal_value); @@ -7228,7 +7727,7 @@ String* Item_cache_decimal::val_str(String *str) my_decimal *Item_cache_decimal::val_decimal(my_decimal *val) { DBUG_ASSERT(fixed); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; return &decimal_value; } @@ -7264,7 +7763,7 @@ double Item_cache_str::val_real() DBUG_ASSERT(fixed == 1); int err_not_used; char *end_not_used; - if (!value_cached && !cache_value()) + if (!has_value()) return 0.0; if (value) return my_strntod(value->charset(), (char*) value->ptr(), @@ -7277,7 +7776,7 @@ longlong Item_cache_str::val_int() { DBUG_ASSERT(fixed == 1); int err; - if (!value_cached && !cache_value()) + if (!has_value()) return 0; if (value) return my_strntoll(value->charset(), value->ptr(), @@ -7290,7 +7789,7 @@ longlong Item_cache_str::val_int() String* Item_cache_str::val_str(String *str) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return 0; return value; } @@ -7299,7 +7798,7 @@ String* Item_cache_str::val_str(String *str) my_decimal *Item_cache_str::val_decimal(my_decimal *decimal_val) { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value()) + if (!has_value()) return NULL; if (value) string2my_decimal(E_DEC_FATAL_ERROR, value, decimal_val); @@ -7311,7 +7810,7 @@ my_decimal *Item_cache_str::val_decimal(my_decimal *decimal_val) int Item_cache_str::save_in_field(Field *field, bool no_conversions) { - if (!value_cached && !cache_value()) + if (!has_value()) return 0; int res= Item_cache::save_in_field(field, no_conversions); return (is_varbinary && field->type() == MYSQL_TYPE_STRING && |