Repository: ClickHouse/ClickHouse
File: src/Parsers/Kusto/KustoFunctions/KQLGeneralFunctions.cpp (C++)

Code before:
    IParser::Pos peek_pos = pos;
    ++peek_pos;
    String original_expr;
    if (peek_pos.isValid() && peek_pos->type != TokenType::Comma && peek_pos->type != TokenType::ClosingRoundBracket)
        original_expr = String(peek_pos->begin, peek_pos->end);

    ++pos;
    String value = getConvertedArgument(fn_name, pos);
    if (value.empty() && pos.isValid() && pos->type == TokenType::ClosingRoundBracket)
        return false;

    ++pos;
    String round_to = getConvertedArgument(fn_name, pos);
    if (round_to.empty())
        return false;

    round_to.erase(std::remove_if(round_to.begin(), round_to.end(), isspace), round_to.end());

    auto t = fmt::format("toFloat64({})", value);

    try
    {
        bin_size = std::stod(round_to);
    }
    catch (const std::exception &)
    {
        ParserKQLDateTypeTimespan time_span_parser;
        if (!time_span_parser.parseConstKQLTimespan(round_to))
            return false;
        bin_size = time_span_parser.toSeconds();
        round_to_is_timespan = true;
    }

    if (bin_size <= 0)
        return false;

    if ((original_expr == "datetime" || original_expr == "date") && !round_to_is_timespan)
    {
        out = fmt::format("toDateTime64(toInt64({0}/{1}) * {1}, 9, 'UTC')", t, bin_size);
    }
    else if (original_expr == "timespan" || original_expr == "time" || ParserKQLDateTypeTimespan().parseConstKQLTimespan(original_expr))
    {
        String bin_value = fmt::format("toInt64({0}/{1}) * {1}", t, bin_size);
        out = fmt::format(
            "concat(toString(toInt32((({}) as x) / 3600)),':', toString(toInt32(x % 3600 / 60)),':',toString(toInt32(x % 3600 % 60)))",
            bin_value);
    }
    else
    {
        out = fmt::format("toInt64({0} / {1}) * {1}", t, bin_size);
    }

Review comment (bug):
this seems to be a bug in original - it doesn't consider timespan argument like `bin(3.5h, 5)` - it will produce expression `toFloat64(3.5h)`
...at least what I see

Code after:

    ++pos;
    String round_to = getConvertedArgument(fn_name, pos);
    round_to.erase(std::remove_if(round_to.begin(), round_to.end(), [](unsigned char c) { return std::isspace(c); }), round_to.end());
    if (round_to.empty())
        return false;

    String value_no_spaces = value;
    value_no_spaces.erase(std::remove_if(value_no_spaces.begin(), value_no_spaces.end(), [](unsigned char c) { return std::isspace(c); }), value_no_spaces.end());
    if (value_no_spaces.empty())
        return false;
    try
    {
        size_t pos_end = 0;
        std::stod(value_no_spaces, &pos_end);
        if (pos_end != value_no_spaces.size())
            throw std::invalid_argument("not a number");
    }
    catch (const std::exception &)
    {
        ParserKQLDateTypeTimespan value_tsp;
        if (value_tsp.parseConstKQLTimespan(value_no_spaces))
            value = std::to_string(value_tsp.toSeconds());
    }

    auto t = fmt::format("toFloat64({})", value);

    try
    {
        bin_size = std::stod(round_to);
    }
    catch (const std::exception &)
    {
        ParserKQLDateTypeTimespan time_span_parser;
        if (!time_span_parser.parseConstKQLTimespan(round_to))
            return false;
        bin_size = time_span_parser.toSeconds();
    }

    if (bin_size <= 0)
        return false;

    // Use datetime output whenever first argument is datetime/date (whether bin size is numeric or timespan)
    if (original_expr == "datetime" || original_expr == "date")
    {
        out = fmt::format("toDateTime64(toInt64({0}/{1}) * {1}, 9, 'UTC')", t, bin_size);
    }
    else if (original_expr == "timespan" || original_expr == "time" || ParserKQLDateTypeTimespan().parseConstKQLTimespan(original_expr))
    {
        String bin_value = fmt::format("toInt64({0}/{1}) * {1}", t, bin_size);
        out = fmt::format(