Repository: ClickHouse/ClickHouse
File: src/Storages/MergeTree/KeyCondition.cpp (C++)

Code before:
        }
    }
    out_column = result_column;
    out_data_type = result_type;

    return true;
}

bool KeyCondition::isFunctionReallyMonotonic(const IFunctionBase & func, const IDataType & arg_type) const
{
    const IDataType * type = &arg_type;
    if (const auto * lowcard_type = typeid_cast<const DataTypeLowCardinality *>(type))
        type = lowcard_type->getDictionaryType().get();
    if (const auto * nullable_type = typeid_cast<const DataTypeNullable *>(type))
        type = nullable_type->getNestedType().get();

    /// toDateTime(date) may overflow, breaking monotonicity.
    if (func.getName() == "toDateTime" && isDateOrDate32(type))
        return false;

    /// signed-to-unsigned overflow: pre-epoch DateTime64/Date32 wraps in Date/DateTime
    /// any function that returns Date or DateTime from DateTime64 or Date32 can overflow on negative values
    if (isDate(type) || isDateTime(type))
    {
        const auto & arg_types = func.getArgumentTypes();
        const IDataType * input_type = !arg_types.empty() ? arg_types[0].get() : nullptr;
        if (input_type)
        {
            if (const auto * lc = typeid_cast<const DataTypeLowCardinality *>(input_type))
                input_type = lc->getDictionaryType().get();
            if (const auto * nt = typeid_cast<const DataTypeNullable *>(input_type))
                input_type = nt->getNestedType().get();
            if (isDateTime64(input_type) || isDate32(input_type))
                return false;
        }
    }

    return true;
}

bool KeyCondition::canConstantBeWrappedByMonotonicFunctions(
    const RPNBuilderTreeNode & node,
    const BuildInfo & info,
    size_t & out_key_column_num,
    DataTypePtr & out_key_column_type,
    Field & out_value,
    DataTypePtr & out_type)
{
    String expr_name = node.getColumnName();

    if (!info.key_subexpr_names.contains(expr_name))

Review comment (suggestion):
We should use `getArgumentTypeOfMonotonicFunction(func)` to get the `input_type` because the argument could be in the first or second position.

Code after:
                if (value < 0)
                    return false;

                UInt32 scale = assert_cast<const DataTypeDateTime64 &>(*arg_type_inner).getScale();
                Int64 seconds = value / intExp10OfSize<Int64>(scale);

                /// timestamps beyond the target range -> small values
                if (isDate(result_type_inner) && seconds >= static_cast<Int64>(DATE_LUT_MAX_DAY_NUM) * 86400)
                    return false;
                if (isDateTime(result_type_inner) && seconds >= DATE_LUT_MAX)
                    return false;
            }
            else if (isDate32(arg_type_inner))
            {
                /// day numbers as Int32 -> Date only fits [0, 65535],
                /// DateTime only fits seconds up to DATE_LUT_MAX
                auto value = (*result_column)[0].safeGet<Int32>();
                if (value < 0)
                    return false;
                if (isDate(result_type_inner) && value > DATE_LUT_MAX_DAY_NUM)
                    return false;
                if (isDateTime(result_type_inner) && value * 86400LL >= DATE_LUT_MAX)
                    return false;
            }
        }

        result_column = func->execute({{result_column, argument_type, ""}}, func_result_type, result_column->size(), /* dry_run = */ false);
        result_column = result_column->convertToFullColumnIfLowCardinality();
        result_type = removeLowCardinality(func_result_type);

        /// the result itself sits at the type's max — next value wraps so any >= / <= condition on
        /// it would give wrong partition ranges -> this catches timezone edge cases that pre-execution check might miss
        if (isDate(result_type_inner))
        {
            if (result_column->getUInt(0) >= DATE_LUT_MAX_DAY_NUM)
                return false;
        }
        else if (isDateTime(result_type_inner))
        {
            if (result_column->get64(0) >= DATE_LUT_MAX)
                return false;
        }

        // Transforming nullable columns to the nested ones, in case no nulls found
        if (result_column->isNullable())
        {
            const auto & result_column_nullable = assert_cast<const ColumnNullable &>(*result_column);
            const auto & null_map_data = result_column_nullable.getNullMapData();
            for (char8_t i : null_map_data)
            {
                if (i != 0)