diff --git a/src/share/object/ob_obj_cast.cpp b/src/share/object/ob_obj_cast.cpp index 784dad996..1712880aa 100644 --- a/src/share/object/ob_obj_cast.cpp +++ b/src/share/object/ob_obj_cast.cpp @@ -67,7 +67,7 @@ namespace common { using namespace number; -// hook definitions(registered in observer/omt/ob_srs_service.cpp and sql ob_expr_json_func_helper.cpp) +// hook definitions(registered in observer/omt/ob_tenant_srs.cpp and sql ob_expr_json_func_helper.cpp) ObObjCastGetSrsItemFn g_obj_cast_get_srs_item = nullptr; ObObjCastJsonMaxDepthFn g_obj_cast_json_max_depth = nullptr; @@ -456,6 +456,8 @@ ObNumber ObNumberConstValue::MYSQL_MIN[ObNumber::MAX_PRECISION + 1][ObNumber::MA ObNumber ObNumberConstValue::MYSQL_MAX[ObNumber::MAX_PRECISION + 1][ObNumber::MAX_SCALE + 1] = {}; ObNumber ObNumberConstValue::MYSQL_CHECK_MIN[ObNumber::MAX_PRECISION + 1][ObNumber::MAX_SCALE + 1] = {}; ObNumber ObNumberConstValue::MYSQL_CHECK_MAX[ObNumber::MAX_PRECISION + 1][ObNumber::MAX_SCALE + 1] = {}; +ObNumber ObNumberConstValue::NUMBER_CHECK_MIN[OB_MAX_NUMBER_PRECISION + 1][ObNumberConstValue::SCALE_RANGE_SIZE + 1] = {}; +ObNumber ObNumberConstValue::NUMBER_CHECK_MAX[OB_MAX_NUMBER_PRECISION + 1][ObNumberConstValue::SCALE_RANGE_SIZE + 1] = {}; int ObNumberConstValue::init(ObIAllocator &allocator) { @@ -512,6 +514,52 @@ int ObNumberConstValue::init(ObIAllocator &allocator) } } + { + for (int16_t precision = OB_MIN_NUMBER_PRECISION; OB_SUCC(ret) && precision <= OB_MAX_NUMBER_PRECISION; ++precision) { + for (int16_t scale = ObNumber::MIN_SCALE; OB_SUCC(ret) && scale <= ObNumber::MAX_SCALE; ++scale) { + pos = 1; + MEMSET(buf + pos, 0, BUFFER_SIZE - pos); + ObNumber &min_check_num = NUMBER_CHECK_MIN[precision][scale + SCALE_DELTA]; + ObNumber &max_check_num = NUMBER_CHECK_MAX[precision][scale + SCALE_DELTA]; + ObString tmp_string; + + if (precision >= scale && scale >= 0) { + /* number(3, 1) => legal range(-99.95, 99.95) */ + if (precision == scale) { + buf[pos++] = '0'; + } + MEMSET(buf + pos, '9', precision + 1); + buf[pos + precision - scale] = '.'; + buf[pos + precision + 1] = '5'; + tmp_string.assign_ptr(buf, pos + precision + 1 + 1); + } else if (scale < 0) { + /* number(2, -3) => legal range: (-99500, 99500) */ + MEMSET(buf + pos, '9', precision); + buf[pos + precision] = '5'; + MEMSET(buf + pos + precision + 1, '0', 0 - scale - 1); + tmp_string.assign_ptr(buf, pos + precision - scale); + } else { + //number(2, 3) => legal range:(-0.0995, 0.0995) + buf[pos++] = '0'; + buf[pos++] = '.'; + MEMSET(buf + pos, '0', scale - precision); + MEMSET(buf + pos + scale - precision, '9', precision); + buf[pos + scale] = '5'; + tmp_string.assign_ptr(buf, pos + scale + 1); + } + + // make min and max numbers. + if (OB_FAIL(min_check_num.from(tmp_string.ptr(), tmp_string.length(), allocator))) { + LOG_ERROR("fail to call from", K(precision), K(scale), K(tmp_string), K(ret)); + } else if (OB_FAIL(max_check_num.from(tmp_string.ptr() + 1, tmp_string.length() - 1, allocator))) { + LOG_ERROR("fail to call from", K(precision), K(scale), K(tmp_string), K(ret)); + } else { + total_alloc_size += sizeof(uint32_t) * (min_check_num.get_length() + max_check_num.get_length()); + LOG_DEBUG("succ to build min max number", K(precision), K(scale), K(tmp_string), K(total_alloc_size), K(min_check_num), K(max_check_num)); + } + } + } + } return ret; } @@ -1444,6 +1492,8 @@ static int common_get_srs_item(ObSrsGuardErased &srs_guard, { int ret = OB_SUCCESS; uint32_t srid = UINT32_MAX; + // todo : get effective tenant id + if (wkb.length() < WKB_GEO_SRID_SIZE) { ret = OB_ERR_GIS_INVALID_DATA; LOG_WARN("invalid data length", K(ret), K(wkb.length())); @@ -2014,23 +2064,7 @@ static int float_uint(const ObObjType expect_type, ObObjCastParams ¶ms, LOG_ERROR("invalid input type", K(ret), K(in), K(expect_type)); } else { - if (in_value <= static_cast(LLONG_MIN) || in_value >= static_cast(ULLONG_MAX)) { - value = static_cast(LLONG_MIN); - ret = OB_DATA_OUT_OF_RANGE; - } else { - // Compatible with MySQL behavior, floating-point numbers within (INT64_MAX, UINT64_MAX) cast to uint column result approximately equals the original float value - // and in other scenarios such as cast as unsigned, the result should be INT64_MAX + 1, i.e., rint(in_value)->int->uint - if (is_column_convert) { - value = static_cast(rint(in_value)); - } else { - value = static_cast(static_cast(rint(in_value))); - } - if (in_value < 0 && value != 0) { - // Here processing the range [LLONG_MIN, 0) of in, converting to unsigned should report OB_DATA_OUT_OF_RANGE. - // out is not equal to 0 to avoid values in the range [-0.5, 0) being misjudged, because their rounded values are 0, which is within the valid range. - ret = OB_DATA_OUT_OF_RANGE; - } - } + ret = round_floating_to_uint64(in_value, true, is_column_convert, value); if (CAST_FAIL(ret)) { LOG_WARN("cast float to uint failed", K(ret), K(in), K(value)); } @@ -2289,7 +2323,8 @@ static int float_bit(const ObObjType expect_type, ObObjCastParams ¶ms, { UNUSED(cast_mode); int ret = OB_SUCCESS; - uint64_t value = static_cast(in.get_float()); + uint64_t value = static_cast( + truncate_floating_to_int64_clamped(in.get_float())); int32_t bit_len = 0; if (OB_UNLIKELY(ObFloatTC != in.get_type_class() || ObBitTC != ob_obj_type_class(expect_type))) { @@ -2313,7 +2348,7 @@ static int float_enum(const ObExpectType &expect_type, ObObjCastParams ¶ms, ret = OB_INVALID_ARGUMENT; LOG_WARN("invalid argument", K(expect_type), K(in), K(ret)); } else { - int64_t value = static_cast(in.get_float()); + int64_t value = truncate_floating_to_int64_clamped(in.get_float()); ObObj int_val(value); if (OB_FAIL(int_enum(expect_type, params, int_val, out))) { LOG_WARN("fail to cast int to enum", K(expect_type), K(in), K(int_val), K(out), K(ret)); @@ -2330,7 +2365,7 @@ static int float_set(const ObExpectType &expect_type, ObObjCastParams ¶ms, c ret = OB_INVALID_ARGUMENT; LOG_WARN("invalid argument", K(expect_type), K(in), K(ret)); } else { - int64_t value = static_cast(in.get_float()); + int64_t value = truncate_floating_to_int64_clamped(in.get_float()); ObObj int_val(value); if (OB_FAIL(int_set(expect_type, params, int_val, out))) { LOG_WARN("fail to cast int to enum", K(expect_type), K(in), K(int_val), K(out), K(ret)); @@ -2476,28 +2511,7 @@ static int double_uint(const ObObjType expect_type, ObObjCastParams ¶ms, LOG_ERROR("invalid input type", K(ret), K(in), K(expect_type)); } else { - if (in_value <= static_cast(LLONG_MIN)) { - value = static_cast(LLONG_MIN); - ret = OB_DATA_OUT_OF_RANGE; - } else if (in_value >= static_cast(ULLONG_MAX)) { - value = static_cast(LLONG_MAX); - ret = OB_DATA_OUT_OF_RANGE; - } else { - if (is_column_convert) { - value = static_cast(rint(in_value)); - // Slightly different from float_uint, when converting double to uint for values in the range [INT64_MAX, UINT64_MAX), the result in scenarios other than column_convert - // should be INT64_MAX instead of INT64_MAX + 1. - } else if (in_value >= static_cast(LLONG_MAX)) { - value = static_cast(LLONG_MAX); - } else { - value = static_cast(static_cast(rint(in_value))); - } - if (in_value < 0 && value != 0) { - // Here processing the range [LLONG_MIN, 0) of in, converting to unsigned should report OB_DATA_OUT_OF_RANGE. - // out is not equal to 0 to avoid values in the range [-0.5, 0) being misjudged, because their rounded values are 0, which is within the valid range. - ret = OB_DATA_OUT_OF_RANGE; - } - } + ret = round_floating_to_uint64(in_value, false, is_column_convert, value); if (CAST_FAIL(ret)) { LOG_WARN("cast float to uint failed", K(ret), K(in), K(value)); } @@ -2821,7 +2835,8 @@ static int double_bit(const ObObjType expect_type, ObObjCastParams ¶ms, { UNUSED(cast_mode); int ret = OB_SUCCESS; - uint64_t value = static_cast(in.get_double()); + uint64_t value = static_cast( + truncate_floating_to_int64_clamped(in.get_double())); int32_t bit_len = 0; if (OB_UNLIKELY(ObDoubleTC != in.get_type_class() || ObBitTC != ob_obj_type_class(expect_type))) { @@ -2844,7 +2859,7 @@ static int double_enum(const ObExpectType &expect_type, ObObjCastParams ¶ms, ret = OB_INVALID_ARGUMENT; LOG_WARN("invalid argument", K(expect_type), K(in), K(ret)); } else { - int64_t value = static_cast(in.get_double()); + int64_t value = truncate_floating_to_int64_clamped(in.get_double()); ObObj int_val(value); if (OB_FAIL(int_enum(expect_type, params, int_val, out))) { LOG_WARN("fail to cast int to enum", K(expect_type), K(in), K(int_val), K(out), K(ret)); @@ -2861,7 +2876,7 @@ static int double_set(const ObExpectType &expect_type, ObObjCastParams ¶ms, ret = OB_INVALID_ARGUMENT; LOG_WARN("invalid argument", K(expect_type), K(in), K(ret)); } else { - int64_t value = static_cast(in.get_double()); + int64_t value = truncate_floating_to_int64_clamped(in.get_double()); ObObj int_val(value); if (OB_FAIL(int_set(expect_type, params, int_val, out))) { LOG_WARN("fail to cast int to enum", K(expect_type), K(in), K(int_val), K(out), K(ret)); @@ -12439,7 +12454,10 @@ int ob_obj_accuracy_check_only(const ObAccuracy &accuracy, const ObCollationType } // ob_obj_to_ob_time_with_date moved definition to sql/engine/expr/ob_datum_cast.cpp(transitional state) +// Note: master tenant-elim removed the MTL_ID() argument to ObArenaAllocator, HOST must be synced (see routing item) + // ob_obj_to_ob_time_without_date moved definition to sql/engine/expr/ob_datum_cast.cpp(transitional state) +// Note: master tenant-elim removed the MTL_ID() argument to ObArenaAllocator, HOST must be synced (see routing item) int ObObjCaster::to_type(const ObObjType expect_type, ObCastCtx &cast_ctx, const ObObj &in_obj, ObObj &buf_obj, const ObObj *&res_obj) diff --git a/src/share/object/ob_obj_cast_util.h b/src/share/object/ob_obj_cast_util.h index fd1d11518..a81800e5c 100644 --- a/src/share/object/ob_obj_cast_util.h +++ b/src/share/object/ob_obj_cast_util.h @@ -17,6 +17,7 @@ #ifndef OCEANBASE_COMMON_OB_OBJ_CAST_UTIL_ #define OCEANBASE_COMMON_OB_OBJ_CAST_UTIL_ +#include #include "share/ob_errno.h" #include "common/object/ob_object.h" #include "common/object/ob_obj_type.h" @@ -38,6 +39,79 @@ struct ObJsonZeroVal char json_bin_[4]; }; +// Floating-point values at and beyond these exact powers of two cannot be +// converted to int64_t/uint64_t before checking the range. Such conversions +// have undefined behavior in C++ and produce different results on x86_64 and +// arm64. +static constexpr double INT64_MIN_AS_DOUBLE = -9223372036854775808.0; +static constexpr double INT64_UPPER_BOUND_AS_DOUBLE = 9223372036854775808.0; +static constexpr double UINT64_UPPER_BOUND_AS_DOUBLE = 18446744073709551616.0; + +// MySQL Field_bit/Field_enum/Field_set::store(double) first clamps the value to +// the signed 64-bit range and then applies the destination type's own range +// and warning rules. +template +OB_INLINE int64_t truncate_floating_to_int64_clamped(const FloatingType in_val) +{ + const double value = static_cast(in_val); + int64_t out_val = 0; + if (std::isnan(value)) { + out_val = 0; + } else if (value < INT64_MIN_AS_DOUBLE) { + out_val = INT64_MIN; + } else if (value >= INT64_UPPER_BOUND_AS_DOUBLE) { + out_val = INT64_MAX; + } else { + out_val = static_cast(value); + } + return out_val; +} + +// Convert FLOAT/DOUBLE to an unsigned integer without executing an +// out-of-range floating-to-integer cast. FLOAT has a MySQL-compatible +// CAST-AS-UNSIGNED special case at 2^63, while DOUBLE keeps the existing +// INT64_MAX result for that non-column conversion. +OB_INLINE int round_floating_to_uint64(const double in_val, + const bool is_float_source, + const bool is_column_convert, + uint64_t &out_val) +{ + int ret = OB_SUCCESS; + const double rounded = std::rint(in_val); + out_val = 0; + if (std::isnan(rounded)) { + ret = OB_DATA_OUT_OF_RANGE; + } else if (is_column_convert) { + if (rounded < 0) { + out_val = 0; + ret = OB_DATA_OUT_OF_RANGE; + } else if (rounded >= UINT64_UPPER_BOUND_AS_DOUBLE) { + out_val = UINT64_MAX; + ret = OB_DATA_OUT_OF_RANGE; + } else { + out_val = static_cast(rounded); + } + } else if (rounded <= INT64_MIN_AS_DOUBLE) { + out_val = static_cast(INT64_MIN); + ret = OB_DATA_OUT_OF_RANGE; + } else if (rounded >= UINT64_UPPER_BOUND_AS_DOUBLE) { + out_val = is_float_source + ? static_cast(INT64_MIN) + : static_cast(INT64_MAX); + ret = OB_DATA_OUT_OF_RANGE; + } else if (rounded < 0) { + out_val = static_cast(static_cast(rounded)); + ret = OB_DATA_OUT_OF_RANGE; + } else if (rounded >= INT64_UPPER_BOUND_AS_DOUBLE) { + out_val = is_float_source + ? static_cast(INT64_MIN) + : static_cast(INT64_MAX); + } else { + out_val = static_cast(static_cast(rounded)); + } + return ret; +} + // check with given lower and upper limit. template diff --git a/src/sql/engine/expr/ob_datum_cast.cpp b/src/sql/engine/expr/ob_datum_cast.cpp index 2dd4a7d55..89e334921 100644 --- a/src/sql/engine/expr/ob_datum_cast.cpp +++ b/src/sql/engine/expr/ob_datum_cast.cpp @@ -33,7 +33,7 @@ #include "sql/engine/expr/ob_expr_xml_func_helper.h" #include "common/enumset/ob_enum_set_meta.h" #include "sql/engine/expr/ob_expr_type_to_str.h" -#include "observer/omt/ob_srs_service.h" +#include "observer/omt/ob_tenant_srs.h" namespace oceanbase { namespace sql @@ -154,9 +154,7 @@ static const int64_t power_of_10[INT32_MAX_DIGITS_LEN] = { static OB_INLINE int get_cast_ret(const ObCastMode &cast_mode, int ret, int &warning) { - // Timestamp parsing can produce a valid local datetime before discovering that the - // remaining suffix is not a valid time zone. Treat that as an invalid datetime so - // column conversion reports the normal 1292 error instead of storing the partial value. + // compatibility for old ob if (OB_UNLIKELY(OB_ERR_UNEXPECTED_TZ_TRANSITION == ret) || OB_UNLIKELY(OB_ERR_UNKNOWN_TIME_ZONE == ret)) { ret = OB_INVALID_DATE_VALUE; @@ -1295,6 +1293,128 @@ int ObDataTypeCastUtil::common_string_decimalint_wrap(const ObExpr &expr, const return common_string_decimalint(expr, in_str, user_logging_ctx, res_val); } +int ObOdpsDataTypeCastUtil::common_string_decimalint_wrap(const ObExpr &expr, const ObString &in_str, + const ObUserLoggingCtx *user_logging_ctx, + ObDecimalIntBuilder &res_val) +{// TODO: add cases +#define SET_ZERO(int_type) \ + int_type v = 0; \ + res_val.from(v); \ + break + + int ret = OB_SUCCESS; + ObObjType in_type = ObVarcharType; + int16_t in_scale = 0, in_precision = 0; + ObScale out_scale = expr.datum_meta_.scale_; + ObPrecision out_prec = expr.datum_meta_.precision_; + ObDecimalIntBuilder tmp_alloc; + ObDecimalInt *decint = nullptr; + int32_t int_bytes = 0; + // set default value + switch (get_decimalint_type(out_prec)) { + case common::DECIMAL_INT_32: { + SET_ZERO(int32_t); + } + case common::DECIMAL_INT_64: { + SET_ZERO(int64_t); + } + case common::DECIMAL_INT_128: { + SET_ZERO(int128_t); + } + case common::DECIMAL_INT_256: { + SET_ZERO(int256_t); + } + case common::DECIMAL_INT_512: { + SET_ZERO(int512_t); + } + default: + ret = OB_ERR_UNEXPECTED; + LOG_WARN("unexpected precision", K(out_prec)); + } + if (OB_FAIL(ret)) { + } else { + if (ObHexStringType == in_type) { + uint64_t in_val = hex_to_uint64(in_str); + in_precision = ob_fast_digits10(in_val); + if (OB_FAIL(wide::from_integer(in_val, tmp_alloc, decint, int_bytes, in_precision))) { + LOG_WARN("from integer failed", K(in_val), K(ret)); + } else { + in_scale = 0; + } + } else if (0 == in_str.length()) { + ret = OB_ERR_TRUNCATED_WRONG_VALUE_FOR_FIELD; + } else if (OB_FAIL(wide::from_string(in_str.ptr(), in_str.length(), tmp_alloc, in_scale, + in_precision, int_bytes, decint))) { + LOG_WARN("failed to parse string", K(ret)); + if (OB_NUMERIC_OVERFLOW == ret) { + // bug: 4263211. compatible with mysql behavior when value overflows type range. + // select cast('1e500' as decimal); -> max_val + // select cast('-1e500' as decimal); -> min_val + int64_t i = 0; + while (i < in_str.length() && isspace(in_str[i])) { ++i; } + bool is_neg = (in_str[i] == '-'); + const ObDecimalInt *limit_decint = nullptr; + if (is_neg) { + limit_decint = wide::ObDecimalIntConstValue::get_min_value(out_prec); + int_bytes = wide::ObDecimalIntConstValue::get_int_bytes_by_precision(out_prec); + } else { + limit_decint = wide::ObDecimalIntConstValue::get_max_value(out_prec); + int_bytes = wide::ObDecimalIntConstValue::get_int_bytes_by_precision(out_prec); + } + in_scale = out_scale; + in_precision = out_prec; + if (OB_ISNULL(limit_decint)) { + ret = OB_ERR_UNEXPECTED; + LOG_WARN("unexpected null decimal int", K(ret)); + } else if (OB_ISNULL(decint = (ObDecimalInt *)tmp_alloc.alloc(int_bytes))) { + ret = OB_ALLOCATE_MEMORY_FAILED; + LOG_WARN("failed to allocate memory", K(ret)); + } else { + MEMCPY(decint, limit_decint, int_bytes); + } + } + } + int warning = ret; + ret = OB_SUCCESS; + if (decint != nullptr && int_bytes != 0) { + // Decimal int not null means a valid decimal int was parsed regardless of wether there's + // error or not.We then do scale and calculate res_datum as normal in order to be compatible + // with mysql. + // e.g. + // OceanBase(root@test)>set sql_mode = ''; + // Query OK, 0 rows affected (0.00 sec) + // + // OceanBase(root@test)>insert into t2 values ('1ab'); + // Query OK, 1 row affected (0.00 sec) + // + // OceanBase(root@test)>select * from t2; + // +-------+ + // | a | + // +-------+ + // | 1.000 | + // +-------+ + // 1 row in set (0.01 sec) + if (ObDatumCast::need_scale_decimalint(in_scale, in_precision, out_scale, out_prec)) { + if (OB_FAIL(ObDatumCast::common_scale_decimalint(decint, int_bytes, in_scale, out_scale, + out_prec, expr.extra_, res_val, + user_logging_ctx))) { + LOG_WARN("scale decimal int failed", K(ret)); + } + } else { + res_val.from(decint, int_bytes); + } + } + if (OB_SUCC(ret)) { + const ObCastMode cast_mode = expr.extra_; + if (CAST_FAIL(warning)) { + LOG_WARN("string_decimalint failed", K(ret), K(in_type), K(cast_mode), K(in_str)); + } + } + } + return ret; +#undef SET_ZERO +} + static OB_INLINE int common_string_datetime(const ObExpr &expr, const ObString &in_str, ObEvalCtx &ctx, @@ -1670,6 +1790,83 @@ static int common_string_string(const ObExpr &expr, return ret; } +int ObOdpsDataTypeCastUtil::common_check_convert_string(const ObExpr &expr, + ObEvalCtx &ctx, + const ObString &in_str, + ObObjType in_type, + ObCollationType in_cs_type, + ObDatum &res_datum, + bool &has_set_res) +{ + int ret = OB_SUCCESS; + ObObjType out_type = expr.datum_meta_.type_; + ObCollationType out_cs_type = expr.datum_meta_.cs_type_; + // When convert blob/binary/varbinary to other charset, need to align to mbminlen of destination charset + // by add '\0' prefix in mysql mode. (see mysql String::copy) + const ObCharsetInfo *cs = NULL; + int64_t align_offset = 0; + if (CS_TYPE_BINARY == in_cs_type + && (NULL != (cs = ObCharset::get_charset(out_cs_type)))) { + if (cs->mbminlen > 0 && in_str.length() % cs->mbminlen != 0) { + align_offset = cs->mbminlen - in_str.length() % cs->mbminlen; + } + } + if (OB_FAIL(common_copy_string_zf(expr, in_str, ctx, res_datum, align_offset))) { + LOG_WARN("common_copy_string_zf failed", K(ret), K(in_str)); + } + return ret; +} + +int ObOdpsDataTypeCastUtil::common_string_string_wrap(const ObExpr &expr, + const ObObjType in_type, + const ObCollationType in_cs_type, + const ObObjType out_type, + const ObCollationType out_cs_type, + const ObString &in_str, + ObEvalCtx &ctx, + ObDatum &res_datum, + bool& has_set_res) +{ + int ret = OB_SUCCESS; + if (CS_TYPE_BINARY != in_cs_type && + CS_TYPE_BINARY != out_cs_type && + (ObCharset::charset_type_by_coll(in_cs_type) != + ObCharset::charset_type_by_coll(out_cs_type))) { + // handle !blob->!blob + char *buf = NULL; + //latin1 1bytes,utf8mb4 4bytes,the factor should be 4 + int64_t buf_len = in_str.length() * ObCharset::CharConvertFactorNum; + uint32_t result_len = 0; + buf = expr.get_str_res_mem(ctx, buf_len); + if (OB_ISNULL(buf)) { + ret = OB_ALLOCATE_MEMORY_FAILED; + LOG_WARN("alloc memory failed", K(ret)); + } else if (OB_FAIL(ObCharset::charset_convert(in_cs_type, in_str.ptr(), + in_str.length(), out_cs_type, buf, + buf_len, result_len, true, + !CM_IS_IGNORE_CHARSET_CONVERT_ERR(expr.extra_) && CM_IS_IMPLICIT_CAST(expr.extra_), + ObCharset::is_cs_unicode(out_cs_type) ? 0xFFFD : '?'))) { + LOG_WARN("charset convert failed", K(ret)); + } else { + res_datum.set_string(buf, result_len); + } + } else { + if (CS_TYPE_BINARY == in_cs_type || CS_TYPE_BINARY == out_cs_type) { + // just copy string when in_cs_type or out_cs_type is binary + if (OB_FAIL(ObOdpsDataTypeCastUtil::common_check_convert_string(expr, ctx, in_str, in_type, in_cs_type, res_datum, has_set_res))) { + LOG_WARN("fail to common_check_convert_string", K(ret), K(in_str)); + } + } else { + ret = OB_ERR_UNEXPECTED; + LOG_WARN("same charset should not be here, just use cast_eval_arg", K(ret), + K(in_type), K(out_type), K(in_cs_type), K(out_cs_type)); + } + } + LOG_DEBUG("string_string cast", K(ret), K(in_str), + K(ObString(res_datum.len_, res_datum.ptr_))); + return ret; +} + static int common_string_otimestamp(const ObExpr &expr, const ObString &in_str, ObEvalCtx &ctx, @@ -1886,6 +2083,53 @@ static int common_string_text(const ObExpr &expr, return ret; } +int ObOdpsDataTypeCastUtil::common_string_text_wrap(const ObExpr &expr, + const ObString &in_str, + ObEvalCtx &ctx, + const ObLobLocatorV2 *lob_locator, + ObDatum &res_datum, + ObObjType &in_type, + ObCollationType &in_cs_type) +{ + int ret = OB_SUCCESS; + ObObjType out_type = expr.datum_meta_.type_; // ObLongTextType + ObCollationType out_cs_type = expr.datum_meta_.cs_type_; + ObString res_str = in_str; + bool is_final_res = false; + bool is_different_charset_type = (ObCharset::charset_type_by_coll(in_cs_type) + != ObCharset::charset_type_by_coll(out_cs_type)); + OB_ASSERT(ob_is_text_tc(out_type)); + if (is_different_charset_type) { + if (OB_FAIL(ObOdpsDataTypeCastUtil::common_string_string_wrap(expr, in_type, in_cs_type, out_type, + out_cs_type, in_str, ctx, res_datum, is_final_res))) { + LOG_WARN("Lob: fail to cast string to longtext", K(ret), K(in_str), K(expr)); + } else if (res_datum.is_null()) { + // Charset conversion produced a final NULL result. + is_final_res = true; + } else if (is_final_res) { + // is_final_res = true; // hex to text + } else if (OB_FAIL(copy_datum_str_with_tmp_alloc(ctx, res_datum, res_str))) { + LOG_WARN("Lob: copy datum str with tmp alloc", K(ret)); + } else { /* do nothing */ } + } + + if (OB_SUCC(ret) && !is_final_res) { + ObTextStringDatumResult str_result(expr.datum_meta_.type_, &expr, &ctx, &res_datum); + if (lob_locator == NULL) { + if (OB_FAIL(str_result.init(res_str.length()))) { + LOG_WARN("Lob: init lob result failed"); + } else if (OB_FAIL(str_result.append(res_str.ptr(), res_str.length()))) { + LOG_WARN("Lob: append lob result failed"); + } else { /* do nothing */ } + } else if (OB_FAIL(str_result.copy(lob_locator))) { + LOG_WARN("Lob: copy lob result failed"); + } else { /* do nothing*/ } + str_result.set_result(); + } + + return ret; +} + static int common_uint_bit(const ObExpr &expr, const uint64_t &in_value, ObEvalCtx &ctx, @@ -4310,21 +4554,8 @@ CAST_FUNC_NAME(float, uint) EVAL_ARG() { DEF_IN_OUT_VAL(float, uint64_t, 0); - if (in_val <= static_cast(LLONG_MIN) || in_val >= static_cast(ULLONG_MAX)) { - out_val = static_cast(LLONG_MIN); - ret = OB_DATA_OUT_OF_RANGE; - } else { - if (CM_IS_COLUMN_CONVERT(expr.extra_)) { - out_val = static_cast(rint(in_val)); - } else { - out_val = static_cast(static_cast(rint(in_val))); - } - if (in_val < 0 && out_val != 0) { - // Here processing [LLONG_MIN, 0) range of in, converting to unsigned should report OB_DATA_OUT_OF_RANGE. - // out not equal to 0 to avoid values in [-0.5, 0) being misjudged, because their rounded values are 0, which are within the valid range. - ret = OB_DATA_OUT_OF_RANGE; - } - } + ret = round_floating_to_uint64( + static_cast(in_val), true, CM_IS_COLUMN_CONVERT(expr.extra_), out_val); if (CAST_FAIL(ret)) { LOG_WARN("cast float to uint failed", K(ret), K(in_val), K(out_val)); } else if (CM_NEED_RANGE_CHECK(expr.extra_) && @@ -4576,7 +4807,8 @@ CAST_FUNC_NAME(float, bit) { float val_float = child_res->get_float(); // Here there is no need to call SET_RES_BIT, because ret must be OB_SUCCESS - res_datum.set_bit(static_cast(val_float)); + res_datum.set_bit(static_cast( + truncate_floating_to_int64_clamped(val_float))); } return ret; } @@ -4642,26 +4874,8 @@ CAST_FUNC_NAME(double, uint) EVAL_ARG() { DEF_IN_OUT_VAL(double, uint64_t, 0); - if (in_val <= static_cast(LLONG_MIN)) { - out_val = static_cast(LLONG_MIN); - ret = OB_DATA_OUT_OF_RANGE; - } else if (in_val >= static_cast(ULLONG_MAX)) { - out_val = static_cast(LLONG_MAX); - ret = OB_DATA_OUT_OF_RANGE; - } else { - if (CM_IS_COLUMN_CONVERT(expr.extra_)) { - out_val = static_cast(rint(in_val)); - } else if (in_val >= static_cast(LLONG_MAX)) { - out_val = static_cast(LLONG_MAX); - } else { - out_val = static_cast(static_cast(rint(in_val))); - } - if (in_val < 0 && out_val != 0) { - // Here processing [LLONG_MIN, 0) range of in, converting to unsigned should report OB_DATA_OUT_OF_RANGE. - // out is not equal to 0 to avoid values in the range [-0.5, 0) being misjudged, because their rounded values are 0, which fall within the valid range. - ret = OB_DATA_OUT_OF_RANGE; - } - } + ret = round_floating_to_uint64( + in_val, false, CM_IS_COLUMN_CONVERT(expr.extra_), out_val); if (CAST_FAIL(ret)) { LOG_WARN("cast float to uint failed", K(ret), K(in_val), K(out_val)); } else if (CM_NEED_RANGE_CHECK(expr.extra_) && @@ -4881,7 +5095,8 @@ CAST_FUNC_NAME(double, bit) { EVAL_ARG() { - res_datum.set_bit(static_cast(child_res->get_double())); + res_datum.set_bit(static_cast( + truncate_floating_to_int64_clamped(child_res->get_double()))); } return ret; } @@ -9917,7 +10132,8 @@ CAST_ENUMSET_FUNC_NAME(float, enum) res_datum.set_null(); } else { int warning = 0; - uint64_t val_uint = static_cast(static_cast(child_res->get_float())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(child_res->get_float())); uint64_t value = 0; ret = uint_to_enum(val_uint, str_values, cast_mode, warning, value); SET_RES_ENUM(value); @@ -9930,7 +10146,8 @@ CAST_ENUMSET_FUNC_NAME(float, set) { EVAL_ARG() { int warning = 0; - uint64_t val_uint = static_cast(static_cast(child_res->get_float())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(child_res->get_float())); uint64_t value = 0; ret = uint_to_set(val_uint, str_values, cast_mode, warning, value); SET_RES_SET(value); @@ -9945,7 +10162,8 @@ CAST_ENUMSET_FUNC_NAME(double, enum) res_datum.set_null(); } else { int warning = 0; - uint64_t val_uint = static_cast(static_cast(child_res->get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(child_res->get_double())); uint64_t value = 0; ret = uint_to_enum(val_uint, str_values, cast_mode, warning, value); SET_RES_ENUM(value); @@ -9958,7 +10176,8 @@ CAST_ENUMSET_FUNC_NAME(double, set) { EVAL_ARG() { int warning = 0; - uint64_t val_uint = static_cast(static_cast(child_res->get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(child_res->get_double())); uint64_t value = 0; ret = uint_to_set(val_uint, str_values, cast_mode, warning, value); SET_RES_SET(value); @@ -9975,7 +10194,8 @@ CAST_ENUMSET_FUNC_NAME(number, enum) } else if (res_datum.is_null()) { //do nothing } else { - uint64_t val_uint = static_cast(static_cast(res_datum.get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(res_datum.get_double())); uint64_t value = 0; ret = uint_to_enum(val_uint, str_values, cast_mode, warning, value); SET_RES_ENUM(value); @@ -9992,7 +10212,8 @@ CAST_ENUMSET_FUNC_NAME(number, set) } else if (res_datum.is_null()) { //do nothing } else { - uint64_t val_uint = static_cast(static_cast(res_datum.get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(res_datum.get_double())); uint64_t value = 0; ret = uint_to_set(val_uint, str_values, cast_mode, warning, value); SET_RES_SET(value); @@ -10718,7 +10939,8 @@ CAST_ENUMSET_FUNC_NAME(decimalint, enum) } else if (res_datum.is_null()) { // do nothing } else { - uint64_t val_uint = static_cast(static_cast(res_datum.get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(res_datum.get_double())); uint64_t value = 0; ret = uint_to_enum(val_uint, str_values, cast_mode, warning, value); SET_RES_ENUM(value); @@ -10735,7 +10957,8 @@ CAST_ENUMSET_FUNC_NAME(decimalint, set) } else if (res_datum.is_null()) { // do nothing } else { - uint64_t val_uint = static_cast(static_cast(res_datum.get_double())); + uint64_t val_uint = static_cast( + truncate_floating_to_int64_clamped(res_datum.get_double())); uint64_t value = 0; ret = uint_to_set(val_uint, str_values, cast_mode, warning, value); SET_RES_SET(value); @@ -11150,7 +11373,12 @@ int anytype_to_varchar_char_explicit(const sql::ObExpr &expr, bool has_result = false; int64_t max_allowed_packet = 0; if (OB_FAIL(session->get_max_allowed_packet(max_allowed_packet))) { - LOG_WARN("Failed to get max allow packet size", K(ret)); + if (OB_ENTRY_NOT_EXIST == ret) { // for compatibility with server before 1470 + ret = OB_SUCCESS; + max_allowed_packet = OB_MAX_VARCHAR_LENGTH; + } else { + LOG_WARN("Failed to get max allow packet size", K(ret)); + } } else if (out_acc.get_length() > max_allowed_packet && out_acc.get_length() <= INT32_MAX) { res_datum.set_null(); diff --git a/src/sql/engine/expr/vector_cast/cast_to_int.ipp b/src/sql/engine/expr/vector_cast/cast_to_int.ipp index 4553b3830..df20c438f 100644 --- a/src/sql/engine/expr/vector_cast/cast_to_int.ipp +++ b/src/sql/engine/expr/vector_cast/cast_to_int.ipp @@ -208,27 +208,11 @@ struct ToIntegerCastImpl int warning = OB_SUCCESS; IN_TYPE in_val = *reinterpret_cast(arg_vec_->get_payload(idx)); uint64_t out_val = 0; - if (in_val <= static_cast(LLONG_MIN)) { - out_val = static_cast(LLONG_MIN); - ret = OB_DATA_OUT_OF_RANGE; - } else if (in_val >= static_cast(ULLONG_MAX)) { - out_val = std::is_same::value - ? static_cast(LLONG_MIN) : static_cast(LLONG_MAX); - ret = OB_DATA_OUT_OF_RANGE; - } else { - if (CM_IS_COLUMN_CONVERT(expr.extra_)) { - out_val = static_cast(rint(in_val)); - } else if (std::is_same::value && in_val >= static_cast(LLONG_MAX)) { - out_val = static_cast(LLONG_MAX); - } else { - out_val = static_cast(static_cast(rint(in_val))); - } - if (in_val < 0 && out_val != 0) { - // 这里处理[LLONG_MIN, 0)范围内的in,转换为unsigned应该报OB_DATA_OUT_OF_RANGE。 - // out不等于0避免[-0.5, 0)内的值被误判,因为它们round后的值是0,处于合法范围内。 - ret = OB_DATA_OUT_OF_RANGE; - } - } + ret = round_floating_to_uint64( + static_cast(in_val), + std::is_same::value, + CM_IS_COLUMN_CONVERT(expr.extra_), + out_val); if (CAST_FAIL(ret)) { SQL_LOG(WARN, "cast float to uint failed", K(ret), K(in_val), K(out_val)); } else if (CAST_FAIL(uint_range_check(out_type_, out_val, out_val))) { diff --git a/tools/deploy/mysql_test/test_suite/pl/r/mysql/sp-vars_mysql.result b/tools/deploy/mysql_test/test_suite/pl/r/mysql/sp-vars_mysql.result index 0543b12e0..92adfe853 100644 --- a/tools/deploy/mysql_test/test_suite/pl/r/mysql/sp-vars_mysql.result +++ b/tools/deploy/mysql_test/test_suite/pl/r/mysql/sp-vars_mysql.result @@ -150,7 +150,7 @@ CALL sp_vars_check_dflt(); +-------+-------+-------+-------+ | v17 | v18 | v19 | v20 | +-------+-------+-------+-------+ -| 127 | 255 | -128 | 255 | 127 | 255 | -128 | 0 | +| 127 | 255 | -128 | 0 | 127 | 255 | -128 | 0 | | 127 | 255 | -128 | 0 | | 10 | 10 | 10 | 0 | 0 | 10 | 10 | 10 | 10 | 0 | | 12.00 | 12.12 | 12.00 | 12.12 | @@ -180,7 +180,7 @@ CALL sp_vars_check_assignment(); +---------+---------+---------+ | 127 | -128 | 127 | -128 | | 127 | -128 | 127 | 127 | -| 255 | 255 | 255 | 0 | +| 255 | 0 | 255 | 0 | | 255 | 0 | 200 | 200 | | 1234.00 | 1234.12 | 1234.12 | | 1234.00 | 1234.12 | 1234.12 | @@ -248,7 +248,7 @@ CALL sp_vars_check_dflt(); +-------+-------+-------+-------+ | v17 | v18 | v19 | v20 | +-------+-------+-------+-------+ -| 127 | 255 | -128 | 255 | 127 | 255 | -128 | 0 | +| 127 | 255 | -128 | 0 | 127 | 255 | -128 | 0 | | 127 | 255 | -128 | 0 | | 10 | 10 | 10 | 0 | 0 | 10 | 10 | 10 | 10 | 0 | | 12.00 | 12.12 | 12.00 | 12.12 | @@ -278,7 +278,7 @@ CALL sp_vars_check_assignment(); +---------+---------+---------+ | 127 | -128 | 127 | -128 | | 127 | -128 | 127 | 127 | -| 255 | 255 | 255 | 0 | +| 255 | 0 | 255 | 0 | | 255 | 0 | 200 | 200 | | 1234.00 | 1234.12 | 1234.12 | | 1234.00 | 1234.12 | 1234.12 | diff --git a/unittest/share/test_obj_cast.cpp b/unittest/share/test_obj_cast.cpp index 573f9b114..464140d58 100644 --- a/unittest/share/test_obj_cast.cpp +++ b/unittest/share/test_obj_cast.cpp @@ -16,7 +16,9 @@ #define USING_LOG_PREFIX SHARE #include "share/object/ob_obj_cast.h" +#include "share/object/ob_obj_cast_util.h" #include +#include namespace oceanbase @@ -87,6 +89,72 @@ TEST_F(TestObjCast, test_number_range_check_mysql_new) _OB_LOG(INFO, "test_number_range_check_mysql_new(%d) cost time: %f", (ObNumber::MAX_PRECISION + 1) * (ObNumber::MAX_SCALE + 1) / 2, (double)get_range_cost / (double)1000); } +TEST_F(TestObjCast, deterministic_floating_to_integer) +{ + EXPECT_EQ(INT64_MIN, truncate_floating_to_int64_clamped(-2.0e28)); + EXPECT_EQ(INT64_MAX, truncate_floating_to_int64_clamped(2.0e28)); + EXPECT_EQ(INT64_MAX, + truncate_floating_to_int64_clamped( + static_cast(INT64_UPPER_BOUND_AS_DOUBLE))); + EXPECT_EQ(-1, truncate_floating_to_int64_clamped(-1.9)); + EXPECT_EQ(0, + truncate_floating_to_int64_clamped( + std::numeric_limits::quiet_NaN())); + + uint64_t out_val = 0; + EXPECT_EQ(OB_SUCCESS, round_floating_to_uint64(-0.5, true, true, out_val)); + EXPECT_EQ(0, out_val); + EXPECT_EQ(OB_DATA_OUT_OF_RANGE, + round_floating_to_uint64( + static_cast(static_cast(-0.50001)), + true, + true, + out_val)); + EXPECT_EQ(0, out_val); + EXPECT_EQ(OB_DATA_OUT_OF_RANGE, + round_floating_to_uint64( + static_cast(static_cast(-2.0e28)), + true, + true, + out_val)); + EXPECT_EQ(0, out_val); + EXPECT_EQ(OB_DATA_OUT_OF_RANGE, + round_floating_to_uint64( + static_cast(static_cast(2.0e28)), + true, + true, + out_val)); + EXPECT_EQ(UINT64_MAX, out_val); + EXPECT_EQ(OB_DATA_OUT_OF_RANGE, + round_floating_to_uint64(-2.0e28, false, true, out_val)); + EXPECT_EQ(0, out_val); + EXPECT_EQ(OB_DATA_OUT_OF_RANGE, + round_floating_to_uint64(2.0e28, false, true, out_val)); + EXPECT_EQ(UINT64_MAX, out_val); + EXPECT_EQ(OB_SUCCESS, + round_floating_to_uint64( + static_cast( + static_cast(INT64_UPPER_BOUND_AS_DOUBLE)), + true, + false, + out_val)); + EXPECT_EQ(static_cast(INT64_MIN), out_val); + EXPECT_EQ(OB_SUCCESS, + round_floating_to_uint64( + static_cast(static_cast(1.2e19)), + true, + false, + out_val)); + EXPECT_EQ(static_cast(INT64_MIN), out_val); + EXPECT_EQ(OB_SUCCESS, + round_floating_to_uint64( + INT64_UPPER_BOUND_AS_DOUBLE, + false, + false, + out_val)); + EXPECT_EQ(static_cast(INT64_MAX), out_val); +} + } // end namespace share } // end namespace oceanbase