diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index b249d2db136..6fbe8537853 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -53,7 +53,8 @@ bool isValidSeperator(char c, int previous_parts) if (isPunctuation(c)) return true; - return previous_parts == 2 && (c == ' ' || c == 'T'); + // for https://github.com/pingcap/tics/issues/4036 + return previous_parts == 2 && (c == 'T' || isWhitespaceASCII(c)); } std::vector parseDateFormat(String format) @@ -496,8 +497,8 @@ Field parseMyDateTime(const String & str, int8_t fsp) bool truncated_or_incorrect = false; - // noAbsorb tests if can absorb FSP or TZ - auto noAbsorb = [](const std::vector & seps) { + // no_absorb tests if can absorb FSP or TZ + auto no_absorb = [](const std::vector & seps) { // if we have more than 5 parts (i.e. 6), the tailing part can't be absorbed // or if we only have 1 part, but its length is longer than 4, then it is at least YYMMD, in this case, FSP can // not be absorbed, and it will be handled later, and the leading sign prevents TZ from being absorbed, because @@ -507,7 +508,7 @@ Field parseMyDateTime(const String & str, int8_t fsp) if (!frac_str.empty()) { - if (!noAbsorb(seps)) + if (!no_absorb(seps)) { seps.push_back(frac_str); frac_str = ""; @@ -518,7 +519,11 @@ Field parseMyDateTime(const String & str, int8_t fsp) { // if tz_sign is empty, it's sure that the string literal contains timezone (e.g., 2010-10-10T10:10:10Z), // therefore we could safely skip this branch. +<<<<<<< HEAD if (!noAbsorb(seps) && !(tz_minute != "" && tz_sep == "")) +======= + if (!no_absorb(seps) && !(!tz_minute.empty() && tz_sep.empty())) +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) { // we can't absorb timezone if there is no separate between tz_hour and tz_minute if (!tz_hour.empty()) @@ -535,6 +540,7 @@ Field parseMyDateTime(const String & str, int8_t fsp) switch (seps.size()) { +<<<<<<< HEAD // No delimiter case 1: { @@ -643,38 +649,173 @@ Field parseMyDateTime(const String & str, int8_t fsp) { throw TiFlashException("Datetime truncated: " + str, Errors::Types::Truncated); } +======= + // No delimiter + case 1: + { + size_t l = seps[0].size(); + switch (l) + { + case 14: // YYYYMMDDHHMMSS + { + std::sscanf(seps[0].c_str(), "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second); //NOLINT + hhmmss = true; + break; + } + case 12: // YYMMDDHHMMSS + { + std::sscanf(seps[0].c_str(), "%2d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second); //NOLINT + year = adjustYear(year); + hhmmss = true; + break; + } + case 11: // YYMMDDHHMMS + { + std::sscanf(seps[0].c_str(), "%2d%2d%2d%2d%2d%1d", &year, &month, &day, &hour, &minute, &second); //NOLINT + year = adjustYear(year); + hhmmss = true; + break; + } + case 10: // YYMMDDHHMM + { + std::sscanf(seps[0].c_str(), "%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute); //NOLINT + year = adjustYear(year); +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) break; } case 3: { +<<<<<<< HEAD // YYYY-MM-DD scanTimeArgs(seps, {&year, &month, &day}); +======= + std::sscanf(seps[0].c_str(), "%2d%2d%2d%2d%1d", &year, &month, &day, &hour, &minute); //NOLINT + year = adjustYear(year); +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) break; } case 4: { +<<<<<<< HEAD // YYYY-MM-DD HH scanTimeArgs(seps, {&year, &month, &day, &hour}); +======= + std::sscanf(seps[0].c_str(), "%4d%2d%2d", &year, &month, &day); //NOLINT +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) break; } case 5: { +<<<<<<< HEAD // YYYY-MM-DD HH-MM scanTimeArgs(seps, {&year, &month, &day, &hour, &minute}); +======= + std::sscanf(seps[0].c_str(), "%2d%2d%2d%1d", &year, &month, &day, &hour); //NOLINT + year = adjustYear(year); +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) break; } case 6: { +<<<<<<< HEAD // We don't have fractional seconds part. // YYYY-MM-DD HH-MM-SS scanTimeArgs(seps, {&year, &month, &day, &hour, &minute, &second}); hhmmss = true; +======= + std::sscanf(seps[0].c_str(), "%2d%2d%2d", &year, &month, &day); //NOLINT + year = adjustYear(year); +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) break; } default: { throw Exception("Wrong datetime format"); } +<<<<<<< HEAD +======= + if (l == 5 || l == 6 || l == 8) + { + // YYMMDD or YYYYMMDD + // We must handle float => string => datetime, the difference is that fractional + // part of float type is discarded directly, while fractional part of string type + // is parsed to HH:MM:SS. + int ret = 0; + switch (frac_str.size()) + { + case 0: + ret = 1; + is_date = true; + break; + case 1: + case 2: + { + ret = std::sscanf(frac_str.c_str(), "%2d ", &hour); //NOLINT + break; + } + case 3: + case 4: + { + ret = std::sscanf(frac_str.c_str(), "%2d%2d ", &hour, &minute); //NOLINT + break; + } + default: + { + ret = std::sscanf(frac_str.c_str(), "%2d%2d%2d ", &hour, &minute, &second); //NOLINT + break; + } + } + truncated_or_incorrect = (ret == 0); + } + if (l == 9 || l == 10) + { + if (frac_str.empty()) + { + second = 0; + } + else + { + truncated_or_incorrect = (std::sscanf(frac_str.c_str(), "%2d ", &second) == 0); //NOLINT + } + } + if (truncated_or_incorrect) + { + throw TiFlashException("Datetime truncated: " + str, Errors::Types::Truncated); + } + break; + } + case 3: + { + // YYYY-MM-DD + scanTimeArgs(seps, {&year, &month, &day}); + is_date = true; + break; + } + case 4: + { + // YYYY-MM-DD HH + scanTimeArgs(seps, {&year, &month, &day, &hour}); + break; + } + case 5: + { + // YYYY-MM-DD HH-MM + scanTimeArgs(seps, {&year, &month, &day, &hour, &minute}); + break; + } + case 6: + { + // We don't have fractional seconds part. + // YYYY-MM-DD HH-MM-SS + scanTimeArgs(seps, {&year, &month, &day, &hour, &minute, &second}); + hhmmss = true; + break; + } + default: + { + throw Exception("Wrong datetime format"); + } +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) } // If str is sepereated by delimiters, the first one is year, and if the year is 2 digit, @@ -951,8 +1092,15 @@ void MyTimeBase::check(bool allow_zero_in_date, bool allow_invalid_date) const if (!allow_invalid_date) { constexpr static UInt8 max_days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +<<<<<<< HEAD static auto is_leap_year = [](UInt16 _year) { return ((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0); }; max_day = max_days_in_month[month - 1]; +======= + static auto is_leap_year = [](UInt16 _year) { + return ((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0); + }; + max_day = max_days_in_month[month - 1]; // NOLINT +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) if (month == 2 && is_leap_year(year)) { max_day = 29; @@ -1217,4 +1365,706 @@ MyDateTimeFormatter::MyDateTimeFormatter(const String & layout) } } +<<<<<<< HEAD +======= +struct MyDateTimeParser::Context +{ + // Some state for `mysqlTimeFix` + uint32_t state = 0; + static constexpr uint32_t ST_DAY_OF_YEAR = 0x01; + static constexpr uint32_t ST_MERIDIEM = 0x02; + static constexpr uint32_t ST_HOUR_0_23 = 0x04; + static constexpr uint32_t ST_HOUR_1_12 = 0x08; + + int32_t day_of_year = 0; + // 0 - invalid, 1 - am, 2 - pm + int32_t meridiem = 0; + + // The input string view + const StringRef view; + // The pos we are parsing from + size_t pos = 0; + + explicit Context(StringRef view_) + : view(std::move(view_)) + {} +}; + +// Try to parse digits with number of `limit` starting from view[pos] +// Return if success. +// Return <0, _> if fail. +static std::tuple parseNDigits(const StringRef & view, const size_t pos, const size_t limit) +{ + size_t step = 0; + int32_t num = 0; + while (step < limit && (pos + step) < view.size && isNumericASCII(view.data[pos + step])) + { + num = num * 10 + (view.data[pos + step] - '0'); + step += 1; + } + return std::make_tuple(step, num); +} + +static std::tuple parseYearNDigits(const StringRef & view, const size_t pos, const size_t limit) +{ + // Try to parse a "year" within `limit` digits + size_t step = 0; + int32_t year = 0; + std::tie(step, year) = parseNDigits(view, pos, limit); + if (step == 0) + return std::make_tuple(step, 0); + else if (step <= 2) + year = adjustYear(year); + return std::make_tuple(step, year); +} + +enum class ParseState +{ + NORMAL = 0, // Parsing + FAIL = 1, // Fail to parse + END_OF_FILE = 2, // The end of input +}; + +//"%r": Time, 12-hour (hh:mm:ss followed by AM or PM) +static bool parseTime12Hour(MyDateTimeParser::Context & ctx, MyTimeBase & time) +{ + // Use temp_pos instead of changing `ctx.pos` directly in case of parsing failure + size_t temp_pos = ctx.pos; + auto check_if_end = [&temp_pos, &ctx]() -> ParseState { + // To the end + if (temp_pos == ctx.view.size) + return ParseState::END_OF_FILE; + return ParseState::NORMAL; + }; + auto skip_whitespaces = [&temp_pos, &ctx, &check_if_end]() -> ParseState { + while (temp_pos < ctx.view.size && isWhitespaceASCII(ctx.view.data[temp_pos])) + ++temp_pos; + return check_if_end(); + }; + auto parse_sep = [&temp_pos, &ctx, &skip_whitespaces]() -> ParseState { + if (skip_whitespaces() == ParseState::END_OF_FILE) + return ParseState::END_OF_FILE; + // parse ":" + if (ctx.view.data[temp_pos] != ':') + return ParseState::FAIL; + temp_pos += 1; // move forward + return ParseState::NORMAL; + }; + auto try_parse = [&]() -> ParseState { + ParseState state = ParseState::NORMAL; + /// Note that we should update `time` as soon as possible, or we + /// can not get correct result for incomplete input like "12:13" + /// that is less than "hh:mm:ssAM" + + // hh + size_t step = 0; + int32_t hour = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, hour) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || hour > 12 || hour == 0) + return ParseState::FAIL; + // Handle special case: 12:34:56 AM -> 00:34:56 + // For PM, we will add 12 it later + if (hour == 12) + hour = 0; + time.hour = hour; + temp_pos += step; // move forward + + if (state = parse_sep(); state != ParseState::NORMAL) + return state; + + int32_t minute = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, minute) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || minute > 59) + return ParseState::FAIL; + time.minute = minute; + temp_pos += step; // move forward + + if (state = parse_sep(); state != ParseState::NORMAL) + return state; + + int32_t second = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, second) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || second > 59) + return ParseState::FAIL; + time.second = second; + temp_pos += step; // move forward + + int meridiem = 0; // 0 - invalid, 1 - am, 2 - pm + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + // "AM"/"PM" must be parsed as a single element + // "11:13:56a" is an invalid input for "%r". + if (auto size_to_end = ctx.view.size - temp_pos; size_to_end < 2) + return ParseState::FAIL; + if (toLowerIfAlphaASCII(ctx.view.data[temp_pos]) == 'a') + meridiem = 1; + else if (toLowerIfAlphaASCII(ctx.view.data[temp_pos]) == 'p') + meridiem = 2; + + if (toLowerIfAlphaASCII(ctx.view.data[temp_pos + 1]) != 'm') + meridiem = 0; + switch (meridiem) + { + case 0: + return ParseState::FAIL; + case 1: + break; + case 2: + time.hour += 12; + break; + } + temp_pos += 2; // move forward + return ParseState::NORMAL; + }; + if (auto state = try_parse(); state == ParseState::FAIL) + return false; + // Other state, forward the `ctx.pos` and return true + ctx.pos = temp_pos; + return true; +} + +//"%T": Time, 24-hour (hh:mm:ss) +static bool parseTime24Hour(MyDateTimeParser::Context & ctx, MyTimeBase & time) +{ + // Use temp_pos instead of changing `ctx.pos` directly in case of parsing failure + size_t temp_pos = ctx.pos; + auto check_if_end = [&temp_pos, &ctx]() -> ParseState { + // To the end + if (temp_pos == ctx.view.size) + return ParseState::END_OF_FILE; + return ParseState::NORMAL; + }; + auto skip_whitespaces = [&temp_pos, &ctx, &check_if_end]() -> ParseState { + while (temp_pos < ctx.view.size && isWhitespaceASCII(ctx.view.data[temp_pos])) + ++temp_pos; + return check_if_end(); + }; + auto parse_sep = [&temp_pos, &ctx, &skip_whitespaces]() -> ParseState { + if (skip_whitespaces() == ParseState::END_OF_FILE) + return ParseState::END_OF_FILE; + // parse ":" + if (ctx.view.data[temp_pos] != ':') + return ParseState::FAIL; + temp_pos += 1; // move forward + return ParseState::NORMAL; + }; + auto try_parse = [&]() -> ParseState { + ParseState state = ParseState::NORMAL; + /// Note that we should update `time` as soon as possible, or we + /// can not get correct result for incomplete input like "12:13" + /// that is less than "hh:mm:ss" + + // hh + size_t step = 0; + int32_t hour = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, hour) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || hour > 23) + return ParseState::FAIL; + time.hour = hour; + temp_pos += step; // move forward + + if (state = parse_sep(); state != ParseState::NORMAL) + return state; + + int32_t minute = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, minute) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || minute > 59) + return ParseState::FAIL; + time.minute = minute; + temp_pos += step; // move forward + + if (state = parse_sep(); state != ParseState::NORMAL) + return state; + + int32_t second = 0; + if (state = skip_whitespaces(); state != ParseState::NORMAL) + return state; + std::tie(step, second) = parseNDigits(ctx.view, temp_pos, 2); + if (step == 0 || second > 59) + return ParseState::FAIL; + time.second = second; + temp_pos += step; // move forward + + return ParseState::NORMAL; + }; + if (auto state = try_parse(); state == ParseState::FAIL) + return false; + // Other state, forward the `ctx.pos` and return true + ctx.pos = temp_pos; + return true; +} + +// Refer: https://github.com/pingcap/tidb/blob/v5.0.1/types/time.go#L2946 +MyDateTimeParser::MyDateTimeParser(String format_) + : format(std::move(format_)) +{ + // Ignore all prefix white spaces (TODO: handle unicode space?) + size_t format_pos = 0; + while (format_pos < format.size() && isWhitespaceASCII(format[format_pos])) + format_pos++; + + bool in_pattern_match = false; + while (format_pos < format.size()) + { + char x = format[format_pos]; + if (in_pattern_match) + { + switch (x) + { + case 'b': + { + //"%b": Abbreviated month name (Jan..Dec) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + size_t step = 0; + auto v = removePrefix(ctx.view, ctx.pos); + for (size_t p = 0; p < 12; p++) + { + if (startsWithCI(v, abbrev_month_names[p])) + { + time.month = p + 1; + step = abbrev_month_names[p].size(); + break; + } + } + if (step == 0) + return false; + ctx.pos += step; + return true; + }); + break; + } + case 'm': + //"%m": Month, numeric (00..12) + [[fallthrough]]; + case 'c': + { + //"%c": Month, numeric (0..12) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + // To be compatible with TiDB & MySQL, first try to take two digit and parse it as `num` + auto [step, month] = parseNDigits(ctx.view, ctx.pos, 2); + // Then check whether num is valid month + // Note that 0 is valid when sql_mode does not contain NO_ZERO_IN_DATE,NO_ZERO_DATE + if (step == 0 || month > 12) + return false; + time.month = month; + ctx.pos += step; + return true; + }); + break; + } + case 'd': //"%d": Day of the month, numeric (00..31) + [[fallthrough]]; + case 'e': //"%e": Day of the month, numeric (0..31) + { + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, day] = parseNDigits(ctx.view, ctx.pos, 2); + if (step == 0 || day > 31) + return false; + time.day = day; + ctx.pos += step; + return true; + }); + break; + } + case 'f': + { + //"%f": Microseconds (000000..999999) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, ms] = parseNDigits(ctx.view, ctx.pos, 6); + // Empty string is a valid input + if (step == 0) + { + time.micro_second = 0; + return true; + } + // The suffix '0' can be ignored. + // "9" means 900000 + for (size_t i = step; i < 6; i++) + { + ms *= 10; + } + time.micro_second = ms; + ctx.pos += step; + return true; + }); + break; + } + case 'k': + //"%k": Hour (0..23) + [[fallthrough]]; + case 'H': + { + //"%H": Hour (00..23) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, hour] = parseNDigits(ctx.view, ctx.pos, 2); + if (step == 0 || hour > 23) + return false; + ctx.state |= MyDateTimeParser::Context::ST_HOUR_0_23; + time.hour = hour; + ctx.pos += step; + return true; + }); + break; + } + case 'l': + //"%l": Hour (1..12) + [[fallthrough]]; + case 'I': + //"%I": Hour (01..12) + [[fallthrough]]; + case 'h': + { + //"%h": Hour (01..12) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, hour] = parseNDigits(ctx.view, ctx.pos, 2); + if (step == 0 || hour <= 0 || hour > 12) + return false; + ctx.state |= MyDateTimeParser::Context::ST_HOUR_1_12; + time.hour = hour; + ctx.pos += step; + return true; + }); + break; + } + case 'i': + { + //"%i": Minutes, numeric (00..59) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, num] = parseNDigits(ctx.view, ctx.pos, 2); + if (step == 0 || num > 59) + return false; + time.minute = num; + ctx.pos += step; + return true; + }); + break; + } + case 'j': + { + //"%j": Day of year (001..366) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { + auto [step, num] = parseNDigits(ctx.view, ctx.pos, 3); + if (step == 0 || num == 0 || num > 366) + return false; + ctx.state |= MyDateTimeParser::Context::ST_DAY_OF_YEAR; + ctx.day_of_year = num; + ctx.pos += step; + return true; + }); + break; + } + case 'M': + { + //"%M": Month name (January..December) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto v = removePrefix(ctx.view, ctx.pos); + size_t step = 0; + for (size_t p = 0; p < 12; p++) + { + if (startsWithCI(v, month_names[p])) + { + time.month = p + 1; + step = month_names[p].size(); + break; + } + } + if (step == 0) + return false; + ctx.pos += step; + return true; + }); + break; + } + case 'S': + //"%S": Seconds (00..59) + [[fallthrough]]; + case 's': + { + //"%s": Seconds (00..59) + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, second] = parseNDigits(ctx.view, ctx.pos, 2); + if (step == 0 || second > 59) + return false; + time.second = second; + ctx.pos += step; + return true; + }); + break; + } + case 'p': + { + //"%p": AM or PM + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { + // Check the offset that will visit + if (ctx.view.size - ctx.pos < 2) + return false; + + int meridiem = 0; // 0 - invalid, 1 - am, 2 - pm + if (toLowerIfAlphaASCII(ctx.view.data[ctx.pos]) == 'a') + meridiem = 1; + else if (toLowerIfAlphaASCII(ctx.view.data[ctx.pos]) == 'p') + meridiem = 2; + + if (toLowerIfAlphaASCII(ctx.view.data[ctx.pos + 1]) != 'm') + meridiem = 0; + + if (meridiem == 0) + return false; + + ctx.state |= MyDateTimeParser::Context::ST_MERIDIEM; + ctx.meridiem = meridiem; + ctx.pos += 2; + return true; + }); + break; + } + case 'r': + { + //"%r": Time, 12-hour (hh:mm:ss followed by AM or PM) + parsers.emplace_back(parseTime12Hour); + break; + } + case 'T': + { + //"%T": Time, 24-hour (hh:mm:ss) + parsers.emplace_back(parseTime24Hour); + break; + } + case 'Y': + { + //"%Y": Year, numeric, four digits + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, year] = parseYearNDigits(ctx.view, ctx.pos, 4); + if (step == 0) + return false; + time.year = year; + ctx.pos += step; + return true; + }); + break; + } + case 'y': + { + //"%y": Year, numeric, two digits. Deprecated since MySQL 5.7.5 + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase & time) -> bool { + auto [step, year] = parseYearNDigits(ctx.view, ctx.pos, 2); + if (step == 0) + return false; + time.year = year; + ctx.pos += step; + return true; + }); + break; + } + case '#': + { + //"%#": Skip all numbers + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { + // TODO: Does ASCII numeric the same with unicode numeric? + size_t temp_pos = ctx.pos; + while (temp_pos < ctx.view.size && isNumericASCII(ctx.view.data[temp_pos])) + temp_pos++; + ctx.pos = temp_pos; + return true; + }); + break; + } + case '.': + { + //"%.": Skip all punctation characters + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { + // TODO: Does ASCII punctuation the same with unicode punctuation? + size_t temp_pos = ctx.pos; + while (temp_pos < ctx.view.size && isPunctuation(ctx.view.data[temp_pos])) + temp_pos++; + ctx.pos = temp_pos; + return true; + }); + break; + } + case '@': + { + //"%@": Skip all alpha characters + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { + // TODO: Does ASCII alpha the same with unicode alpha? + size_t temp_pos = ctx.pos; + while (temp_pos < ctx.view.size && isAlphaASCII(ctx.view.data[temp_pos])) + temp_pos++; + ctx.pos = temp_pos; + return true; + }); + break; + } + case '%': + { + //"%%": A literal % character + parsers.emplace_back([](MyDateTimeParser::Context & ctx, MyTimeBase &) -> bool { +#if 0 + if (ctx.view.data[ctx.pos] != '%') + return false; + ctx.pos++; + return true; +#else + // FIXME: Ignored by now, both tidb 5.0.0 and mariadb 10.3.14 can not handle it + std::ignore = ctx; + return false; +#endif + }); + break; + } + default: + throw Exception( + "Unknown date format pattern, [format=" + format + "] [pattern=" + x + "] [pos=" + DB::toString(format_pos) + "]", + ErrorCodes::BAD_ARGUMENTS); + } + // end the state of pattern match + in_pattern_match = false; + // move format_pos forward + format_pos++; + continue; + } + + if (x == '%') + { + in_pattern_match = true; + // move format_pos forward + format_pos++; + } + else + { + // Ignore whitespace for literal forwarding (TODO: handle unicode space?) + while (format_pos < format.size() && isWhitespaceASCII(format[format_pos])) + format_pos++; + // Move forward ctx.view with a sequence of literal `format[format_pos:span_end]` + size_t span_end = format_pos; + while (span_end < format.size() && format[span_end] != '%' && !isWhitespaceASCII(format[span_end])) + ++span_end; + const size_t span_size = span_end - format_pos; + if (span_size > 0) + { + StringRef format_view{format.data() + format_pos, span_size}; + parsers.emplace_back([format_view](MyDateTimeParser::Context & ctx, MyTimeBase &) { + assert(format_view.size > 0); + if (format_view.size == 1) + { + // Shortcut for only 1 char + if (ctx.view.data[ctx.pos] != format_view.data[0]) + return false; + ctx.pos += 1; + return true; + } + // Try best to match input as most literal as possible + auto v = removePrefix(ctx.view, ctx.pos); + size_t v_step = 0; + for (size_t format_step = 0; format_step < format_view.size; ++format_step) + { + // Ignore prefix whitespace for input + while (v_step < v.size && isWhitespaceASCII(v.data[v_step])) + ++v_step; + if (v_step == v.size) // To the end + break; + // Try to match literal + if (v.data[v_step] != format_view.data[format_step]) + return false; + ++v_step; + } + ctx.pos += v_step; + return true; + }); + } + // move format_pos forward + format_pos = span_end; + } + } +} + +bool mysqlTimeFix(const MyDateTimeParser::Context & ctx, MyTimeBase & my_time) +{ + // TODO: Implement the function that converts day of year to yy:mm:dd + if (ctx.state & MyDateTimeParser::Context::ST_DAY_OF_YEAR) + { + // %j Day of year (001..366) set + throw Exception("%j set, parsing day of year is not implemented", ErrorCodes::NOT_IMPLEMENTED); + } + + if (ctx.state & MyDateTimeParser::Context::ST_MERIDIEM) + { + // %H (00..23) set, should not set AM/PM + if (ctx.state & MyDateTimeParser::Context::ST_HOUR_0_23) + return false; + if (my_time.hour == 0) + return false; + if (my_time.hour == 12) + { + // 12 is a special hour. + if (ctx.meridiem == 1) // AM + my_time.hour = 0; + else if (ctx.meridiem == 2) // PM + my_time.hour = 12; + return true; + } + if (ctx.meridiem == 2) // PM + my_time.hour += 12; + } + else + { + // %h (01..12) set + if ((ctx.state & MyDateTimeParser::Context::ST_HOUR_1_12) && my_time.hour == 12) + my_time.hour = 0; // why? + } + return true; +} + +std::optional MyDateTimeParser::parseAsPackedUInt(const StringRef & str_view) const +{ + MyTimeBase my_time{0, 0, 0, 0, 0, 0, 0}; + MyDateTimeParser::Context ctx(str_view); + + // TODO: can we return warnings to TiDB? + for (const auto & f : parsers) + { + // Ignore all prefix white spaces before each pattern match (TODO: handle unicode space?) + while (ctx.pos < str_view.size && isWhitespaceASCII(str_view.data[ctx.pos])) + ctx.pos++; + // To the end of input, exit (successfully) even if there is more patterns to match + if (ctx.pos == ctx.view.size) + break; + + if (!f(ctx, my_time)) + { +#ifndef NDEBUG + LOG_TRACE(&Poco::Logger::get("MyDateTimeParser"), + "parse error, [str=" << ctx.view.toString() << "] [format=" << format << "] [parse_pos=" << ctx.pos << "]"); +#endif + return std::nullopt; + } + + // `ctx.pos` > `ctx.view.size` after callback, must be something wrong + if (unlikely(ctx.pos > ctx.view.size)) + { + throw Exception(String(__PRETTY_FUNCTION__) + ": parse error, pos overflow. [str=" + ctx.view.toString() + "] [format=" + format + + "] [parse_pos=" + DB::toString(ctx.pos) + "] [size=" + DB::toString(ctx.view.size) + "]"); + } + } + // Extra characters at the end of date are ignored, but a warning should be reported at this case + // if (ctx.pos < ctx.view.size) {} + + // Handle the var in `ctx` + if (!mysqlTimeFix(ctx, my_time)) + return std::nullopt; + + return my_time.toPackedUInt(); +} + +>>>>>>> 745bcce2a5 (fix date format identifies '\n' as invalid separator (#4046)) } // namespace DB diff --git a/dbms/src/Functions/tests/gtest_tidb_conversion.cpp b/dbms/src/Functions/tests/gtest_tidb_conversion.cpp new file mode 100644 index 00000000000..e9690fd2191 --- /dev/null +++ b/dbms/src/Functions/tests/gtest_tidb_conversion.cpp @@ -0,0 +1,1461 @@ +#include + +#include "Columns/ColumnsNumber.h" +#include "Core/ColumnWithTypeAndName.h" +#include "DataTypes/DataTypeMyDateTime.h" +#include "DataTypes/DataTypeMyDuration.h" +#include "DataTypes/DataTypeNullable.h" +#include "DataTypes/DataTypesNumber.h" +#include "Functions/FunctionHelpers.h" +#include "TestUtils/FunctionTestUtils.h" +#include "common/types.h" +#include "gtest/gtest.h" + +namespace DB::tests +{ +namespace +{ +auto getDatetimeColumn(bool single_field = false) +{ + MyDateTime datetime(2021, 10, 26, 16, 8, 59, 0); + MyDateTime datetime_frac(2021, 10, 26, 16, 8, 59, 123456); + + auto col_datetime = ColumnUInt64::create(); + col_datetime->insert(Field(datetime.toPackedUInt())); + if (!single_field) + col_datetime->insert(Field(datetime_frac.toPackedUInt())); + return col_datetime; +} + +auto createCastTypeConstColumn(String str) +{ + return createConstColumn(1, str); +} + +const std::string func_name = "tidb_cast"; + +const Int8 MAX_INT8 = std::numeric_limits::max(); +const Int8 MIN_INT8 = std::numeric_limits::min(); +const Int16 MAX_INT16 = std::numeric_limits::max(); +const Int16 MIN_INT16 = std::numeric_limits::min(); +const Int32 MAX_INT32 = std::numeric_limits::max(); +const Int32 MIN_INT32 = std::numeric_limits::min(); +const Int64 MAX_INT64 = std::numeric_limits::max(); +const Int64 MIN_INT64 = std::numeric_limits::min(); +const UInt8 MAX_UINT8 = std::numeric_limits::max(); +const UInt16 MAX_UINT16 = std::numeric_limits::max(); +const UInt32 MAX_UINT32 = std::numeric_limits::max(); +const UInt64 MAX_UINT64 = std::numeric_limits::max(); + +const Float32 MAX_FLOAT32 = std::numeric_limits::max(); +const Float32 MIN_FLOAT32 = std::numeric_limits::min(); +const Float64 MAX_FLOAT64 = std::numeric_limits::max(); +const Float64 MIN_FLOAT64 = std::numeric_limits::min(); + +class TestTidbConversion : public DB::tests::FunctionTest +{ +public: + template + void testNotOnlyNull(const Input & input, const Output & output) + { + static_assert(!IsDecimal && !std::is_same_v); + auto inner_test = [&](bool is_const) { + ASSERT_COLUMN_EQ( + is_const ? createConstColumn>(1, output) : createColumn>({output}), + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable({})", TypeName::get()))})); + }; + inner_test(true); + inner_test(false); + } + + template + typename std::enable_if, void>::type testNotOnlyNull(const Input & input, const DecimalField & output, const std::tuple & meta) + { + auto inner_test = [&](bool is_const) { + ASSERT_COLUMN_EQ( + is_const ? createConstColumn>(meta, 1, output) : createColumn>(meta, {output}), + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable(Decimal({},{}))", std::get<0>(meta), std::get<1>(meta)))})); + }; + inner_test(true); + inner_test(false); + } + + template + typename std::enable_if, void>::type testNotOnlyNull(const Input & input, const MyDateTime & output, int fraction) + { + auto inner_test = [&](bool is_const) { + ASSERT_COLUMN_EQ( + is_const ? createDateTimeColumnConst(1, output, fraction) : createDateTimeColumn({output}, fraction), + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable(MyDateTime({}))", fraction))})); + }; + inner_test(true); + inner_test(false); + } + + template + void testThrowException(const Input & input) + { + static_assert(!IsDecimal && !std::is_same_v); + auto inner_test = [&](bool is_const) { + ASSERT_THROW( + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable({})", TypeName::get()))}), + TiFlashException); + }; + inner_test(true); + inner_test(false); + } + + template + typename std::enable_if, void>::type testThrowException(const Input & input, const std::tuple & meta) + { + auto inner_test = [&](bool is_const) { + ASSERT_THROW( + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable(Decimal({},{}))", std::get<0>(meta), std::get<1>(meta)))}), + TiFlashException); + }; + inner_test(true); + inner_test(false); + } + + template + typename std::enable_if, void>::type testThrowException(const Input & input, int fraction) + { + auto inner_test = [&](bool is_const) { + ASSERT_THROW( + executeFunction( + func_name, + {is_const ? createConstColumn>(1, input) : createColumn>({input}), + createCastTypeConstColumn(fmt::format("Nullable(MyDateTime({}))", fraction))}), + TiFlashException); + }; + inner_test(true); + inner_test(false); + } + + template + void testOnlyNull() + { + std::vector nulls = { + createOnlyNullColumnConst(1), + createOnlyNullColumn(1), + createColumn>({{}}), + createConstColumn>(1, {})}; + + auto inner_test = [&](const ColumnWithTypeAndName & null_one) { + if constexpr (IsDecimal) + { + auto precision = 0; + if constexpr (std::is_same_v) + { + precision = 9; + } + else if constexpr (std::is_same_v) + { + precision = 18; + } + else if constexpr (std::is_same_v) + { + precision = 38; + } + else + { + static_assert(std::is_same_v); + precision = 65; + } + auto meta = std::make_tuple(precision, 0); + auto res = null_one.column->isColumnConst() + ? createConstColumn>(meta, 1, std::optional>{}) + : createColumn>(meta, {std::optional>{}}); + ASSERT_COLUMN_EQ( + res, + executeFunction( + func_name, + {null_one, + createCastTypeConstColumn(fmt::format("Nullable(Decimal({},0))", precision))})); + } + else if constexpr (std::is_same_v) + { + auto res = null_one.column->isColumnConst() ? createDateTimeColumnConst(1, {}, 6) : createDateTimeColumn({{}}, 6); + ASSERT_COLUMN_EQ( + res, + executeFunction( + func_name, + {null_one, + createCastTypeConstColumn("Nullable(MyDateTime(6))")})); + } + else + { + auto res = null_one.column->isColumnConst() ? createConstColumn>(1, {}) : createColumn>({{}}); + ASSERT_COLUMN_EQ( + res, + executeFunction( + func_name, + {null_one, + createCastTypeConstColumn(fmt::format("Nullable({})", TypeName::get()))})); + } + }; + for (const auto & null_one : nulls) + { + inner_test(null_one); + } + } +}; + +using DecimalField32 = DecimalField; +using DecimalField64 = DecimalField; +using DecimalField128 = DecimalField; +using DecimalField256 = DecimalField; + +TEST_F(TestTidbConversion, castIntAsInt) +try +{ + /// null only cases + ASSERT_COLUMN_EQ( + createColumn>({{}}), + executeFunction(func_name, + {createOnlyNullColumn(1), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({{}}), + executeFunction(func_name, + {createOnlyNullColumn(1), + createCastTypeConstColumn("Nullable(Int64)")})); + + /// const cases + // uint8/16/32/64 -> uint64, no overflow + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT8), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT8), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT16), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT16), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT32), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT32), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT64), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT64), + createCastTypeConstColumn("UInt64")})); + // int8/16/32/64 -> uint64, no overflow + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT8), + executeFunction(func_name, + {createConstColumn(1, MAX_INT8), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT16), + executeFunction(func_name, + {createConstColumn(1, MAX_INT16), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT32), + executeFunction(func_name, + {createConstColumn(1, MAX_INT32), + createCastTypeConstColumn("UInt64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT64), + executeFunction(func_name, + {createConstColumn(1, MAX_INT64), + createCastTypeConstColumn("UInt64")})); + // uint8/16/32 -> int64, no overflow + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT8), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT8), + createCastTypeConstColumn("Int64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT16), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT16), + createCastTypeConstColumn("Int64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_UINT32), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT32), + createCastTypeConstColumn("Int64")})); + // uint64 -> int64, will overflow + ASSERT_COLUMN_EQ( + createConstColumn(1, -1), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT64), + createCastTypeConstColumn("Int64")})); + // int8/16/32/64 -> int64, no overflow + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT8), + executeFunction(func_name, + {createConstColumn(1, MAX_INT8), + createCastTypeConstColumn("Int64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT16), + executeFunction(func_name, + {createConstColumn(1, MAX_INT16), + createCastTypeConstColumn("Int64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT32), + executeFunction(func_name, + {createConstColumn(1, MAX_INT32), + createCastTypeConstColumn("Int64")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, MAX_INT64), + executeFunction(func_name, + {createConstColumn(1, MAX_INT64), + createCastTypeConstColumn("Int64")})); + + /// normal cases + // uint8/16/32/64 -> uint64, no overflow + ASSERT_COLUMN_EQ( + createColumn>({0, 1, MAX_UINT8, {}}), + executeFunction(func_name, + {createColumn>({0, 1, MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, 1, MAX_UINT16, {}}), + executeFunction(func_name, + {createColumn>({0, 1, MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, 1, MAX_UINT32, {}}), + executeFunction(func_name, + {createColumn>({0, 1, MAX_UINT32, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, 1, MAX_UINT64, {}}), + executeFunction(func_name, + {createColumn>({0, 1, MAX_UINT64, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + // int8/16/32/64 -> uint64, no overflow + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT8, MAX_UINT64, MAX_UINT64 - MAX_INT8, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT8, -1, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT16, MAX_UINT64, MAX_UINT64 - MAX_INT16, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT16, -1, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT32, MAX_UINT64, MAX_UINT64 - MAX_INT32, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT32, -1, MIN_INT32, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT64, MAX_UINT64, MAX_UINT64 - MAX_INT64, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT64, -1, MIN_INT64, {}}), + createCastTypeConstColumn("Nullable(UInt64)")})); + // uint8/16/32 -> int64, no overflow + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT8, MAX_UINT8, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT8, MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT16, MAX_UINT16, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT16, MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT32, MAX_UINT32, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT32, MAX_UINT32, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + // uint64 -> int64, overflow may happen + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT64, -1, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT64, MAX_UINT64, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + // int8/16/32/64 -> int64, no overflow + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT8, -1, MIN_INT8, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT8, -1, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT16, -1, MIN_INT16, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT16, -1, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT32, -1, MIN_INT32, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT32, -1, MIN_INT32, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); + ASSERT_COLUMN_EQ( + createColumn>({0, MAX_INT64, -1, MIN_INT64, {}}), + executeFunction(func_name, + {createColumn>({0, MAX_INT64, -1, MIN_INT64, {}}), + createCastTypeConstColumn("Nullable(Int64)")})); +} +CATCH + +TEST_F(TestTidbConversion, castIntAsReal) +try +{ + // uint64/int64 -> float64, may be not precise + ASSERT_COLUMN_EQ( + createColumn>( + {1234567890.0, + 123456789012345680.0, + 0.0, + {}}), + executeFunction(func_name, + {createColumn>( + {1234567890, // this is fine + 123456789012345678, // but this cannot be represented precisely in the IEEE 754 64-bit float format + 0, + {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>( + {1234567890.0, + 123456789012345680.0, + 0.0, + {}}), + executeFunction(func_name, + {createColumn>( + {1234567890, // this is fine + 123456789012345678, // but this cannot be represented precisely in the IEEE 754 64-bit float format + 0, + {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + // uint32/16/8 and int32/16/8 -> float64, precise + ASSERT_COLUMN_EQ( + createColumn>({MAX_UINT32, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT32, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>({MAX_UINT16, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>({MAX_UINT8, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>({MAX_INT32, MIN_INT32, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_INT32, MIN_INT32, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>({MAX_INT16, MIN_INT16, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); + ASSERT_COLUMN_EQ( + createColumn>({MAX_INT8, MIN_INT8, 0, {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, 0, {}}), + createCastTypeConstColumn("Nullable(Float64)")})); +} +CATCH + +TEST_F(TestTidbConversion, castIntAsString) +try +{ + /// null only cases + ASSERT_COLUMN_EQ( + createColumn>({{}}), + executeFunction(func_name, + {createOnlyNullColumn(1), + createCastTypeConstColumn("Nullable(String)")})); + + /// const cases + // uint64/32/16/8 -> string + ASSERT_COLUMN_EQ( + createConstColumn(1, "18446744073709551615"), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT64), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "4294967295"), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT32), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "65535"), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT16), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "255"), + executeFunction(func_name, + {createConstColumn(1, MAX_UINT8), + createCastTypeConstColumn("String")})); + // int64/32/16/8 -> string + ASSERT_COLUMN_EQ( + createConstColumn(1, "9223372036854775807"), + executeFunction(func_name, + {createConstColumn(1, MAX_INT64), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "2147483647"), + executeFunction(func_name, + {createConstColumn(1, MAX_INT32), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "32767"), + executeFunction(func_name, + {createConstColumn(1, MAX_INT16), + createCastTypeConstColumn("String")})); + ASSERT_COLUMN_EQ( + createConstColumn(1, "127"), + executeFunction(func_name, + {createConstColumn(1, MAX_INT8), + createCastTypeConstColumn("String")})); + + /// normal cases + // uint64/32/16/8 -> string + ASSERT_COLUMN_EQ( + createColumn>({"18446744073709551615", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT64, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"4294967295", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT32, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"65535", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"255", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + // int64/32/16/8 -> string + ASSERT_COLUMN_EQ( + createColumn>({"9223372036854775807", "-9223372036854775808", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_INT64, MIN_INT64, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"2147483647", "-2147483648", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_INT32, MIN_INT32, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"32767", "-32768", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); + ASSERT_COLUMN_EQ( + createColumn>({"127", "-128", "0", {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, 0, {}}), + createCastTypeConstColumn("Nullable(String)")})); +} +CATCH + +TEST_F(TestTidbConversion, castIntAsDecimal) +try +{ + // int8 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(MAX_INT8, 0), DecimalField32(MIN_INT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_INT8, 0), DecimalField64(MIN_INT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_INT8, 0), DecimalField128(MIN_INT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_INT8), 0), DecimalField256(static_cast(MIN_INT8), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT8, MIN_INT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // int16 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(MAX_INT16, 0), DecimalField32(MIN_INT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_INT16, 0), DecimalField64(MIN_INT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_INT16, 0), DecimalField128(MIN_INT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_INT16), 0), DecimalField256(static_cast(MIN_INT16), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT16, MIN_INT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // int32 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(999999999, 0), DecimalField32(-999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999, -999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000, -1000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_INT32, 0), DecimalField64(MIN_INT32, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT32, MIN_INT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_INT32, 0), DecimalField128(MIN_INT32, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT32, MIN_INT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_INT32), 0), DecimalField256(static_cast(MIN_INT32), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT32, MIN_INT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // int64 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(999999999, 0), DecimalField32(-999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999, -999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000, -1000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(999999999999999999, 0), DecimalField64(-999999999999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999999999999, -999999999999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000000000000, -1000000000000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_INT64, 0), DecimalField128(MIN_INT64, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT64, MIN_INT64, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_INT64), 0), DecimalField256(static_cast(MIN_INT64), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT64, MIN_INT64, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // uint8 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(MAX_UINT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_UINT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_UINT8, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_UINT8), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT8, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // uint16 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(MAX_UINT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_UINT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_UINT16, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_UINT16), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT16, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // uint32 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(MAX_UINT32, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_UINT32, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_UINT32), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_UINT32, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + // uint64 -> decimal32/64/128/256 + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(9, 0), + {DecimalField32(999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(9,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(18, 0), + {DecimalField64(999999999999999999, 0), {}}), + executeFunction(func_name, + {createColumn>({999999999999999999, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")})); + ASSERT_THROW(executeFunction(func_name, + {createColumn>({1000000000000000000, {}}), + createCastTypeConstColumn("Nullable(Decimal(18,0))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(38, 0), + {DecimalField128(MAX_INT64, 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT64, {}}), + createCastTypeConstColumn("Nullable(Decimal(38,0))")})); + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(65, 0), + {DecimalField256(static_cast(MAX_INT64), 0), {}}), + executeFunction(func_name, + {createColumn>({MAX_INT64, {}}), + createCastTypeConstColumn("Nullable(Decimal(65,0))")})); + + ASSERT_THROW(executeFunction(func_name, + {createColumn>({9999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")}), + TiFlashException); + + ASSERT_THROW(executeFunction(func_name, + {createColumn>({-9999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")}), + TiFlashException); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(4, 1), + {DecimalField32(static_cast(9990), 1)}), + executeFunction(func_name, + {createColumn>({999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")})); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(4, 1), + {DecimalField32(static_cast(-9990), 1)}), + executeFunction(func_name, + {createColumn>({-999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")})); + + DAGContext * dag_context = context.getDAGContext(); + UInt64 ori_flags = dag_context->getFlags(); + dag_context->addFlag(TiDBSQLFlags::OVERFLOW_AS_WARNING); + dag_context->clearWarnings(); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(4, 1), + {DecimalField32(static_cast(9999), 1)}), + executeFunction(func_name, + {createColumn>({9999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")})); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(4, 1), + {DecimalField32(static_cast(-9999), 1)}), + executeFunction(func_name, + {createColumn>({-9999}), createCastTypeConstColumn("Nullable(Decimal(4, 1))")})); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(2, 2), + {DecimalField32(static_cast(99), 2)}), + executeFunction(func_name, + {createColumn>({9999}), createCastTypeConstColumn("Nullable(Decimal(2, 2))")})); + + ASSERT_COLUMN_EQ( + createColumn>( + std::make_tuple(2, 2), + {DecimalField32(static_cast(-99), 2)}), + executeFunction(func_name, + {createColumn>({-9999}), createCastTypeConstColumn("Nullable(Decimal(2, 2))")})); + + dag_context->setFlags(ori_flags); +} +CATCH + +TEST_F(TestTidbConversion, castIntAsTime) +try +{ + ASSERT_COLUMN_EQ( + createDateTimeColumn({{}, {{2021, 10, 26, 16, 8, 59, 0}}}, 6), + executeFunction(func_name, + {createColumn>({{}, 20211026160859}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")})); + ASSERT_COLUMN_EQ( + createDateTimeColumn({{}, {{2021, 10, 26, 16, 8, 59, 0}}}, 6), + executeFunction(func_name, + {createColumn>({{}, 20211026160859}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")})); + ASSERT_THROW( + executeFunction(func_name, + {createColumn>({MAX_UINT8}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")}), + TiFlashException); + ASSERT_THROW( + executeFunction(func_name, + {createColumn>({MAX_UINT16}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")}), + TiFlashException); + ASSERT_THROW( + executeFunction(func_name, + {createColumn>({MAX_UINT32}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")}), + TiFlashException); + ASSERT_COLUMN_EQ( + createDateTimeColumn({{}}, 6), + executeFunction(func_name, + {createColumn>({0}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")})); + ASSERT_THROW( + executeFunction(func_name, + {createColumn>({{}, -20211026160859}), + createCastTypeConstColumn("Nullable(MyDateTime(6))")}), + TiFlashException); +} +CATCH + +TEST_F(TestTidbConversion, castRealAsInt) +try +{ + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + + testNotOnlyNull(0, 0); + testThrowException(MAX_FLOAT32); + testNotOnlyNull(MIN_FLOAT32, 0); + testNotOnlyNull(12.213f, 12); + testNotOnlyNull(-12.213f, -12); + testNotOnlyNull(12.513f, 13); + testNotOnlyNull(-12.513f, -13); + + testNotOnlyNull(0, 0); + testThrowException(MAX_FLOAT32); + testNotOnlyNull(MIN_FLOAT32, 0); + testNotOnlyNull(12.213f, 12); + testThrowException(-12.213f); + testNotOnlyNull(12.513f, 13); + testThrowException(-12.513f); + + testNotOnlyNull(0, 0); + testThrowException(MAX_FLOAT64); + testNotOnlyNull(MIN_FLOAT64, 0); + testNotOnlyNull(12.213, 12); + testNotOnlyNull(-12.213, -12); + testNotOnlyNull(12.513, 13); + testNotOnlyNull(-12.513, -13); + + testNotOnlyNull(0, 0); + testThrowException(MAX_FLOAT64); + testNotOnlyNull(MIN_FLOAT64, 0); + testNotOnlyNull(12.213, 12); + testThrowException(-12.213); + testNotOnlyNull(12.513, 13); + testNotOnlyNull(-12.513, -13); +} +CATCH + +TEST_F(TestTidbConversion, castRealAsReal) +try +{ + testOnlyNull(); + testOnlyNull(); + + testNotOnlyNull(0, 0); + testNotOnlyNull(12.213, 12.213000297546387); + testNotOnlyNull(-12.213, -12.213000297546387); + testNotOnlyNull(MIN_FLOAT32, MIN_FLOAT32); + testNotOnlyNull(MAX_FLOAT32, MAX_FLOAT32); + + testNotOnlyNull(0, 0); + testNotOnlyNull(12.213, 12.213); + testNotOnlyNull(-12.213, -12.213); + testNotOnlyNull(MIN_FLOAT64, MIN_FLOAT64); + testNotOnlyNull(MAX_FLOAT64, MAX_FLOAT64); +} +CATCH + +TEST_F(TestTidbConversion, castRealAsString) +try +{ + testOnlyNull(); + testOnlyNull(); + + // TODO add tests after non-expected results fixed + + testNotOnlyNull(0, "0"); + testNotOnlyNull(12.213, "12.213"); + testNotOnlyNull(-12.213, "-12.213"); + // tiflash: 3.4028235e38 + // tidb: 340282350000000000000000000000000000000 + // mysql: 3.40282e38 + // testNotOnlyNull(MAX_FLOAT32, "3.4028235e38"); + // tiflash: 1.1754944e-38 + // tidb: 0.000000000000000000000000000000000000011754944 + // mysql: 1.17549e-38 + // testNotOnlyNull(MIN_FLOAT32, "1.1754944e-38"); + + testNotOnlyNull(0, "0"); + testNotOnlyNull(12.213, "12.213"); + testNotOnlyNull(-12.213, "-12.213"); + // tiflash: 1.7976931348623157e308 + // tidb: 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + // mysql: 1.7976931348623157e308 + // testNotOnlyNull(MAX_FLOAT64, "1.7976931348623157e308"); + // tiflash: 2.2250738585072014e-308 + // tidb: 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014 + // mysql: 2.2250738585072014e-308 + // testNotOnlyNull(MIN_FLOAT64, "2.2250738585072014e-308"); +} +CATCH + +TEST_F(TestTidbConversion, castRealAsDecimal) +try +{ + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + testOnlyNull(); + + // TODO fix: + // for tidb, cast(12.213f as decimal(x, x)) throw warnings: Truncated incorrect DECIMAL value: '-12.21300029754638. + // tiflash is same as mysql, don't throw warnings. + + testNotOnlyNull(0, DecimalField32(0, 0), std::make_tuple(9, 0)); + testNotOnlyNull(12.213f, DecimalField32(12213, 3), std::make_tuple(9, 3)); + testNotOnlyNull(-12.213f, DecimalField32(-12213, 3), std::make_tuple(9, 3)); + testThrowException(MAX_FLOAT32, std::make_tuple(9, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField32(0, 9), std::make_tuple(9, 9)); + + testNotOnlyNull(0, DecimalField64(0, 0), std::make_tuple(18, 0)); + testNotOnlyNull(12.213f, DecimalField64(12213, 3), std::make_tuple(18, 3)); + testNotOnlyNull(-12.213f, DecimalField64(-12213, 3), std::make_tuple(18, 3)); + testThrowException(MAX_FLOAT32, std::make_tuple(18, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField64(0, 18), std::make_tuple(18, 18)); + + testNotOnlyNull(0, DecimalField128(0, 0), std::make_tuple(38, 0)); + testNotOnlyNull(12.213f, DecimalField128(12213, 3), std::make_tuple(38, 3)); + testNotOnlyNull(-12.213f, DecimalField128(-12213, 3), std::make_tuple(38, 3)); + testThrowException(MAX_FLOAT32, std::make_tuple(38, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField128(0, 30), std::make_tuple(38, 30)); + + testNotOnlyNull(0, DecimalField256(static_cast(0), 0), std::make_tuple(65, 0)); + testNotOnlyNull(12.213f, DecimalField256(static_cast(12213), 3), std::make_tuple(65, 3)); + testNotOnlyNull(-12.213f, DecimalField256(static_cast(-12213), 3), std::make_tuple(65, 3)); + // TODO add test after bug fixed + // ERROR 1105 (HY000): other error for mpp stream: Cannot convert a non-finite number to an integer. + // testNotOnlyNull(MAX_FLOAT32, DecimalField256(Int256("340282346638528860000000000000000000000"), 0), std::make_tuple(65, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField256(static_cast(0), 30), std::make_tuple(65, 30)); + + testNotOnlyNull(0, DecimalField32(0, 0), std::make_tuple(9, 0)); + testNotOnlyNull(12.213, DecimalField32(12213, 3), std::make_tuple(9, 3)); + testNotOnlyNull(-12.213, DecimalField32(-12213, 3), std::make_tuple(9, 3)); + testThrowException(MAX_FLOAT64, std::make_tuple(9, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField32(0, 9), std::make_tuple(9, 9)); + + testNotOnlyNull(0, DecimalField64(0, 0), std::make_tuple(18, 0)); + testNotOnlyNull(12.213, DecimalField64(12213, 3), std::make_tuple(18, 3)); + testNotOnlyNull(-12.213, DecimalField64(-12213, 3), std::make_tuple(18, 3)); + testThrowException(MAX_FLOAT64, std::make_tuple(18, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField64(0, 18), std::make_tuple(18, 18)); + + testNotOnlyNull(0, DecimalField128(0, 0), std::make_tuple(38, 0)); + testNotOnlyNull(12.213, DecimalField128(12213, 3), std::make_tuple(38, 3)); + testNotOnlyNull(-12.213, DecimalField128(-12213, 3), std::make_tuple(38, 3)); + testThrowException(MAX_FLOAT64, std::make_tuple(38, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField128(0, 30), std::make_tuple(38, 30)); + + testNotOnlyNull(0, DecimalField256(static_cast(0), 0), std::make_tuple(65, 0)); + testNotOnlyNull(12.213, DecimalField256(static_cast(12213), 3), std::make_tuple(65, 3)); + testNotOnlyNull(-12.213, DecimalField256(static_cast(-12213), 3), std::make_tuple(65, 3)); + testThrowException(MAX_FLOAT64, std::make_tuple(65, 0)); + testNotOnlyNull(MIN_FLOAT64, DecimalField256(static_cast(0), 30), std::make_tuple(65, 30)); + + + // test round + // TODO fix: + // in default mode + // for round test, tidb throw warnings: Truncated incorrect DECIMAL value: xxx + // tiflash is same as mysql, don't throw warnings. + DAGContext * dag_context = context.getDAGContext(); + UInt64 ori_flags = dag_context->getFlags(); + dag_context->addFlag(TiDBSQLFlags::TRUNCATE_AS_WARNING); + dag_context->clearWarnings(); + + testNotOnlyNull(12.213f, DecimalField32(1221, 2), std::make_tuple(9, 2)); + testNotOnlyNull(-12.213f, DecimalField32(-1221, 2), std::make_tuple(9, 2)); + testNotOnlyNull(12.215f, DecimalField32(1222, 2), std::make_tuple(9, 2)); + testNotOnlyNull(-12.215f, DecimalField32(-1222, 2), std::make_tuple(9, 2)); + + testNotOnlyNull(12.213f, DecimalField64(1221, 2), std::make_tuple(18, 2)); + testNotOnlyNull(-12.213f, DecimalField64(-1221, 2), std::make_tuple(18, 2)); + testNotOnlyNull(12.215f, DecimalField64(1222, 2), std::make_tuple(18, 2)); + testNotOnlyNull(-12.215f, DecimalField64(-1222, 2), std::make_tuple(18, 2)); + + testNotOnlyNull(12.213f, DecimalField128(1221, 2), std::make_tuple(38, 2)); + testNotOnlyNull(-12.213f, DecimalField128(-1221, 2), std::make_tuple(38, 2)); + testNotOnlyNull(12.215f, DecimalField128(1222, 2), std::make_tuple(38, 2)); + testNotOnlyNull(-12.215f, DecimalField128(-1222, 2), std::make_tuple(38, 2)); + + testNotOnlyNull(12.213f, DecimalField256(static_cast(1221), 2), std::make_tuple(65, 2)); + testNotOnlyNull(-12.213f, DecimalField256(static_cast(-1221), 2), std::make_tuple(65, 2)); + testNotOnlyNull(12.215f, DecimalField256(static_cast(1222), 2), std::make_tuple(65, 2)); + testNotOnlyNull(-12.215f, DecimalField256(static_cast(-1222), 2), std::make_tuple(65, 2)); + + testNotOnlyNull(12.213, DecimalField32(1221, 2), std::make_tuple(9, 2)); + testNotOnlyNull(-12.213, DecimalField32(-1221, 2), std::make_tuple(9, 2)); + testNotOnlyNull(12.215, DecimalField32(1222, 2), std::make_tuple(9, 2)); + testNotOnlyNull(-12.215, DecimalField32(-1222, 2), std::make_tuple(9, 2)); + + testNotOnlyNull(12.213, DecimalField64(1221, 2), std::make_tuple(18, 2)); + testNotOnlyNull(-12.213, DecimalField64(-1221, 2), std::make_tuple(18, 2)); + testNotOnlyNull(12.215, DecimalField64(1222, 2), std::make_tuple(18, 2)); + testNotOnlyNull(-12.215, DecimalField64(-1222, 2), std::make_tuple(18, 2)); + + testNotOnlyNull(12.213, DecimalField128(1221, 2), std::make_tuple(38, 2)); + testNotOnlyNull(-12.213, DecimalField128(-1221, 2), std::make_tuple(38, 2)); + testNotOnlyNull(12.215, DecimalField128(1222, 2), std::make_tuple(38, 2)); + testNotOnlyNull(-12.215, DecimalField128(-1222, 2), std::make_tuple(38, 2)); + + testNotOnlyNull(12.213, DecimalField256(static_cast(1221), 2), std::make_tuple(65, 2)); + testNotOnlyNull(-12.213, DecimalField256(static_cast(-1221), 2), std::make_tuple(65, 2)); + testNotOnlyNull(12.215, DecimalField256(static_cast(1222), 2), std::make_tuple(65, 2)); + testNotOnlyNull(-12.215, DecimalField256(static_cast(-1222), 2), std::make_tuple(65, 2)); + + dag_context->setFlags(ori_flags); + dag_context->clearWarnings(); +} +CATCH + +TEST_F(TestTidbConversion, castRealAsTime) +try +{ + testOnlyNull(); + testOnlyNull(); + + // TODO add tests after non-expected results fixed + + // mysql: null, warning. + // tiflash: null, no warning. + // tidb: 0000-00-00 00:00:00 + // testThrowException(0, 6); + testThrowException(12.213, 6); + testThrowException(-12.213, 6); + testThrowException(MAX_FLOAT32, 6); + testThrowException(MIN_FLOAT32, 6); + // mysql: 2000-01-11 00:00:00 + // tiflash / tidb: null, warnings + // testNotOnlyNull(111, {2000, 1, 11, 0, 0, 0, 0}, 6); + testThrowException(-111, 6); + // mysql: 2000-01-11 00:00:00 + // tiflash / tidb: null, warnings + // testNotOnlyNull(111.1, {2000, 1, 11, 0, 0, 0, 0}, 6); + + // mysql: null, warning. + // tiflash: null, no warning. + // tidb: 0000-00-00 00:00:00 + // testThrowException(0, 6); + testThrowException(12.213, 6); + testThrowException(-12.213, 6); + testThrowException(MAX_FLOAT64, 6); + testThrowException(MIN_FLOAT64, 6); + // mysql: 2000-01-11 00:00:00 + // tiflash / tidb: null, warnings + // testNotOnlyNull(111, {2000, 1, 11, 0, 0, 0, 0}, 6); + testThrowException(-111, 6); + // mysql: 2000-01-11 00:00:00 + // tiflash / tidb: null, warnings + // testNotOnlyNull(111.1, {2000, 1, 11, 0, 0, 0, 0}, 6); + testNotOnlyNull(20210201, {2021, 2, 1, 0, 0, 0, 0}, 6); + // mysql: 2021-02-01 00:00:00 + // tiflash / tidb: 2021-02-01 01:00:00 + // testNotOnlyNull(20210201.1, {2021, 2, 1, 0, 0, 0, 0}, 6); +} +CATCH + +TEST_F(TestTidbConversion, castTimeAsReal) +try +{ + const auto data_type_ptr = std::make_shared(6); + const Float64 datetime_float = 20211026160859; + const Float64 datetime_frac_float = 20211026160859.125; + + // cast datetime to float + auto col_datetime1 = getDatetimeColumn(); + auto ctn_datetime1 = ColumnWithTypeAndName(std::move(col_datetime1), data_type_ptr, "datetime"); + ASSERT_COLUMN_EQ( + createColumn({datetime_float, datetime_frac_float}), + executeFunction(func_name, + {ctn_datetime1, + createCastTypeConstColumn("Float64")})); + + // cast datetime to nullable float + auto col_datetime2 = getDatetimeColumn(); + auto ctn_datetime2 = ColumnWithTypeAndName(std::move(col_datetime2), data_type_ptr, "datetime"); + ASSERT_COLUMN_EQ( + createColumn>({datetime_float, datetime_frac_float}), + executeFunction(func_name, + {ctn_datetime2, + createCastTypeConstColumn("Nullable(Float64)")})); + + // cast nullable datetime to nullable float + auto col_datetime3 = getDatetimeColumn(); + auto datetime3_null_map = ColumnUInt8::create(2, 0); + datetime3_null_map->getData()[1] = 1; + auto col_datetime3_nullable = ColumnNullable::create(std::move(col_datetime3), std::move(datetime3_null_map)); + auto ctn_datetime3_nullable = ColumnWithTypeAndName(std::move(col_datetime3_nullable), makeNullable(data_type_ptr), "datetime"); + ASSERT_COLUMN_EQ( + createColumn>({datetime_float, {}}), + executeFunction(func_name, + {ctn_datetime3_nullable, + createCastTypeConstColumn("Nullable(Float64)")})); + + // cast const datetime to float + auto col_datetime4_const = ColumnConst::create(getDatetimeColumn(true), 1); + auto ctn_datetime4_const = ColumnWithTypeAndName(std::move(col_datetime4_const), data_type_ptr, "datetime"); + ASSERT_COLUMN_EQ( + createConstColumn(1, datetime_float), + executeFunction(func_name, + {ctn_datetime4_const, + createCastTypeConstColumn("Float64")})); + + // cast nullable const datetime to float + auto col_datetime5 = getDatetimeColumn(true); + auto datetime5_null_map = ColumnUInt8::create(1, 0); + auto col_datetime5_nullable = ColumnNullable::create(std::move(col_datetime5), std::move(datetime5_null_map)); + auto col_datetime5_nullable_const = ColumnConst::create(std::move(col_datetime5_nullable), 1); + auto ctn_datetime5_nullable_const = ColumnWithTypeAndName(std::move(col_datetime5_nullable_const), makeNullable(data_type_ptr), "datetime"); + ASSERT_COLUMN_EQ( + createConstColumn>(1, datetime_float), + executeFunction(func_name, + {ctn_datetime5_nullable_const, + createCastTypeConstColumn("Nullable(Float64)")})); +} +CATCH + +TEST_F(TestTidbConversion, castDurationAsDuration) +try +{ + const auto from_type = std::make_shared(3); + const auto to_type_1 = std::make_shared(5); // from_fsp < to_fsp + const auto to_type_2 = std::make_shared(3); // from_fsp == to_fsp + const auto to_type_3 = std::make_shared(2); // from_fsp < to_fsp + + ColumnWithTypeAndName input( + createColumn({(20 * 3600 + 20 * 60 + 20) * 1000000000L + 555000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 555000000L, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 554000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 554000000L, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 999000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 999000000L}) + .column, + from_type, + "input"); + + ColumnWithTypeAndName output1(input.column, to_type_1, "output1"); + ColumnWithTypeAndName output2(input.column, to_type_2, "output2"); + ColumnWithTypeAndName output3( + createColumn({(20 * 3600 + 20 * 60 + 20) * 1000000000L + 560000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 560000000L, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 550000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 550000000L, + (20 * 3600 + 20 * 60 + 21) * 1000000000L + 000000000L, + -(20 * 3600 + 20 * 60 + 21) * 1000000000L - 000000000L}) + .column, + to_type_3, + "output3"); + + ASSERT_COLUMN_EQ(output1, executeFunction(func_name, {input, createCastTypeConstColumn(to_type_1->getName())})); + ASSERT_COLUMN_EQ(output2, executeFunction(func_name, {input, createCastTypeConstColumn(to_type_2->getName())})); + ASSERT_COLUMN_EQ(output3, executeFunction(func_name, {input, createCastTypeConstColumn(to_type_3->getName())})); + + // Test Nullable + ColumnWithTypeAndName input_nullable( + createColumn>({(20 * 3600 + 20 * 60 + 20) * 1000000000L + 555000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 555000000L, + {}, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 554000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 554000000L, + {}, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 999000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 999000000L}) + .column, + makeNullable(input.type), + "input_nullable"); + ColumnWithTypeAndName output1_nullable(input_nullable.column, makeNullable(to_type_1), "output1_nullable"); + ColumnWithTypeAndName output2_nullable(input_nullable.column, makeNullable(to_type_2), "output2_nullable"); + ColumnWithTypeAndName output3_nullable( + createColumn>({(20 * 3600 + 20 * 60 + 20) * 1000000000L + 560000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 560000000L, + {}, + (20 * 3600 + 20 * 60 + 20) * 1000000000L + 550000000L, + -(20 * 3600 + 20 * 60 + 20) * 1000000000L - 550000000L, + {}, + (20 * 3600 + 20 * 60 + 21) * 1000000000L + 000000000L, + -(20 * 3600 + 20 * 60 + 21) * 1000000000L - 000000000L}) + .column, + makeNullable(to_type_3), + "output3_output"); + + ASSERT_COLUMN_EQ(output1_nullable, executeFunction(func_name, {input_nullable, createCastTypeConstColumn(makeNullable(to_type_1)->getName())})); + ASSERT_COLUMN_EQ(output2_nullable, executeFunction(func_name, {input_nullable, createCastTypeConstColumn(makeNullable(to_type_2)->getName())})); + ASSERT_COLUMN_EQ(output3_nullable, executeFunction(func_name, {input_nullable, createCastTypeConstColumn(makeNullable(to_type_3)->getName())})); + + // Test Const + ColumnWithTypeAndName input_const(createConstColumn(1, (20 * 3600 + 20 * 60 + 20) * 1000000000L + 999000000L).column, from_type, "input_const"); + ColumnWithTypeAndName output1_const(input_const.column, to_type_1, "output1_const"); + ColumnWithTypeAndName output2_const(input_const.column, to_type_2, "output2_const"); + ColumnWithTypeAndName output3_const(createConstColumn(1, (20 * 3600 + 20 * 60 + 21) * 1000000000L + 000000000L).column, to_type_3, "output3_const"); + + ASSERT_COLUMN_EQ(output1_const, executeFunction(func_name, {input_const, createCastTypeConstColumn(to_type_1->getName())})); + ASSERT_COLUMN_EQ(output2_const, executeFunction(func_name, {input_const, createCastTypeConstColumn(to_type_2->getName())})); + ASSERT_COLUMN_EQ(output3_const, executeFunction(func_name, {input_const, createCastTypeConstColumn(to_type_3->getName())})); +} +CATCH + +TEST_F(TestTidbConversion, StrToDateTypeTest) +try +{ + // Arg1 is ColumnVector, Arg2 is ColumnVector + auto arg1_column = createColumn>({{}, "1/12/2020", "00:59:60 ", "1/12/2020"}); + auto arg2_column = createColumn>({"%d/%c/%Y", {}, "%H:%i:%S ", "%d/%c/%Y"}); + ColumnWithTypeAndName result_column( + createColumn>({{}, {}, {}, MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt()}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(non-null value)), Arg2 is ColumnVector + arg1_column = createConstColumn>(2, {"1/12/2020"}); + arg2_column = createColumn>({"%d/%c/%Y", "%d/%c/%Y"}); + result_column = ColumnWithTypeAndName( + createColumn>({MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt(), MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt()}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(null value)), Arg2 is ColumnVector + arg1_column = createConstColumn>(2, {}); + arg2_column = createColumn>({"%d/%c/%Y", "%d/%c/%Y"}); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnVector, Arg2 is ColumnConst(ColumnNullable(non-null value)) + arg1_column = createColumn>({"1/12/2020", "1/12/2020"}); + arg2_column = createConstColumn>(2, "%d/%c/%Y"); + result_column = ColumnWithTypeAndName( + createColumn>({MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt(), MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt()}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(non-null value)), Arg2 is ColumnConst(ColumnNullable(non-null value)) + arg1_column = createConstColumn>(2, "1/12/2020"); + arg2_column = createConstColumn>(2, "%d/%c/%Y"); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {MyDateTime{2020, 12, 1, 0, 0, 0, 0}.toPackedUInt()}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(null value)), Arg2 is ColumnConst(ColumnNullable(non-null value)) + arg1_column = createConstColumn>(2, {}); + arg2_column = createConstColumn>(2, "%d/%c/%Y"); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnVector, Arg2 is ColumnConst(ColumnNullable(null value)) + arg1_column = createColumn>({"1/12/2020", "1/12/2020"}); + arg2_column = createConstColumn>(2, {}); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(non-null value)), Arg2 is ColumnConst(ColumnNullable(null value)) + arg1_column = createConstColumn>(2, {"1/12/2020"}); + arg2_column = createConstColumn>(2, {}); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); + + // Arg1 is ColumnConst(ColumnNullable(null value)), Arg2 is ColumnConst(ColumnNullable(null value)) + arg1_column = createConstColumn>(2, {}); + arg2_column = createConstColumn>(2, {}); + result_column = ColumnWithTypeAndName( + createConstColumn>(2, {}).column, + makeNullable(std::make_shared(0)), + "result"); + ASSERT_COLUMN_EQ(result_column, executeFunction("strToDateDatetime", arg1_column, arg2_column)); +} +CATCH + +// for https://github.com/pingcap/tics/issues/4036 +TEST_F(TestTidbConversion, castStringAsDateTime) +try +{ + auto input = std::vector{"2012-12-12 12:12:12", "2012-12-12\t12:12:12", "2012-12-12\n12:12:12", "2012-12-12\v12:12:12", "2012-12-12\f12:12:12", "2012-12-12\r12:12:12"}; + auto to_column = createConstColumn(1, "MyDateTime(6)"); + + // vector + auto from_column = createColumn(input); + UInt64 except_packed = MyDateTime(2012, 12, 12, 12, 12, 12, 0).toPackedUInt(); + auto vector_result = executeFunction("tidb_cast", {from_column, to_column}); + for (size_t i = 0; i < input.size(); i++) + { + ASSERT_EQ(except_packed, vector_result.column.get()->get64(i)); + } + + // const + auto const_from_column = createConstColumn(1, "2012-12-12\n12:12:12"); + auto const_result = executeFunction("tidb_cast", {from_column, to_column}); + ASSERT_EQ(except_packed, const_result.column.get()->get64(0)); + + // nullable + auto nullable_from_column = createColumn>({"2012-12-12 12:12:12", "2012-12-12\t12:12:12", "2012-12-12\n12:12:12", "2012-12-12\v12:12:12", "2012-12-12\f12:12:12", "2012-12-12\r12:12:12"}); + auto nullable_result = executeFunction("tidb_cast", {from_column, to_column}); + for (size_t i = 0; i < input.size(); i++) + { + ASSERT_EQ(except_packed, nullable_result.column.get()->get64(i)); + } +} +CATCH + +} // namespace +} // namespace DB::tests