You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1001 lines
35 KiB
1001 lines
35 KiB
5 months ago
|
/* Copyright (c) 2000, 2019, 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, version 2.0,
|
||
|
as published by the Free Software Foundation.
|
||
|
|
||
|
This program is also distributed with certain software (including
|
||
|
but not limited to OpenSSL) that is licensed under separate terms,
|
||
|
as designated in a particular file or component or in included license
|
||
|
documentation. The authors of MySQL hereby grant you an additional
|
||
|
permission to link the program and your derivative works with the
|
||
|
separately licensed software that they have included with MySQL.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License, version 2.0, for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
||
|
|
||
|
/**
|
||
|
@file
|
||
|
Functions to copy data to or from fields.
|
||
|
*/
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <algorithm>
|
||
|
|
||
|
#include "m_ctype.h"
|
||
|
#include "my_byteorder.h"
|
||
|
#include "my_compare.h"
|
||
|
#include "my_compiler.h"
|
||
|
#include "my_dbug.h"
|
||
|
#include "my_inttypes.h"
|
||
|
#include "my_sys.h"
|
||
|
#include "my_time.h"
|
||
|
#include "mysql/udf_registration_types.h"
|
||
|
#include "mysql_com.h"
|
||
|
#include "mysql_time.h"
|
||
|
#include "mysqld_error.h"
|
||
|
#include "nullable.h"
|
||
|
#include "sql/current_thd.h"
|
||
|
#include "sql/field.h"
|
||
|
#include "sql/item_timefunc.h" // Item_func_now_local
|
||
|
#include "sql/my_decimal.h"
|
||
|
#include "sql/sql_class.h" // THD
|
||
|
#include "sql/sql_const.h"
|
||
|
#include "sql/sql_error.h"
|
||
|
#include "sql/sql_time.h"
|
||
|
#include "sql/system_variables.h"
|
||
|
#include "sql/table.h"
|
||
|
#include "sql_string.h"
|
||
|
#include "template_utils.h" // down_cast
|
||
|
|
||
|
/**
|
||
|
Check if geometry type sub is a subtype of super.
|
||
|
|
||
|
@param sub The type to check
|
||
|
@param super The supertype
|
||
|
|
||
|
@return True if sub is a subtype of super
|
||
|
*/
|
||
|
inline static bool is_subtype_of(Field::geometry_type sub,
|
||
|
Field::geometry_type super) {
|
||
|
return (
|
||
|
super == Field::GEOM_GEOMETRY ||
|
||
|
(super == Field::GEOM_GEOMETRYCOLLECTION &&
|
||
|
(sub == Field::GEOM_MULTIPOINT || sub == Field::GEOM_MULTILINESTRING ||
|
||
|
sub == Field::GEOM_MULTIPOLYGON)));
|
||
|
}
|
||
|
|
||
|
static void do_field_eq(Copy_field *copy) {
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy->from_length());
|
||
|
}
|
||
|
|
||
|
static void do_field_1(Copy_field *copy) {
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
}
|
||
|
|
||
|
static void do_field_2(Copy_field *copy) {
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
copy->to_ptr[1] = copy->from_ptr[1];
|
||
|
}
|
||
|
|
||
|
static void do_field_3(Copy_field *copy) {
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
copy->to_ptr[1] = copy->from_ptr[1];
|
||
|
copy->to_ptr[2] = copy->from_ptr[2];
|
||
|
}
|
||
|
|
||
|
static void do_field_4(Copy_field *copy) {
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
copy->to_ptr[1] = copy->from_ptr[1];
|
||
|
copy->to_ptr[2] = copy->from_ptr[2];
|
||
|
copy->to_ptr[3] = copy->from_ptr[3];
|
||
|
}
|
||
|
|
||
|
static void do_field_6(Copy_field *copy) { // For blob field
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
copy->to_ptr[1] = copy->from_ptr[1];
|
||
|
copy->to_ptr[2] = copy->from_ptr[2];
|
||
|
copy->to_ptr[3] = copy->from_ptr[3];
|
||
|
copy->to_ptr[4] = copy->from_ptr[4];
|
||
|
copy->to_ptr[5] = copy->from_ptr[5];
|
||
|
}
|
||
|
|
||
|
static void do_field_8(Copy_field *copy) {
|
||
|
copy->to_ptr[0] = copy->from_ptr[0];
|
||
|
copy->to_ptr[1] = copy->from_ptr[1];
|
||
|
copy->to_ptr[2] = copy->from_ptr[2];
|
||
|
copy->to_ptr[3] = copy->from_ptr[3];
|
||
|
copy->to_ptr[4] = copy->from_ptr[4];
|
||
|
copy->to_ptr[5] = copy->from_ptr[5];
|
||
|
copy->to_ptr[6] = copy->from_ptr[6];
|
||
|
copy->to_ptr[7] = copy->from_ptr[7];
|
||
|
}
|
||
|
|
||
|
static void do_field_to_null_str(Copy_field *copy) {
|
||
|
if (copy->from_is_null()) {
|
||
|
memset(copy->to_ptr, 0, copy->from_length());
|
||
|
copy->set_to_is_null(true);
|
||
|
} else {
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy->from_length());
|
||
|
copy->set_to_is_null(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type_conversion_status set_field_to_null(Field *field) {
|
||
|
if (field->real_maybe_null() || field->is_tmp_nullable()) {
|
||
|
field->set_null();
|
||
|
field->reset();
|
||
|
return TYPE_OK;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
The following piece of code is run for the case when a BLOB column
|
||
|
that has value NULL is queried with GROUP BY NULL and the result
|
||
|
is inserted into a some table's column declared having primitive type
|
||
|
(e.g. INT) and NOT NULL.
|
||
|
|
||
|
For example, the following test case will hit this piece of code:
|
||
|
CREATE TABLE t1 (a BLOB);
|
||
|
CREATE TABLE t2 (a INT NOT NULL);
|
||
|
|
||
|
INSERT t1 VALUES (NULL);
|
||
|
INSERT INTO t2(a) SELECT a FROM t1 GROUP BY NULL; <<== Hit here
|
||
|
|
||
|
In general, when set_field_to_null() is called a Field has to be either
|
||
|
declared as NULL-able or be marked as temporary NULL-able.
|
||
|
But in case of INSERT SELECT from a BLOB field and when GROUP BY NULL
|
||
|
is specified the Field object for a destination column doesn't set
|
||
|
neither NULL-able nor temporary NULL-able (see setup_copy_fields()).
|
||
|
*/
|
||
|
field->reset();
|
||
|
switch (field->table->in_use->check_for_truncated_fields) {
|
||
|
case CHECK_FIELD_WARN:
|
||
|
field->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1);
|
||
|
/* fall through */
|
||
|
case CHECK_FIELD_IGNORE:
|
||
|
return TYPE_OK;
|
||
|
case CHECK_FIELD_ERROR_FOR_NULL:
|
||
|
my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
|
||
|
return TYPE_ERR_NULL_CONSTRAINT_VIOLATION;
|
||
|
}
|
||
|
DBUG_ASSERT(false); // impossible
|
||
|
|
||
|
my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
|
||
|
return TYPE_ERR_NULL_CONSTRAINT_VIOLATION; // to avoid compiler's warning
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Set field to NULL or TIMESTAMP or to next auto_increment number.
|
||
|
|
||
|
@param field Field to update
|
||
|
@param no_conversions Set to 1 if we should return 1 if field can't
|
||
|
take null values.
|
||
|
If set to 0 we will do store the 'default value'
|
||
|
if the field is a special field. If not we will
|
||
|
give an error.
|
||
|
|
||
|
@retval
|
||
|
0 Field could take 0 or an automatic conversion was used
|
||
|
@retval
|
||
|
-1 Field could not take NULL and no conversion was used.
|
||
|
If no_conversion was not set, an error message is printed
|
||
|
*/
|
||
|
|
||
|
type_conversion_status set_field_to_null_with_conversions(Field *field,
|
||
|
bool no_conversions) {
|
||
|
if (field->real_maybe_null()) {
|
||
|
field->set_null();
|
||
|
field->reset();
|
||
|
return TYPE_OK;
|
||
|
}
|
||
|
|
||
|
if (no_conversions) return TYPE_ERR_NULL_CONSTRAINT_VIOLATION;
|
||
|
|
||
|
/*
|
||
|
Check if this is a special type, which will get a special walue
|
||
|
when set to NULL (TIMESTAMP fields which allow setting to NULL
|
||
|
are handled by first check).
|
||
|
|
||
|
From the manual:
|
||
|
|
||
|
TIMESTAMP columns [...] assigning NULL assigns the current timestamp.
|
||
|
|
||
|
But if explicit_defaults_for_timestamp, use standard-compliant behaviour:
|
||
|
no special value.
|
||
|
*/
|
||
|
if (field->type() == MYSQL_TYPE_TIMESTAMP &&
|
||
|
!field->table->in_use->variables.explicit_defaults_for_timestamp) {
|
||
|
Item_func_now_local::store_in(field);
|
||
|
return TYPE_OK; // Ok to set time to NULL
|
||
|
}
|
||
|
|
||
|
// Note: we ignore any potential failure of reset() here.
|
||
|
field->reset();
|
||
|
|
||
|
if (field == field->table->next_number_field) {
|
||
|
field->table->autoinc_field_has_explicit_non_null_value = false;
|
||
|
return TYPE_OK; // field is set in fill_record()
|
||
|
}
|
||
|
|
||
|
if (field->is_tmp_nullable()) {
|
||
|
field->set_null();
|
||
|
field->reset();
|
||
|
return TYPE_OK;
|
||
|
}
|
||
|
|
||
|
// Conversion of NULL to empty string does not apply to geometry columns.
|
||
|
if (field->type() == MYSQL_TYPE_GEOMETRY) {
|
||
|
my_error(ER_BAD_NULL_ERROR_NOT_IGNORED, MYF(0), field->field_name);
|
||
|
return TYPE_ERR_NULL_CONSTRAINT_VIOLATION;
|
||
|
}
|
||
|
|
||
|
switch (field->table->in_use->check_for_truncated_fields) {
|
||
|
case CHECK_FIELD_WARN:
|
||
|
field->set_warning(Sql_condition::SL_WARNING, ER_BAD_NULL_ERROR, 1);
|
||
|
/* fall through */
|
||
|
case CHECK_FIELD_IGNORE:
|
||
|
if (field->type() == MYSQL_TYPE_BLOB) {
|
||
|
/*
|
||
|
BLOB/TEXT fields only store a pointer to their actual contents
|
||
|
in the record. Make this a valid pointer to an empty string
|
||
|
instead of nullptr.
|
||
|
*/
|
||
|
return field->store("", 0, field->charset());
|
||
|
}
|
||
|
return TYPE_OK;
|
||
|
case CHECK_FIELD_ERROR_FOR_NULL:
|
||
|
my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
|
||
|
return TYPE_ERR_NULL_CONSTRAINT_VIOLATION;
|
||
|
}
|
||
|
DBUG_ASSERT(false); // impossible
|
||
|
my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name);
|
||
|
return TYPE_ERR_NULL_CONSTRAINT_VIOLATION;
|
||
|
}
|
||
|
|
||
|
static void do_skip(Copy_field *copy MY_ATTRIBUTE((unused))) {}
|
||
|
|
||
|
static void do_copy_null(Copy_field *copy) {
|
||
|
if (copy->from_is_null()) {
|
||
|
copy->set_to_is_null(true);
|
||
|
copy->to_field()->reset();
|
||
|
} else {
|
||
|
copy->set_to_is_null(false);
|
||
|
copy->invoke_do_copy2(copy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void do_copy_not_null(Copy_field *copy) {
|
||
|
if (copy->from_is_null()) {
|
||
|
if (copy->to_field()->reset() == TYPE_ERR_NULL_CONSTRAINT_VIOLATION)
|
||
|
my_error(ER_INVALID_USE_OF_NULL, MYF(0));
|
||
|
else
|
||
|
copy->to_field()->set_warning(Sql_condition::SL_WARNING,
|
||
|
WARN_DATA_TRUNCATED, 1);
|
||
|
} else
|
||
|
copy->invoke_do_copy2(copy);
|
||
|
}
|
||
|
|
||
|
static void do_copy_maybe_null(Copy_field *copy) {
|
||
|
/*
|
||
|
NOTE: In reverse copying (see bring_back_frame_row() for windowing),
|
||
|
"to" is "from".
|
||
|
*/
|
||
|
copy->set_to_is_null(false);
|
||
|
copy->invoke_do_copy2(copy);
|
||
|
}
|
||
|
|
||
|
/* timestamp and next_number has special handling in case of NULL values */
|
||
|
|
||
|
static void do_copy_timestamp(Copy_field *copy) {
|
||
|
if (copy->from_is_null()) {
|
||
|
/* Same as in set_field_to_null_with_conversions() */
|
||
|
Item_func_now_local::store_in(copy->to_field());
|
||
|
} else
|
||
|
copy->invoke_do_copy2(copy);
|
||
|
}
|
||
|
|
||
|
static void do_copy_next_number(Copy_field *copy) {
|
||
|
if (copy->from_is_null()) {
|
||
|
/* Same as in set_field_to_null_with_conversions() */
|
||
|
copy->to_field()->table->autoinc_field_has_explicit_non_null_value = false;
|
||
|
copy->to_field()->reset();
|
||
|
} else
|
||
|
copy->invoke_do_copy2(copy);
|
||
|
}
|
||
|
|
||
|
static void do_copy_blob(Copy_field *copy) {
|
||
|
ulong from_length = ((Field_blob *)copy->from_field())->get_length();
|
||
|
((Field_blob *)copy->to_field())->store_length(from_length);
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, sizeof(char *));
|
||
|
ulong to_length = ((Field_blob *)copy->to_field())->get_length();
|
||
|
if (to_length < from_length) {
|
||
|
if (copy->to_field()->table->in_use->is_strict_mode()) {
|
||
|
copy->to_field()->set_warning(Sql_condition::SL_WARNING, ER_DATA_TOO_LONG,
|
||
|
1);
|
||
|
} else {
|
||
|
copy->to_field()->set_warning(Sql_condition::SL_WARNING,
|
||
|
WARN_DATA_TRUNCATED, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void do_conv_blob(Copy_field *copy) {
|
||
|
copy->from_field()->val_str(©->tmp);
|
||
|
((Field_blob *)copy->to_field())
|
||
|
->store(copy->tmp.ptr(), copy->tmp.length(), copy->tmp.charset());
|
||
|
}
|
||
|
|
||
|
/** Save blob in copy->tmp for GROUP BY. */
|
||
|
|
||
|
static void do_save_blob(Copy_field *copy) {
|
||
|
char buff[MAX_FIELD_WIDTH];
|
||
|
String res(buff, sizeof(buff), copy->tmp.charset());
|
||
|
copy->from_field()->val_str(&res);
|
||
|
copy->tmp.copy(res);
|
||
|
((Field_blob *)copy->to_field())
|
||
|
->store(copy->tmp.ptr(), copy->tmp.length(), copy->tmp.charset());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Copy the contents of one Field_json into another Field_json.
|
||
|
*/
|
||
|
static void do_save_json(Copy_field *copy) {
|
||
|
Field_json *from = down_cast<Field_json *>(copy->from_field());
|
||
|
Field_json *to = down_cast<Field_json *>(copy->to_field());
|
||
|
to->store(from);
|
||
|
}
|
||
|
|
||
|
static void do_field_string(Copy_field *copy) {
|
||
|
char buff[MAX_FIELD_WIDTH];
|
||
|
String res(buff, sizeof(buff), copy->from_field()->charset());
|
||
|
res.length(0U);
|
||
|
|
||
|
copy->from_field()->val_str(&res);
|
||
|
copy->to_field()->store(res.c_ptr_quick(), res.length(), res.charset());
|
||
|
}
|
||
|
|
||
|
static void do_field_enum(Copy_field *copy) {
|
||
|
if (copy->from_field()->val_int() == 0)
|
||
|
((Field_enum *)copy->to_field())->store_type((ulonglong)0);
|
||
|
else
|
||
|
do_field_string(copy);
|
||
|
}
|
||
|
|
||
|
static void do_field_varbinary_pre50(Copy_field *copy) {
|
||
|
char buff[MAX_FIELD_WIDTH];
|
||
|
copy->tmp.set_quick(buff, sizeof(buff), copy->tmp.charset());
|
||
|
copy->from_field()->val_str(©->tmp);
|
||
|
|
||
|
/* Use the same function as in 4.1 to trim trailing spaces */
|
||
|
size_t length = my_lengthsp_8bit(&my_charset_bin, copy->tmp.c_ptr_quick(),
|
||
|
copy->from_field()->field_length);
|
||
|
|
||
|
copy->to_field()->store(copy->tmp.c_ptr_quick(), length, copy->tmp.charset());
|
||
|
}
|
||
|
|
||
|
static void do_field_int(Copy_field *copy) {
|
||
|
longlong value = copy->from_field()->val_int();
|
||
|
copy->to_field()->store(value, copy->from_field()->flags & UNSIGNED_FLAG);
|
||
|
}
|
||
|
|
||
|
static void do_field_real(Copy_field *copy) {
|
||
|
double value = copy->from_field()->val_real();
|
||
|
copy->to_field()->store(value);
|
||
|
}
|
||
|
|
||
|
static void do_field_decimal(Copy_field *copy) {
|
||
|
my_decimal value;
|
||
|
copy->to_field()->store_decimal(copy->from_field()->val_decimal(&value));
|
||
|
}
|
||
|
|
||
|
inline type_conversion_status copy_time_to_time(Field *from, Field *to) {
|
||
|
MYSQL_TIME ltime;
|
||
|
from->get_time(<ime);
|
||
|
return to->store_time(<ime);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Convert between fields using time representation.
|
||
|
*/
|
||
|
static void do_field_time(Copy_field *copy) {
|
||
|
(void)copy_time_to_time(copy->from_field(), copy->to_field());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
string copy for single byte characters set when to string is shorter than
|
||
|
from string.
|
||
|
*/
|
||
|
|
||
|
static void do_cut_string(Copy_field *copy) {
|
||
|
const CHARSET_INFO *cs = copy->from_field()->charset();
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy->to_length());
|
||
|
|
||
|
/* Check if we loosed any important characters */
|
||
|
if (cs->cset->scan(cs, (char *)copy->from_ptr + copy->to_length(),
|
||
|
(char *)copy->from_ptr + copy->from_length(),
|
||
|
MY_SEQ_SPACES) < copy->from_length() - copy->to_length()) {
|
||
|
copy->to_field()->set_warning(Sql_condition::SL_WARNING,
|
||
|
WARN_DATA_TRUNCATED, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
string copy for multi byte characters set when to string is shorter than
|
||
|
from string.
|
||
|
*/
|
||
|
|
||
|
static void do_cut_string_complex(Copy_field *copy) { // Shorter string field
|
||
|
int well_formed_error;
|
||
|
const CHARSET_INFO *cs = copy->from_field()->charset();
|
||
|
const uchar *from_end = copy->from_ptr + copy->from_length();
|
||
|
size_t copy_length = cs->cset->well_formed_len(
|
||
|
cs, pointer_cast<const char *>(copy->from_ptr),
|
||
|
pointer_cast<const char *>(from_end), copy->to_length() / cs->mbmaxlen,
|
||
|
&well_formed_error);
|
||
|
if (copy->to_length() < copy_length) copy_length = copy->to_length();
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy_length);
|
||
|
|
||
|
/* Check if we lost any important characters */
|
||
|
if (well_formed_error ||
|
||
|
cs->cset->scan(cs,
|
||
|
pointer_cast<const char *>(copy->from_ptr) + copy_length,
|
||
|
pointer_cast<const char *>(from_end),
|
||
|
MY_SEQ_SPACES) < (copy->from_length() - copy_length)) {
|
||
|
copy->to_field()->set_warning(Sql_condition::SL_WARNING,
|
||
|
WARN_DATA_TRUNCATED, 1);
|
||
|
}
|
||
|
|
||
|
if (copy_length < copy->to_length())
|
||
|
cs->cset->fill(cs, (char *)copy->to_ptr + copy_length,
|
||
|
copy->to_length() - copy_length, ' ');
|
||
|
}
|
||
|
|
||
|
static void do_expand_binary(Copy_field *copy) {
|
||
|
const CHARSET_INFO *cs = copy->from_field()->charset();
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy->from_length());
|
||
|
cs->cset->fill(cs, (char *)copy->to_ptr + copy->from_length(),
|
||
|
copy->to_length() - copy->from_length(), '\0');
|
||
|
}
|
||
|
|
||
|
static void do_expand_string(Copy_field *copy) {
|
||
|
const CHARSET_INFO *cs = copy->from_field()->charset();
|
||
|
memcpy(copy->to_ptr, copy->from_ptr, copy->from_length());
|
||
|
cs->cset->fill(cs, (char *)copy->to_ptr + copy->from_length(),
|
||
|
copy->to_length() - copy->from_length(), ' ');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Find how many bytes should be copied between Field_varstring fields
|
||
|
so that only the bytes in use in the 'from' field are copied.
|
||
|
Handles single and multi-byte charsets. Adds warning if not all
|
||
|
bytes in 'from' will fit into 'to'.
|
||
|
|
||
|
@param to Variable length field we're copying to
|
||
|
@param from Variable length field we're copying from
|
||
|
|
||
|
@return Number of bytes that should be copied from 'from' to 'to'.
|
||
|
*/
|
||
|
static size_t get_varstring_copy_length(Field_varstring *to,
|
||
|
const Field_varstring *from) {
|
||
|
const CHARSET_INFO *const cs = from->charset();
|
||
|
const bool is_multibyte_charset = (cs->mbmaxlen != 1);
|
||
|
const uint to_byte_length = to->row_pack_length();
|
||
|
|
||
|
size_t bytes_to_copy;
|
||
|
if (from->length_bytes == 1)
|
||
|
bytes_to_copy = *from->ptr;
|
||
|
else
|
||
|
bytes_to_copy = uint2korr(from->ptr);
|
||
|
|
||
|
if (from->pack_length() - from->length_bytes <= to_byte_length) {
|
||
|
/*
|
||
|
There's room for everything in the destination buffer;
|
||
|
no need to truncate.
|
||
|
*/
|
||
|
return bytes_to_copy;
|
||
|
}
|
||
|
|
||
|
if (is_multibyte_charset) {
|
||
|
int well_formed_error;
|
||
|
const char *from_beg =
|
||
|
reinterpret_cast<char *>(from->ptr + from->length_bytes);
|
||
|
const uint to_char_length = (to_byte_length) / cs->mbmaxlen;
|
||
|
const size_t from_byte_length = bytes_to_copy;
|
||
|
bytes_to_copy =
|
||
|
cs->cset->well_formed_len(cs, from_beg, from_beg + from_byte_length,
|
||
|
to_char_length, &well_formed_error);
|
||
|
if (bytes_to_copy < from_byte_length) {
|
||
|
if (from->table->in_use->check_for_truncated_fields)
|
||
|
to->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1);
|
||
|
}
|
||
|
} else {
|
||
|
if (bytes_to_copy > (to_byte_length)) {
|
||
|
bytes_to_copy = to_byte_length;
|
||
|
if (from->table->in_use->check_for_truncated_fields)
|
||
|
to->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1);
|
||
|
}
|
||
|
}
|
||
|
return bytes_to_copy;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
A variable length string field consists of:
|
||
|
(a) 1 or 2 length bytes, depending on the VARCHAR column definition
|
||
|
(b) as many relevant character bytes, as defined in the length byte(s)
|
||
|
(c) unused padding up to the full length of the column
|
||
|
|
||
|
This function only copies (a) and (b)
|
||
|
|
||
|
Condition for using this function: to and from must use the same
|
||
|
number of bytes for length, i.e: to->length_bytes==from->length_bytes
|
||
|
|
||
|
@param to Variable length field we're copying to
|
||
|
@param from Variable length field we're copying from
|
||
|
*/
|
||
|
static void copy_field_varstring(Field_varstring *const to,
|
||
|
const Field_varstring *const from) {
|
||
|
const uint length_bytes = from->length_bytes;
|
||
|
DBUG_ASSERT(length_bytes == to->length_bytes);
|
||
|
DBUG_ASSERT(length_bytes == 1 || length_bytes == 2);
|
||
|
|
||
|
const size_t bytes_to_copy = get_varstring_copy_length(to, from);
|
||
|
if (length_bytes == 1)
|
||
|
*to->ptr = static_cast<uchar>(bytes_to_copy);
|
||
|
else
|
||
|
int2store(to->ptr, bytes_to_copy);
|
||
|
|
||
|
// memcpy should not be used for overlaping memory blocks
|
||
|
DBUG_ASSERT(to->ptr != from->ptr);
|
||
|
memcpy(to->ptr + length_bytes, from->ptr + length_bytes, bytes_to_copy);
|
||
|
}
|
||
|
|
||
|
static void do_varstring(Copy_field *copy) {
|
||
|
copy_field_varstring(static_cast<Field_varstring *>(copy->to_field()),
|
||
|
static_cast<Field_varstring *>(copy->from_field()));
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
** The different functions that fills in a Copy_field class
|
||
|
***************************************************************************/
|
||
|
|
||
|
void Copy_field::invoke_do_copy(Copy_field *f) {
|
||
|
(*(this->m_do_copy))(f);
|
||
|
|
||
|
f->check_and_set_temporary_null();
|
||
|
}
|
||
|
|
||
|
void Copy_field::invoke_do_copy2(Copy_field *f) {
|
||
|
(*(this->m_do_copy2))(f);
|
||
|
|
||
|
f->check_and_set_temporary_null();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
copy of field to maybe null string.
|
||
|
If field is null then the all bytes are set to 0.
|
||
|
if field is not null then the first byte is set to 1 and the rest of the
|
||
|
string is the field value.
|
||
|
The 'to' buffer should have a size of field->pack_length()+1
|
||
|
*/
|
||
|
|
||
|
void Copy_field::set(uchar *to, Field *from) {
|
||
|
from_ptr = from->ptr;
|
||
|
to_ptr = to;
|
||
|
m_from_length = from->pack_length();
|
||
|
m_from_field = from;
|
||
|
if (from->maybe_null()) {
|
||
|
if ((from_null_ptr = from->get_null_ptr())) from_bit = from->null_bit;
|
||
|
to_ptr[0] = 1; // Null as default value
|
||
|
to_null_ptr = to_ptr++;
|
||
|
to_bit = 1;
|
||
|
m_do_copy = do_field_to_null_str;
|
||
|
} else {
|
||
|
to_null_ptr = 0; // For easy debugging
|
||
|
m_do_copy = do_field_eq;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
To do:
|
||
|
|
||
|
If 'save' is set to true and the 'from' is a blob field, m_do_copy is set to
|
||
|
do_save_blob rather than do_conv_blob. The only differences between them
|
||
|
appears to be:
|
||
|
|
||
|
- do_save_blob allocates and uses an intermediate buffer before calling
|
||
|
Field_blob::store. Is this in order to trigger the call to
|
||
|
well_formed_copy_nchars, by changing the pointer copy->tmp.ptr()?
|
||
|
That call will take place anyway in all known cases.
|
||
|
*/
|
||
|
void Copy_field::set(Field *to, Field *from, bool save) {
|
||
|
if (to->type() == MYSQL_TYPE_NULL) {
|
||
|
to_null_ptr = 0; // For easy debugging
|
||
|
to_ptr = 0;
|
||
|
m_do_copy = do_skip;
|
||
|
return;
|
||
|
}
|
||
|
m_from_field = from;
|
||
|
m_to_field = to;
|
||
|
from_ptr = from->ptr;
|
||
|
m_from_length = from->pack_length();
|
||
|
to_ptr = to->ptr;
|
||
|
m_to_length = m_to_field->pack_length();
|
||
|
|
||
|
// set up null handling
|
||
|
from_null_ptr = to_null_ptr = 0;
|
||
|
if (from->maybe_null()) {
|
||
|
if ((from_null_ptr = from->get_null_ptr())) from_bit = from->null_bit;
|
||
|
if (m_to_field->real_maybe_null()) {
|
||
|
to_null_ptr = to->get_null_ptr();
|
||
|
to_bit = to->null_bit;
|
||
|
m_do_copy = do_copy_null;
|
||
|
} else {
|
||
|
if (m_to_field->type() == MYSQL_TYPE_TIMESTAMP)
|
||
|
m_do_copy = do_copy_timestamp; // Automatic timestamp
|
||
|
else if (m_to_field == m_to_field->table->next_number_field)
|
||
|
m_do_copy = do_copy_next_number;
|
||
|
else
|
||
|
m_do_copy = do_copy_not_null;
|
||
|
}
|
||
|
} else if (m_to_field->real_maybe_null()) {
|
||
|
to_null_ptr = to->get_null_ptr();
|
||
|
to_bit = to->null_bit;
|
||
|
m_do_copy = do_copy_maybe_null;
|
||
|
} else
|
||
|
m_do_copy = NULL;
|
||
|
|
||
|
if ((to->flags & BLOB_FLAG) && save) {
|
||
|
if (to->real_type() == MYSQL_TYPE_JSON &&
|
||
|
from->real_type() == MYSQL_TYPE_JSON)
|
||
|
m_do_copy2 = do_save_json;
|
||
|
else
|
||
|
m_do_copy2 = do_save_blob;
|
||
|
} else
|
||
|
m_do_copy2 = get_copy_func(to, from);
|
||
|
|
||
|
if (!m_do_copy) // Not null
|
||
|
m_do_copy = m_do_copy2;
|
||
|
}
|
||
|
|
||
|
bool Copy_field::from_is_null() const {
|
||
|
if (from_null_ptr != nullptr) {
|
||
|
return *from_null_ptr & from_bit;
|
||
|
} else if (m_from_field != nullptr) {
|
||
|
// Even if the source field is not nullable (from_null_ptr is nullptr),
|
||
|
// we can still read NULLs out of it if the table row is nullable.
|
||
|
// (The typical case is that the table is on the inner side of an outer
|
||
|
// join, but it can also happen with aggregation.)
|
||
|
return m_from_field->table->has_null_row();
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Copy_field::set_to_is_null(bool is_null) {
|
||
|
if (to_null_ptr != nullptr) {
|
||
|
if (is_null) {
|
||
|
*to_null_ptr |= to_bit;
|
||
|
} else {
|
||
|
*to_null_ptr &= ~to_bit;
|
||
|
}
|
||
|
} else if (m_to_field != nullptr) {
|
||
|
// This is used in the case of window functions, where
|
||
|
// bring_back_frame_row() may want to set TABLE::null_row to be what it was
|
||
|
// when the row was buffered. See also from_is_null().
|
||
|
if (is_null) {
|
||
|
m_to_field->table->set_null_row();
|
||
|
} else {
|
||
|
m_to_field->table->reset_null_row();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Copy_field::Copy_func *Copy_field::get_copy_func(Field *to, Field *from) {
|
||
|
bool compatible_db_low_byte_first =
|
||
|
(to->table->s->db_low_byte_first == from->table->s->db_low_byte_first);
|
||
|
if (to->type() == MYSQL_TYPE_GEOMETRY) {
|
||
|
if (from->type() != MYSQL_TYPE_GEOMETRY ||
|
||
|
to->maybe_null() != from->maybe_null())
|
||
|
return do_conv_blob;
|
||
|
|
||
|
const Field_geom *to_geom = down_cast<const Field_geom *>(to);
|
||
|
const Field_geom *from_geom = down_cast<const Field_geom *>(from);
|
||
|
|
||
|
// If changing the SRID property of the field, we must do a full conversion.
|
||
|
if (to_geom->get_srid() != from_geom->get_srid() &&
|
||
|
to_geom->get_srid().has_value())
|
||
|
return do_conv_blob;
|
||
|
|
||
|
// to is same as or a wider type than from
|
||
|
if (to_geom->get_geometry_type() == from_geom->get_geometry_type() ||
|
||
|
is_subtype_of(from_geom->get_geometry_type(),
|
||
|
to_geom->get_geometry_type()))
|
||
|
return do_field_eq;
|
||
|
|
||
|
return do_conv_blob;
|
||
|
} else if (to->flags & BLOB_FLAG) {
|
||
|
/*
|
||
|
We need to do conversion if we are copying from BLOB to
|
||
|
non-BLOB, or if we are copying between BLOBs with different
|
||
|
character sets, or if we are copying between JSON and non-JSON.
|
||
|
*/
|
||
|
if (!(from->flags & BLOB_FLAG) || from->charset() != to->charset() ||
|
||
|
((to->type() == MYSQL_TYPE_JSON) != (from->type() == MYSQL_TYPE_JSON)))
|
||
|
return do_conv_blob;
|
||
|
if (m_from_length != m_to_length || !compatible_db_low_byte_first) {
|
||
|
// Correct pointer to point at char pointer
|
||
|
to_ptr += m_to_length - portable_sizeof_char_ptr;
|
||
|
from_ptr += m_from_length - portable_sizeof_char_ptr;
|
||
|
return do_copy_blob;
|
||
|
}
|
||
|
} else {
|
||
|
if (to->real_type() == MYSQL_TYPE_BIT ||
|
||
|
from->real_type() == MYSQL_TYPE_BIT)
|
||
|
return do_field_int;
|
||
|
if (to->result_type() == DECIMAL_RESULT) return do_field_decimal;
|
||
|
// Check if identical fields
|
||
|
if (from->result_type() == STRING_RESULT) {
|
||
|
if (from->is_temporal()) {
|
||
|
if (to->is_temporal()) {
|
||
|
return do_field_time;
|
||
|
} else {
|
||
|
if (to->result_type() == INT_RESULT) return do_field_int;
|
||
|
if (to->result_type() == REAL_RESULT) return do_field_real;
|
||
|
/* Note: conversion from any to DECIMAL_RESULT is handled earlier */
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
Detect copy from pre 5.0 varbinary to varbinary as of 5.0 and
|
||
|
use special copy function that removes trailing spaces and thus
|
||
|
repairs data.
|
||
|
*/
|
||
|
if (from->type() == MYSQL_TYPE_VAR_STRING && !from->has_charset() &&
|
||
|
to->type() == MYSQL_TYPE_VARCHAR && !to->has_charset())
|
||
|
return do_field_varbinary_pre50;
|
||
|
|
||
|
/*
|
||
|
If we are copying date or datetime's we have to check the dates
|
||
|
if we don't allow 'all' dates.
|
||
|
*/
|
||
|
if (to->real_type() != from->real_type() ||
|
||
|
to->decimals() != from->decimals() /* e.g. TIME vs TIME(6) */ ||
|
||
|
!compatible_db_low_byte_first ||
|
||
|
(((to->table->in_use->variables.sql_mode &
|
||
|
(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) &&
|
||
|
to->type() == MYSQL_TYPE_DATE) ||
|
||
|
to->type() == MYSQL_TYPE_DATETIME)) {
|
||
|
if (from->real_type() == MYSQL_TYPE_ENUM ||
|
||
|
from->real_type() == MYSQL_TYPE_SET)
|
||
|
if (to->result_type() != STRING_RESULT)
|
||
|
return do_field_int; // Convert SET to number
|
||
|
return do_field_string;
|
||
|
}
|
||
|
if (to->real_type() == MYSQL_TYPE_ENUM ||
|
||
|
to->real_type() == MYSQL_TYPE_SET) {
|
||
|
if (!to->eq_def(from)) {
|
||
|
if (from->real_type() == MYSQL_TYPE_ENUM &&
|
||
|
to->real_type() == MYSQL_TYPE_ENUM)
|
||
|
return do_field_enum;
|
||
|
else
|
||
|
return do_field_string;
|
||
|
}
|
||
|
} else if (to->charset() != from->charset())
|
||
|
return do_field_string;
|
||
|
else if (to->real_type() == MYSQL_TYPE_VARCHAR) {
|
||
|
if (((Field_varstring *)to)->length_bytes !=
|
||
|
((Field_varstring *)from)->length_bytes)
|
||
|
return do_field_string;
|
||
|
else
|
||
|
return do_varstring;
|
||
|
} else if (m_to_length < m_from_length)
|
||
|
return (from->charset()->mbmaxlen == 1 ? do_cut_string
|
||
|
: do_cut_string_complex);
|
||
|
else if (m_to_length > m_from_length) {
|
||
|
if (to->charset() == &my_charset_bin)
|
||
|
return do_expand_binary;
|
||
|
else
|
||
|
return do_expand_string;
|
||
|
}
|
||
|
|
||
|
} else if (to->real_type() != from->real_type() ||
|
||
|
m_to_length != m_from_length || !compatible_db_low_byte_first) {
|
||
|
if (to->real_type() == MYSQL_TYPE_DECIMAL ||
|
||
|
to->result_type() == STRING_RESULT)
|
||
|
return do_field_string;
|
||
|
if (to->result_type() == INT_RESULT) return do_field_int;
|
||
|
return do_field_real;
|
||
|
} else {
|
||
|
if (!to->eq_def(from) || !compatible_db_low_byte_first) {
|
||
|
if (to->real_type() == MYSQL_TYPE_DECIMAL) return do_field_string;
|
||
|
if (to->result_type() == INT_RESULT)
|
||
|
return do_field_int;
|
||
|
else
|
||
|
return do_field_real;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/* Eq fields */
|
||
|
switch (m_to_length) {
|
||
|
case 1:
|
||
|
return do_field_1;
|
||
|
case 2:
|
||
|
return do_field_2;
|
||
|
case 3:
|
||
|
return do_field_3;
|
||
|
case 4:
|
||
|
return do_field_4;
|
||
|
case 6:
|
||
|
return do_field_6;
|
||
|
case 8:
|
||
|
return do_field_8;
|
||
|
}
|
||
|
return do_field_eq;
|
||
|
}
|
||
|
|
||
|
void Copy_field::swap_direction() {
|
||
|
std::swap(from_ptr, to_ptr);
|
||
|
std::swap(from_null_ptr, to_null_ptr);
|
||
|
std::swap(from_bit, to_bit);
|
||
|
std::swap(m_from_length, m_to_length);
|
||
|
std::swap(m_from_field, m_to_field);
|
||
|
}
|
||
|
|
||
|
static inline bool is_blob_type(Field *to) {
|
||
|
return (to->type() == MYSQL_TYPE_BLOB || to->type() == MYSQL_TYPE_GEOMETRY);
|
||
|
}
|
||
|
|
||
|
/** Simple quick field convert that is called on insert. */
|
||
|
|
||
|
type_conversion_status field_conv(Field *to, Field *from) {
|
||
|
const int from_type = from->type();
|
||
|
const int to_type = to->type();
|
||
|
|
||
|
if ((to_type == MYSQL_TYPE_JSON) && (from_type == MYSQL_TYPE_JSON)) {
|
||
|
Field_json *to_json = down_cast<Field_json *>(to);
|
||
|
Field_json *from_json = down_cast<Field_json *>(from);
|
||
|
return to_json->store(from_json);
|
||
|
}
|
||
|
|
||
|
if (to->real_type() == from->real_type() &&
|
||
|
!((is_blob_type(to)) && to->table->copy_blobs) &&
|
||
|
to->charset() == from->charset() && to_type != MYSQL_TYPE_GEOMETRY) {
|
||
|
if (to->real_type() == MYSQL_TYPE_VARCHAR &&
|
||
|
from->real_type() == MYSQL_TYPE_VARCHAR) {
|
||
|
Field_varstring *to_vc = static_cast<Field_varstring *>(to);
|
||
|
const Field_varstring *from_vc = static_cast<Field_varstring *>(from);
|
||
|
if (to_vc->length_bytes == from_vc->length_bytes) {
|
||
|
copy_field_varstring(to_vc, from_vc);
|
||
|
return TYPE_OK;
|
||
|
}
|
||
|
}
|
||
|
if (to->pack_length() == from->pack_length() &&
|
||
|
!(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) &&
|
||
|
to->real_type() != MYSQL_TYPE_ENUM &&
|
||
|
to->real_type() != MYSQL_TYPE_SET &&
|
||
|
to->real_type() != MYSQL_TYPE_BIT &&
|
||
|
(!to->is_temporal_with_time() || to->decimals() == from->decimals()) &&
|
||
|
(to->real_type() != MYSQL_TYPE_NEWDECIMAL ||
|
||
|
(to->field_length == from->field_length &&
|
||
|
(((Field_num *)to)->dec == ((Field_num *)from)->dec))) &&
|
||
|
to->table->s->db_low_byte_first == from->table->s->db_low_byte_first &&
|
||
|
(!(to->table->in_use->variables.sql_mode &
|
||
|
(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) ||
|
||
|
(to->type() != MYSQL_TYPE_DATE && to->type() != MYSQL_TYPE_DATETIME &&
|
||
|
(!to->table->in_use->variables.explicit_defaults_for_timestamp ||
|
||
|
to->type() != MYSQL_TYPE_TIMESTAMP))) &&
|
||
|
(from->real_type() != MYSQL_TYPE_VARCHAR)) { // Identical fields
|
||
|
// to->ptr==from->ptr may happen if one does 'UPDATE ... SET x=x'
|
||
|
memmove(to->ptr, from->ptr, to->pack_length());
|
||
|
return TYPE_OK;
|
||
|
}
|
||
|
}
|
||
|
if (to->type() == MYSQL_TYPE_BLOB) { // Be sure the value is stored
|
||
|
Field_blob *blob = (Field_blob *)to;
|
||
|
from->val_str(&blob->value);
|
||
|
|
||
|
/*
|
||
|
Copy value if copy_blobs is set, or source is part of the table's
|
||
|
writeset.
|
||
|
*/
|
||
|
if (to->table->copy_blobs ||
|
||
|
(!blob->value.is_alloced() && from->is_updatable()))
|
||
|
blob->value.copy();
|
||
|
|
||
|
return blob->store(blob->value.ptr(), blob->value.length(),
|
||
|
from->charset());
|
||
|
}
|
||
|
if (from->real_type() == MYSQL_TYPE_ENUM &&
|
||
|
to->real_type() == MYSQL_TYPE_ENUM && from->val_int() == 0) {
|
||
|
((Field_enum *)(to))->store_type(0);
|
||
|
return TYPE_OK;
|
||
|
} else if (from->is_temporal() && to->result_type() == INT_RESULT) {
|
||
|
MYSQL_TIME ltime;
|
||
|
longlong nr;
|
||
|
if (from->type() == MYSQL_TYPE_TIME) {
|
||
|
from->get_time(<ime);
|
||
|
if (current_thd->is_fsp_truncate_mode())
|
||
|
nr = TIME_to_ulonglong_time(ltime);
|
||
|
else
|
||
|
nr = TIME_to_ulonglong_time_round(ltime);
|
||
|
} else {
|
||
|
from->get_date(<ime, TIME_FUZZY_DATE);
|
||
|
if (current_thd->is_fsp_truncate_mode())
|
||
|
nr = TIME_to_ulonglong_datetime(ltime);
|
||
|
else {
|
||
|
nr = propagate_datetime_overflow(current_thd, [&](int *w) {
|
||
|
return TIME_to_ulonglong_datetime_round(ltime, w);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
return to->store(ltime.neg ? -nr : nr, 0);
|
||
|
} else if (from->is_temporal() && (to->result_type() == REAL_RESULT ||
|
||
|
to->result_type() == DECIMAL_RESULT ||
|
||
|
to->result_type() == INT_RESULT)) {
|
||
|
my_decimal tmp;
|
||
|
/*
|
||
|
We prefer DECIMAL as the safest precise type:
|
||
|
double supports only 15 digits, which is not enough for DATETIME(6).
|
||
|
*/
|
||
|
return to->store_decimal(from->val_decimal(&tmp));
|
||
|
} else if (from->is_temporal() && to->is_temporal()) {
|
||
|
return copy_time_to_time(from, to);
|
||
|
} else if (from_type == MYSQL_TYPE_JSON &&
|
||
|
(to_type == MYSQL_TYPE_TINY || to_type == MYSQL_TYPE_SHORT ||
|
||
|
to_type == MYSQL_TYPE_INT24 || to_type == MYSQL_TYPE_LONG ||
|
||
|
to_type == MYSQL_TYPE_LONGLONG)) {
|
||
|
return to->store(from->val_int(), from->flags & UNSIGNED_FLAG);
|
||
|
} else if (from_type == MYSQL_TYPE_JSON && to_type == MYSQL_TYPE_NEWDECIMAL) {
|
||
|
my_decimal buff;
|
||
|
return to->store_decimal(from->val_decimal(&buff));
|
||
|
} else if (from_type == MYSQL_TYPE_JSON &&
|
||
|
(to_type == MYSQL_TYPE_FLOAT || to_type == MYSQL_TYPE_DOUBLE)) {
|
||
|
return to->store(from->val_real());
|
||
|
} else if (from_type == MYSQL_TYPE_JSON && to->is_temporal()) {
|
||
|
MYSQL_TIME ltime;
|
||
|
bool res = true;
|
||
|
switch (to_type) {
|
||
|
case MYSQL_TYPE_TIME:
|
||
|
res = from->get_time(<ime);
|
||
|
break;
|
||
|
case MYSQL_TYPE_DATETIME:
|
||
|
case MYSQL_TYPE_TIMESTAMP:
|
||
|
case MYSQL_TYPE_DATE:
|
||
|
case MYSQL_TYPE_NEWDATE:
|
||
|
res = from->get_date(<ime, 0);
|
||
|
break;
|
||
|
default:
|
||
|
DBUG_ASSERT(0);
|
||
|
}
|
||
|
if (res) return TYPE_ERR_BAD_VALUE;
|
||
|
return to->store_time(<ime);
|
||
|
} else if ((from->result_type() == STRING_RESULT &&
|
||
|
(to->result_type() == STRING_RESULT ||
|
||
|
(from->real_type() != MYSQL_TYPE_ENUM &&
|
||
|
from->real_type() != MYSQL_TYPE_SET))) ||
|
||
|
to->type() == MYSQL_TYPE_DECIMAL) {
|
||
|
char buff[MAX_FIELD_WIDTH];
|
||
|
String result(buff, sizeof(buff), from->charset());
|
||
|
from->val_str(&result);
|
||
|
/*
|
||
|
We use c_ptr_quick() here to make it easier if to is a float/double
|
||
|
as the conversion routines will do a copy of the result doesn't
|
||
|
end with \0. Can be replaced with .ptr() when we have our own
|
||
|
string->double conversion.
|
||
|
*/
|
||
|
return to->store(result.c_ptr_quick(), result.length(), from->charset());
|
||
|
} else if (from->result_type() == REAL_RESULT)
|
||
|
return to->store(from->val_real());
|
||
|
else if (from->result_type() == DECIMAL_RESULT) {
|
||
|
my_decimal buff;
|
||
|
return to->store_decimal(from->val_decimal(&buff));
|
||
|
} else
|
||
|
return to->store(from->val_int(), from->flags & UNSIGNED_FLAG);
|
||
|
}
|