/* Copyright (c) 2000, 2024, Oracle and/or its affiliates. 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 designed to work 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 either included with the program or referenced in the documentation. 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 #include #include #include #include #include "field_types.h" #include "my_byteorder.h" #include "my_compare.h" #include "my_compiler.h" #include "my_inttypes.h" #include "my_sys.h" #include "my_time.h" #include "mysql/strings/m_ctype.h" #include "mysql/udf_registration_types.h" #include "mysql_com.h" #include "mysql_time.h" #include "mysqld_error.h" #include "sql-common/my_decimal.h" #include "sql/current_thd.h" #include "sql/field.h" #include "sql/item_timefunc.h" // Item_func_now_local #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 *, const Field *from_field, Field *to_field) { memcpy(to_field->field_ptr(), from_field->field_ptr(), from_field->pack_length()); } static void set_to_is_null(Field *to_field, bool is_null) { if (to_field->is_nullable() || to_field->is_tmp_nullable()) { if (is_null) { to_field->set_null(); } else { to_field->set_notnull(); } } } type_conversion_status set_field_to_null(Field *field) { if (field->is_nullable() || 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 (current_thd->check_for_truncated_fields) { case CHECK_FIELD_WARN: field->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); [[fallthrough]]; 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; } 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) { THD *thd = current_thd; if (field->is_nullable()) { 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 && !thd->variables.explicit_defaults_for_timestamp) { /* With explicit_defaults_for_timestamp disabled, if a NULL value is inserted into a timestamp column with NOT NULL attribute, would attempt to convert the column value to CURRENT_TIMESTAMP. However, this is inconsistent with the source of the generated value, so the insertion is rejected. */ if (field->is_gcol()) { my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name); return TYPE_ERR_NULL_CONSTRAINT_VIOLATION; } else { 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 (thd->check_for_truncated_fields) { case CHECK_FIELD_WARN: field->set_warning(Sql_condition::SL_WARNING, ER_BAD_NULL_ERROR, 1); [[fallthrough]]; 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; } 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 *, const Field *, Field *) {} static void do_copy_null(Copy_field *copy, const Field *from_field, Field *to_field) { if (from_field->is_null()) { set_to_is_null(to_field, true); to_field->reset(); } else { set_to_is_null(to_field, false); copy->invoke_do_copy2(from_field, to_field); } } static void do_copy_not_null(Copy_field *copy, const Field *from_field, Field *to_field) { if (from_field->is_null()) { if (to_field->reset() == TYPE_ERR_NULL_CONSTRAINT_VIOLATION) my_error(ER_INVALID_USE_OF_NULL, MYF(0)); else to_field->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); } else copy->invoke_do_copy2(from_field, to_field); } static void do_copy_maybe_null(Copy_field *copy, const Field *from_field, Field *to_field) { /* NOTE: In reverse copying (see bring_back_frame_row() for windowing), "to" is "from". */ set_to_is_null(to_field, false); copy->invoke_do_copy2(from_field, to_field); } /* timestamp and next_number has special handling in case of NULL values */ static void do_copy_timestamp(Copy_field *copy, const Field *from_field, Field *to_field) { if (from_field->is_null()) { /* Same as in set_field_to_null_with_conversions() */ Item_func_now_local::store_in(to_field); } else copy->invoke_do_copy2(from_field, to_field); } static void do_copy_next_number(Copy_field *copy, const Field *from_field, Field *to_field) { if (from_field->is_null()) { /* Same as in set_field_to_null_with_conversions() */ to_field->table->autoinc_field_has_explicit_non_null_value = false; to_field->reset(); } else copy->invoke_do_copy2(from_field, to_field); } static void do_copy_blob(Copy_field *, const Field *from_field, Field *to_field) { const Field_blob *from_blob = down_cast(from_field); Field_blob *to_blob = down_cast(to_field); const uint32 from_length = from_blob->get_length(); to_blob->set_ptr(std::min(from_length, to_field->max_data_length()), from_blob->get_blob_data()); if (to_blob->get_length() < from_length) { if (current_thd->is_strict_mode()) { to_field->set_warning(Sql_condition::SL_WARNING, ER_DATA_TOO_LONG, 1); } else { to_field->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); } } } static void do_conv_blob(Copy_field *copy, const Field *from_field, Field *to_field) { from_field->val_str(©->tmp); static_cast(to_field)->store( copy->tmp.ptr(), copy->tmp.length(), copy->tmp.charset()); } static void do_field_string(Copy_field *, const Field *from_field, Field *to_field) { StringBuffer res(from_field->charset()); res.length(0U); from_field->val_str(&res); to_field->store(res.ptr(), res.length(), res.charset()); } static void do_field_enum(Copy_field *copy, const Field *from_field, Field *to_field) { if (from_field->val_int() == 0) { down_cast(to_field)->store_type(0ULL); } else do_field_string(copy, from_field, to_field); } static void do_field_varbinary_pre50(Copy_field *copy, const Field *from_field, Field *to_field) { char buff[MAX_FIELD_WIDTH]; copy->tmp.set_quick(buff, sizeof(buff), copy->tmp.charset()); from_field->val_str(©->tmp); /* Use the same function as in 4.1 to trim trailing spaces */ const size_t length = my_charset_latin1.cset->lengthsp( &my_charset_latin1, copy->tmp.c_ptr_quick(), from_field->field_length); to_field->store(copy->tmp.c_ptr_quick(), length, copy->tmp.charset()); } static void do_field_int(Copy_field *, const Field *from_field, Field *to_field) { const longlong value = from_field->val_int(); to_field->store(value, from_field->is_flag_set(UNSIGNED_FLAG)); } static void do_field_real(Copy_field *, const Field *from_field, Field *to_field) { to_field->store(from_field->val_real()); } static void do_field_decimal(Copy_field *, const Field *from_field, Field *to_field) { my_decimal value; to_field->store_decimal(from_field->val_decimal(&value)); } inline type_conversion_status copy_time_to_time(const 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 *, const Field *from_field, Field *to_field) { (void)copy_time_to_time(from_field, to_field); } /** string copy for single byte characters set when to string is shorter than from string. */ static void do_cut_string(Copy_field *, const Field *from_field, Field *to_field) { const CHARSET_INFO *cs = from_field->charset(); memcpy(to_field->field_ptr(), from_field->field_ptr(), to_field->pack_length()); /* Check if we loosed any important characters */ if (cs->cset->scan(cs, pointer_cast(from_field->field_ptr() + to_field->pack_length()), pointer_cast(from_field->field_ptr() + from_field->pack_length()), MY_SEQ_SPACES) < from_field->pack_length() - to_field->pack_length()) { 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 *, const Field *from_field, Field *to_field) { // Shorter string field int well_formed_error; const CHARSET_INFO *cs = from_field->charset(); const uchar *from_end = from_field->field_ptr() + from_field->pack_length(); size_t copy_length = cs->cset->well_formed_len( cs, pointer_cast(from_field->field_ptr()), pointer_cast(from_end), to_field->pack_length() / cs->mbmaxlen, &well_formed_error); if (to_field->pack_length() < copy_length) { copy_length = to_field->pack_length(); } memcpy(to_field->field_ptr(), from_field->field_ptr(), copy_length); /* Check if we lost any important characters */ if (well_formed_error || cs->cset->scan( cs, pointer_cast(from_field->field_ptr()) + copy_length, pointer_cast(from_end), MY_SEQ_SPACES) < (from_field->pack_length() - copy_length)) { to_field->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); } if (copy_length < to_field->pack_length()) { cs->cset->fill(cs, pointer_cast(to_field->field_ptr()) + copy_length, to_field->pack_length() - copy_length, ' '); } } static void do_expand_binary(Copy_field *, const Field *from_field, Field *to_field) { const CHARSET_INFO *cs = from_field->charset(); memcpy(to_field->field_ptr(), from_field->field_ptr(), from_field->pack_length()); cs->cset->fill( cs, pointer_cast(to_field->field_ptr()) + from_field->pack_length(), to_field->pack_length() - from_field->pack_length(), '\0'); } static void do_expand_string(Copy_field *, const Field *from_field, Field *to_field) { const CHARSET_INFO *cs = from_field->charset(); memcpy(to_field->field_ptr(), from_field->field_ptr(), from_field->pack_length()); cs->cset->fill( cs, pointer_cast(to_field->field_ptr()) + from_field->pack_length(), to_field->pack_length() - from_field->pack_length(), ' '); } /** 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) { assert(from->get_length_bytes() == to->get_length_bytes()); size_t bytes_to_copy; const CHARSET_INFO *const from_cs = from->charset(); THD *thd = current_thd; if (from->row_pack_length() <= to->row_pack_length()) { /* There's room for everything in the destination buffer; no need to truncate. */ bytes_to_copy = from->data_length(); } else if (from_cs->mbmaxlen != 1) { int well_formed_error; const char *from_beg = pointer_cast(from->data_ptr()); const uint to_char_length = to->row_pack_length() / from_cs->mbmaxlen; bytes_to_copy = from_cs->cset->well_formed_len( from_cs, from_beg, from_beg + from->data_length(), to_char_length, &well_formed_error); if (bytes_to_copy < from->data_length()) { if (thd->check_for_truncated_fields) to->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); } } else { bytes_to_copy = from->data_length(); if (bytes_to_copy > to->row_pack_length()) { bytes_to_copy = to->row_pack_length(); if (thd->check_for_truncated_fields) to->set_warning(Sql_condition::SL_WARNING, WARN_DATA_TRUNCATED, 1); } } to->store(pointer_cast(from->data_ptr()), bytes_to_copy, from_cs); } static void do_varstring(Copy_field *, const Field *from_field, Field *to_field) { copy_field_varstring(static_cast(to_field), static_cast(from_field)); } /*************************************************************************** ** The different functions that fills in a Copy_field class ***************************************************************************/ void Copy_field::invoke_do_copy(bool reverse) { const Field *from = reverse ? m_to_field : m_from_field; Field *to = reverse ? m_from_field : m_to_field; (*(m_do_copy))(this, from, to); if (from->is_tmp_null() && !to->is_tmp_null()) { to->set_tmp_nullable(); to->set_tmp_null(); } } void Copy_field::invoke_do_copy2(const Field *from, Field *to) { // from will be m_to_field if invoke_do_copy was called with reverse = true (*(m_do_copy2))(this, from, to); } void Copy_field::set(Field *to, Field *from) { if (to->type() == MYSQL_TYPE_NULL) { m_do_copy = do_skip; return; } m_from_field = from; m_to_field = to; m_do_copy2 = get_copy_func(); if (m_from_field->is_nullable() || m_from_field->table->is_nullable()) { if (m_to_field->is_nullable() || m_to_field->is_tmp_nullable()) 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->is_nullable()) { m_do_copy = do_copy_maybe_null; } else { m_do_copy = m_do_copy2; } } Copy_field::Copy_func *Copy_field::get_copy_func() { THD *thd = current_thd; if (m_to_field->is_array() && m_from_field->is_array()) return do_copy_blob; const bool compatible_db_low_byte_first = (m_to_field->table->s->db_low_byte_first == m_from_field->table->s->db_low_byte_first); if (m_to_field->type() == MYSQL_TYPE_GEOMETRY) { if (m_from_field->type() != MYSQL_TYPE_GEOMETRY || m_to_field->is_nullable() != m_from_field->is_nullable() || m_to_field->table->is_nullable() != m_from_field->table->is_nullable()) return do_conv_blob; const Field_geom *to_geom = down_cast(m_to_field); const Field_geom *from_geom = down_cast(m_from_field); // If changing the SRID property of the field, we must do a full conversion. if (to_geom->get_srid().has_value() && to_geom->get_srid() != from_geom->get_srid()) 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 (m_to_field->is_flag_set(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 (!m_from_field->is_flag_set(BLOB_FLAG) || m_from_field->charset() != m_to_field->charset() || ((m_to_field->type() == MYSQL_TYPE_JSON) != (m_from_field->type() == MYSQL_TYPE_JSON))) return do_conv_blob; if (m_from_field->pack_length() != m_to_field->pack_length() || !compatible_db_low_byte_first) { return do_copy_blob; } } else { if (m_to_field->real_type() == MYSQL_TYPE_BIT || m_from_field->real_type() == MYSQL_TYPE_BIT) return do_field_int; if (m_to_field->result_type() == DECIMAL_RESULT) return do_field_decimal; // Check if identical fields if (m_from_field->result_type() == STRING_RESULT) { if (is_temporal_type(m_from_field->type()) && m_from_field->type() != MYSQL_TYPE_YEAR) { if (is_temporal_type(m_to_field->type()) && m_to_field->type() != MYSQL_TYPE_YEAR) { return do_field_time; } else { if (m_to_field->result_type() == INT_RESULT) return do_field_int; if (m_to_field->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 (m_from_field->type() == MYSQL_TYPE_VAR_STRING && !m_from_field->has_charset() && m_to_field->type() == MYSQL_TYPE_VARCHAR && !m_to_field->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 (m_to_field->real_type() != m_from_field->real_type() || m_to_field->decimals() != m_from_field->decimals() /* e.g. TIME vs TIME(6) */ || !compatible_db_low_byte_first || (((thd->variables.sql_mode & (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) && m_to_field->type() == MYSQL_TYPE_DATE) || m_to_field->type() == MYSQL_TYPE_DATETIME)) { if (m_from_field->real_type() == MYSQL_TYPE_ENUM || m_from_field->real_type() == MYSQL_TYPE_SET) if (m_to_field->result_type() != STRING_RESULT) return do_field_int; // Convert SET to number return do_field_string; } if (m_to_field->real_type() == MYSQL_TYPE_ENUM || m_to_field->real_type() == MYSQL_TYPE_SET) { if (!m_to_field->eq_def(m_from_field)) { if (m_from_field->real_type() == MYSQL_TYPE_ENUM && m_to_field->real_type() == MYSQL_TYPE_ENUM) return do_field_enum; else return do_field_string; } } else if (m_to_field->charset() != m_from_field->charset()) return do_field_string; else if (m_to_field->real_type() == MYSQL_TYPE_VARCHAR) { if (m_to_field->get_length_bytes() != m_from_field->get_length_bytes()) return do_field_string; else return do_varstring; } else if (m_to_field->pack_length() < m_from_field->pack_length()) return (m_from_field->charset()->mbmaxlen == 1 ? do_cut_string : do_cut_string_complex); else if (m_to_field->pack_length() > m_from_field->pack_length()) { if (m_to_field->charset() == &my_charset_bin) return do_expand_binary; else return do_expand_string; } } else if (m_to_field->real_type() != m_from_field->real_type() || m_to_field->pack_length() != m_from_field->pack_length() || !compatible_db_low_byte_first) { if (m_to_field->real_type() == MYSQL_TYPE_DECIMAL || m_to_field->result_type() == STRING_RESULT) return do_field_string; if (m_to_field->result_type() == INT_RESULT) return do_field_int; return do_field_real; } else { if (!m_to_field->eq_def(m_from_field) || !compatible_db_low_byte_first) { if (m_to_field->real_type() == MYSQL_TYPE_DECIMAL) return do_field_string; if (m_to_field->result_type() == INT_RESULT) return do_field_int; else return do_field_real; } } } /* Eq fields */ assert(m_to_field->pack_length() == m_from_field->pack_length()); return do_field_eq; } static inline bool is_blob_type(enum_field_types to_type) { return (to_type == MYSQL_TYPE_BLOB || to_type == MYSQL_TYPE_GEOMETRY); } bool fields_are_memcpyable(const Field *to, const Field *from) { assert(to != from); const enum_field_types to_type = to->type(); const enum_field_types from_real_type = from->real_type(); const enum_field_types to_real_type = to->real_type(); THD *thd = current_thd; if (to_real_type != from_real_type) { return false; } if (to_type == MYSQL_TYPE_JSON || to_real_type == MYSQL_TYPE_GEOMETRY || to_real_type == MYSQL_TYPE_VARCHAR || to_real_type == MYSQL_TYPE_ENUM || to_real_type == MYSQL_TYPE_SET || to_real_type == MYSQL_TYPE_BIT) { return false; } if (from->is_array()) { return false; } if (is_blob_type(to_type) && to->table->copy_blobs) { return false; } if (to->charset() != from->charset()) { return false; } if (to->pack_length() != from->pack_length()) { return false; } if (to->is_flag_set(UNSIGNED_FLAG) != from->is_flag_set(UNSIGNED_FLAG)) { return false; } if (to->table->s->db_low_byte_first != from->table->s->db_low_byte_first) { return false; } if (to_real_type == MYSQL_TYPE_NEWDECIMAL) { if (to->field_length != from->field_length || down_cast(to)->dec != down_cast(from)->dec) { return false; } } if (is_temporal_type_with_time(to_type)) { if (to->decimals() != from->decimals()) { return false; } } if (thd->variables.sql_mode & (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) { if (to_type == MYSQL_TYPE_DATE || to_type == MYSQL_TYPE_DATETIME) { return false; } if (thd->variables.explicit_defaults_for_timestamp && to_type == MYSQL_TYPE_TIMESTAMP) { return false; } } return true; } type_conversion_status field_conv_slow(Field *to, const Field *from) { const enum_field_types from_type = from->type(); const enum_field_types to_type = to->type(); const enum_field_types from_real_type = from->real_type(); const enum_field_types to_real_type = to->real_type(); if ((to_type == MYSQL_TYPE_JSON) && (from_type == MYSQL_TYPE_JSON)) { Field_json *to_json = down_cast(to); const Field_json *from_json = down_cast(from); return to_json->store(from_json); } if (from->is_array()) { assert(to->is_array() && from_real_type == to_real_type && from->charset() == to->charset()); const Field_blob *from_blob = down_cast(from); Field_blob *to_blob = down_cast(to); return to_blob->store(from_blob); } if (to_real_type == MYSQL_TYPE_VARCHAR && from_real_type == MYSQL_TYPE_VARCHAR && to->charset() == from->charset()) { Field_varstring *to_vc = down_cast(to); const Field_varstring *from_vc = down_cast(from); if (to_vc->get_length_bytes() == from_vc->get_length_bytes()) { copy_field_varstring(to_vc, from_vc); return TYPE_OK; } } if (to_type == MYSQL_TYPE_BLOB) { // Be sure the value is stored Field_blob *blob = (Field_blob *)to; return blob->store(from); } 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 (is_temporal_type(from_type) && from_type != MYSQL_TYPE_YEAR && 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, false); } else if (is_temporal_type(from_type) && from_type != MYSQL_TYPE_YEAR && (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 (is_temporal_type(from_type) && from_type != MYSQL_TYPE_YEAR && is_temporal_type(to_type) && to_type != MYSQL_TYPE_YEAR) { return copy_time_to_time(from, to); } else if (from_type == MYSQL_TYPE_JSON && (is_integer_type(to_type) || to_type == MYSQL_TYPE_YEAR)) { return to->store(from->val_int(), from->is_flag_set(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 && is_temporal_type(to_type)) { 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: // MYSQL_TYPE_YEAR is handled as an integer above assert(false); } /* Field_json::get_time and get_date set ltime to zero, and we store it in the `to` field, so in case conversion errors are ignored we can read zeros instead of garbage. */ const type_conversion_status store_res = to->store_time(<ime); return res ? TYPE_ERR_BAD_VALUE : store_res; } 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->is_flag_set(UNSIGNED_FLAG)); }