Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions be/src/vec/core/call_on_type_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ bool call_on_index_and_number_data_type(TypeIndex number, F&& f) {
case TypeIndex::Float64:
return f(TypePair<DataTypeNumber<Float64>, T>());

case TypeIndex::TimeV2:
return f(TypePair<DataTypeTimeV2, T>());

case TypeIndex::Decimal32:
return f(TypePair<DataTypeDecimal<Decimal32>, T>());
case TypeIndex::Decimal64:
Expand Down
74 changes: 49 additions & 25 deletions be/src/vec/functions/function_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,8 @@ struct ConvertImplToTimeType {
using FromFieldType = typename FromDataType::FieldType;
using ToFieldType = typename ToDataType::FieldType;

static Status execute(Block& block, const ColumnNumbers& arguments, uint32_t result,
size_t /*input_rows_count*/) {
static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments,
uint32_t result, size_t /*input_rows_count*/) {
const ColumnWithTypeAndName& named_from = block.get_by_position(arguments[0]);

using ColVecFrom =
Expand Down Expand Up @@ -528,29 +528,53 @@ struct ConvertImplToTimeType {
col_null_map_to = ColumnUInt8::create(size);
auto& vec_null_map_to = col_null_map_to->get_data();

UInt32 from_precision = 0;
UInt32 from_scale = 0;
UInt32 to_precision = NumberTraits::max_ascii_len<Int64>();
if constexpr (IsDecimalNumber<FromFieldType>) {
const auto& from_decimal_type = assert_cast<const FromDataType&>(*named_from.type);
from_precision = from_decimal_type.get_precision();
from_scale = from_decimal_type.get_scale();
if constexpr (std::is_same_v<FromDataType, DataTypeTimeV2>) {
DateValueType current_date_value;
current_date_value.from_unixtime(context->state()->timestamp_ms() / 1000,
context->state()->timezone_obj());
uint32_t scale = 0;
// Only DateTimeV2 has scale
if (std::is_same_v<ToDataType, DataTypeDateTimeV2>) {
scale = remove_nullable(block.get_by_position(result).type)->get_scale();
}
// According to MySQL rules, when casting time type to date/datetime,
// the current date is added to the time
// So here we need to clear the time part
current_date_value.reset_time_part();
for (size_t i = 0; i < size; ++i) {
auto& date_value = reinterpret_cast<DateValueType&>(vec_to[i]);
date_value = current_date_value;
int64_t microsecond = TimeValue::round_time(vec_from[i], scale);
// Only TimeV2 type needs microseconds
if constexpr (IsTimeV2Type<ToDataType>) {
vec_null_map_to[i] = !date_value.template date_add_interval<MICROSECOND>(
TimeInterval {MICROSECOND, microsecond, false});
} else {
vec_null_map_to[i] =
!date_value.template date_add_interval<SECOND>(TimeInterval {
SECOND, microsecond / TimeValue::ONE_SECOND_MICROSECONDS,
false});
}

// DateType of VecDateTimeValue should cast to date
if constexpr (IsDateType<ToDataType>) {
date_value.cast_to_date();
} else if constexpr (IsDateTimeType<ToDataType>) {
date_value.to_datetime();
}
}
} else {
for (size_t i = 0; i < size; ++i) {
auto& date_value = reinterpret_cast<DateValueType&>(vec_to[i]);
vec_null_map_to[i] = !date_value.from_date_int64(int64_t(vec_from[i]));
// DateType of VecDateTimeValue should cast to date
if constexpr (IsDateType<ToDataType>) {
date_value.cast_to_date();
} else if constexpr (IsDateTimeType<ToDataType>) {
date_value.to_datetime();
}
}
}
bool narrow_integral = to_precision < (from_precision - from_scale);
std::visit(
[&](auto narrow_integral) {
for (size_t i = 0; i < size; ++i) {
auto& date_value = reinterpret_cast<DateValueType&>(vec_to[i]);
vec_null_map_to[i] = !date_value.from_date_int64(int64_t(vec_from[i]));
// DateType of VecDateTimeValue should cast to date
if constexpr (IsDateType<ToDataType>) {
date_value.cast_to_date();
} else if constexpr (IsDateTimeType<ToDataType>) {
date_value.to_datetime();
}
}
},
make_bool_variant(narrow_integral));
block.get_by_position(result).column =
ColumnNullable::create(std::move(col_to), std::move(col_null_map_to));
} else {
Expand Down Expand Up @@ -1531,7 +1555,7 @@ class FunctionConvertToTimeType : public IFunction {
using RightDataType = typename Types::RightType;

ret_status = ConvertImplToTimeType<LeftDataType, RightDataType, Name>::execute(
block, arguments, result, input_rows_count);
context, block, arguments, result, input_rows_count);
return true;
};

Expand Down
10 changes: 10 additions & 0 deletions be/src/vec/runtime/time_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ class TimeValue {
constexpr static int64_t ONE_HOUR_MICROSECONDS = 60 * ONE_MINUTE_MICROSECONDS;
constexpr static int64_t ONE_MINUTE_SECONDS = 60;
constexpr static int64_t ONE_HOUR_SECONDS = 60 * ONE_MINUTE_SECONDS;
constexpr static uint32_t MICROS_SCALE = 6;

using TimeType = typename PrimitiveTypeTraits<TYPE_TIMEV2>::CppType;
using ColumnTime = vectorized::DataTypeTimeV2::ColumnType;

static int64_t round_time(TimeType value, uint32_t scale) {
int64_t time = value;
DCHECK(scale <= MICROS_SCALE);
int64_t factor = std::pow(10, 6 - scale);
int64_t roundedValue = (time >= 0) ? (time + factor / 2) / factor * factor
: (time - factor / 2) / factor * factor;
return roundedValue;
}

// refer to https://dev.mysql.com/doc/refman/5.7/en/time.html
// the time value between '-838:59:59' and '838:59:59'
/// TODO: Why is the time type stored as double? Can we directly use int64 and remove the time limit?
Expand Down
15 changes: 15 additions & 0 deletions be/src/vec/runtime/vdatetime_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,12 @@ class VecDateTimeValue { // Now this type is a temp solution with little changes
return _hour * SECOND_PER_HOUR + _minute * SECOND_PER_MINUTE + _second;
}

void reset_time_part() {
_hour = 0;
_minute = 0;
_second = 0;
}

bool check_loss_accuracy_cast_to_date() {
auto loss_accuracy = _hour != 0 || _minute != 0 || _second != 0;
cast_to_date();
Expand Down Expand Up @@ -960,6 +966,15 @@ class DateV2Value {
return hour() * SECOND_PER_HOUR + minute() * SECOND_PER_MINUTE + second();
}

void reset_time_part() {
if constexpr (is_datetime) {
date_v2_value_.hour_ = 0;
date_v2_value_.minute_ = 0;
date_v2_value_.second_ = 0;
date_v2_value_.microsecond_ = 0;
}
}

int64_t time_part_to_microsecond() const {
return time_part_to_seconds() * 1000 * 1000 + microsecond();
}
Expand Down
50 changes: 50 additions & 0 deletions be/test/runtime/time_value_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#include <cctz/civil_time.h>
#include <cctz/time_zone.h>
#include <gtest/gtest.h>
#include <vec/runtime/time_value.h>

namespace doris {

TEST(TimeValueTest, make_time) {
int64_t hour = 1;
int64_t minute = 2;
int64_t second = 3;
TimeValue::TimeType time = TimeValue::make_time(hour, minute, second);
EXPECT_EQ(time, 3723000000);
}

TEST(TimeValueTest, round_time) {
// 01:02:03.500000 -> 01:02:04.000000
EXPECT_EQ(TimeValue::round_time(TimeValue::make_time(1, 2, 3, 500000), 0),
TimeValue::make_time(1, 2, 4));

// 01:02:03.499999 -> 01:01:03.000000
EXPECT_EQ(TimeValue::round_time(TimeValue::make_time(1, 2, 3, 499999), 0),
TimeValue::make_time(1, 2, 3));

// -01:02:03.500000 -> -01:01:04.000000
EXPECT_EQ(TimeValue::round_time(-TimeValue::make_time(1, 2, 3, 500000), 0),
-TimeValue::make_time(1, 2, 4));

// -01:02:03.499999 -> -01:01:03.000000
EXPECT_EQ(TimeValue::round_time(-TimeValue::make_time(1, 2, 3, 499999), 0),
-TimeValue::make_time(1, 2, 3));
}
} // namespace doris
137 changes: 137 additions & 0 deletions be/test/vec/function/function_time_cast_to_date_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#include <cctz/civil_time.h>
#include <cctz/time_zone.h>
#include <gtest/gtest.h>
#include <vec/runtime/time_value.h>

#include <memory>

#include "testutil/column_helper.h"
#include "testutil/mock/mock_runtime_state.h"
#include "vec/data_types/data_type.h"
#include "vec/data_types/data_type_nullable.h"
#include "vec/data_types/data_type_time_v2.h"
#include "vec/functions/function_cast.h"

namespace doris::vectorized {

struct MockFunctionCastFromTimeToDateName {
static constexpr auto name = "FunctionCastFromTimeToDate";
};

struct FunctionCastFromTimeToDate : public ::testing::Test {
void SetUp() override {
// 2025-3-25 11:45:14
state._timestamp_ms = 1742874314000;
context._state = &state;
to_type = nullptr;
from_data.clear();
expected.clear();
}

template <typename ToData>
void test() {
using Convert =
ConvertImplToTimeType<DataTypeTimeV2, ToData, MockFunctionCastFromTimeToDateName>;

Block block = ColumnHelper::create_block<DataTypeTimeV2>(from_data);
block.insert(ColumnWithTypeAndName {nullptr, to_type, "to"});
auto st = Convert::execute(&context, block, ColumnNumbers {0}, 1, from_data.size());

EXPECT_TRUE(st) << st.ok();
for (int i = 0; i < expected.size(); i++) {
auto column = block.get_by_position(1).column;
EXPECT_EQ(to_type->to_string(*column, i), expected[i])
<< " time value : " << DataTypeTimeV2 {}.to_string(from_data[i]);
}
}

void add_case(TimeValue::TimeType time, std::string str) {
from_data.push_back(time);
expected.push_back(str);
}

using FromType = DataTypeDateTimeV2;
MockRuntimeState state;
FunctionContext context;

DataTypePtr to_type;
std::vector<TimeValue::TimeType> from_data;
std::vector<std::string> expected;
};

TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_6) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDateTimeV2>(6));
add_case(TimeValue::make_time(1, 2, 3, 123456), "2025-03-25 01:02:03.123456");
add_case(TimeValue::make_time(1, 2, 3, 654321), "2025-03-25 01:02:03.654321");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:56.876544");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56.345679");
test<DataTypeDateTimeV2>();
}

TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_3) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDateTimeV2>(3));
add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03.124");
add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00.000");
add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03.123");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:56.877");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56.346");
test<DataTypeDateTimeV2>();
}

TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_0) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDateTimeV2>(0));
add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03");
add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00");
add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:57");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56");
test<DataTypeDateTimeV2>();
}

TEST_F(FunctionCastFromTimeToDate, time_to_datetimev1_0) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDateTime>());
add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03");
add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00");
add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:57");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56");
test<DataTypeDateTime>();
}

TEST_F(FunctionCastFromTimeToDate, time_to_datetv2) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDateV2>());
add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25");
add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25");
add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24");
test<DataTypeDateV2>();
}

TEST_F(FunctionCastFromTimeToDate, time_to_datetv1) {
to_type = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeDate>());
add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25");
add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25");
add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25");
add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24");
add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24");
test<DataTypeDate>();
}
} // namespace doris::vectorized
Original file line number Diff line number Diff line change
Expand Up @@ -620,13 +620,21 @@ public static ImmutableSetMultimap<PrimitiveType, PrimitiveType> getImplicitCast
builder.put(TIME, DOUBLE);
builder.put(TIME, VARCHAR);
builder.put(TIME, STRING);
builder.put(TIME, DATE);
builder.put(TIME, DATETIME);
builder.put(TIME, DATEV2);
builder.put(TIME, DATETIMEV2);

//TIMEV2
// TIMEV2
builder.put(TIMEV2, TIME);
builder.put(TIMEV2, TIMEV2);
builder.put(TIMEV2, DOUBLE);
builder.put(TIMEV2, VARCHAR);
builder.put(TIMEV2, STRING);
builder.put(TIMEV2, DATE);
builder.put(TIMEV2, DATETIME);
builder.put(TIMEV2, DATEV2);
builder.put(TIMEV2, DATETIMEV2);

implicitCastMap = builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

suite("test_cast_time_to_datetime") {
def result1 = sql """ select datediff(now(), from_unixtime(cast(1742194502 as bigint),'yyyy-MM-dd HH:mm:ss')); """
def result2 = sql """ select datediff(current_time(), from_unixtime(cast(1742194502 as bigint),'yyyy-MM-dd HH:mm:ss')); """
assertEquals(result1[0][0], result2[0][0], "The results of the two SQL queries should be the same.")
}
Loading