From 240ef77d126be015a7a0c6db1cb10535cf0c2e59 Mon Sep 17 00:00:00 2001 From: Xue Zhenliang Date: Wed, 18 Aug 2021 18:26:00 +0800 Subject: [PATCH 1/3] Push down `ROUND(x, d)` (#2625) --- dbms/src/Debug/dbgFuncCoprocessor.cpp | 58 ++ dbms/src/Flash/Coprocessor/DAGUtils.cpp | 6 +- dbms/src/Functions/FunctionsRound.h | 598 +++++++++++++----- .../tests/gtest_functions_round_with_frac.cpp | 529 ++++++++++++++++ dbms/src/TestUtils/FunctionTestUtils.h | 2 +- .../query/expr/round_with_frac.test | 452 +++++++++++++ .../fullstack-test/expr/round_with_frac.test | 500 +++++++++++++++ 7 files changed, 1972 insertions(+), 173 deletions(-) create mode 100644 dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp create mode 100644 tests/delta-merge-test/query/expr/round_with_frac.test create mode 100644 tests/fullstack-test/expr/round_with_frac.test diff --git a/dbms/src/Debug/dbgFuncCoprocessor.cpp b/dbms/src/Debug/dbgFuncCoprocessor.cpp index 149d1e22b6e..1e77ab94ef2 100644 --- a/dbms/src/Debug/dbgFuncCoprocessor.cpp +++ b/dbms/src/Debug/dbgFuncCoprocessor.cpp @@ -128,6 +128,14 @@ std::unordered_map func_name_to_sig({ {"cast_decimal_datetime", tipb::ScalarFuncSig::CastDecimalAsTime}, {"cast_time_datetime", tipb::ScalarFuncSig::CastTimeAsTime}, {"cast_string_datetime", tipb::ScalarFuncSig::CastStringAsTime}, + {"round_int", tipb::ScalarFuncSig::RoundInt}, + {"round_uint", tipb::ScalarFuncSig::RoundInt}, + {"round_dec", tipb::ScalarFuncSig::RoundDec}, + {"round_real", tipb::ScalarFuncSig::RoundReal}, + {"round_with_frac_int", tipb::ScalarFuncSig::RoundWithFracInt}, + {"round_with_frac_uint", tipb::ScalarFuncSig::RoundWithFracInt}, + {"round_with_frac_dec", tipb::ScalarFuncSig::RoundWithFracDec}, + {"round_with_frac_real", tipb::ScalarFuncSig::RoundWithFracReal}, }); @@ -777,11 +785,41 @@ void astToPB(const DAGSchema & input, ASTPtr ast, tipb::Expr * expr, uint32_t co ft->set_collate(collator_id); break; } + case tipb::ScalarFuncSig::RoundInt: + case tipb::ScalarFuncSig::RoundWithFracInt: + { + expr->set_sig(it_sig->second); + auto * ft = expr->mutable_field_type(); + ft->set_tp(TiDB::TypeLongLong); + if (it_sig->first.find("uint") != std::string::npos) + ft->set_flag(TiDB::ColumnFlagUnsigned); + ft->set_collate(collator_id); + break; + } + case tipb::ScalarFuncSig::RoundDec: + case tipb::ScalarFuncSig::RoundWithFracDec: + { + expr->set_sig(it_sig->second); + auto * ft = expr->mutable_field_type(); + ft->set_tp(TiDB::TypeNewDecimal); + ft->set_collate(collator_id); + break; + } + case tipb::ScalarFuncSig::RoundReal: + case tipb::ScalarFuncSig::RoundWithFracReal: + { + expr->set_sig(it_sig->second); + auto * ft = expr->mutable_field_type(); + ft->set_tp(TiDB::TypeDouble); + ft->set_collate(collator_id); + break; + } default: { expr->set_sig(it_sig->second); auto * ft = expr->mutable_field_type(); ft->set_tp(TiDB::TypeLongLong); + std::cerr << ft->flag() << std::endl; ft->set_flag(TiDB::ColumnFlagUnsigned); ft->set_collate(collator_id); break; @@ -1666,6 +1704,26 @@ TiDB::ColumnInfo compileExpr(const DAGSchema & input, ASTPtr ast) ci.tp = TiDB::TypeDouble; break; } + case tipb::ScalarFuncSig::RoundInt: + case tipb::ScalarFuncSig::RoundWithFracInt: + { + ci.tp = TiDB::TypeLongLong; + if (it_sig->first.find("uint") != std::string::npos) + ci.flag = TiDB::ColumnFlagUnsigned; + break; + } + case tipb::ScalarFuncSig::RoundDec: + case tipb::ScalarFuncSig::RoundWithFracDec: + { + ci.tp = TiDB::TypeNewDecimal; + break; + } + case tipb::ScalarFuncSig::RoundReal: + case tipb::ScalarFuncSig::RoundWithFracReal: + { + ci.tp = TiDB::TypeDouble; + break; + } default: ci.tp = TiDB::TypeLongLong; ci.flag = TiDB::ColumnFlagUnsigned; diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index 16d64843a8f..72189a372df 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -643,9 +643,9 @@ std::unordered_map scalar_func_map({ {tipb::ScalarFuncSig::RoundReal, "tidbRound"}, {tipb::ScalarFuncSig::RoundInt, "tidbRound"}, {tipb::ScalarFuncSig::RoundDec, "tidbRound"}, - // {tipb::ScalarFuncSig::RoundWithFracReal, "tidbRoundWithFrac"}, - // {tipb::ScalarFuncSig::RoundWithFracInt, "tidbRoundWithFrac"}, - // {tipb::ScalarFuncSig::RoundWithFracDec, "tidbRoundWithFrac"}, + {tipb::ScalarFuncSig::RoundWithFracReal, "tidbRoundWithFrac"}, + {tipb::ScalarFuncSig::RoundWithFracInt, "tidbRoundWithFrac"}, + {tipb::ScalarFuncSig::RoundWithFracDec, "tidbRoundWithFrac"}, {tipb::ScalarFuncSig::Log1Arg, "log"}, {tipb::ScalarFuncSig::Log2Args, "log2args"}, diff --git a/dbms/src/Functions/FunctionsRound.h b/dbms/src/Functions/FunctionsRound.h index a138d15602d..a4bb3993bd1 100644 --- a/dbms/src/Functions/FunctionsRound.h +++ b/dbms/src/Functions/FunctionsRound.h @@ -25,6 +25,7 @@ namespace DB namespace ErrorCodes { extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int OVERFLOW_ERROR; } @@ -861,180 +862,389 @@ class FunctionRoundingDecimalToInt : public IFunction */ using FracType = Int64; -template -struct TiDBIntegerRound +// build constant table of up to Nth power of 10 at compile time. +template +struct ConstPowOf10 { - static_assert(is_integer_v); + using ArrayType = std::array; - static constexpr InputType eval(const InputType & input, FracType frac [[maybe_unused]]) + static constexpr T base = static_cast(10); + + static constexpr std::pair build() { - // TODO: RoundWithFrac. - assert(frac == 0); + ArrayType result{1}; + + bool overflow = false; + for (size_t i = 1; i <= N; ++i) + { + result[i] = result[i - 1] * base; + overflow |= (result[i - 1] != result[i] / base); + } - return input; + return {result, overflow}; } + + static constexpr auto pair = build(); + static constexpr auto result = pair.first; + static constexpr bool overflow = pair.second; + + static_assert(!overflow, "Computation overflows"); }; -template +template struct TiDBFloatingRound { static_assert(std::is_floating_point_v); + static_assert(std::is_floating_point_v); + static_assert(sizeof(OutputType) >= sizeof(InputType)); - // EvalType is the type used in evaluations. - // in MySQL, floating round always returns Float64. - using EvalType = Float64; - - static constexpr EvalType eval(const InputType & input, FracType frac [[maybe_unused]]) + static OutputType eval(InputType input, FracType frac) { - // TODO: RoundWithFrac. - assert(frac == 0); + // modified from . + + auto value = static_cast(input); + auto base = 1.0; - auto value = static_cast(input); + if (frac != 0) + { + // TODO: `std::pow` is expensive. Need optimization here. + // one possible optimization is pre-computation. See . + base = std::pow(10.0, frac); + auto scaled_value = value * base; + + if (std::isinf(scaled_value)) + { + // if frac is too large, base will be +inf. + // at this time, it is usually beyond the effective precision of floating-point type. + // no rounding is needed. + return value; + } + else + value = scaled_value; + } // floating-point environment is thread-local, so `fesetround` is thread-safe. std::fesetround(FE_TONEAREST); - return std::nearbyint(value); + value = std::nearbyint(value); + + if (frac != 0) + { + value /= base; + + if (!std::isfinite(value)) + { + // if frac is too small, base will be zero. + // at this time, even the maximum of possible floating-point values will be + // rounded to zero. + return 0.0; + } + } + + return value; } }; -// build constant table of up to Nth power of 10 at compile time. -template -struct ConstPowOf10 +template +struct TiDBIntegerRound { - using ArrayType = std::array; + static_assert(is_integer_v); + static_assert(is_integer_v); + static_assert(actual_size_v >= actual_size_v); + + static constexpr auto digits = std::numeric_limits::digits10; + static constexpr auto max_digits = digits + 1; - static constexpr ArrayType build() + using UnsignedOutput = make_unsigned_t; + using Pow = ConstPowOf10; + + static void throwOverflowIf(bool condition) { - ArrayType result = {1}; - for (size_t i = 1; i <= N; ++i) - result[i] = result[i - 1] * static_cast(10); - return result; + if (condition) + throw Exception(fmt::format("integer value is out of range in 'round'"), ErrorCodes::OVERFLOW_ERROR); + } + + static OutputType castBack(bool negative [[maybe_unused]], UnsignedOutput value) + { + if constexpr (is_signed_v) + { + if (negative) + { + auto bound = toSafeUnsigned(std::numeric_limits::min()); + throwOverflowIf(value > bound); + return static_cast(-value); + } + else + { + auto bound = toSafeUnsigned(std::numeric_limits::max()); + throwOverflowIf(value > bound); + return static_cast(value); + } + } + else + { + static_assert(std::is_same_v); + return value; + } + } + + static OutputType eval(InputType input, FracType frac) + { + auto value = static_cast(input); + + if (frac >= 0) + return value; + else if (frac < -max_digits) + return 0; + else + { + frac = -frac; + + // we need to cast input to unsigned first to ensure overflow is not + // undefined behavior. + auto absolute_value = toSafeUnsigned(value); + + if (frac == max_digits) + { + auto base = Pow::result[digits]; + + // test `x >= 5 * base`, but `5 * base` may overflow. + // since `base` is integer, `x / 5 >= base` iff. `floor(x / 5) >= base`. + // if true, x will round up. But rounding up will definitely result in overflow. + throwOverflowIf(absolute_value / 5 >= base); + + // round down. + absolute_value = 0; + } + else + { + auto base = Pow::result[frac]; + auto remainder = absolute_value % base; + + absolute_value -= remainder; + if (remainder >= base / 2) + { + // round up. + auto rounded_value = absolute_value + base; + throwOverflowIf(rounded_value < absolute_value); + absolute_value = rounded_value; + } + } + + return castBack((input < 0), absolute_value); + } } +}; - static constexpr ArrayType result = build(); +struct TiDBDecimalRoundInfo +{ + FracType input_prec; + FracType input_scale; + FracType input_int_prec; // number of digits before decimal point. + + FracType output_prec; + FracType output_scale; + + TiDBDecimalRoundInfo(const IDataType & input_type, const IDataType & output_type) + : input_prec(getDecimalPrecision(input_type, 0)), + input_scale(getDecimalScale(input_type, 0)), + input_int_prec(input_prec - input_scale), + output_prec(getDecimalPrecision(output_type, 0)), + output_scale(getDecimalScale(output_type, 0)) + {} }; template struct TiDBDecimalRound { static_assert(IsDecimal); + static_assert(IsDecimal); - using UnsignedNativeType = make_unsigned_t; - using Pow = ConstPowOf10()>; + // output type is promoted to prevent overflow. + // this case is very rare, such as Decimal(90, 30) truncated to Decimal(65, 30). + using UnsignedInput = make_unsigned_t; + using UnsignedOutput = make_unsigned_t::Type>; - static constexpr OutputType eval(const InputType & input, FracType frac [[maybe_unused]], ScaleType input_scale) - { - // TODO: RoundWithFrac. - assert(frac == 0); + using PowForInput = ConstPowOf10()>; + using PowForOutput = ConstPowOf10()>; - auto divider = Pow::result[input_scale]; - auto absolute_value = toSafeUnsigned(input.value); + static OutputType eval(const InputType & input, FracType frac, const TiDBDecimalRoundInfo & info) + { + // e.g. round(999.9999, -4) = 0. + if (frac < -info.input_int_prec) + return static_cast(0); - // "round half away from zero" - // examples: - // - input.value = 149, input_scale = 2, divider = 100, result = (149 + 100 / 2) / 100 = 1 - // - input.value = 150, input_scale = 2, divider = 100, result = (150 + 100 / 2) / 100 = 2 - auto absolute_result = static_cast((absolute_value + divider / 2) / divider); + // rounding. + auto absolute_value = toSafeUnsigned(input.value); + if (frac < info.input_scale) + { + FracType frac_index = info.input_scale - frac; + assert(frac_index <= info.input_prec); - if (input.value < 0) - return -static_cast(absolute_result); - else - return static_cast(absolute_result); - } -}; + auto base = PowForInput::result[frac_index]; + auto remainder = absolute_value % base; -static FracType getFracFromConstColumn(const ColumnConst * column) -{ - FracType result; - auto frac_field = column->getField(); + absolute_value -= remainder; + if (remainder >= base / 2) + { + // round up. + absolute_value += base; + } + } - if (!frac_field.tryGet(result)) - { - // maybe field is unsigned. - static_assert(is_signed_v); - make_unsigned_t unsigned_frac; + // convert from input_scale to output_scale. + FracType difference = info.input_scale - info.output_scale; - if (!frac_field.tryGet(unsigned_frac)) + if (difference > 0) { - throw Exception( - fmt::format("Illegal frac column with type {}, expected to const Int64/UInt64", column->getField().getTypeName()), - ErrorCodes::ILLEGAL_COLUMN); + // output_scale will be different from input_scale only if frac is const. + // in this case, all digits discarded by the following division should be zeroes. + // they are reset to zeroes because of rounding. + auto base = PowForInput::result[difference]; + assert(absolute_value % base == 0); + + absolute_value /= base; } - result = static_cast(unsigned_frac); - } + auto scaled_value = static_cast(absolute_value); - // in MySQL, frac is clamped to 30, which is identical to decimal_max_scale. - // frac is signed but decimal_max_scale is unsigned, so we have to cast before - // comparison. - if (result > static_cast(decimal_max_scale)) - result = decimal_max_scale; + if (difference < 0) + scaled_value *= PowForOutput::result[-difference]; - return result; -} + // check overflow and construct result. + if (scaled_value > DecimalMaxValue::Get(info.output_prec)) + throw TiFlashException("Data truncated", Errors::Decimal::Overflow); + + auto result = static_cast(scaled_value); + if (input.value < 0) + return -static_cast(result); + else + return static_cast(result); + } +}; struct TiDBRoundPrecisionInferer { - static std::tuple infer( - PrecType prec, ScaleType scale, FracType frac [[maybe_unused]], bool is_const_frac [[maybe_unused]]) + static std::tuple infer(PrecType prec, ScaleType scale, FracType frac, bool is_const_frac) { - // TODO: RoundWithFrac. - assert(is_const_frac); - assert(frac == 0); - assert(prec >= scale); - PrecType new_prec = prec - scale; + PrecType int_prec = prec - scale; + ScaleType new_scale = scale; // +1 for possible overflow, e.g. round(99999.9) => 100000 - if (scale > 0) - new_prec += 1; + ScaleType int_prec_increment = 1; - return std::make_tuple(new_prec, 0); + if (is_const_frac) + { + if (frac > static_cast(decimal_max_scale)) + frac = decimal_max_scale; + + new_scale = std::max(0, frac); + + if (frac >= static_cast(scale)) + int_prec_increment = 0; + } + + PrecType new_prec = std::min(decimal_max_prec, int_prec + int_prec_increment + new_scale); + return std::make_tuple(new_prec, new_scale); } }; -template +struct TiDBRoundArguments +{ + const DataTypePtr & input_type; + const DataTypePtr & frac_type; + const DataTypePtr & output_type; + const ColumnPtr & input_column; + const ColumnPtr & frac_column; + MutableColumnPtr & output_column; +}; + +template struct TiDBRound { - static void apply(const ColumnPtr & input_column_, const ColumnPtr & frac_column_, MutableColumnPtr & output_column_, - ScaleType input_scale [[maybe_unused]], ScaleType output_scale [[maybe_unused]]) + static void apply(const TiDBRoundArguments & args) { - auto input_column = checkAndGetColumn(input_column_.get()); - auto frac_column = checkAndGetColumn(frac_column_.get()); + auto input_column = checkAndGetColumn(args.input_column.get()); + auto frac_column = checkAndGetColumn(args.frac_column.get()); if (input_column == nullptr) - throw Exception(fmt::format("Illegal column {} for the first argument of function round", input_column_->getName()), + throw Exception(fmt::format("Illegal column {} for the first argument of function round", args.input_column->getName()), ErrorCodes::ILLEGAL_COLUMN); if (frac_column == nullptr) - throw Exception(fmt::format("Illegal column {} for the second argument of function round", frac_column_->getName()), + throw Exception(fmt::format("Illegal column {} for the second argument of function round", args.frac_column->getName()), ErrorCodes::ILLEGAL_COLUMN); - // TODO: RoundWithFrac. - assert(frac_column->isColumnConst()); - auto frac_value [[maybe_unused]] = getFracFromConstColumn(frac_column); - assert(frac_value == 0); - - // TODO: const input column. - assert(!input_column->isColumnConst()); - - auto & input_data = input_column->getData(); - size_t size = input_data.size(); - - auto output_column = typeid_cast(output_column_.get()); + auto output_column = typeid_cast(args.output_column.get()); assert(output_column != nullptr); + size_t size = input_column->size(); auto & output_data = output_column->getData(); output_data.resize(size); - for (size_t i = 0; i < size; ++i) + TiDBDecimalRoundInfo info [[maybe_unused]] (*args.input_type, *args.output_type); + + if constexpr (std::is_same_v) { - // TODO: RoundWithFrac. - if constexpr (std::is_floating_point_v) - output_data[i] = TiDBFloatingRound::eval(input_data[i], 0); - else if constexpr (IsDecimal) - output_data[i] = TiDBDecimalRound::eval(input_data[i], 0, input_scale); + if constexpr (std::is_same_v) + { + auto input_data = input_column->template getValue(); + auto frac_data = frac_column->template getValue(); + + if constexpr (std::is_floating_point_v) + output_data[0] = TiDBFloatingRound::eval(input_data, frac_data); + else if constexpr (IsDecimal) + output_data[0] = TiDBDecimalRound::eval(input_data, frac_data, info); + else + output_data[0] = TiDBIntegerRound::eval(input_data, frac_data); + } else - output_data[i] = TiDBIntegerRound::eval(input_data[i], 0); + { + auto input_data = input_column->template getValue(); + const auto & frac_data = frac_column->getData(); + + for (size_t i = 0; i < size; ++i) + { + if constexpr (std::is_floating_point_v) + output_data[i] = TiDBFloatingRound::eval(input_data, frac_data[i]); + else if constexpr (IsDecimal) + output_data[i] = TiDBDecimalRound::eval(input_data, frac_data[i], info); + else + output_data[i] = TiDBIntegerRound::eval(input_data, frac_data[i]); + } + } + } + else + { + if constexpr (std::is_same_v) + { + const auto & input_data = input_column->getData(); + auto frac_data = frac_column->template getValue(); + + for (size_t i = 0; i < size; ++i) + { + if constexpr (std::is_floating_point_v) + output_data[i] = TiDBFloatingRound::eval(input_data[i], frac_data); + else if constexpr (IsDecimal) + output_data[i] = TiDBDecimalRound::eval(input_data[i], frac_data, info); + else + output_data[i] = TiDBIntegerRound::eval(input_data[i], frac_data); + } + } + else + { + const auto & input_data = input_column->getData(); + const auto & frac_data = frac_column->getData(); + + for (size_t i = 0; i < size; ++i) + { + if constexpr (std::is_floating_point_v) + output_data[i] = TiDBFloatingRound::eval(input_data[i], frac_data[i]); + else if constexpr (IsDecimal) + output_data[i] = TiDBDecimalRound::eval(input_data[i], frac_data[i], info); + else + output_data[i] = TiDBIntegerRound::eval(input_data[i], frac_data[i]); + } + } } } }; @@ -1052,23 +1262,59 @@ class FunctionTiDBRoundWithFrac : public IFunction String getName() const override { return name; } size_t getNumberOfArguments() const override { return 2; } bool useDefaultImplementationForNulls() const override { return true; } - bool useDefaultImplementationForConstants() const override { return true; } bool hasInformationAboutMonotonicity() const override { return true; } - Monotonicity getMonotonicityForRange(const IDataType &, const Field &, const Field &) const override { return {true, true, true}; } + // default implementation might make const frac column into a non-const one, while const frac and + // non-const frac column can generate different return types. Plese see TiDBRoundPrecisionInferer for details. + bool useDefaultImplementationForConstants() const override { return false; } + private: + FracType getFracFromConstColumn(const ColumnConst * column) const + { + using UnsignedFrac = make_unsigned_t; + + auto field = column->getField(); + + if (field.getType() == Field::TypeToEnum::value) + return field.get(); + else if (field.getType() == Field::TypeToEnum::value) + { + auto unsigned_frac = field.get(); + + // to prevent overflow. Large frac is useless in fact. + unsigned_frac = std::min(unsigned_frac, static_cast(std::numeric_limits::max())); + + return static_cast(unsigned_frac); + } + else + { + throw Exception(fmt::format("Illegal frac column with type {}, expect const Int64/UInt64", column->getField().getTypeName()), + ErrorCodes::ILLEGAL_COLUMN); + } + } + DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { checkArguments(arguments); auto input_type = arguments[0].type; + auto frac_column = arguments[1].column.get(); + bool frac_column_const = frac_column && frac_column->isColumnConst(); + if (input_type->isInteger()) - return input_type; + { + // in MySQL, integer round always returns 64-bit integer. + // the sign is the same as input. + if (input_type->isUnsignedInteger()) + return std::make_shared(); + else + return std::make_shared(); + } else if (input_type->isFloatingPoint()) { - // in MySQL, floating round always returns Float64. + // in MySQL, floating-point round always returns Float64. return std::make_shared(); } else @@ -1084,8 +1330,7 @@ class FunctionTiDBRoundWithFrac : public IFunction FracType frac = 0; bool is_const_frac = true; - auto frac_column = arguments[1].column.get(); - if (frac_column->isColumnConst()) + if (frac_column_const) { auto column = typeid_cast(frac_column); assert(column != nullptr); @@ -1107,101 +1352,116 @@ class FunctionTiDBRoundWithFrac : public IFunction for (const auto & position : arguments) columns.push_back(block.getByPosition(position)); - auto return_type = getReturnTypeImpl(columns); - auto result_column = return_type->createColumn(); + auto output_type = getReturnTypeImpl(columns); + auto output_column = output_type->createColumn(); auto input_type = columns[0].type; auto input_column = columns[0].column; auto frac_type = columns[1].type; auto frac_column = columns[1].column; - auto input_scale = getDecimalScale(*input_type, 0); - auto result_scale = getDecimalScale(*return_type, 0); + bool is_all_column_const = input_column->isColumnConst() && frac_column->isColumnConst(); + + // if the result is const, we need to only compute once. + // but the column size may be greater than 1, so we resize them here. + assert(input_column->size() == frac_column->size()); + if (is_all_column_const && input_column->size() != 1) + { + input_column = input_column->cloneResized(1); + frac_column = frac_column->cloneResized(1); + } - checkInputTypeAndApply(input_type, return_type, frac_type, input_column, frac_column, result_column, input_scale, result_scale); + checkInputTypeAndApply({input_type, frac_type, output_type, input_column, frac_column, output_column}); - block.getByPosition(result).column = std::move(result_column); + if (is_all_column_const) + block.getByPosition(result).column = ColumnConst::create(std::move(output_column), block.rows()); + else + block.getByPosition(result).column = std::move(output_column); } - void checkInputTypeAndApply(const DataTypePtr & input_type, const DataTypePtr & return_type, const DataTypePtr & frac_type, - const ColumnPtr & input_column, const ColumnPtr & frac_column, MutableColumnPtr & result_column, ScaleType input_scale, - ScaleType result_scale) + template + bool castToNumericDataTypes(const IDataType * input_type, const F & f) { - if (!castTypeToEither( - input_type.get(), [&](const auto & input_type, bool) { - using InputDataType = std::decay_t; - - checkReturnTypeAndApply( - return_type, frac_type, input_column, frac_column, result_column, input_scale, result_scale); + return castTypeToEither(input_type, f); + } - return true; - })) + void checkInputTypeAndApply(const TiDBRoundArguments & args) + { + if (!castToNumericDataTypes(args.input_type.get(), [&](const auto & input_type, bool) { + using InputDataType = std::decay_t; + checkFracTypeAndApply(args); + return true; + })) { - throw Exception(fmt::format("Illegal column type {} for the first argument of function {}", input_type->getName(), getName()), + throw Exception( + fmt::format("Illegal column type {} for the first argument of function {}", args.input_type->getName(), getName()), ErrorCodes::ILLEGAL_COLUMN); } } template - void checkReturnTypeAndApply(const DataTypePtr & return_type, const DataTypePtr & frac_type, const ColumnPtr & input_column, - const ColumnPtr & frac_column, MutableColumnPtr & result_column, ScaleType input_scale, ScaleType result_scale) + void checkFracTypeAndApply(const TiDBRoundArguments & args) + { + if (!castTypeToEither(args.frac_type.get(), [&](const auto & frac_type, bool) { + using FracDataType = std::decay_t; + checkOutputTypeAndApply(args); + return true; + })) + { + throw Exception( + fmt::format("Illegal column type {} for the second argument of function {}", args.frac_type->getName(), getName()), + ErrorCodes::ILLEGAL_COLUMN); + } + } + + template + void checkOutputTypeAndApply(const TiDBRoundArguments & args) { - if (!castTypeToEither( - return_type.get(), [&](const auto & return_type, bool) { - using ReturnDataType = std::decay_t; - - return checkFracTypeAndApply( - frac_type, input_column, frac_column, result_column, input_scale, result_scale); - })) + if (!castToNumericDataTypes(args.output_type.get(), [&](const auto & output_type, bool) { + using OutputDataType = std::decay_t; + return checkColumnsAndApply(args); + })) { throw TiFlashException(fmt::format("Unexpected return type for function {}", getName()), Errors::Coprocessor::Internal); } } - template - bool checkFracTypeAndApply(const DataTypePtr & frac_type, const ColumnPtr & input_column, const ColumnPtr & frac_column, - MutableColumnPtr & result_column, ScaleType input_scale [[maybe_unused]], ScaleType result_scale [[maybe_unused]]) + template + bool checkColumnsAndApply(const TiDBRoundArguments & args) { - if constexpr ((std::is_floating_point_v && !std::is_same_v) - || (IsDecimal && !IsDecimal) || (is_integer_v && !std::is_same_v)) + constexpr bool check_integer_output + = is_signed_v ? std::is_same_v : std::is_same_v; + + if constexpr ((std::is_floating_point_v && !std::is_same_v) + || (IsDecimal && !IsDecimal) || (is_integer_v && !check_integer_output)) return false; else { - if (!castTypeToEither(frac_type.get(), [&](const auto & frac_type, bool) { - using FracDataType = std::decay_t; + using InputColumn = std::conditional_t, ColumnDecimal, ColumnVector>; + using FracColumn = ColumnVector; + using OutputColumn = std::conditional_t, ColumnDecimal, ColumnVector>; - checkColumnsAndApply( - input_column, frac_column, result_column, input_scale, result_scale); - - return true; - })) + if (args.input_column->isColumnConst()) { - throw Exception( - fmt::format("Illegal column type {} for the second argument of function {}", frac_type->getName(), getName()), - ErrorCodes::ILLEGAL_COLUMN); + if (args.frac_column->isColumnConst()) + TiDBRound::apply(args); + else + TiDBRound::apply(args); + } + else + { + if (args.frac_column->isColumnConst()) + TiDBRound::apply(args); + else + TiDBRound::apply(args); } return true; } } - template - void checkColumnsAndApply(const ColumnPtr & input_column, const ColumnPtr & frac_column, MutableColumnPtr & result_column, - ScaleType input_scale, ScaleType result_scale) - { - using InputColumn = std::conditional_t, ColumnDecimal, ColumnVector>; - using ResultColumn = std::conditional_t, ColumnDecimal, ColumnVector>; - - // TODO: RoundWithFrac - assert(!input_column->isColumnConst()); - assert(frac_column->isColumnConst()); - - TiDBRound::apply( - input_column, frac_column, result_column, input_scale, result_scale); - } - void checkArguments(const ColumnsWithTypeAndName & arguments) const { if (arguments.size() != getNumberOfArguments()) diff --git a/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp b/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp new file mode 100644 index 00000000000..83c23025986 --- /dev/null +++ b/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp @@ -0,0 +1,529 @@ +#include +#include +#include +#include + +namespace DB +{ + +namespace tests +{ + +class TestFunctionsRoundWithFrac : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + try + { + registerFunctions(); + } + catch (Exception &) + { + // maybe other tests have already registered. + } + } + + String func_name = "tidbRoundWithFrac"; + + auto getFrac(size_t size, const std::optional & frac) { return createConstColumn>(size, {frac}); } + + auto execute(const ColumnWithTypeAndName & input, const ColumnWithTypeAndName & frac) + { + return executeFunction(func_name, input, frac); + } + + auto execute(const ColumnWithTypeAndName & input, std::optional frac) + { + return execute(input, getFrac(input.column->size(), frac)); + } +}; + +TEST_F(TestFunctionsRoundWithFrac, PrecisionInfer) +{ + constexpr auto infer = TiDBRoundPrecisionInferer::infer; + using Result = std::tuple; + + EXPECT_EQ(infer(9, 3, 2, true), Result(9, 2)); + EXPECT_EQ(infer(9, 3, 1, true), Result(8, 1)); + EXPECT_EQ(infer(9, 3, 0, true), Result(7, 0)); + EXPECT_EQ(infer(9, 3, -1, true), Result(7, 0)); + EXPECT_EQ(infer(9, 3, -2, true), Result(7, 0)); + EXPECT_EQ(infer(9, 3, 40, true), Result(36, 30)); + EXPECT_EQ(infer(9, 3, -100, true), Result(7, 0)); + EXPECT_EQ(infer(9, 3, 0, false), Result(10, 3)); + EXPECT_EQ(infer(9, 3, 233, false), Result(10, 3)); + + EXPECT_EQ(infer(18, 6, 2, true), Result(15, 2)); + EXPECT_EQ(infer(18, 6, 1, true), Result(14, 1)); + EXPECT_EQ(infer(18, 6, 0, true), Result(13, 0)); + EXPECT_EQ(infer(18, 6, -1, true), Result(13, 0)); + EXPECT_EQ(infer(18, 6, -2, true), Result(13, 0)); + EXPECT_EQ(infer(18, 6, 40, true), Result(42, 30)); + EXPECT_EQ(infer(18, 6, -100, true), Result(13, 0)); + EXPECT_EQ(infer(18, 6, 0, false), Result(19, 6)); + EXPECT_EQ(infer(18, 6, -233, false), Result(19, 6)); + + EXPECT_EQ(infer(30, 30, 2, true), Result(3, 2)); + EXPECT_EQ(infer(30, 30, 1, true), Result(2, 1)); + EXPECT_EQ(infer(30, 30, 0, true), Result(1, 0)); + EXPECT_EQ(infer(30, 30, -1, true), Result(1, 0)); + EXPECT_EQ(infer(30, 30, -2, true), Result(1, 0)); + EXPECT_EQ(infer(30, 30, 40, true), Result(30, 30)); + EXPECT_EQ(infer(30, 30, -100, true), Result(1, 0)); + EXPECT_EQ(infer(30, 30, 0, false), Result(31, 30)); + EXPECT_EQ(infer(30, 30, 233, false), Result(31, 30)); + + EXPECT_EQ(infer(64, 0, 40, true), Result(65, 30)); + EXPECT_EQ(infer(64, 0, -100, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, -62, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, -63, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, -64, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, -65, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, -66, true), Result(65, 0)); + EXPECT_EQ(infer(64, 0, 0, false), Result(65, 0)); + EXPECT_EQ(infer(64, 0, 233, false), Result(65, 0)); + + EXPECT_EQ(infer(65, 30, 40, true), Result(65, 30)); + EXPECT_EQ(infer(65, 30, -100, true), Result(36, 0)); + EXPECT_EQ(infer(65, 30, 0, false), Result(65, 30)); + EXPECT_EQ(infer(65, 30, 233, false), Result(65, 30)); + + EXPECT_EQ(infer(65, 0, 233, false), Result(65, 0)); + EXPECT_EQ(infer(65, 1, 233, false), Result(65, 1)); + EXPECT_EQ(infer(64, 0, 233, false), Result(65, 0)); + + EXPECT_EQ(infer(18, 6, 4, true), Result(17, 4)); + EXPECT_EQ(infer(18, 6, 5, true), Result(18, 5)); + EXPECT_EQ(infer(18, 6, 6, true), Result(18, 6)); + EXPECT_EQ(infer(18, 6, 7, true), Result(19, 7)); + EXPECT_EQ(infer(18, 6, 8, true), Result(20, 8)); +} + +template +class TestFunctionsRoundWithFracSigned : public TestFunctionsRoundWithFrac +{ +}; + +using TestFunctionsRoundWithFracSignedTypes = ::testing::Types; +TYPED_TEST_CASE(TestFunctionsRoundWithFracSigned, TestFunctionsRoundWithFracSignedTypes); + +TYPED_TEST(TestFunctionsRoundWithFracSigned, ConstFrac) +try +{ + using Int = TypeParam; + + int digits = std::numeric_limits::digits10; + Int large = 5; + for (int i = 0; i < digits - 1; ++i) + large *= 10; + +#define DATA \ + { \ + 0, 1, -1, 4, 5, -4, -5, 49, 50, -49, -50, large - 1, large, -(large - 1), -large, {} \ + } + + auto input = createColumn>(DATA); + auto output = createColumn>(DATA); +#undef DATA + + ASSERT_COLUMN_EQ(output, this->execute(input, std::numeric_limits::max())); + for (int i = 0; i <= 100; ++i) + ASSERT_COLUMN_EQ(output, this->execute(input, i)) << "i = " << i; + + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 10, 0, -10, 50, 50, -50, -50, large, large, -large, -large, {}}), + this->execute(input, -1)); + + int start = -2; + if (digits > 2) + { + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 0, 0, 0, 0, 100, 0, -100, large, large, -large, -large, {}}), + this->execute(input, -2)); + + start = -3; + } + for (int i = start; i >= -(digits - 1); --i) + ASSERT_COLUMN_EQ( + createColumn>({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, large, large, -large, -large, {}}), this->execute(input, i)) + << "i = " << i; + + if (digits > 2) + { + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, large * 2, 0, -large * 2, {}}), + this->execute(input, -digits)); + } + + auto zeroes = createColumn>({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {}}); + for (int i = -(digits + 1); i >= -100; --i) + ASSERT_COLUMN_EQ(zeroes, this->execute(input, i)) << "i = " << i; + ASSERT_COLUMN_EQ(zeroes, this->execute(input, std::numeric_limits::min())); +} +CATCH + +template +class TestFunctionsRoundWithFracUnsigned : public TestFunctionsRoundWithFrac +{ +}; + +using TestFunctionsRoundWithFracUnsignedTypes = ::testing::Types; +TYPED_TEST_CASE(TestFunctionsRoundWithFracUnsigned, TestFunctionsRoundWithFracUnsignedTypes); + +TYPED_TEST(TestFunctionsRoundWithFracUnsigned, ConstFrac) +try +{ + using UInt = TypeParam; + + UInt large = 5; + int digits = std::numeric_limits::digits10; + for (int i = 0; i < digits - 1; ++i) + large *= 10; + +#define DATA \ + { \ + 0, 1, 4, 5, 49, 50, large - 1, large, {} \ + } + + auto input = createColumn>(DATA); + auto output = createColumn>(DATA); +#undef DATA + + ASSERT_COLUMN_EQ(output, this->execute(input, std::numeric_limits::max())); + for (int i = 0; i <= 100; ++i) + ASSERT_COLUMN_EQ(output, this->execute(input, i)) << "i = " << i; + + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 10, 50, 50, large, large, {}}), this->execute(input, -1)); + + int start = -2; + if (digits > 2) + { + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 0, 100, large, large, {}}), this->execute(input, -2)); + + start = -3; + } + for (int i = start; i >= -(digits - 1); --i) + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 0, 0, large, large, {}}), this->execute(input, i)) << "i = " << i; + + if (digits > 2) + { + ASSERT_COLUMN_EQ(createColumn>({0, 0, 0, 0, 0, 0, 0, large * 2, {}}), this->execute(input, -digits)); + } + + auto zeroes = createColumn>({0, 0, 0, 0, 0, 0, 0, 0, {}}); + for (int i = -(digits + 1); i >= -100; --i) + ASSERT_COLUMN_EQ(zeroes, this->execute(input, i)) << "i = " << i; + ASSERT_COLUMN_EQ(zeroes, this->execute(input, std::numeric_limits::min())); +} +CATCH + +TEST_F(TestFunctionsRoundWithFrac, Int64ConstFracOverflow) +{ + using Limits = std::numeric_limits; + + { + auto input = createColumn({Limits::max(), Limits::min()}); + + EXPECT_NO_THROW(execute(input, Limits::max())); + for (int i = 0; i <= 100; ++i) + EXPECT_NO_THROW(execute(input, 0)) << "i = " << i; + } + + for (Int64 v : {Limits::max(), Limits::min()}) + { + auto input = createColumn({v}); + UInt64 value = v; + + for (int i = 0; i <= 100; ++i) + { + UInt64 digit = value % 10; + value /= 10; + + auto message = fmt::format("value = {}, i = {}", value, i); + if (digit >= 5) + EXPECT_THROW(execute(input, -(i + 1)), Exception) << message; + else + EXPECT_NO_THROW(execute(input, -(i + 1))) << message; + } + + EXPECT_NO_THROW(execute(input, Limits::min())); + } +} + +TEST_F(TestFunctionsRoundWithFrac, UInt64ConstFracOverflow) +{ + UInt64 value = std::numeric_limits::max(); + auto input = createColumn({value}); + + EXPECT_NO_THROW(execute(input, std::numeric_limits::max())); + for (int i = 0; i <= 100; ++i) + EXPECT_NO_THROW(execute(input, 0)) << "i = " << i; + + for (int i = 0; i <= 100; ++i) + { + UInt64 digit = value % 10; + value /= 10; + + auto message = fmt::format("value = {}, i = {}", value, i); + if (digit >= 5) + EXPECT_THROW(execute(input, -(i + 1)), Exception) << message; + else + EXPECT_NO_THROW(execute(input, -(i + 1))) << message; + } + + EXPECT_NO_THROW(execute(input, std::numeric_limits::min())); +} + +TEST_F(TestFunctionsRoundWithFrac, IntConstInput) +{ + InferredDataVector> frac_data{0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, {}}; + size_t size = frac_data.size(); + + Int32 int32_input = 2147398765; + InferredDataVector> int32_result + = {2147398765, 2147398770, 2147398800, 2147399000, 2147400000, 2147400000, 2147000000, 2150000000, 2100000000, 2000000000, 0, {}}; + + UInt32 uint32_input = 4293876543; + InferredDataVector> uint32_result + = {4293876543, 4293876540, 4293876500, 4293877000, 4293880000, 4293900000, 4294000000, 4290000000, 4300000000, 4000000000, 0, {}}; + + auto frac = createColumn>(frac_data); + + { + // const signed + auto input = createConstColumn(size, int32_input); + auto result = createColumn>(int32_result); + ASSERT_COLUMN_EQ(result, execute(input, frac)); + } + + { + // const unsigned + auto input = createConstColumn(size, uint32_input); + auto result = createColumn>(uint32_result); + ASSERT_COLUMN_EQ(result, execute(input, frac)); + } + + { + // null constant + auto input = createConstColumn>(size, {}); + auto result = createConstColumn>(size, {}); + ASSERT_COLUMN_EQ(result, execute(input, frac)); + } + + { + // const signed - const frac + for (size_t i = 0; i < size; ++i) + { + ASSERT_COLUMN_EQ(createConstColumn>(1, int32_result[i]), + execute(createConstColumn(1, int32_input), createConstColumn>(1, frac_data[i]))); + ASSERT_COLUMN_EQ(createConstColumn>(1, uint32_result[i]), + execute(createConstColumn(1, uint32_input), createConstColumn>(1, frac_data[i]))); + ASSERT_COLUMN_EQ(createConstColumn>(1, {}), + execute(createConstColumn>(1, {}), createConstColumn>(1, frac_data[i]))); + } + } +} + +template +class TestFunctionsRoundWithFracFloating : public TestFunctionsRoundWithFrac +{ +}; + +using TestFunctionsRoundWithFracFloatingTypes = ::testing::Types; +TYPED_TEST_CASE(TestFunctionsRoundWithFracFloating, TestFunctionsRoundWithFracFloatingTypes); + +TYPED_TEST(TestFunctionsRoundWithFracFloating, All) +{ + using Float = TypeParam; + + auto input = createColumn>({0.0, 2.5, -2.5, 0.25, -0.25, 0.125, -0.125, 25, -25, 250, -250, 2.6, 2.4, -2.6, -2.4, {}}); + + // const frac + + ASSERT_COLUMN_EQ( + createColumn>({0, 2, -2, 0.0, -0.0, 0.0, -0.0, 25, -25, 250, -250, 3, 2, -3, -2, {}}), this->execute(input, 0)); + ASSERT_COLUMN_EQ(createColumn>({0, 2.5, -2.5, 0.2, -0.2, 0.1, -0.1, 25, -25, 250, -250, 2.6, 2.4, -2.6, -2.4, {}}), + this->execute(input, 1)); + ASSERT_COLUMN_EQ( + createColumn>({0, 2.5, -2.5, 0.25, -0.25, 0.12, -0.12, 25, -25, 250, -250, 2.6, 2.4, -2.6, -2.4, {}}), + this->execute(input, 2)); + ASSERT_COLUMN_EQ( + createColumn>({0, 2.5, -2.5, 0.25, -0.25, 0.125, -0.125, 25, -25, 250, -250, 2.6, 2.4, -2.6, -2.4, {}}), + this->execute(input, 3)); + ASSERT_COLUMN_EQ(createColumn>({0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 20, -20, 250, -250, 0.0, 0.0, -0.0, -0.0, {}}), + this->execute(input, -1)); + ASSERT_COLUMN_EQ(createColumn>({0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 200, -200, 0.0, 0.0, -0.0, -0.0, {}}), + this->execute(input, -2)); + ASSERT_COLUMN_EQ(createColumn>({0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 0.0, 0.0, -0.0, -0.0, {}}), + this->execute(input, -3)); + + // const input + + ASSERT_COLUMN_EQ(createColumn>({0, 0.1, 0.12, 0.125, {}}), + this->execute(createConstColumn(5, 0.125), createColumn>({0, 1, 2, 3, {}}))); + ASSERT_COLUMN_EQ(createConstColumn>(5, {}), + this->execute(createConstColumn>(5, {}), createColumn>({0, 1, 2, 3, {}}))); + + // const input & frac + + ASSERT_COLUMN_EQ( + createConstColumn(1, 0.12), this->execute(createConstColumn(1, 0.125), createConstColumn(1, 2))); + ASSERT_COLUMN_EQ(createConstColumn>(1, {}), + this->execute(createConstColumn(1, 0.125), createConstColumn>(1, {}))); + ASSERT_COLUMN_EQ(createConstColumn>(1, {}), + this->execute(createConstColumn>(1, {}), createConstColumn(1, 2))); +} + +template +class TestFunctionsRoundWithFracDecimal : public TestFunctionsRoundWithFrac +{ +}; + +using TestFunctionsRoundWithFracDecimalTypes = ::testing::Types; +TYPED_TEST_CASE(TestFunctionsRoundWithFracDecimal, TestFunctionsRoundWithFracDecimalTypes); + +TYPED_TEST(TestFunctionsRoundWithFracDecimal, Basic) +try +{ + using Decimal = TypeParam; + constexpr int max_prec = maxDecimalPrecision(); + + auto column = [](std::tuple args, const std::vector> & data) { + return createColumn>(args, data); + }; + + auto constColumn = [](std::tuple args, size_t size, const std::optional & data) { + return createConstColumn>(args, size, data); + }; + + // const frac + + { + // Decimal(max_prec - 1, 3) + constexpr int prec = max_prec - 1; + auto large = String(prec - 3, '9') + ".999"; + auto rounded = "1" + String(prec - 3, '0'); + auto input = column( + {prec, 3}, {"0.000", "2.490", "-2.490", "2.500", "-2.500", "0.250", "-0.250", "25.000", "-25.000", large, "-" + large, {}}); + + ASSERT_COLUMN_EQ(input, this->execute(input, 3)); + ASSERT_COLUMN_EQ( + column({prec, 2}, + {"0.00", "2.49", "-2.49", "2.50", "-2.50", "0.25", "-0.25", "25.00", "-25.00", rounded + ".00", "-" + rounded + ".00", {}}), + this->execute(input, 2)); + ASSERT_COLUMN_EQ( + column({prec - 1, 1}, + {"0.0", "2.5", "-2.5", "2.5", "-2.5", "0.3", "-0.3", "25.0", "-25.0", rounded + ".0", "-" + rounded + ".0", {}}), + this->execute(input, 1)); + ASSERT_COLUMN_EQ( + column({prec - 2, 0}, {"0", "2", "-2", "3", "-3", "0", "0", "25", "-25", rounded, "-" + rounded, {}}), this->execute(input, 0)); + ASSERT_COLUMN_EQ( + column({prec - 2, 0}, {"0", "0", "0", "0", "0", "0", "0", "30", "-30", rounded, "-" + rounded, {}}), this->execute(input, -1)); + + for (int i = -2; i >= -(prec - 3); --i) + ASSERT_COLUMN_EQ( + column({prec - 2, 0}, {"0", "0", "0", "0", "0", "0", "0", "0", "0", rounded, "-" + rounded, {}}), this->execute(input, i)) + << "i = " << i; + + ASSERT_COLUMN_EQ( + column({prec - 2, 0}, {"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", {}}), this->execute(input, -(prec - 2))); + ASSERT_COLUMN_EQ(column({prec - 2, 0}, {"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", {}}), + this->execute(input, std::numeric_limits::min())); + } + + // const input + + { + auto frac = createColumn>({3, 2, 1, 0, -1, -2, -3, -4, -5, -6, {}}); + + ASSERT_COLUMN_EQ(column({max_prec, 3}, + {"98765.432", "98765.430", "98765.400", "98765.000", "98770.000", "98800.000", "99000.000", "100000.000", + "100000.000", "0.000", {}}), + this->execute(constColumn({max_prec - 1, 3}, 11, "98765.432"), frac)); + ASSERT_COLUMN_EQ(constColumn({max_prec, 3}, 11, {}), this->execute(constColumn({max_prec - 1, 3}, 11, {}), frac)); + } + + // const input & frac + + ASSERT_COLUMN_EQ(constColumn({max_prec - 1, 2}, 1, "0.03"), + this->execute(constColumn({max_prec - 1, 3}, 1, "0.025"), createConstColumn(1, 2))); + ASSERT_COLUMN_EQ( + constColumn({max_prec - 1, 2}, 1, {}), this->execute(constColumn({max_prec - 1, 3}, 1, {}), createConstColumn(1, 2))); + ASSERT_COLUMN_EQ(constColumn({max_prec - 3, 0}, 1, {}), + this->execute(constColumn({max_prec - 1, 3}, 1, "0.025"), createConstColumn>(1, {}))); + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(max_prec, 5), 100, "1." + String(5, '0')), + this->execute(createConstColumn(std::make_tuple(max_prec - 5, 0), 100, "1"), createConstColumn(100, 5))); +} +CATCH + +TEST_F(TestFunctionsRoundWithFrac, DecimalRound) +{ + // decimal downgrade + + { + // Decimal(65, 30) -> Decimal(36, 0) + auto small = "0.5" + String(29, '0'); + auto large = String(35, '9') + "." + String(30, '9'); + auto large_rounded = "1" + String(35, '0'); + ASSERT_COLUMN_EQ(createColumn(std::make_tuple(36, 0), {"1", large_rounded}), + execute(createColumn(std::make_tuple(65, 30), {small, large}), createConstColumn(2, 0))); + } + + { + // Decimal(38, 30) -> Decimal(9, 0) + auto small = "0.49" + String(28, '0'); + auto large = String(8, '9') + "." + String(30, '9'); + auto large_rounded = "1" + String(8, '0'); + ASSERT_COLUMN_EQ(createColumn(std::make_tuple(9, 0), {"0", large_rounded}), + execute(createColumn(std::make_tuple(38, 30), {small, large}), createConstColumn(2, 0))); + } + + // decimal upgrade + + // Decimal(2, 1) -> Decimal(11, 10) + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(11, 10), 100, "9.9" + String(9, '0')), + execute(createConstColumn(std::make_tuple(2, 1), 100, "9.9"), createConstColumn(100, 10))); + + // Decimal(5, 0) -> Decimal(35, 30) + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(35, 30), 100, "99999." + String(30, '0')), + execute(createConstColumn(std::make_tuple(5, 0), 100, "99999"), createConstColumn(100, 1000))); + + // Decimal(9, 0) -> Decimal(39, 30) + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(39, 30), 100, "999999999." + String(30, '0')), + execute(createConstColumn(std::make_tuple(9, 0), 100, "999999999"), createConstColumn(100, 30))); + + // decimal overflow + + { + auto large = String(65, '9'); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), 0)); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {"0"}), 1)); + ASSERT_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), 1), TiFlashException); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {"0"}), -1)); + ASSERT_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), -1), TiFlashException); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), createColumn({1}))); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), createColumn({0}))); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(65, 0), {"0"}), createColumn({-1}))); + ASSERT_THROW(execute(createColumn(std::make_tuple(65, 0), {large}), createColumn({-1})), TiFlashException); + } + + { + auto large = String(35, '9'); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(35, 0), {large}), 30)); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(35, 0), {large}), 100)); + } + + { + auto large = String(36, '9'); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {"0"}), 30)); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {"0"}), 100)); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {"0"}), 30)); + ASSERT_THROW(execute(createColumn(std::make_tuple(36, 0), {large}), 30), TiFlashException); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {"0"}), 100)); + ASSERT_THROW(execute(createColumn(std::make_tuple(36, 0), {large}), 100), TiFlashException); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {large}), createColumn({30}))); + ASSERT_NO_THROW(execute(createColumn(std::make_tuple(36, 0), {large}), createColumn({100}))); + } +} + +} // namespace tests + +} // namespace DB diff --git a/dbms/src/TestUtils/FunctionTestUtils.h b/dbms/src/TestUtils/FunctionTestUtils.h index 11cb7c46d7b..da7f2640d94 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.h +++ b/dbms/src/TestUtils/FunctionTestUtils.h @@ -54,7 +54,7 @@ struct TypeTraits>> static constexpr bool is_nullable = true; static constexpr bool is_decimal = true; using DecimalType = Decimal; - using FieldType = std::optional>;; + using FieldType = std::optional>; }; template diff --git a/tests/delta-merge-test/query/expr/round_with_frac.test b/tests/delta-merge-test/query/expr/round_with_frac.test new file mode 100644 index 00000000000..0a0704b4421 --- /dev/null +++ b/tests/delta-merge-test/query/expr/round_with_frac.test @@ -0,0 +1,452 @@ +# Preparation. +=> DBGInvoke __enable_schema_sync_service('true') +=> DBGInvoke __set_flush_threshold(1000000, 1000000) + +# Drop old tables. +=> DBGInvoke __drop_tidb_table(default, i8) +=> drop table if exists default.i8 +=> DBGInvoke __drop_tidb_table(default, u8) +=> drop table if exists default.u8 +=> DBGInvoke __drop_tidb_table(default, i32) +=> drop table if exists default.i32 +=> DBGInvoke __drop_tidb_table(default, u32) +=> drop table if exists default.u32 +=> DBGInvoke __drop_tidb_table(default, i64) +=> drop table if exists default.i64 +=> DBGInvoke __drop_tidb_table(default, u64) +=> drop table if exists default.u64 +=> DBGInvoke __drop_tidb_table(default, t) +=> drop table if exists default.t + +# Data. +=> DBGInvoke __mock_tidb_table(default, i8, 'a Nullable(Int8)') +=> DBGInvoke __mock_tidb_table(default, u8, 'a Nullable(UInt8)') +=> DBGInvoke __mock_tidb_table(default, i32, 'a Nullable(Int32)') +=> DBGInvoke __mock_tidb_table(default, u32, 'a Nullable(UInt32)') +=> DBGInvoke __mock_tidb_table(default, i64, 'a Nullable(Int64)') +=> DBGInvoke __mock_tidb_table(default, u64, 'a Nullable(UInt64)') +=> DBGInvoke __mock_tidb_table(default, t, 'i Nullable(Int64)') + +=> DBGInvoke __refresh_schemas() + +=> DBGInvoke __put_region(1, 0, 100, default, i8) +=> DBGInvoke __raft_insert_row(default, i8, 1, 1, 0) +=> DBGInvoke __raft_insert_row(default, i8, 1, 2, 1) +=> DBGInvoke __raft_insert_row(default, i8, 1, 3, -1) +=> DBGInvoke __raft_insert_row(default, i8, 1, 4, 49) +=> DBGInvoke __raft_insert_row(default, i8, 1, 5, 50) +=> DBGInvoke __raft_insert_row(default, i8, 1, 6, -49) +=> DBGInvoke __raft_insert_row(default, i8, 1, 7, -50) +=> DBGInvoke __raft_insert_row(default, i8, 1, 8, 127) +=> DBGInvoke __raft_insert_row(default, i8, 1, 9, -128) +=> DBGInvoke __raft_insert_row(default, i8, 1, 10, NULL) + +=> DBGInvoke __put_region(2, 0, 100, default, u8) +=> DBGInvoke __raft_insert_row(default, u8, 2, 1, 0) +=> DBGInvoke __raft_insert_row(default, u8, 2, 2, 4) +=> DBGInvoke __raft_insert_row(default, u8, 2, 3, 5) +=> DBGInvoke __raft_insert_row(default, u8, 2, 4, 49) +=> DBGInvoke __raft_insert_row(default, u8, 2, 5, 50) +=> DBGInvoke __raft_insert_row(default, u8, 2, 6, 255) +=> DBGInvoke __raft_insert_row(default, u8, 2, 7, NULL) + +=> DBGInvoke __put_region(3, 0, 100, default, i32) +=> DBGInvoke __raft_insert_row(default, i32, 3, 1, 0) +=> DBGInvoke __raft_insert_row(default, i32, 3, 2, 4) +=> DBGInvoke __raft_insert_row(default, i32, 3, 3, 5) +=> DBGInvoke __raft_insert_row(default, i32, 3, 4, -4) +=> DBGInvoke __raft_insert_row(default, i32, 3, 5, -5) +=> DBGInvoke __raft_insert_row(default, i32, 3, 6, 499999999) +=> DBGInvoke __raft_insert_row(default, i32, 3, 7, 500000000) +=> DBGInvoke __raft_insert_row(default, i32, 3, 8, -499999999) +=> DBGInvoke __raft_insert_row(default, i32, 3, 9, -500000000) +=> DBGInvoke __raft_insert_row(default, i32, 3, 10, 2147483647) +=> DBGInvoke __raft_insert_row(default, i32, 3, 11, -2147483648) +=> DBGInvoke __raft_insert_row(default, i32, 3, 12, NULL) + +=> DBGInvoke __put_region(4, 0, 100, default, u32) +=> DBGInvoke __raft_insert_row(default, u32, 4, 1, 0) +=> DBGInvoke __raft_insert_row(default, u32, 4, 2, 4) +=> DBGInvoke __raft_insert_row(default, u32, 4, 3, 5) +=> DBGInvoke __raft_insert_row(default, u32, 4, 4, 499999999) +=> DBGInvoke __raft_insert_row(default, u32, 4, 5, 500000000) +=> DBGInvoke __raft_insert_row(default, u32, 4, 6, 4294967295) +=> DBGInvoke __raft_insert_row(default, u32, 4, 7, null) + +=> DBGInvoke __put_region(5, 0, 100, default, i64) +=> DBGInvoke __raft_insert_row(default, i64, 5, 1, 0) +=> DBGInvoke __raft_insert_row(default, i64, 5, 2, 4) +=> DBGInvoke __raft_insert_row(default, i64, 5, 3, 5) +=> DBGInvoke __raft_insert_row(default, i64, 5, 4, -4) +=> DBGInvoke __raft_insert_row(default, i64, 5, 5, -5) +=> DBGInvoke __raft_insert_row(default, i64, 5, 6, 499999999999999999) +=> DBGInvoke __raft_insert_row(default, i64, 5, 7, 500000000000000000) +=> DBGInvoke __raft_insert_row(default, i64, 5, 8, -499999999999999999) +=> DBGInvoke __raft_insert_row(default, i64, 5, 9, -500000000000000000) +=> DBGInvoke __raft_insert_row(default, i64, 5, 10, NULL) + +=> DBGInvoke __put_region(6, 0, 100, default, u64) +=> DBGInvoke __raft_insert_row(default, u64, 6, 1, 0) +=> DBGInvoke __raft_insert_row(default, u64, 6, 2, 4) +=> DBGInvoke __raft_insert_row(default, u64, 6, 3, 5) +=> DBGInvoke __raft_insert_row(default, u64, 6, 4, 4999999999999999999) +=> DBGInvoke __raft_insert_row(default, u64, 6, 5, 5000000000000000000) +=> DBGInvoke __raft_insert_row(default, u64, 6, 6, NULL) + +=> DBGInvoke __put_region(7, 0, 200, default, t) +=> DBGInvoke __raft_insert_row(default, t, 7, 1, -66) +=> DBGInvoke __raft_insert_row(default, t, 7, 2, -65) +=> DBGInvoke __raft_insert_row(default, t, 7, 3, -64) +=> DBGInvoke __raft_insert_row(default, t, 7, 4, -63) +=> DBGInvoke __raft_insert_row(default, t, 7, 5, -62) +=> DBGInvoke __raft_insert_row(default, t, 7, 6, -61) +=> DBGInvoke __raft_insert_row(default, t, 7, 7, -60) +=> DBGInvoke __raft_insert_row(default, t, 7, 8, -59) +=> DBGInvoke __raft_insert_row(default, t, 7, 9, -58) +=> DBGInvoke __raft_insert_row(default, t, 7, 10, -57) +=> DBGInvoke __raft_insert_row(default, t, 7, 11, -56) +=> DBGInvoke __raft_insert_row(default, t, 7, 12, -55) +=> DBGInvoke __raft_insert_row(default, t, 7, 13, -54) +=> DBGInvoke __raft_insert_row(default, t, 7, 14, -53) +=> DBGInvoke __raft_insert_row(default, t, 7, 15, -52) +=> DBGInvoke __raft_insert_row(default, t, 7, 16, -51) +=> DBGInvoke __raft_insert_row(default, t, 7, 17, -50) +=> DBGInvoke __raft_insert_row(default, t, 7, 18, -49) +=> DBGInvoke __raft_insert_row(default, t, 7, 19, -48) +=> DBGInvoke __raft_insert_row(default, t, 7, 20, -47) +=> DBGInvoke __raft_insert_row(default, t, 7, 21, -46) +=> DBGInvoke __raft_insert_row(default, t, 7, 22, -45) +=> DBGInvoke __raft_insert_row(default, t, 7, 23, -44) +=> DBGInvoke __raft_insert_row(default, t, 7, 24, -43) +=> DBGInvoke __raft_insert_row(default, t, 7, 25, -42) +=> DBGInvoke __raft_insert_row(default, t, 7, 26, -41) +=> DBGInvoke __raft_insert_row(default, t, 7, 27, -40) +=> DBGInvoke __raft_insert_row(default, t, 7, 28, -39) +=> DBGInvoke __raft_insert_row(default, t, 7, 29, -38) +=> DBGInvoke __raft_insert_row(default, t, 7, 30, -37) +=> DBGInvoke __raft_insert_row(default, t, 7, 31, -36) +=> DBGInvoke __raft_insert_row(default, t, 7, 32, -35) +=> DBGInvoke __raft_insert_row(default, t, 7, 33, -34) +=> DBGInvoke __raft_insert_row(default, t, 7, 34, -33) +=> DBGInvoke __raft_insert_row(default, t, 7, 35, -32) +=> DBGInvoke __raft_insert_row(default, t, 7, 36, -31) +=> DBGInvoke __raft_insert_row(default, t, 7, 37, -30) +=> DBGInvoke __raft_insert_row(default, t, 7, 38, -29) +=> DBGInvoke __raft_insert_row(default, t, 7, 39, -28) +=> DBGInvoke __raft_insert_row(default, t, 7, 40, -27) +=> DBGInvoke __raft_insert_row(default, t, 7, 41, -26) +=> DBGInvoke __raft_insert_row(default, t, 7, 42, -25) +=> DBGInvoke __raft_insert_row(default, t, 7, 43, -24) +=> DBGInvoke __raft_insert_row(default, t, 7, 44, -23) +=> DBGInvoke __raft_insert_row(default, t, 7, 45, -22) +=> DBGInvoke __raft_insert_row(default, t, 7, 46, -21) +=> DBGInvoke __raft_insert_row(default, t, 7, 47, -20) +=> DBGInvoke __raft_insert_row(default, t, 7, 48, -19) +=> DBGInvoke __raft_insert_row(default, t, 7, 49, -18) +=> DBGInvoke __raft_insert_row(default, t, 7, 50, -17) +=> DBGInvoke __raft_insert_row(default, t, 7, 51, -16) +=> DBGInvoke __raft_insert_row(default, t, 7, 52, -15) +=> DBGInvoke __raft_insert_row(default, t, 7, 53, -14) +=> DBGInvoke __raft_insert_row(default, t, 7, 54, -13) +=> DBGInvoke __raft_insert_row(default, t, 7, 55, -12) +=> DBGInvoke __raft_insert_row(default, t, 7, 56, -11) +=> DBGInvoke __raft_insert_row(default, t, 7, 57, -10) +=> DBGInvoke __raft_insert_row(default, t, 7, 58, -9) +=> DBGInvoke __raft_insert_row(default, t, 7, 59, -8) +=> DBGInvoke __raft_insert_row(default, t, 7, 60, -7) +=> DBGInvoke __raft_insert_row(default, t, 7, 61, -6) +=> DBGInvoke __raft_insert_row(default, t, 7, 62, -5) +=> DBGInvoke __raft_insert_row(default, t, 7, 63, -4) +=> DBGInvoke __raft_insert_row(default, t, 7, 64, -3) +=> DBGInvoke __raft_insert_row(default, t, 7, 65, -2) +=> DBGInvoke __raft_insert_row(default, t, 7, 66, -1) +=> DBGInvoke __raft_insert_row(default, t, 7, 67, 0) +=> DBGInvoke __raft_insert_row(default, t, 7, 68, 1) +=> DBGInvoke __raft_insert_row(default, t, 7, 69, 2) +=> DBGInvoke __raft_insert_row(default, t, 7, 70, 3) +=> DBGInvoke __raft_insert_row(default, t, 7, 71, 4) +=> DBGInvoke __raft_insert_row(default, t, 7, 72, 5) +=> DBGInvoke __raft_insert_row(default, t, 7, 73, 6) +=> DBGInvoke __raft_insert_row(default, t, 7, 74, 7) +=> DBGInvoke __raft_insert_row(default, t, 7, 75, 8) +=> DBGInvoke __raft_insert_row(default, t, 7, 76, 9) +=> DBGInvoke __raft_insert_row(default, t, 7, 77, 10) +=> DBGInvoke __raft_insert_row(default, t, 7, 78, 11) +=> DBGInvoke __raft_insert_row(default, t, 7, 79, 12) +=> DBGInvoke __raft_insert_row(default, t, 7, 80, 13) +=> DBGInvoke __raft_insert_row(default, t, 7, 81, 14) +=> DBGInvoke __raft_insert_row(default, t, 7, 82, 15) +=> DBGInvoke __raft_insert_row(default, t, 7, 83, 16) +=> DBGInvoke __raft_insert_row(default, t, 7, 84, 17) +=> DBGInvoke __raft_insert_row(default, t, 7, 85, 18) +=> DBGInvoke __raft_insert_row(default, t, 7, 86, 19) +=> DBGInvoke __raft_insert_row(default, t, 7, 87, 20) +=> DBGInvoke __raft_insert_row(default, t, 7, 88, 21) +=> DBGInvoke __raft_insert_row(default, t, 7, 89, 22) +=> DBGInvoke __raft_insert_row(default, t, 7, 90, 23) +=> DBGInvoke __raft_insert_row(default, t, 7, 91, 24) +=> DBGInvoke __raft_insert_row(default, t, 7, 92, 25) +=> DBGInvoke __raft_insert_row(default, t, 7, 93, 26) +=> DBGInvoke __raft_insert_row(default, t, 7, 94, 27) +=> DBGInvoke __raft_insert_row(default, t, 7, 95, 28) +=> DBGInvoke __raft_insert_row(default, t, 7, 96, 29) +=> DBGInvoke __raft_insert_row(default, t, 7, 97, 30) +=> DBGInvoke __raft_insert_row(default, t, 7, 98, 31) +=> DBGInvoke __raft_insert_row(default, t, 7, 99, 9223372036854775807) +=> DBGInvoke __raft_insert_row(default, t, 7, 100, -9223372036854775808) +=> DBGInvoke __raft_insert_row(default, t, 7, 101, NULL) + +# Queries. +=> DBGInvoke dag('select a, round_with_frac_int(a, 1), round_with_frac_int(a, 0), round_with_frac_int(a, -1), round_with_frac_int(a, -2), round_with_frac_int(a, -3), round_with_frac_int(a, -4) from default.i8') +┌────a─┬─round_with_frac_int(a, 1)─┬─round_with_frac_int(a, 0)─┬─round_with_frac_int(a, -1)─┬─round_with_frac_int(a, -2)─┬─round_with_frac_int(a, -3)─┬─round_with_frac_int(a, -4)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ +│ -1 │ -1 │ -1 │ 0 │ 0 │ 0 │ 0 │ +│ 49 │ 49 │ 49 │ 50 │ 0 │ 0 │ 0 │ +│ 50 │ 50 │ 50 │ 50 │ 100 │ 0 │ 0 │ +│ -49 │ -49 │ -49 │ -50 │ 0 │ 0 │ 0 │ +│ -50 │ -50 │ -50 │ -50 │ -100 │ 0 │ 0 │ +│ 127 │ 127 │ 127 │ 130 │ 100 │ 0 │ 0 │ +│ -128 │ -128 │ -128 │ -130 │ -100 │ 0 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└──────┴───────────────────────────┴───────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┘ + +=> DBGInvoke dag('select a, round_with_frac_uint(a, 1), round_with_frac_uint(a, 0), round_with_frac_uint(a, -1), round_with_frac_uint(a, -2), round_with_frac_uint(a, -3), round_with_frac_uint(a, -4) from default.u8') +┌───a─┬─round_with_frac_uint(a, 1)─┬─round_with_frac_uint(a, 0)─┬─round_with_frac_uint(a, -1)─┬─round_with_frac_uint(a, -2)─┬─round_with_frac_uint(a, -3)─┬─round_with_frac_uint(a, -4)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4 │ 4 │ 4 │ 0 │ 0 │ 0 │ 0 │ +│ 5 │ 5 │ 5 │ 10 │ 0 │ 0 │ 0 │ +│ 49 │ 49 │ 49 │ 50 │ 0 │ 0 │ 0 │ +│ 50 │ 50 │ 50 │ 50 │ 100 │ 0 │ 0 │ +│ 255 │ 255 │ 255 │ 260 │ 300 │ 0 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└─────┴────────────────────────────┴────────────────────────────┴─────────────────────────────┴─────────────────────────────┴─────────────────────────────┴─────────────────────────────┘ + +=> DBGInvoke dag('select a, round_with_frac_int(a, 1), round_with_frac_int(a, 0), round_with_frac_int(a, -1), round_with_frac_int(a, -2), round_with_frac_int(a, -8), round_with_frac_int(a, -9), round_with_frac_int(a, -10) from default.i32') +┌───────────a─┬─round_with_frac_int(a, 1)─┬─round_with_frac_int(a, 0)─┬─round_with_frac_int(a, -1)─┬─round_with_frac_int(a, -2)─┬─round_with_frac_int(a, -8)─┬─round_with_frac_int(a, -9)─┬─round_with_frac_int(a, -10)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4 │ 4 │ 4 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 5 │ 5 │ 5 │ 10 │ 0 │ 0 │ 0 │ 0 │ +│ -4 │ -4 │ -4 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ -5 │ -5 │ -5 │ -10 │ 0 │ 0 │ 0 │ 0 │ +│ 499999999 │ 499999999 │ 499999999 │ 500000000 │ 500000000 │ 500000000 │ 0 │ 0 │ +│ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 1000000000 │ 0 │ +│ -499999999 │ -499999999 │ -499999999 │ -500000000 │ -500000000 │ -500000000 │ 0 │ 0 │ +│ -500000000 │ -500000000 │ -500000000 │ -500000000 │ -500000000 │ -500000000 │ -1000000000 │ 0 │ +│ 2147483647 │ 2147483647 │ 2147483647 │ 2147483650 │ 2147483600 │ 2100000000 │ 2000000000 │ 0 │ +│ -2147483648 │ -2147483648 │ -2147483648 │ -2147483650 │ -2147483600 │ -2100000000 │ -2000000000 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└─────────────┴───────────────────────────┴───────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┴─────────────────────────────┘ + +=> DBGInvoke dag('select a, round_with_frac_uint(a, 1), round_with_frac_uint(a, 0), round_with_frac_uint(a, -1), round_with_frac_uint(a, -2), round_with_frac_uint(a, -8), round_with_frac_uint(a, -9), round_with_frac_uint(a, -10) from default.u32') +┌──────────a─┬─round_with_frac_uint(a, 1)─┬─round_with_frac_uint(a, 0)─┬─round_with_frac_uint(a, -1)─┬─round_with_frac_uint(a, -2)─┬─round_with_frac_uint(a, -8)─┬─round_with_frac_uint(a, -9)─┬─round_with_frac_uint(a, -10)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4 │ 4 │ 4 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 5 │ 5 │ 5 │ 10 │ 0 │ 0 │ 0 │ 0 │ +│ 499999999 │ 499999999 │ 499999999 │ 500000000 │ 500000000 │ 500000000 │ 0 │ 0 │ +│ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 500000000 │ 1000000000 │ 0 │ +│ 4294967295 │ 4294967295 │ 4294967295 │ 4294967300 │ 4294967300 │ 4300000000 │ 4000000000 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└────────────┴────────────────────────────┴────────────────────────────┴─────────────────────────────┴─────────────────────────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────────────┘ + +=> DBGInvoke dag('select a, round_with_frac_int(a, 1), round_with_frac_int(a, 0), round_with_frac_int(a, -1), round_with_frac_int(a, -2), round_with_frac_int(a, -18), round_with_frac_int(a, -19), round_with_frac_int(a, -20) from default.i64') +┌───────────────────a─┬─round_with_frac_int(a, 1)─┬─round_with_frac_int(a, 0)─┬─round_with_frac_int(a, -1)─┬─round_with_frac_int(a, -2)─┬─round_with_frac_int(a, -18)─┬─round_with_frac_int(a, -19)─┬─round_with_frac_int(a, -20)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4 │ 4 │ 4 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 5 │ 5 │ 5 │ 10 │ 0 │ 0 │ 0 │ 0 │ +│ -4 │ -4 │ -4 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ -5 │ -5 │ -5 │ -10 │ 0 │ 0 │ 0 │ 0 │ +│ 499999999999999999 │ 499999999999999999 │ 499999999999999999 │ 500000000000000000 │ 500000000000000000 │ 0 │ 0 │ 0 │ +│ 500000000000000000 │ 500000000000000000 │ 500000000000000000 │ 500000000000000000 │ 500000000000000000 │ 1000000000000000000 │ 0 │ 0 │ +│ -499999999999999999 │ -499999999999999999 │ -499999999999999999 │ -500000000000000000 │ -500000000000000000 │ 0 │ 0 │ 0 │ +│ -500000000000000000 │ -500000000000000000 │ -500000000000000000 │ -500000000000000000 │ -500000000000000000 │ -1000000000000000000 │ 0 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└─────────────────────┴───────────────────────────┴───────────────────────────┴────────────────────────────┴────────────────────────────┴─────────────────────────────┴─────────────────────────────┴─────────────────────────────┘ + +=> DBGInvoke dag('select a, round_with_frac_uint(a, 1), round_with_frac_uint(a, 0), round_with_frac_uint(a, -1), round_with_frac_uint(a, -2), round_with_frac_uint(a, -16), round_with_frac_uint(a, -18), round_with_frac_uint(a, -19), round_with_frac_uint(a, -20) from default.u64') +┌───────────────────a─┬─round_with_frac_uint(a, 1)─┬─round_with_frac_uint(a, 0)─┬─round_with_frac_uint(a, -1)─┬─round_with_frac_uint(a, -2)─┬─round_with_frac_uint(a, -16)─┬─round_with_frac_uint(a, -18)─┬─round_with_frac_uint(a, -19)─┬─round_with_frac_uint(a, -20)─┐ +│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4 │ 4 │ 4 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 5 │ 5 │ 5 │ 10 │ 0 │ 0 │ 0 │ 0 │ 0 │ +│ 4999999999999999999 │ 4999999999999999999 │ 4999999999999999999 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 0 │ 0 │ +│ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 5000000000000000000 │ 10000000000000000000 │ 0 │ +│ \N │ \N │ \N │ \N │ \N │ \N │ \N │ \N │ \N │ +└─────────────────────┴────────────────────────────┴────────────────────────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ + +=> DBGInvoke dag('select i, round_with_frac_int(-123456789123456789, i) from default.t') +┌────────────────────i─┬─round_with_frac_int(-123456789123456789, i)─┐ +│ -66 │ 0 │ +│ -65 │ 0 │ +│ -64 │ 0 │ +│ -63 │ 0 │ +│ -62 │ 0 │ +│ -61 │ 0 │ +│ -60 │ 0 │ +│ -59 │ 0 │ +│ -58 │ 0 │ +│ -57 │ 0 │ +│ -56 │ 0 │ +│ -55 │ 0 │ +│ -54 │ 0 │ +│ -53 │ 0 │ +│ -52 │ 0 │ +│ -51 │ 0 │ +│ -50 │ 0 │ +│ -49 │ 0 │ +│ -48 │ 0 │ +│ -47 │ 0 │ +│ -46 │ 0 │ +│ -45 │ 0 │ +│ -44 │ 0 │ +│ -43 │ 0 │ +│ -42 │ 0 │ +│ -41 │ 0 │ +│ -40 │ 0 │ +│ -39 │ 0 │ +│ -38 │ 0 │ +│ -37 │ 0 │ +│ -36 │ 0 │ +│ -35 │ 0 │ +│ -34 │ 0 │ +│ -33 │ 0 │ +│ -32 │ 0 │ +│ -31 │ 0 │ +│ -30 │ 0 │ +│ -29 │ 0 │ +│ -28 │ 0 │ +│ -27 │ 0 │ +│ -26 │ 0 │ +│ -25 │ 0 │ +│ -24 │ 0 │ +│ -23 │ 0 │ +│ -22 │ 0 │ +│ -21 │ 0 │ +│ -20 │ 0 │ +│ -19 │ 0 │ +│ -18 │ 0 │ +│ -17 │ -100000000000000000 │ +│ -16 │ -120000000000000000 │ +│ -15 │ -123000000000000000 │ +│ -14 │ -123500000000000000 │ +│ -13 │ -123460000000000000 │ +│ -12 │ -123457000000000000 │ +│ -11 │ -123456800000000000 │ +│ -10 │ -123456790000000000 │ +│ -9 │ -123456789000000000 │ +│ -8 │ -123456789100000000 │ +│ -7 │ -123456789120000000 │ +│ -6 │ -123456789123000000 │ +│ -5 │ -123456789123500000 │ +│ -4 │ -123456789123460000 │ +│ -3 │ -123456789123457000 │ +│ -2 │ -123456789123456800 │ +│ -1 │ -123456789123456790 │ +│ 0 │ -123456789123456789 │ +│ 1 │ -123456789123456789 │ +│ 2 │ -123456789123456789 │ +│ 3 │ -123456789123456789 │ +│ 4 │ -123456789123456789 │ +│ 5 │ -123456789123456789 │ +│ 6 │ -123456789123456789 │ +│ 7 │ -123456789123456789 │ +│ 8 │ -123456789123456789 │ +│ 9 │ -123456789123456789 │ +│ 10 │ -123456789123456789 │ +│ 11 │ -123456789123456789 │ +│ 12 │ -123456789123456789 │ +│ 13 │ -123456789123456789 │ +│ 14 │ -123456789123456789 │ +│ 15 │ -123456789123456789 │ +│ 16 │ -123456789123456789 │ +│ 17 │ -123456789123456789 │ +│ 18 │ -123456789123456789 │ +│ 19 │ -123456789123456789 │ +│ 20 │ -123456789123456789 │ +│ 21 │ -123456789123456789 │ +│ 22 │ -123456789123456789 │ +│ 23 │ -123456789123456789 │ +│ 24 │ -123456789123456789 │ +│ 25 │ -123456789123456789 │ +│ 26 │ -123456789123456789 │ +│ 27 │ -123456789123456789 │ +│ 28 │ -123456789123456789 │ +│ 29 │ -123456789123456789 │ +│ 30 │ -123456789123456789 │ +│ 31 │ -123456789123456789 │ +│ 9223372036854775807 │ -123456789123456789 │ +│ -9223372036854775808 │ 0 │ +│ \N │ \N │ +└──────────────────────┴─────────────────────────────────────────────┘ + +# TODO: these tests are disabled because cross join is not available. +#=> DBGInvoke dag('select sum(round_with_frac_int(a, i)) from default.i32 cross join default.t where a #>= 0 and i is not null') +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| 132001391875 | +#+------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_int(a, i)) from default.i32 cross join default.t where a < #0 and i is not null') +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| -132001391908 | +#+------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_uint(a, i)) from default.u32 cross join default.t where a #is null and i is not null') +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| NULL | +#+------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_uint(a, i)) from default.u32 cross join default.t where i #is not null') +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| 222093792609 | +#+------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_int(a, i)) from default.i64 cross join default.t where a #>= 0 and i is not null') +#+----------------------+ +#| sum(round(a, i)) | +#+----------------------+ +#| 51000000000000000274 | +#+----------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_int(a, i)) from default.i64 cross join default.t where a < #0 and i is not null') +#+-----------------------+ +#| sum(round(a, i)) | +#+-----------------------+ +#| -51000000000000000274 | +#+-----------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_uint(a, i)) from default.u64 cross join default.t where a #is null and i is not null') +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| NULL | +#+------------------+ +# +#=> DBGInvoke dag('select sum(round_with_frac_uint(a, i)) from default.u64 cross join default.t where i #is not null') +#+-----------------------+ +#| sum(round(a, i)) | +#+-----------------------+ +#| 520000000000000000274 | +#+-----------------------+ + +# Clean up. +=> DBGInvoke __drop_tidb_table(default, i8) +=> drop table if exists default.i8 +=> DBGInvoke __drop_tidb_table(default, u8) +=> drop table if exists default.u8 +=> DBGInvoke __drop_tidb_table(default, i32) +=> drop table if exists default.i32 +=> DBGInvoke __drop_tidb_table(default, u32) +=> drop table if exists default.u32 +=> DBGInvoke __drop_tidb_table(default, i64) +=> drop table if exists default.i64 +=> DBGInvoke __drop_tidb_table(default, u64) +=> drop table if exists default.u64 +=> DBGInvoke __drop_tidb_table(default, t) +=> drop table if exists default.t diff --git a/tests/fullstack-test/expr/round_with_frac.test b/tests/fullstack-test/expr/round_with_frac.test new file mode 100644 index 00000000000..ba608a74b86 --- /dev/null +++ b/tests/fullstack-test/expr/round_with_frac.test @@ -0,0 +1,500 @@ +# const frac + +#mysql> drop table if exists test.i8 +#mysql> create table test.i8 (id bigint, a tinyint) +#mysql> alter table test.i8 set tiflash replica 1 +#mysql> insert into test.i8 values (1, 0), (2, 1), (3, -1), (4, 49), (5, 50), (6, -49), (7, -50), (8, 127), (9, -128), (10, null) +#func> wait_table test i8 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -3)), sum(round(a, -4)) from test.i8 group by id order by id +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -3)) | sum(round(a, -4)) | +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 1 | 1 | 1 | 0 | 0 | 0 | 0 | +#| -1 | -1 | -1 | 0 | 0 | 0 | 0 | +#| 49 | 49 | 49 | 50 | 0 | 0 | 0 | +#| 50 | 50 | 50 | 50 | 100 | 0 | 0 | +#| -49 | -49 | -49 | -50 | 0 | 0 | 0 | +#| -50 | -50 | -50 | -50 | -100 | 0 | 0 | +#| 127 | 127 | 127 | 130 | 100 | 0 | 0 | +#| -128 | -128 | -128 | -130 | -100 | 0 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +# +#mysql> drop table if exists test.u8 +#mysql> create table test.u8 (id bigint, a tinyint unsigned) +#mysql> alter table test.u8 set tiflash replica 1 +#mysql> insert into test.u8 values (1, 0), (2, 4), (3, 5), (4, 49), (5, 50), (6, 255), (7, null) +#func> wait_table test u8 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -3)), sum(round(a, -4)) from test.u8 group by id order by id +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -3)) | sum(round(a, -4)) | +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 4 | 4 | 4 | 0 | 0 | 0 | 0 | +#| 5 | 5 | 5 | 10 | 0 | 0 | 0 | +#| 49 | 49 | 49 | 50 | 0 | 0 | 0 | +#| 50 | 50 | 50 | 50 | 100 | 0 | 0 | +#| 255 | 255 | 255 | 260 | 300 | 0 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+--------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+ +# +#mysql> drop table if exists test.i32 +#mysql> create table test.i32 (id bigint, a int) +#mysql> alter table test.i32 set tiflash replica 1 +#mysql> insert into test.i32 value (1, 0), (2, 4), (3, 5), (4, -4), (5, -5), (6, 499999999), (7, 500000000), (8, -499999999), (9, -500000000), (10, 2147483647), (11, -2147483648), (12, null) +#func> wait_table test i32 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -8)), sum(round(a, -9)), sum(round(a, -10)) from test.i32 group by id order by id +#+-------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -8)) | sum(round(a, -9)) | sum(round(a, -10)) | +#+-------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | +#| 5 | 5 | 5 | 10 | 0 | 0 | 0 | 0 | +#| -4 | -4 | -4 | 0 | 0 | 0 | 0 | 0 | +#| -5 | -5 | -5 | -10 | 0 | 0 | 0 | 0 | +#| 499999999 | 499999999 | 499999999 | 500000000 | 500000000 | 500000000 | 0 | 0 | +#| 500000000 | 500000000 | 500000000 | 500000000 | 500000000 | 500000000 | 1000000000 | 0 | +#| -499999999 | -499999999 | -499999999 | -500000000 | -500000000 | -500000000 | 0 | 0 | +#| -500000000 | -500000000 | -500000000 | -500000000 | -500000000 | -500000000 | -1000000000 | 0 | +#| 2147483647 | 2147483647 | 2147483647 | 2147483650 | 2147483600 | 2100000000 | 2000000000 | 0 | +#| -2147483648 | -2147483648 | -2147483648 | -2147483650 | -2147483600 | -2100000000 | -2000000000 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+-------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +# +#mysql> drop table if exists test.u32 +#mysql> create table test.u32 (id bigint, a int unsigned) +#mysql> alter table test.u32 set tiflash replica 1 +#mysql> insert into test.u32 value (1, 0), (2, 4), (3, 5), (4, 499999999), (5, 500000000), (6, 4294967295), (7, null) +#func> wait_table test u32 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -8)), sum(round(a, -9)), sum(round(a, -10)) from test.u32 group by id order by id +#+------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -8)) | sum(round(a, -9)) | sum(round(a, -10)) | +#+------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | +#| 5 | 5 | 5 | 10 | 0 | 0 | 0 | 0 | +#| 499999999 | 499999999 | 499999999 | 500000000 | 500000000 | 500000000 | 0 | 0 | +#| 500000000 | 500000000 | 500000000 | 500000000 | 500000000 | 500000000 | 1000000000 | 0 | +#| 4294967295 | 4294967295 | 4294967295 | 4294967300 | 4294967300 | 4300000000 | 4000000000 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+------------+------------------+------------------+-------------------+-------------------+-------------------+-------------------+--------------------+ +# +#mysql> drop table if exists test.i64 +#mysql> create table test.i64 (id bigint, a bigint) +#mysql> alter table test.i64 set tiflash replica 1 +#mysql> insert into test.i64 values (1, 0), (2, 4), (3, 5), (4, -4), (5, -5), (6, 499999999999999999), (7, 500000000000000000), (8, -499999999999999999), (9, -500000000000000000), (10, null) +#func> wait_table test i64 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -18)), sum(round(a, -19)), sum(round(a, -20)) from test.i64 group by id order by id +#+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+--------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -18)) | sum(round(a, -19)) | sum(round(a, -20)) | +#+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+--------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | +#| 5 | 5 | 5 | 10 | 0 | 0 | 0 | 0 | +#| -4 | -4 | -4 | 0 | 0 | 0 | 0 | 0 | +#| -5 | -5 | -5 | -10 | 0 | 0 | 0 | 0 | +#| 499999999999999999 | 499999999999999999 | 499999999999999999 | 500000000000000000 | 500000000000000000 | 0 | 0 | 0 | +#| 500000000000000000 | 500000000000000000 | 500000000000000000 | 500000000000000000 | 500000000000000000 | 1000000000000000000 | 0 | 0 | +#| -499999999999999999 | -499999999999999999 | -499999999999999999 | -500000000000000000 | -500000000000000000 | 0 | 0 | 0 | +#| -500000000000000000 | -500000000000000000 | -500000000000000000 | -500000000000000000 | -500000000000000000 | -1000000000000000000 | 0 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+--------------------+ +# +#mysql> drop table if exists test.u64 +#mysql> create table test.u64 (id bigint, a bigint unsigned) +#mysql> alter table test.u64 set tiflash replica 1 +#mysql> insert into test.u64 values (1, 0), (2, 4), (3, 5), (4, 4999999999999999999), (5, 5000000000000000000), (6, null) +#func> wait_table test u64 +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)), sum(round(a, -16)), sum(round(a, -18)), sum(round(a, -19)), sum(round(a, -20)) from test.u64 group by id order by id +#+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+ +#| sum(a) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | sum(round(a, -16)) | sum(round(a, -18)) | sum(round(a, -19)) | sum(round(a, -20)) | +#+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+ +#| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | +#| 5 | 5 | 5 | 10 | 0 | 0 | 0 | 0 | 0 | +#| 4999999999999999999 | 4999999999999999999 | 4999999999999999999 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 0 | 0 | +#| 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 5000000000000000000 | 10000000000000000000 | 0 | +#| NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +#+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+----------------------+--------------------+ + +mysql> drop table if exists test.f32 +mysql> create table test.f32 (id bigint, a float) +mysql> alter table test.f32 set tiflash replica 1 +mysql> insert into test.f32 values (1, 0), (2, 2.5), (3, -2.5), (4, 0.25), (5, -0.25), (6, 0.25e-10), (7, -0.25e-10), (8, 49e10), (9, 50e10), (10, -49e10), (11, -50e10), (12, 0.3), (13, null) +func> wait_table test f32 +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 2)), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)) from test.f32 group by id order by id ++------------------------------+------------------+------------------+------------------+-------------------+-------------------+ +| sum(a) | sum(round(a, 2)) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | ++------------------------------+------------------+------------------+------------------+-------------------+-------------------+ +| 0 | 0.00 | 0.0 | 0 | 0 | 0 | +| 2.5 | 2.50 | 2.5 | 2 | 0 | 0 | +| -2.5 | -2.50 | -2.5 | -2 | 0 | 0 | +| 0.25 | 0.25 | 0.2 | 0 | 0 | 0 | +| -0.25 | -0.25 | -0.2 | 0 | 0 | 0 | +| 0.0000000000250000003337858 | 0.00 | 0.0 | 0 | 0 | 0 | +| -0.0000000000250000003337858 | 0.00 | 0.0 | 0 | 0 | 0 | +| 489999990784 | 489999990784.00 | 489999990784.0 | 489999990784 | 489999990780 | 489999990800 | +| 499999997952 | 499999997952.00 | 499999997952.0 | 499999997952 | 499999997950 | 499999998000 | +| -489999990784 | -489999990784.00 | -489999990784.0 | -489999990784 | -489999990780 | -489999990800 | +| -499999997952 | -499999997952.00 | -499999997952.0 | -499999997952 | -499999997950 | -499999998000 | +| 0.30000001192092896 | 0.30 | 0.3 | 0 | 0 | 0 | +| NULL | NULL | NULL | NULL | NULL | NULL | ++------------------------------+------------------+------------------+------------------+-------------------+-------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 8)), sum(round(a, 9)), sum(round(a, -9)), sum(round(a, -10)) from test.f32 group by id order by id ++------------------------------+------------------------+-------------------------+---------------------+--------------------+ +| sum(a) | sum(round(a, 8)) | sum(round(a, 9)) | sum(round(a, -9)) | sum(round(a, -10)) | ++------------------------------+------------------------+-------------------------+---------------------+--------------------+ +| 0 | 0.00000000 | 0.000000000 | 0 | 0 | +| 2.5 | 2.50000000 | 2.500000000 | 0 | 0 | +| -2.5 | -2.50000000 | -2.500000000 | 0 | 0 | +| 0.25 | 0.25000000 | 0.250000000 | 0 | 0 | +| -0.25 | -0.25000000 | -0.250000000 | 0 | 0 | +| 0.0000000000250000003337858 | 0.00000000 | 0.000000000 | 0 | 0 | +| -0.0000000000250000003337858 | 0.00000000 | 0.000000000 | 0 | 0 | +| 489999990784 | 489999990784.00000000 | 489999990784.000000000 | 489999999999.99994 | 490000000000 | +| 499999997952 | 499999997952.00000000 | 499999997952.000000000 | 499999999999.99994 | 500000000000 | +| -489999990784 | -489999990784.00000000 | -489999990784.000000000 | -489999999999.99994 | -490000000000 | +| -499999997952 | -499999997952.00000000 | -499999997952.000000000 | -499999999999.99994 | -500000000000 | +| 0.30000001192092896 | 0.30000001 | 0.300000012 | 0 | 0 | +| NULL | NULL | NULL | NULL | NULL | ++------------------------------+------------------------+-------------------------+---------------------+--------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 30)), sum(round(a, -30)), sum(round(a, 1000000)), sum(round(a, -1000000)) from test.f32 group by id order by id ++------------------------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ +| sum(a) | sum(round(a, 30)) | sum(round(a, -30)) | sum(round(a, 1000000)) | sum(round(a, -1000000)) | ++------------------------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ +| 0 | 0.000000000000000000000000000000 | 0 | 0.000000000000000000000000000000 | 0 | +| 2.5 | 2.500000000000000000000000000000 | 0 | 2.500000000000000000000000000000 | 0 | +| -2.5 | -2.500000000000000000000000000000 | 0 | -2.500000000000000000000000000000 | 0 | +| 0.25 | 0.250000000000000000000000000000 | 0 | 0.250000000000000000000000000000 | 0 | +| -0.25 | -0.250000000000000000000000000000 | 0 | -0.250000000000000000000000000000 | 0 | +| 0.0000000000250000003337858 | 0.000000000025000000333785799000 | 0 | 0.000000000025000000333785799000 | 0 | +| -0.0000000000250000003337858 | -0.000000000025000000333785799000 | 0 | -0.000000000025000000333785799000 | 0 | +| 489999990784 | 489999990784.000000000000000000000000000000 | 0 | 489999990784.000000000000000000000000000000 | 0 | +| 499999997952 | 499999997952.000000000000000000000000000000 | 0 | 499999997952.000000000000000000000000000000 | 0 | +| -489999990784 | -489999990784.000000000000000000000000000000 | 0 | -489999990784.000000000000000000000000000000 | 0 | +| -499999997952 | -499999997952.000000000000000000000000000000 | 0 | -499999997952.000000000000000000000000000000 | 0 | +| 0.30000001192092896 | 0.300000011920928955078125000000 | 0 | 0.300000011920928955078125000000 | 0 | +| NULL | NULL | NULL | NULL | NULL | ++------------------------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ + +mysql> drop table if exists test.f64 +mysql> create table test.f64 (id bigint, a double) +mysql> alter table test.f64 set tiflash replica 1 +mysql> insert into test.f64 values (1, 0), (2, 2.5), (3, -2.5), (4, 0.25), (5, -0.25), (6, 0.25e-10), (7, -0.25e-10), (8, 49e10), (9, 50e10), (10, -49e10), (11, -50e10), (12, 0.3), (13, null) +func> wait_table test f64 +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 2)), sum(round(a, 1)), sum(round(a, 0)), sum(round(a, -1)), sum(round(a, -2)) from test.f64 group by id order by id ++-----------------+------------------+------------------+------------------+-------------------+-------------------+ +| sum(a) | sum(round(a, 2)) | sum(round(a, 1)) | sum(round(a, 0)) | sum(round(a, -1)) | sum(round(a, -2)) | ++-----------------+------------------+------------------+------------------+-------------------+-------------------+ +| 0 | 0.00 | 0.0 | 0 | 0 | 0 | +| 2.5 | 2.50 | 2.5 | 2 | 0 | 0 | +| -2.5 | -2.50 | -2.5 | -2 | 0 | 0 | +| 0.25 | 0.25 | 0.2 | 0 | 0 | 0 | +| -0.25 | -0.25 | -0.2 | 0 | 0 | 0 | +| 0.000000000025 | 0.00 | 0.0 | 0 | 0 | 0 | +| -0.000000000025 | 0.00 | 0.0 | 0 | 0 | 0 | +| 490000000000 | 490000000000.00 | 490000000000.0 | 490000000000 | 490000000000 | 490000000000 | +| 500000000000 | 500000000000.00 | 500000000000.0 | 500000000000 | 500000000000 | 500000000000 | +| -490000000000 | -490000000000.00 | -490000000000.0 | -490000000000 | -490000000000 | -490000000000 | +| -500000000000 | -500000000000.00 | -500000000000.0 | -500000000000 | -500000000000 | -500000000000 | +| 0.3 | 0.30 | 0.3 | 0 | 0 | 0 | +| NULL | NULL | NULL | NULL | NULL | NULL | ++-----------------+------------------+------------------+------------------+-------------------+-------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 8)), sum(round(a, 9)), sum(round(a, -9)), sum(round(a, -10)) from test.f64 group by id order by id ++-----------------+------------------------+-------------------------+---------------------+--------------------+ +| sum(a) | sum(round(a, 8)) | sum(round(a, 9)) | sum(round(a, -9)) | sum(round(a, -10)) | ++-----------------+------------------------+-------------------------+---------------------+--------------------+ +| 0 | 0.00000000 | 0.000000000 | 0 | 0 | +| 2.5 | 2.50000000 | 2.500000000 | 0 | 0 | +| -2.5 | -2.50000000 | -2.500000000 | 0 | 0 | +| 0.25 | 0.25000000 | 0.250000000 | 0 | 0 | +| -0.25 | -0.25000000 | -0.250000000 | 0 | 0 | +| 0.000000000025 | 0.00000000 | 0.000000000 | 0 | 0 | +| -0.000000000025 | 0.00000000 | 0.000000000 | 0 | 0 | +| 490000000000 | 490000000000.00000000 | 490000000000.000000000 | 489999999999.99994 | 490000000000 | +| 500000000000 | 500000000000.00000000 | 500000000000.000000000 | 499999999999.99994 | 500000000000 | +| -490000000000 | -490000000000.00000000 | -490000000000.000000000 | -489999999999.99994 | -490000000000 | +| -500000000000 | -500000000000.00000000 | -500000000000.000000000 | -499999999999.99994 | -500000000000 | +| 0.3 | 0.30000000 | 0.300000000 | 0 | 0 | +| NULL | NULL | NULL | NULL | NULL | ++-----------------+------------------------+-------------------------+---------------------+--------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 30)), sum(round(a, -30)), sum(round(a, 1000000)), sum(round(a, -1000000)) from test.f64 group by id order by id ++-----------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ +| sum(a) | sum(round(a, 30)) | sum(round(a, -30)) | sum(round(a, 1000000)) | sum(round(a, -1000000)) | ++-----------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ +| 0 | 0.000000000000000000000000000000 | 0 | 0.000000000000000000000000000000 | 0 | +| 2.5 | 2.500000000000000000000000000000 | 0 | 2.500000000000000000000000000000 | 0 | +| -2.5 | -2.500000000000000000000000000000 | 0 | -2.500000000000000000000000000000 | 0 | +| 0.25 | 0.250000000000000000000000000000 | 0 | 0.250000000000000000000000000000 | 0 | +| -0.25 | -0.250000000000000000000000000000 | 0 | -0.250000000000000000000000000000 | 0 | +| 0.000000000025 | 0.000000000025000000000000000911 | 0 | 0.000000000025000000000000000911 | 0 | +| -0.000000000025 | -0.000000000025000000000000000911 | 0 | -0.000000000025000000000000000911 | 0 | +| 490000000000 | 490000000000.000000000000000000000000000000 | 0 | 490000000000.000000000000000000000000000000 | 0 | +| 500000000000 | 500000000000.000000000000000000000000000000 | 0 | 500000000000.000000000000000000000000000000 | 0 | +| -490000000000 | -490000000000.000000000000000000000000000000 | 0 | -490000000000.000000000000000000000000000000 | 0 | +| -500000000000 | -500000000000.000000000000000000000000000000 | 0 | -500000000000.000000000000000000000000000000 | 0 | +| 0.3 | 0.299999999999999988897769753748 | 0 | 0.299999999999999988897769753748 | 0 | +| NULL | NULL | NULL | NULL | NULL | ++-----------------+----------------------------------------------+--------------------+----------------------------------------------+-------------------------+ + +mysql> drop table if exists test.d9 +mysql> create table test.d9 (id bigint, a decimal(9, 4)) +mysql> alter table test.d9 set tiflash replica 1 +mysql> insert into test.d9 values (1, 0), (2, 0.25), (3, -0.25), (4, 0.0499), (5, 0.05), (6, -0.0499), (7, -0.05), (8, 49999.9999), (9, 50000), (10, -49999.9999), (11, -50000), (12, 99999.9999), (13, -99999.9999), (14, 25), (15, -25), (16, null) +func> wait_table test d9 +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, -1)), sum(round(a, 0)), sum(round(a, 1)) from test.d9 group by id order by id ++-------------+-------------------+------------------+------------------+ +| sum(a) | sum(round(a, -1)) | sum(round(a, 0)) | sum(round(a, 1)) | ++-------------+-------------------+------------------+------------------+ +| 0.0000 | 0 | 0 | 0.0 | +| 0.2500 | 0 | 0 | 0.3 | +| -0.2500 | 0 | 0 | -0.3 | +| 0.0499 | 0 | 0 | 0.0 | +| 0.0500 | 0 | 0 | 0.1 | +| -0.0499 | 0 | 0 | 0.0 | +| -0.0500 | 0 | 0 | -0.1 | +| 49999.9999 | 50000 | 50000 | 50000.0 | +| 50000.0000 | 50000 | 50000 | 50000.0 | +| -49999.9999 | -50000 | -50000 | -50000.0 | +| -50000.0000 | -50000 | -50000 | -50000.0 | +| 99999.9999 | 100000 | 100000 | 100000.0 | +| -99999.9999 | -100000 | -100000 | -100000.0 | +| 25.0000 | 30 | 25 | 25.0 | +| -25.0000 | -30 | -25 | -25.0 | +| NULL | NULL | NULL | NULL | ++-------------+-------------------+------------------+------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 3)), sum(round(a, 4)), sum(round(a, 5)), sum(round(a, 40)) from test.d9 group by id order by id ++-------------+------------------+------------------+------------------+---------------------------------------+ +| sum(a) | sum(round(a, 3)) | sum(round(a, 4)) | sum(round(a, 5)) | sum(round(a, 40)) | ++-------------+------------------+------------------+------------------+---------------------------------------+ +| 0.0000 | 0.000 | 0.0000 | 0.00000 | 0.000000000000000000000000000000 | +| 0.2500 | 0.250 | 0.2500 | 0.25000 | 0.250000000000000000000000000000 | +| -0.2500 | -0.250 | -0.2500 | -0.25000 | -0.250000000000000000000000000000 | +| 0.0499 | 0.050 | 0.0499 | 0.04990 | 0.049900000000000000000000000000 | +| 0.0500 | 0.050 | 0.0500 | 0.05000 | 0.050000000000000000000000000000 | +| -0.0499 | -0.050 | -0.0499 | -0.04990 | -0.049900000000000000000000000000 | +| -0.0500 | -0.050 | -0.0500 | -0.05000 | -0.050000000000000000000000000000 | +| 49999.9999 | 50000.000 | 49999.9999 | 49999.99990 | 49999.999900000000000000000000000000 | +| 50000.0000 | 50000.000 | 50000.0000 | 50000.00000 | 50000.000000000000000000000000000000 | +| -49999.9999 | -50000.000 | -49999.9999 | -49999.99990 | -49999.999900000000000000000000000000 | +| -50000.0000 | -50000.000 | -50000.0000 | -50000.00000 | -50000.000000000000000000000000000000 | +| 99999.9999 | 100000.000 | 99999.9999 | 99999.99990 | 99999.999900000000000000000000000000 | +| -99999.9999 | -100000.000 | -99999.9999 | -99999.99990 | -99999.999900000000000000000000000000 | +| 25.0000 | 25.000 | 25.0000 | 25.00000 | 25.000000000000000000000000000000 | +| -25.0000 | -25.000 | -25.0000 | -25.00000 | -25.000000000000000000000000000000 | +| NULL | NULL | NULL | NULL | NULL | ++-------------+------------------+------------------+------------------+---------------------------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, -4)), sum(round(a, -5)), sum(round(a, -6)), sum(round(a, -100)) from test.d9 group by id order by id ++-------------+-------------------+-------------------+-------------------+---------------------+ +| sum(a) | sum(round(a, -4)) | sum(round(a, -5)) | sum(round(a, -6)) | sum(round(a, -100)) | ++-------------+-------------------+-------------------+-------------------+---------------------+ +| 0.0000 | 0 | 0 | 0 | 0 | +| 0.2500 | 0 | 0 | 0 | 0 | +| -0.2500 | 0 | 0 | 0 | 0 | +| 0.0499 | 0 | 0 | 0 | 0 | +| 0.0500 | 0 | 0 | 0 | 0 | +| -0.0499 | 0 | 0 | 0 | 0 | +| -0.0500 | 0 | 0 | 0 | 0 | +| 49999.9999 | 50000 | 0 | 0 | 0 | +| 50000.0000 | 50000 | 100000 | 0 | 0 | +| -49999.9999 | -50000 | 0 | 0 | 0 | +| -50000.0000 | -50000 | -100000 | 0 | 0 | +| 99999.9999 | 100000 | 100000 | 0 | 0 | +| -99999.9999 | -100000 | -100000 | 0 | 0 | +| 25.0000 | 0 | 0 | 0 | 0 | +| -25.0000 | 0 | 0 | 0 | 0 | +| NULL | NULL | NULL | NULL | NULL | ++-------------+-------------------+-------------------+-------------------+---------------------+ + +mysql> drop table if exists test.d64 +mysql> create table test.d64 (id bigint, a decimal(64, 2)) +mysql> alter table test.d64 set tiflash replica 1 +mysql> insert into test.d64 values (1, 0), (2, 0.25), (3, -0.25), (4, 25), (5, -25), (6, 49999999999999999999999999999999999999999999999999999999999999.99), (7, 50000000000000000000000000000000000000000000000000000000000000), (8, -49999999999999999999999999999999999999999999999999999999999999.99), (9, -50000000000000000000000000000000000000000000000000000000000000), (10, 0.49), (11, 0.5), (12, -0.49), (13, -0.5), (14, 99999999999999999999999999999999999999999999999999999999999999.99), (15, -99999999999999999999999999999999999999999999999999999999999999.99), (16, null) +func> wait_table test d64 +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, -1)), sum(round(a, 0)), sum(round(a, 1)) from test.d64 group by id order by id ++--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+--------------------------------------------------------------------+ +| sum(a) | sum(round(a, -1)) | sum(round(a, 0)) | sum(round(a, 1)) | ++--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+--------------------------------------------------------------------+ +| 0.00 | 0 | 0 | 0.0 | +| 0.25 | 0 | 0 | 0.3 | +| -0.25 | 0 | 0 | -0.3 | +| 25.00 | 30 | 25 | 25.0 | +| -25.00 | -30 | -25 | -25.0 | +| 49999999999999999999999999999999999999999999999999999999999999.99 | 50000000000000000000000000000000000000000000000000000000000000 | 50000000000000000000000000000000000000000000000000000000000000 | 50000000000000000000000000000000000000000000000000000000000000.0 | +| 50000000000000000000000000000000000000000000000000000000000000.00 | 50000000000000000000000000000000000000000000000000000000000000 | 50000000000000000000000000000000000000000000000000000000000000 | 50000000000000000000000000000000000000000000000000000000000000.0 | +| -49999999999999999999999999999999999999999999999999999999999999.99 | -50000000000000000000000000000000000000000000000000000000000000 | -50000000000000000000000000000000000000000000000000000000000000 | -50000000000000000000000000000000000000000000000000000000000000.0 | +| -50000000000000000000000000000000000000000000000000000000000000.00 | -50000000000000000000000000000000000000000000000000000000000000 | -50000000000000000000000000000000000000000000000000000000000000 | -50000000000000000000000000000000000000000000000000000000000000.0 | +| 0.49 | 0 | 0 | 0.5 | +| 0.50 | 0 | 1 | 0.5 | +| -0.49 | 0 | 0 | -0.5 | +| -0.50 | 0 | -1 | -0.5 | +| 99999999999999999999999999999999999999999999999999999999999999.99 | 100000000000000000000000000000000000000000000000000000000000000 | 100000000000000000000000000000000000000000000000000000000000000 | 100000000000000000000000000000000000000000000000000000000000000.0 | +| -99999999999999999999999999999999999999999999999999999999999999.99 | -100000000000000000000000000000000000000000000000000000000000000 | -100000000000000000000000000000000000000000000000000000000000000 | -100000000000000000000000000000000000000000000000000000000000000.0 | +| NULL | NULL | NULL | NULL | ++--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+--------------------------------------------------------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, 2)), sum(round(a, -61)), sum(round(a, -62)) from test.d64 group by id order by id ++--------------------------------------------------------------------+--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+ +| sum(a) | sum(round(a, 2)) | sum(round(a, -61)) | sum(round(a, -62)) | ++--------------------------------------------------------------------+--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+ +| 0.00 | 0.00 | 0 | 0 | +| 0.25 | 0.25 | 0 | 0 | +| -0.25 | -0.25 | 0 | 0 | +| 25.00 | 25.00 | 0 | 0 | +| -25.00 | -25.00 | 0 | 0 | +| 49999999999999999999999999999999999999999999999999999999999999.99 | 49999999999999999999999999999999999999999999999999999999999999.99 | 50000000000000000000000000000000000000000000000000000000000000 | 0 | +| 50000000000000000000000000000000000000000000000000000000000000.00 | 50000000000000000000000000000000000000000000000000000000000000.00 | 50000000000000000000000000000000000000000000000000000000000000 | 100000000000000000000000000000000000000000000000000000000000000 | +| -49999999999999999999999999999999999999999999999999999999999999.99 | -49999999999999999999999999999999999999999999999999999999999999.99 | -50000000000000000000000000000000000000000000000000000000000000 | 0 | +| -50000000000000000000000000000000000000000000000000000000000000.00 | -50000000000000000000000000000000000000000000000000000000000000.00 | -50000000000000000000000000000000000000000000000000000000000000 | -100000000000000000000000000000000000000000000000000000000000000 | +| 0.49 | 0.49 | 0 | 0 | +| 0.50 | 0.50 | 0 | 0 | +| -0.49 | -0.49 | 0 | 0 | +| -0.50 | -0.50 | 0 | 0 | +| 99999999999999999999999999999999999999999999999999999999999999.99 | 99999999999999999999999999999999999999999999999999999999999999.99 | 100000000000000000000000000000000000000000000000000000000000000 | 100000000000000000000000000000000000000000000000000000000000000 | +| -99999999999999999999999999999999999999999999999999999999999999.99 | -99999999999999999999999999999999999999999999999999999999999999.99 | -100000000000000000000000000000000000000000000000000000000000000 | -100000000000000000000000000000000000000000000000000000000000000 | +| NULL | NULL | NULL | NULL | ++--------------------------------------------------------------------+--------------------------------------------------------------------+------------------------------------------------------------------+------------------------------------------------------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(a), sum(round(a, -63)), sum(round(a, -100)) from test.d64 group by id order by id ++--------------------------------------------------------------------+--------------------+---------------------+ +| sum(a) | sum(round(a, -63)) | sum(round(a, -100)) | ++--------------------------------------------------------------------+--------------------+---------------------+ +| 0.00 | 0 | 0 | +| 0.25 | 0 | 0 | +| -0.25 | 0 | 0 | +| 25.00 | 0 | 0 | +| -25.00 | 0 | 0 | +| 49999999999999999999999999999999999999999999999999999999999999.99 | 0 | 0 | +| 50000000000000000000000000000000000000000000000000000000000000.00 | 0 | 0 | +| -49999999999999999999999999999999999999999999999999999999999999.99 | 0 | 0 | +| -50000000000000000000000000000000000000000000000000000000000000.00 | 0 | 0 | +| 0.49 | 0 | 0 | +| 0.50 | 0 | 0 | +| -0.49 | 0 | 0 | +| -0.50 | 0 | 0 | +| 99999999999999999999999999999999999999999999999999999999999999.99 | 0 | 0 | +| -99999999999999999999999999999999999999999999999999999999999999.99 | 0 | 0 | +| NULL | NULL | NULL | ++--------------------------------------------------------------------+--------------------+---------------------+ + +# const input, variable frac + +mysql> drop table if exists test.t +mysql> create table test.t (i bigint) +mysql> alter table test.t set tiflash replica 1 +mysql> insert into test.t values (-66), (-65), (-64), (-63), (-62), (-61), (-60), (-59), (-58), (-57), (-56), (-55), (-54), (-53), (-52), (-51), (-50), (-49), (-48), (-47), (-46), (-45), (-44), (-43), (-42), (-41), (-40), (-39), (-38), (-37), (-36), (-35), (-34), (-33), (-32), (-31), (-30), (-29), (-28), (-27), (-26), (-25), (-24), (-23), (-22), (-21), (-20), (-19), (-18), (-17), (-16), (-15), (-14), (-13), (-12), (-11), (-10), (-9), (-8), (-7), (-6), (-5), (-4), (-3), (-2), (-1), (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), (21), (22), (23), (24), (25), (26), (27), (28), (29), (30), (31), (9223372036854775807), (-9223372036854775808) +func> wait_table test t + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(null, i)) from test.t ++---------------------+ +| sum(round(null, i)) | ++---------------------+ +| NULL | ++---------------------+ + +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(-123456789123456789, i)) from test.t +#+------------------------------------+ +#| sum(round(-123456789123456789, i)) | +#+------------------------------------+ +#| -6145515733034404627 | +#+------------------------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(123456789123456789.123456789123456789, i)) from test.t ++------------------------------------------------------+ +| sum(round(123456789123456789.123456789123456789, i)) | ++------------------------------------------------------+ +| 6145515733034404630.923293528812182425 | ++------------------------------------------------------+ + +# variable input & frac + +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.i32 cross join test.t where a >= 0 +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| 132001391875 | +#+------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.i32 cross join test.t where a < 0 +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| -132001391908 | +#+------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.u32 cross join test.t where a is null +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| NULL | +#+------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.u32 cross join test.t +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| 222093792609 | +#+------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.i64 cross join test.t where a >= 0 +#+----------------------+ +#| sum(round(a, i)) | +#+----------------------+ +#| 51000000000000000274 | +#+----------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.i64 cross join test.t where a < 0 +#+-----------------------+ +#| sum(round(a, i)) | +#+-----------------------+ +#| -51000000000000000274 | +#+-----------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.u64 cross join test.t where a is null +#+------------------+ +#| sum(round(a, i)) | +#+------------------+ +#| NULL | +#+------------------+ +# +#mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.u64 cross join test.t +#+-----------------------+ +#| sum(round(a, i)) | +#+-----------------------+ +#| 520000000000000000274 | +#+-----------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.d9 cross join test.t where a >= 0 ++------------------+ +| sum(round(a, i)) | ++------------------+ +| 7600866.2413 | ++------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.d9 cross join test.t where a < 0 ++------------------+ +| sum(round(a, i)) | ++------------------+ +| -7600866.2413 | ++------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.d64 cross join test.t where a >= 0 ++----------------------------------------------------------------------+ +| sum(round(a, i)) | ++----------------------------------------------------------------------+ +| 19000000000000000000000000000000000000000000000000000000000000895.12 | ++----------------------------------------------------------------------+ + +mysql> set @@session.tidb_isolation_read_engines='tiflash'; select sum(round(a, i)) from test.d64 cross join test.t where a < 0 ++-----------------------------------------------------------------------+ +| sum(round(a, i)) | ++-----------------------------------------------------------------------+ +| -19000000000000000000000000000000000000000000000000000000000000895.12 | ++-----------------------------------------------------------------------+ From 484d9ef9bb177dd2e2cf091b1d7def3094a6742c Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Wed, 18 Aug 2021 18:58:00 +0800 Subject: [PATCH 2/3] Fix tiflash storage metrics problem (#2646) --- dbms/src/Storages/PathCapacityMetrics.cpp | 84 +++++--- dbms/src/Storages/PathCapacityMetrics.h | 22 ++- dbms/src/Storages/PathPool.cpp | 2 +- dbms/src/Storages/PathPool.h | 1 - .../Storages/tests/TiFlashStorageTestBasic.h | 8 +- dbms/src/Storages/tests/gtest_path_pool.cpp | 180 +++++++++++++++++- 6 files changed, 256 insertions(+), 41 deletions(-) diff --git a/dbms/src/Storages/PathCapacityMetrics.cpp b/dbms/src/Storages/PathCapacityMetrics.cpp index 8454cf81399..dae7f901922 100644 --- a/dbms/src/Storages/PathCapacityMetrics.cpp +++ b/dbms/src/Storages/PathCapacityMetrics.cpp @@ -92,40 +92,81 @@ void PathCapacityMetrics::freeUsedSize(std::string_view file_path, size_t used_b path_infos[path_idx].used_bytes -= used_bytes; } -FsStats PathCapacityMetrics::getFsStats() const +std::map PathCapacityMetrics::getDiskStats() { - /// Note that some disk usage is not count by this function. - /// - kvstore <-- can be resolved if we use PageStorage instead of stable::PageStorage for `RegionPersister` later - /// - proxy's data - /// This function only report approximate used size and available size, - /// and we limit available size by first path. It is good enough for now. + std::map disk_stats_map; + for (size_t i = 0; i < path_infos.size(); ++i) + { + struct statvfs vfs; + FsStats path_stat; + + std::tie(path_stat, vfs) = path_infos[i].getStats(log); + if (!path_stat.ok) + { + // Disk may be hot remove, Ignore this disk. + continue; + } + + auto entry = disk_stats_map.find(vfs.f_fsid); + if (entry == disk_stats_map.end()) + { + disk_stats_map.insert(std::pair(vfs.f_fsid, {vfs, {path_stat}})); + } + else + { + entry->second.path_stats.emplace_back(path_stat); + } + } + return disk_stats_map; +} +FsStats PathCapacityMetrics::getFsStats() +{ // Now we assume the size of `path_infos` will not change, don't acquire heavy lock on `path_infos`. FsStats total_stat{}; - for (size_t i = 0; i < path_infos.size(); ++i) + + // Build the disk stats map + // which use to measure single disk capacoty and available size + auto disk_stats_map = getDiskStats(); + + for (auto fs_it = disk_stats_map.begin(); fs_it != disk_stats_map.end(); ++fs_it) { - FsStats path_stat = path_infos[i].getStats(log); - if (!path_stat.ok) + FsStats disk_stat{}; + + auto & disk_stat_vec = fs_it->second; + auto & vfs_info = disk_stat_vec.vfs_info; + + for (const auto & single_path_stats : disk_stat_vec.path_stats) { - LOG_WARNING(log, "Can not get path_stat for path: " << path_infos[i].path); - return total_stat; + disk_stat.capacity_size += single_path_stats.capacity_size; + disk_stat.used_size += single_path_stats.used_size; + disk_stat.avail_size += single_path_stats.avail_size; } + const uint64_t disk_capacity_size = vfs_info.f_blocks * vfs_info.f_frsize; + if (disk_stat.capacity_size == 0 || disk_capacity_size < disk_stat.capacity_size) + disk_stat.capacity_size = disk_capacity_size; + + // Calutate single disk info + const uint64_t disk_free_bytes = vfs_info.f_bavail * vfs_info.f_frsize; + disk_stat.avail_size = std::min(disk_free_bytes, disk_stat.avail_size); + // sum of all path's capacity and used_size - total_stat.capacity_size += path_stat.capacity_size; - total_stat.used_size += path_stat.used_size; + total_stat.capacity_size += disk_stat.capacity_size; + total_stat.used_size += disk_stat.used_size; + total_stat.avail_size += disk_stat.avail_size; } // If user set quota on the global quota, set the capacity to the quota. if (capacity_quota != 0 && capacity_quota < total_stat.capacity_size) + { total_stat.capacity_size = capacity_quota; + total_stat.avail_size = std::min(total_stat.avail_size, total_stat.capacity_size - total_stat.used_size); + } // PD get weird if used_size == 0, make it 1 byte at least total_stat.used_size = std::max(1, total_stat.used_size); - // avail size - total_stat.avail_size = total_stat.capacity_size - total_stat.used_size; - const double avail_rate = 1.0 * total_stat.avail_size / total_stat.capacity_size; // Default threshold "schedule.low-space-ratio" in PD is 0.8, log warning message if avail ratio is low. if (avail_rate <= 0.2) @@ -143,13 +184,13 @@ FsStats PathCapacityMetrics::getFsStats() const return total_stat; } -FsStats PathCapacityMetrics::getFsStatsOfPath(std::string_view file_path) const +std::tuple PathCapacityMetrics::getFsStatsOfPath(std::string_view file_path) const { ssize_t path_idx = locatePath(file_path); if (unlikely(path_idx == INVALID_INDEX)) { LOG_ERROR(log, "Can not locate path in getFsStatsOfPath. File: " + String(file_path)); - return FsStats{}; + return {FsStats{}, {}}; } return path_infos[path_idx].getStats(nullptr); @@ -188,7 +229,7 @@ ssize_t PathCapacityMetrics::locatePath(std::string_view file_path) const return max_match_index; } -FsStats PathCapacityMetrics::CapacityInfo::getStats(Poco::Logger * log) const +std::tuple PathCapacityMetrics::CapacityInfo::getStats(Poco::Logger * log) const { FsStats res{}; /// Get capacity, used, available size for one path. @@ -198,7 +239,7 @@ FsStats PathCapacityMetrics::CapacityInfo::getStats(Poco::Logger * log) const if (int code = statvfs(path.data(), &vfs); code != 0) { LOG_ERROR(log, "Could not calculate available disk space (statvfs) of path: " << path << ", errno: " << errno); - return res; + return {}; } // capacity is limited by the actual disk capacity @@ -228,8 +269,7 @@ FsStats PathCapacityMetrics::CapacityInfo::getStats(Poco::Logger * log) const res.avail_size = avail; res.ok = 1; - return res; + return {res, vfs}; } - } // namespace DB diff --git a/dbms/src/Storages/PathCapacityMetrics.h b/dbms/src/Storages/PathCapacityMetrics.h index 67e693bb247..fab6e4dcfa3 100644 --- a/dbms/src/Storages/PathCapacityMetrics.h +++ b/dbms/src/Storages/PathCapacityMetrics.h @@ -11,9 +11,15 @@ namespace DB { class PathCapacityMetrics; using PathCapacityMetricsPtr = std::shared_ptr; - +using FSID = UInt32; struct FsStats; +struct DiskCapacity +{ + struct statvfs vfs_info = {}; + std::vector path_stats; +}; + class PathCapacityMetrics : private boost::noncopyable { public: @@ -21,13 +27,17 @@ class PathCapacityMetrics : private boost::noncopyable const Strings & main_paths_, const std::vector main_capacity_quota_, // const Strings & latest_paths_, const std::vector latest_capacity_quota_); + virtual ~PathCapacityMetrics(){}; + void addUsedSize(std::string_view file_path, size_t used_bytes); void freeUsedSize(std::string_view file_path, size_t used_bytes); - FsStats getFsStats() const; + FsStats getFsStats(); + + virtual std::map getDiskStats(); - FsStats getFsStatsOfPath(std::string_view file_path) const; + std::tuple getFsStatsOfPath(std::string_view file_path) const; #ifndef DBMS_PUBLIC_GTEST private: @@ -49,13 +59,11 @@ class PathCapacityMetrics : private boost::noncopyable // Used bytes for this path std::atomic used_bytes = 0; - FsStats getStats(Poco::Logger * log) const; + std::tuple getStats(Poco::Logger * log) const; CapacityInfo() = default; CapacityInfo(String p, uint64_t c) : path(std::move(p)), capacity_bytes(c) {} - CapacityInfo(const CapacityInfo & rhs) - : path(rhs.path), capacity_bytes(rhs.capacity_bytes), used_bytes(rhs.used_bytes.load()) - {} + CapacityInfo(const CapacityInfo & rhs) : path(rhs.path), capacity_bytes(rhs.capacity_bytes), used_bytes(rhs.used_bytes.load()) {} }; // Max quota bytes can be use for this TiFlash instance. diff --git a/dbms/src/Storages/PathPool.cpp b/dbms/src/Storages/PathPool.cpp index 3b991da0b6c..a086e3c4ff4 100644 --- a/dbms/src/Storages/PathPool.cpp +++ b/dbms/src/Storages/PathPool.cpp @@ -279,7 +279,7 @@ String genericChoosePath(const std::vector & paths, const PathCapacityMetrics std::vector stats; for (size_t i = 0; i < paths.size(); ++i) { - stats.emplace_back(global_capacity->getFsStatsOfPath(paths[i].path)); + stats.emplace_back(std::get<0>(global_capacity->getFsStatsOfPath(paths[i].path))); total_available_size += stats.back().avail_size; } diff --git a/dbms/src/Storages/PathPool.h b/dbms/src/Storages/PathPool.h index 587a3b7dcc4..3bd79329462 100644 --- a/dbms/src/Storages/PathPool.h +++ b/dbms/src/Storages/PathPool.h @@ -33,7 +33,6 @@ class PSDiskDelegatorMulti; class PSDiskDelegatorSingle; class PSDiskDelegatorRaft; - /// A class to manage global paths. class PathPool { diff --git a/dbms/src/Storages/tests/TiFlashStorageTestBasic.h b/dbms/src/Storages/tests/TiFlashStorageTestBasic.h index 01a3653c2ea..5442cd4faef 100644 --- a/dbms/src/Storages/tests/TiFlashStorageTestBasic.h +++ b/dbms/src/Storages/tests/TiFlashStorageTestBasic.h @@ -53,7 +53,7 @@ class TiFlashStorageTestBasic : public ::testing::Test } protected: - void dropDataOnDisk(String path) + void dropDataOnDisk(const String & path) { if (Poco::File file(path); file.exists()) { @@ -61,6 +61,12 @@ class TiFlashStorageTestBasic : public ::testing::Test } } + void createIfNotExist(const String & path) + { + if (Poco::File file(path); !file.exists()) + file.createDirectories(); + } + void SetUp() override { dropDataOnDisk(getTemporaryPath()); diff --git a/dbms/src/Storages/tests/gtest_path_pool.cpp b/dbms/src/Storages/tests/gtest_path_pool.cpp index 119f1293a09..b0f26aa679b 100644 --- a/dbms/src/Storages/tests/gtest_path_pool.cpp +++ b/dbms/src/Storages/tests/gtest_path_pool.cpp @@ -3,8 +3,10 @@ #include #include #include -#include +#include #include +#include + namespace DB { @@ -63,7 +65,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -96,7 +98,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -130,7 +132,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -164,7 +166,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -207,7 +209,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -240,7 +242,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -274,7 +276,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -308,7 +310,7 @@ try for (size_t i = 0; i < res.size(); ++i) { - auto stat = ctx.getPathCapacity()->getFsStatsOfPath(res[i]); + auto stat = std::get<0>(ctx.getPathCapacity()->getFsStatsOfPath(res[i])); LOG_INFO(log, "[path=" << res[i] << "] [used_size=" << stat.used_size << "]"); } @@ -321,5 +323,165 @@ try } CATCH +class MockPathCapacityMetrics : public PathCapacityMetrics +{ +public: + MockPathCapacityMetrics(const size_t capacity_quota_, const Strings & main_paths_, const std::vector main_capacity_quota_, // + const Strings & latest_paths_, const std::vector latest_capacity_quota_) + : PathCapacityMetrics(capacity_quota_, main_paths_, main_capacity_quota_, latest_paths_, latest_capacity_quota_) + {} + + std::map getDiskStats() override { return disk_stats_map; } + + void setDiskStats(std::map & disk_stats_map_) { disk_stats_map = disk_stats_map_; } + +private: + std::map disk_stats_map; +}; + +class PathCapcatity : public DB::base::TiFlashStorageTestBasic +{ + void SetUp() override + { + TiFlashStorageTestBasic::SetUp(); + if (int code = statvfs(".", &vfs_info); code != 0) + { + FAIL() << "statvfs failed."; + } + + main_data_path = getTemporaryPath() + "/main"; + createIfNotExist(main_data_path); + + lastest_data_path = getTemporaryPath() + "/lastest"; + createIfNotExist(lastest_data_path); + } + + void TearDown() override + { + dropDataOnDisk(main_data_path); + dropDataOnDisk(lastest_data_path); + TiFlashStorageTestBasic::TearDown(); + } + +protected: + struct statvfs vfs_info; + std::string main_data_path; + std::string lastest_data_path; +}; + +TEST_F(PathCapcatity, SingleDiskSinglePathTest) +{ + size_t capactity = 100; + size_t used = 10; + + ASSERT_GE(vfs_info.f_bavail * vfs_info.f_frsize, capactity * 2); + + // Single disk with single path + { + auto capacity = PathCapacityMetrics(0, {main_data_path}, {capactity}, {lastest_data_path}, {capactity}); + + capacity.addUsedSize(main_data_path, used); + auto stats = capacity.getFsStats(); + ASSERT_EQ(stats.capacity_size, capactity * 2); + ASSERT_EQ(stats.used_size, used); + ASSERT_EQ(stats.avail_size, capactity * 2 - used); + + auto main_path_stats = std::get<0>(capacity.getFsStatsOfPath(main_data_path)); + ASSERT_EQ(main_path_stats.capacity_size, capactity); + ASSERT_EQ(main_path_stats.used_size, used); + ASSERT_EQ(main_path_stats.avail_size, capactity - used); + + auto lastest_path_stats = std::get<0>(capacity.getFsStatsOfPath(lastest_data_path)); + ASSERT_EQ(lastest_path_stats.capacity_size, capactity); + ASSERT_EQ(lastest_path_stats.used_size, 0); + ASSERT_EQ(lastest_path_stats.avail_size, capactity); + } + + // Single disk with multi path + { + String main_data_path1 = getTemporaryPath() + "/main1"; + createIfNotExist(main_data_path1); + String lastest_data_path1 = getTemporaryPath() + "/lastest1"; + createIfNotExist(lastest_data_path1); + + // Not use the capacity limit + auto capacity = PathCapacityMetrics(0, {main_data_path, main_data_path1}, {capactity * 2, capactity * 2}, + {lastest_data_path, lastest_data_path1}, {capactity, capactity}); + + capacity.addUsedSize(main_data_path, used); + capacity.addUsedSize(main_data_path1, used); + capacity.addUsedSize(lastest_data_path, used); + + auto stats = capacity.getFsStats(); + ASSERT_EQ(stats.capacity_size, capactity * 6); + ASSERT_EQ(stats.used_size, 3 * used); + ASSERT_EQ(stats.avail_size, capactity * 6 - (3 * used)); + + dropDataOnDisk(main_data_path1); + dropDataOnDisk(lastest_data_path1); + } +} + +TEST_F(PathCapcatity, MultiDiskMultiPathTest) +{ + MockPathCapacityMetrics capacity = MockPathCapacityMetrics(0, {main_data_path}, {100}, {lastest_data_path}, {100}); + + std::map disk_capacity_map; + + /// disk 1 : + /// - disk status: + /// - total size = 100 * 1 + /// - avail size = 50 * 1 + /// - path status: + /// - path1: + /// - capacity size : 100 + /// - used size : 4 + /// - avail size : 50 // min(capacity size - used size, disk avail size); + /// - path2: + /// - capacity size : 1000 + /// - used size : 12 + /// - avail size : 50 // min(capacity size - used size, disk avail size); + struct statvfs fake_vfs = {}; + fake_vfs.f_blocks = 100; + fake_vfs.f_bavail = 50; + fake_vfs.f_frsize = 1; + + disk_capacity_map[100] = {.vfs_info = fake_vfs, + .path_stats = { + {.used_size = 4, .avail_size = 50, .capacity_size = 100, .ok = 1}, + {.used_size = 12, .avail_size = 50, .capacity_size = 1000, .ok = 1}, + }}; + capacity.setDiskStats(disk_capacity_map); + FsStats total_stats = capacity.getFsStats(); + ASSERT_EQ(total_stats.capacity_size, 100); + ASSERT_EQ(total_stats.used_size, 16); + ASSERT_EQ(total_stats.avail_size, 50); + + /// disk 2: + /// - disk status: + /// - total size = 100 * 1 + /// - avail size = 50 * 1 + /// - path status: + /// - path1: + /// - capacity size : 48 + /// - used size : 40 + /// - avail size : 8 // min(capacity size - used size, disk avail size); + /// - path2: + /// - capacity size : 50 + /// - used size : 12 + /// - avail size : 38 // min(capacity size - used size, disk avail size); + disk_capacity_map[101] = {.vfs_info = fake_vfs, + .path_stats = { + {.used_size = 40, .avail_size = 8, .capacity_size = 48, .ok = 1}, + {.used_size = 12, .avail_size = 38, .capacity_size = 50, .ok = 1}, + }}; + capacity.setDiskStats(disk_capacity_map); + + total_stats = capacity.getFsStats(); + ASSERT_EQ(total_stats.capacity_size, 100 + 98); + ASSERT_EQ(total_stats.used_size, 16 + 52); + ASSERT_EQ(total_stats.avail_size, 50 + 46); +} + } // namespace tests } // namespace DB From 00f985c3ca4f05f20b4529c2ffa6316b8011adfd Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Thu, 19 Aug 2021 22:42:01 +0800 Subject: [PATCH 3/3] Fix darwin crc64 compiler (#2721) --- CMakeLists.txt | 12 +++-- libs/libcommon/CMakeLists.txt | 1 + libs/libcommon/include/common/crc64.h | 53 ++----------------- libs/libcommon/include/common/crc64_fast.h | 17 +++---- libs/libcommon/src/crc64.cpp | 59 ++++++++++++++++++++++ libs/libcommon/src/crc64_avx2.cpp | 6 ++- libs/libcommon/src/crc64_avx512.cpp | 6 ++- libs/libcommon/src/crc64_sse2_asimd.cpp | 8 ++- 8 files changed, 95 insertions(+), 67 deletions(-) create mode 100644 libs/libcommon/src/crc64.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 21d5407eda7..3a68e104b69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,10 +361,14 @@ if (ENABLE_TESTS) enable_testing() endif () -include(CheckCXXCompilerFlag) -check_cxx_compiler_flag("-mvpclmulqdq -Werror -Wall -Wextra" TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) -if(TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) - add_definitions(-DTIFLASH_COMPILER_VPCLMULQDQ_SUPPORT=1) +if (ARCH_AMD64) + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("-mvpclmulqdq -Werror -Wall -Wextra" TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) + if(TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) + add_definitions(-DTIFLASH_COMPILER_VPCLMULQDQ_SUPPORT=1) + else() + add_definitions(-DTIFLASH_COMPILER_VPCLMULQDQ_SUPPORT=0) + endif() else() add_definitions(-DTIFLASH_COMPILER_VPCLMULQDQ_SUPPORT=0) endif() diff --git a/libs/libcommon/CMakeLists.txt b/libs/libcommon/CMakeLists.txt index 65055732bbe..e381189452e 100644 --- a/libs/libcommon/CMakeLists.txt +++ b/libs/libcommon/CMakeLists.txt @@ -35,6 +35,7 @@ add_library (common ${SPLIT_SHARED} src/crc64_sse2_asimd.cpp src/crc64_avx2.cpp src/crc64_avx512.cpp + src/crc64.cpp src/simd.cpp include/common/types.h diff --git a/libs/libcommon/include/common/crc64.h b/libs/libcommon/include/common/crc64.h index 843de31934d..700e3c74154 100644 --- a/libs/libcommon/include/common/crc64.h +++ b/libs/libcommon/include/common/crc64.h @@ -1,7 +1,6 @@ #pragma once -#include -#include -#include +#include +#include namespace crc64 { enum class Mode @@ -15,53 +14,7 @@ enum class Mode class Digest { public: - explicit Digest(Mode mode = Mode::Auto) - { - // clang-format off - using namespace simd_option; -#if TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT - if ((mode == Mode::Auto || mode >= Mode::SIMD_512) && ENABLE_AVX512 - && __builtin_cpu_supports("vpclmulqdq") && __builtin_cpu_supports("avx512dq")) - { - update_fn = [](uint64_t _state, const void * _src, size_t _length) { - return crc64::_detail::update_fast<512>(crc64::_detail::update_vpclmulqdq_avx512, _state, _src, _length); - }; - } - else if ((mode == Mode::Auto || mode >= Mode::SIMD_256) && ENABLE_AVX - && __builtin_cpu_supports("vpclmulqdq") && __builtin_cpu_supports("avx2")) - { - update_fn = [](uint64_t _state, const void * _src, size_t _length) { - return crc64::_detail::update_fast<256>(crc64::_detail::update_vpclmulqdq_avx2, _state, _src, _length); - }; - } - else -#endif -#if __SSE2__ || defined(TIFLASH_ENABLE_ASIMD_SUPPORT) - if (mode == Mode::Auto || mode >= Mode::SIMD_128) - { - update_fn = [](uint64_t _state, const void * _src, size_t _length) { - return crc64::_detail::update_fast(crc64::_detail::update_simd, _state, _src, _length); - }; -#ifdef TIFLASH_ENABLE_ASIMD_SUPPORT - if (!ENABLE_ASIMD || !SIMDRuntimeSupport(SIMDFeature::pmull)) - { - update_fn = _detail::update_table; - } -#endif -#if __SSE2__ - if (!__builtin_cpu_supports("pclmul")) - { - update_fn = _detail::update_table; - } -#endif - } - else // NOLINT(readability-misleading-indentation) -#endif - { - update_fn = _detail::update_table; - } - // clang-format on - }; + explicit Digest(Mode mode = Mode::Auto); void update(const void * src, size_t length) { state = update_fn(state, src, length); } diff --git a/libs/libcommon/include/common/crc64_fast.h b/libs/libcommon/include/common/crc64_fast.h index 6ed5430af4c..5d712da404d 100644 --- a/libs/libcommon/include/common/crc64_fast.h +++ b/libs/libcommon/include/common/crc64_fast.h @@ -3,21 +3,19 @@ namespace crc64::_detail { -#if __SSE2__ || defined(TIFLASH_ENABLE_ASIMD_SUPPORT) -uint64_t update_simd(uint64_t state, const void * src, size_t length); -#endif - +#if defined(TIFLASH_ENABLE_ASIMD_SUPPORT) || __SSE2__ +#define TIFLASH_CRC64_HAS_SIMD_SUPPORT +// avx2 and avx512 variants #if TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT - #ifdef TIFLASH_ENABLE_AVX512_SUPPORT extern uint64_t update_vpclmulqdq_avx512(uint64_t state, const void * src, size_t length); -#endif - +#endif // TIFLASH_ENABLE_AVX512_SUPPORT #ifdef TIFLASH_ENABLE_AVX_SUPPORT extern uint64_t update_vpclmulqdq_avx2(uint64_t state, const void * src, size_t length); -#endif +#endif // TIFLASH_ENABLE_AVX_SUPPORT +#endif // TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT -#endif +uint64_t update_simd(uint64_t state, const void * src, size_t length); template static inline uint64_t update_fast(Fn func, uint64_t state, const void * src, size_t length) @@ -51,5 +49,6 @@ static inline uint64_t update_fast(Fn func, uint64_t state, const void * src, si } return state; } +#endif } // namespace crc64::_detail \ No newline at end of file diff --git a/libs/libcommon/src/crc64.cpp b/libs/libcommon/src/crc64.cpp new file mode 100644 index 00000000000..ef073f85dac --- /dev/null +++ b/libs/libcommon/src/crc64.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +namespace crc64 +{ + +Digest::Digest(Mode mode) +{ + // clang-format off +#ifdef TIFLASH_CRC64_HAS_SIMD_SUPPORT + using namespace simd_option; +#if TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT +#ifdef TIFLASH_ENABLE_AVX512_SUPPORT + if ((mode == Mode::Auto || mode >= Mode::SIMD_512) && ENABLE_AVX512 + && __builtin_cpu_supports("vpclmulqdq") && __builtin_cpu_supports("avx512dq")) + { + update_fn = [](uint64_t _state, const void * _src, size_t _length) { + return crc64::_detail::update_fast<512>(crc64::_detail::update_vpclmulqdq_avx512, _state, _src, _length); + }; + } + else +#endif // TIFLASH_ENABLE_AVX512_SUPPORT +#ifdef TIFLASH_ENABLE_AVX_SUPPORT + if ((mode == Mode::Auto || mode >= Mode::SIMD_256) && ENABLE_AVX + && __builtin_cpu_supports("vpclmulqdq") && __builtin_cpu_supports("avx2")) + { + update_fn = [](uint64_t _state, const void * _src, size_t _length) { + return crc64::_detail::update_fast<256>(crc64::_detail::update_vpclmulqdq_avx2, _state, _src, _length); + }; + } + else +#endif // TIFLASH_ENABLE_AVX_SUPPORT +#endif // TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT + if (mode == Mode::Auto || mode >= Mode::SIMD_128) + { + update_fn = [](uint64_t _state, const void * _src, size_t _length) { + return crc64::_detail::update_fast(crc64::_detail::update_simd, _state, _src, _length); + }; +#ifdef TIFLASH_ENABLE_ASIMD_SUPPORT + if (!ENABLE_ASIMD || !SIMDRuntimeSupport(SIMDFeature::pmull)) + { + update_fn = _detail::update_table; + } +#else // must be SSE case then + if (!__builtin_cpu_supports("pclmul")) + { + update_fn = _detail::update_table; + } +#endif // TIFLASH_ENABLE_ASIMD_SUPPORT + } + else +#endif // TIFLASH_CRC64_HAS_SIMD_SUPPORT + { + update_fn = _detail::update_table; + } + // clang-format on +} +} // namespace crc64 diff --git a/libs/libcommon/src/crc64_avx2.cpp b/libs/libcommon/src/crc64_avx2.cpp index 386062a9d1d..9d01cd7d8bb 100644 --- a/libs/libcommon/src/crc64_avx2.cpp +++ b/libs/libcommon/src/crc64_avx2.cpp @@ -1,6 +1,10 @@ -#if defined(TIFLASH_ENABLE_AVX_SUPPORT) && TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT +#include +#if defined(TIFLASH_CRC64_HAS_SIMD_SUPPORT) && defined(TIFLASH_ENABLE_AVX_SUPPORT) && TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT #include #include +#include + +#include namespace crc64::_detail { using avx256_t = __m256i; diff --git a/libs/libcommon/src/crc64_avx512.cpp b/libs/libcommon/src/crc64_avx512.cpp index 1acd2d90017..04bfeca668f 100644 --- a/libs/libcommon/src/crc64_avx512.cpp +++ b/libs/libcommon/src/crc64_avx512.cpp @@ -1,6 +1,10 @@ -#if defined(TIFLASH_ENABLE_AVX512_SUPPORT) && TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT +#include +#if defined(TIFLASH_CRC64_HAS_SIMD_SUPPORT) && defined(TIFLASH_ENABLE_AVX512_SUPPORT) && TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT #include #include +#include + +#include namespace crc64::_detail { using avx512_t = __m512i; diff --git a/libs/libcommon/src/crc64_sse2_asimd.cpp b/libs/libcommon/src/crc64_sse2_asimd.cpp index 2ed72e011bb..17d00cb2a3e 100644 --- a/libs/libcommon/src/crc64_sse2_asimd.cpp +++ b/libs/libcommon/src/crc64_sse2_asimd.cpp @@ -1,4 +1,8 @@ -#if __SSE2__ || defined(TIFLASH_ENABLE_ASIMD_SUPPORT) +#include +#include + +#ifdef TIFLASH_CRC64_HAS_SIMD_SUPPORT + #include #if __SSE2__ #include @@ -65,4 +69,4 @@ uint64_t update_simd(uint64_t state, const void * src, size_t length) return acc.fold8(K_127).barrett(POLY, MU); } } // namespace crc64::_detail -#endif \ No newline at end of file +#endif // TIFLASH_CRC64_HAS_SIMD_SUPPORT \ No newline at end of file