blob: ec78d261434f0d108db5d9bf1c0efca719f3ff0d [file]
/*
* 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 "paimon/core/casting/casting_utils.h"
#include <memory>
#include "arrow/ipc/api.h"
#include "gtest/gtest.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/checked_cast.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {
class CastingUtilsTest : public ::testing::Test {
std::shared_ptr<arrow::MemoryPool> arrow_pool_ = GetArrowPool(GetDefaultPool());
};
TEST_F(CastingUtilsTest, TestDictionaryToString) {
auto dict =
arrow::ipc::internal::json::ArrayFromJSON(arrow::utf8(), R"(["foo", "bar", "bazr"])")
.ValueOrDie();
auto dict_type = arrow::dictionary(arrow::int32(), arrow::utf8());
auto indices =
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), "[1, 2, 0, 2, 0]").ValueOrDie();
std::shared_ptr<arrow::DictionaryArray> dict_array =
std::make_shared<arrow::DictionaryArray>(dict_type, indices, dict);
auto string_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::utf8(), R"(["bar", "bazr", "foo", "bazr", "foo"])")
.ValueOrDie();
auto pool = GetArrowPool(GetDefaultPool());
arrow::compute::CastOptions options = arrow::compute::CastOptions::Safe();
ASSERT_OK_AND_ASSIGN(
auto result_array,
CastingUtils::Cast(dict_array, /*target_type=*/arrow::utf8(), options, pool.get()));
ASSERT_TRUE(result_array->Equals(string_array));
}
TEST_F(CastingUtilsTest, TestDecodeDictionaryPreservesValueType) {
for (const auto& value_type : {arrow::utf8(), arrow::large_utf8(), arrow::binary(),
arrow::large_binary(), arrow::int32()}) {
SCOPED_TRACE(value_type->ToString());
const bool is_integer = value_type->id() == arrow::Type::INT32;
auto dictionary =
arrow::ipc::internal::json::ArrayFromJSON(
value_type, is_integer ? "[10, null, 20]" : R"(["a\u0000b", null, ""])")
.ValueOrDie();
const auto target_type =
value_type->id() == arrow::Type::LARGE_STRING ? arrow::utf8() : value_type;
auto expected = arrow::ipc::internal::json::ArrayFromJSON(
target_type, is_integer ? "[10, null, null, 20, 10]"
: R"(["a\u0000b", null, null, "", "a\u0000b"])")
.ValueOrDie();
for (const auto& index_type :
{arrow::int8(), arrow::int16(), arrow::int32(), arrow::int64()}) {
SCOPED_TRACE(index_type->ToString());
auto indices =
arrow::ipc::internal::json::ArrayFromJSON(index_type, "[2, 0, null, 1, 2, 0]")
.ValueOrDie();
auto array = arrow::DictionaryArray::FromArrays(indices, dictionary).ValueOrDie();
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::Array> decoded,
CastingUtils::DecodeDictionary(array->Slice(1), arrow_pool_.get()));
ASSERT_TRUE(decoded->ValidateFull().ok());
ASSERT_TRUE(decoded->Equals(expected)) << decoded->ToString();
}
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> unchanged,
CastingUtils::DecodeDictionary(dictionary, arrow_pool_.get()));
ASSERT_EQ(unchanged, dictionary);
}
}
TEST_F(CastingUtilsTest, TestDecodeBinaryDictionaryPreservesNonUtf8) {
const std::string bytes("\x00\xff\x80", 3);
arrow::BinaryBuilder builder;
ASSERT_TRUE(builder.Append(bytes).ok());
std::shared_ptr<arrow::Array> dictionary;
ASSERT_TRUE(builder.Finish(&dictionary).ok());
auto indices =
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), "[0, null, 0]").ValueOrDie();
auto array = arrow::DictionaryArray::FromArrays(indices, dictionary).ValueOrDie();
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> decoded,
CastingUtils::DecodeDictionary(array, arrow_pool_.get()));
ASSERT_TRUE(decoded->ValidateFull().ok());
ASSERT_EQ(decoded->type_id(), arrow::Type::BINARY);
auto binary = checked_pointer_cast<arrow::BinaryArray>(decoded);
ASSERT_EQ(bytes, binary->GetView(0));
ASSERT_TRUE(binary->IsNull(1));
ASSERT_EQ(bytes, binary->GetView(2));
}
TEST_F(CastingUtilsTest, TestTimestampToTimestampWithTimezone) {
// local no tz -> utc tz
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::timestamp(arrow::TimeUnit::SECOND), R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND, "Asia/Shanghai");
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
auto target_array =
arrow::ipc::internal::json::ArrayFromJSON(target_type, R"(["1969-12-31 16:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(target_array);
ASSERT_OK_AND_ASSIGN(auto result_array, CastingUtils::TimestampToTimestampWithTimezone(
src_array, target_ts_type, arrow_pool_.get()));
ASSERT_TRUE(target_array->Equals(result_array));
}
TEST_F(CastingUtilsTest, TestTimestampToTimestampWithTimezoneInvalid) {
// local no tz -> utc tz
{
auto src_array =
arrow::ipc::internal::json::ArrayFromJSON(arrow::timestamp(arrow::TimeUnit::SECOND),
R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::NANO, "Asia/Shanghai");
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
ASSERT_NOK_WITH_MSG(CastingUtils::TimestampToTimestampWithTimezone(
src_array, target_ts_type, arrow_pool_.get()),
"time unit of src and target type mismatch");
}
{
auto src_array =
arrow::ipc::internal::json::ArrayFromJSON(arrow::timestamp(arrow::TimeUnit::SECOND),
R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND);
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
ASSERT_NOK_WITH_MSG(
CastingUtils::TimestampToTimestampWithTimezone(src_array, target_ts_type,
arrow_pool_.get()),
"src value must be local time (no tz), target value must be UTC (with tz)");
}
{
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::timestamp(arrow::TimeUnit::SECOND),
R"(["2015-03-29 02:30:00", "2015-03-29 03:30:00"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND, "Europe/Warsaw");
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
ASSERT_NOK_WITH_MSG(CastingUtils::TimestampToTimestampWithTimezone(
src_array, target_ts_type, arrow_pool_.get()),
"Timestamp doesn't exist in timezone 'Europe/Warsaw': 2015-03-29 "
"02:30:00 is in a gap between");
}
}
TEST_F(CastingUtilsTest, TestTimestampWithTimezoneToTimestamp) {
// utc tz -> local no tz
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::timestamp(arrow::TimeUnit::SECOND, "Asia/Shanghai"),
R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND);
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
auto target_array =
arrow::ipc::internal::json::ArrayFromJSON(target_type, R"(["1970-01-01 08:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(target_array);
ASSERT_OK_AND_ASSIGN(auto result_array, CastingUtils::TimestampWithTimezoneToTimestamp(
src_array, target_ts_type, arrow_pool_.get()));
ASSERT_TRUE(target_array->Equals(result_array));
}
TEST_F(CastingUtilsTest, TestTimestampWithTimezoneToTimestampInvalid) {
// utc tz -> local no tz
{
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::timestamp(arrow::TimeUnit::NANO, "Asia/Shanghai"),
R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND);
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
ASSERT_NOK_WITH_MSG(CastingUtils::TimestampWithTimezoneToTimestamp(
src_array, target_ts_type, arrow_pool_.get()),
"in timezone converter, time unit of src and target type mismatch");
}
{
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(
arrow::timestamp(arrow::TimeUnit::SECOND, "Asia/Shanghai"),
R"(["1970-01-01 00:00:01"])")
.ValueOr(nullptr);
ASSERT_TRUE(src_array);
auto target_type = arrow::timestamp(arrow::TimeUnit::SECOND, "Asia/Tokyo");
auto target_ts_type = checked_pointer_cast<arrow::TimestampType>(target_type);
ASSERT_NOK_WITH_MSG(CastingUtils::TimestampWithTimezoneToTimestamp(
src_array, target_ts_type, arrow_pool_.get()),
"target value must be local time (no tz)");
}
}
} // namespace paimon::test