This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 033fa2d test: construct temporal values from structural inputs (#267)
033fa2d is described below
commit 033fa2d2abd2b85c7f266bb9c4ee17fe980bb06d
Author: Junwang Zhao <[email protected]>
AuthorDate: Tue Oct 21 23:41:57 2025 +0800
test: construct temporal values from structural inputs (#267)
---
src/iceberg/test/bucket_util_test.cc | 59 +++++++++++----
src/iceberg/test/temporal_test_helper.h | 128 ++++++++++++++++++++++++++++++++
src/iceberg/test/transform_test.cc | 121 +++++++++++++++++++++---------
src/iceberg/util/temporal_util.cc | 1 +
4 files changed, 258 insertions(+), 51 deletions(-)
diff --git a/src/iceberg/test/bucket_util_test.cc
b/src/iceberg/test/bucket_util_test.cc
index 69a04ef..8c80f04 100644
--- a/src/iceberg/test/bucket_util_test.cc
+++ b/src/iceberg/test/bucket_util_test.cc
@@ -25,6 +25,7 @@
#include "iceberg/util/decimal.h"
#include "iceberg/util/uuid.h"
+#include "temporal_test_helper.h"
namespace iceberg {
@@ -41,27 +42,55 @@ TEST(BucketUtilsTest, HashHelper) {
EXPECT_EQ(BucketUtils::HashBytes(decimal->ToBigEndian()), -500754589);
// date hash
- std::chrono::sys_days sd = std::chrono::year{2017} / 11 / 16;
- std::chrono::sys_days epoch{std::chrono::year{1970} / 1 / 1};
- int32_t days = (sd - epoch).count();
- EXPECT_EQ(BucketUtils::HashInt(days), -653330422);
+ EXPECT_EQ(BucketUtils::HashInt(
+ TemporalTestHelper::CreateDate({.year = 2017, .month = 11,
.day = 16})),
+ -653330422);
// time
- // 22:31:08 in microseconds
- int64_t time_micros = (22 * 3600 + 31 * 60 + 8) * 1000000LL;
- EXPECT_EQ(BucketUtils::HashLong(time_micros), -662762989);
+ EXPECT_EQ(BucketUtils::HashLong(
+ TemporalTestHelper::CreateTime({.hour = 22, .minute = 31,
.second = 8})),
+ -662762989);
// timestamp
// 2017-11-16T22:31:08 in microseconds
- std::chrono::system_clock::time_point tp =
- std::chrono::sys_days{std::chrono::year{2017} / 11 / 16} +
std::chrono::hours{22} +
- std::chrono::minutes{31} + std::chrono::seconds{8};
- int64_t timestamp_micros =
-
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch())
- .count();
- EXPECT_EQ(BucketUtils::HashLong(timestamp_micros), -2047944441);
+ EXPECT_EQ(
+ BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp(
+ {.year = 2017, .month = 11, .day = 16, .hour = 22, .minute = 31,
.second = 8})),
+ -2047944441);
+
// 2017-11-16T22:31:08.000001 in microseconds
- EXPECT_EQ(BucketUtils::HashLong(timestamp_micros + 1), -1207196810);
+ EXPECT_EQ(
+ BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp({.year = 2017,
+ .month = 11,
+ .day = 16,
+ .hour = 22,
+ .minute = 31,
+ .second = 8,
+ .microsecond
= 1})),
+ -1207196810);
+
+ // 2017-11-16T14:31:08-08:00 in microseconds
+ EXPECT_EQ(BucketUtils::HashLong(
+ TemporalTestHelper::CreateTimestampTz({.year = 2017,
+ .month = 11,
+ .day = 16,
+ .hour = 14,
+ .minute = 31,
+ .second = 8,
+ .tz_offset_minutes =
-480})),
+ -2047944441);
+
+ // 2017-11-16T14:31:08.000001-08:00 in microseconds
+ EXPECT_EQ(BucketUtils::HashLong(
+ TemporalTestHelper::CreateTimestampTz({.year = 2017,
+ .month = 11,
+ .day = 16,
+ .hour = 14,
+ .minute = 31,
+ .second = 8,
+ .microsecond = 1,
+ .tz_offset_minutes =
-480})),
+ -1207196810);
// string
std::string str = "iceberg";
diff --git a/src/iceberg/test/temporal_test_helper.h
b/src/iceberg/test/temporal_test_helper.h
new file mode 100644
index 0000000..0f29048
--- /dev/null
+++ b/src/iceberg/test/temporal_test_helper.h
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+
+namespace iceberg {
+
+using namespace std::chrono; // NOLINT
+
+struct DateParts {
+ int32_t year{0};
+ uint8_t month{0};
+ uint8_t day{0};
+};
+
+struct TimeParts {
+ int32_t hour{0};
+ int32_t minute{0};
+ int32_t second{0};
+ int32_t microsecond{0};
+};
+
+struct TimestampParts {
+ int32_t year{0};
+ uint8_t month{0};
+ uint8_t day{0};
+ int32_t hour{0};
+ int32_t minute{0};
+ int32_t second{0};
+ int32_t microsecond{0};
+ // e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
+ int32_t tz_offset_minutes{0};
+};
+
+struct TimestampNanosParts {
+ int32_t year{0};
+ uint8_t month{0};
+ uint8_t day{0};
+ int32_t hour{0};
+ int32_t minute{0};
+ int32_t second{0};
+ int32_t nanosecond{0};
+ // e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
+ int32_t tz_offset_minutes{0};
+};
+
+class TemporalTestHelper {
+ static constexpr auto kEpochDays = sys_days(year{1970} / January / 1);
+
+ public:
+ /// \brief Construct a Calendar date without timezone or time
+ static int32_t CreateDate(const DateParts& parts) {
+ return static_cast<int32_t>(
+ (sys_days(year{parts.year} / month{parts.month} / day{parts.day}) -
kEpochDays)
+ .count());
+ }
+
+ /// \brief Construct a time-of-day, microsecond precision, without date,
timezone
+ static int64_t CreateTime(const TimeParts& parts) {
+ return duration_cast<microseconds>(hours(parts.hour) +
minutes(parts.minute) +
+ seconds(parts.second) +
+ microseconds(parts.microsecond))
+ .count();
+ }
+
+ /// \brief Construct a timestamp, microsecond precision, without timezone
+ static int64_t CreateTimestamp(const TimestampParts& parts) {
+ year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+ auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
+ minutes{parts.minute} +
seconds{parts.second} +
+ microseconds{parts.microsecond})
+ .time_since_epoch()};
+ return tp.time_since_epoch().count();
+ }
+
+ /// \brief Construct a timestamp, microsecond precision, with timezone
+ static int64_t CreateTimestampTz(const TimestampParts& parts) {
+ year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+ auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
+ minutes{parts.minute} +
seconds{parts.second} +
+ microseconds{parts.microsecond} -
+ minutes{parts.tz_offset_minutes})
+ .time_since_epoch()};
+ return tp.time_since_epoch().count();
+ }
+
+ /// \brief Construct a timestamp, nanosecond precision, without timezone
+ static int64_t CreateTimestampNanos(const TimestampNanosParts& parts) {
+ year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+ auto tp =
+ sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} +
minutes{parts.minute} +
+ seconds{parts.second} +
nanoseconds{parts.nanosecond})
+ .time_since_epoch()};
+ return tp.time_since_epoch().count();
+ }
+
+ /// \brief Construct a timestamp, nanosecond precision, with timezone
+ static int64_t CreateTimestampTzNanos(const TimestampNanosParts& parts) {
+ year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+ auto tp =
+ sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} +
minutes{parts.minute} +
+ seconds{parts.second} +
nanoseconds{parts.nanosecond} -
+ minutes{parts.tz_offset_minutes})
+ .time_since_epoch()};
+ return tp.time_since_epoch().count();
+ }
+};
+
+} // namespace iceberg
diff --git a/src/iceberg/test/transform_test.cc
b/src/iceberg/test/transform_test.cc
index 1003b95..6d72bdc 100644
--- a/src/iceberg/test/transform_test.cc
+++ b/src/iceberg/test/transform_test.cc
@@ -27,10 +27,10 @@
#include <gtest/gtest.h>
#include "iceberg/expression/literal.h"
-#include "iceberg/transform_function.h"
#include "iceberg/type.h"
#include "iceberg/util/formatter.h" // IWYU pragma: keep
#include "matchers.h"
+#include "temporal_test_helper.h"
namespace iceberg {
@@ -315,25 +315,40 @@ INSTANTIATE_TEST_SUITE_P(
.source = Literal::Decimal(1420, 4, 2),
.expected = Literal::Int(3)},
TransformParam{.str = "Date",
- // 2017-11-16
.source_type = iceberg::date(),
- .source = Literal::Date(17486),
+ .source = Literal::Date(TemporalTestHelper::CreateDate(
+ {.year = 2017, .month = 11, .day = 16})),
.expected = Literal::Int(2)},
TransformParam{.str = "Time",
- // 22:31:08 in microseconds
.source_type = iceberg::time(),
- .source = Literal::Time(81068000000),
+ .source = Literal::Time(TemporalTestHelper::CreateTime(
+ {.hour = 22, .minute = 31, .second = 8})),
.expected = Literal::Int(3)},
TransformParam{.str = "Timestamp",
// 2017-11-16T22:31:08 in microseconds
.source_type = iceberg::timestamp(),
- .source = Literal::Timestamp(1510871468000000),
+ .source = Literal::Timestamp(
+ TemporalTestHelper::CreateTimestamp({.year = 2017,
+ .month = 11,
+ .day = 16,
+ .hour = 22,
+ .minute = 31,
+ .second = 8})),
.expected = Literal::Int(3)},
- TransformParam{.str = "TimestampTz",
- // 2017-11-16T22:31:08.000001 in microseconds
- .source_type = iceberg::timestamp_tz(),
- .source = Literal::TimestampTz(1510871468000001),
- .expected = Literal::Int(2)},
+ TransformParam{
+ .str = "TimestampTz",
+ // 2017-11-16T14:31:08.000001-08:00 in microseconds
+ .source_type = iceberg::timestamp_tz(),
+ .source = Literal::TimestampTz(
+ TemporalTestHelper::CreateTimestampTz({.year = 2017,
+ .month = 11,
+ .day = 16,
+ .hour = 14,
+ .minute = 31,
+ .second = 8,
+ .microsecond = 1,
+ .tz_offset_minutes =
-480})),
+ .expected = Literal::Int(2)},
TransformParam{.str = "String",
.source_type = iceberg::string(),
.source = Literal::String("iceberg"),
@@ -428,19 +443,36 @@ TEST_P(YearTransformTest, YearTransform) {
INSTANTIATE_TEST_SUITE_P(
YearTransformTests, YearTransformTest,
- ::testing::Values(TransformParam{.str = "Timestamp",
- // 2021-06-01T11:43:20Z
- .source_type = iceberg::timestamp(),
- .source =
Literal::Timestamp(1622547800000000),
- .expected = Literal::Int(2021)},
- TransformParam{.str = "TimestampTz",
- .source_type = iceberg::timestamp_tz(),
- .source =
Literal::TimestampTz(1622547800000000),
- .expected = Literal::Int(2021)},
- TransformParam{.str = "Date",
- .source_type = iceberg::date(),
- .source = Literal::Date(30000),
- .expected = Literal::Int(2052)}),
+ ::testing::Values(
+ TransformParam{.str = "Timestamp",
+ // 2021-06-01T11:43:20Z
+ .source_type = iceberg::timestamp(),
+ .source = Literal::Timestamp(
+ TemporalTestHelper::CreateTimestamp({.year = 2021,
+ .month = 6,
+ .day = 1,
+ .hour = 11,
+ .minute = 43,
+ .second =
20})),
+ .expected = Literal::Int(2021)},
+ TransformParam{
+ .str = "TimestampTz",
+ // 2021-01-01T07:43:20+08:00, which is 2020-12-31T23:43:20Z
+ .source_type = iceberg::timestamp_tz(),
+ .source = Literal::TimestampTz(
+ TemporalTestHelper::CreateTimestampTz({.year = 2021,
+ .month = 1,
+ .day = 1,
+ .hour = 7,
+ .minute = 43,
+ .second = 20,
+ .tz_offset_minutes =
480})),
+ .expected = Literal::Int(2020)},
+ TransformParam{.str = "Date",
+ .source_type = iceberg::date(),
+ .source = Literal::Date(TemporalTestHelper::CreateDate(
+ {.year = 2052, .month = 2, .day = 20})),
+ .expected = Literal::Int(2052)}),
[](const ::testing::TestParamInfo<TransformParam>& info) { return
info.param.str; });
class MonthTransformTest : public ::testing::TestWithParam<TransformParam> {};
@@ -495,18 +527,35 @@ TEST_P(DayTransformTest, DayTransform) {
INSTANTIATE_TEST_SUITE_P(
DayTransformTests, DayTransformTest,
- ::testing::Values(TransformParam{.str = "Timestamp",
- .source_type = iceberg::timestamp(),
- .source =
Literal::Timestamp(1622547800000000),
- .expected = Literal::Int(18779)},
- TransformParam{.str = "TimestampTz",
- .source_type = iceberg::timestamp_tz(),
- .source =
Literal::TimestampTz(1622547800000000),
- .expected = Literal::Int(18779)},
- TransformParam{.str = "Date",
- .source_type = iceberg::date(),
- .source = Literal::Date(30000),
- .expected = Literal::Int(30000)}),
+ ::testing::Values(
+ TransformParam{.str = "Timestamp",
+ .source_type = iceberg::timestamp(),
+ .source = Literal::Timestamp(
+ TemporalTestHelper::CreateTimestamp({.year = 2021,
+ .month = 6,
+ .day = 1,
+ .hour = 11,
+ .minute = 43,
+ .second =
20})),
+ .expected = Literal::Int(TemporalTestHelper::CreateDate(
+ {.year = 2021, .month = 6, .day = 1}))},
+ TransformParam{
+ .str = "TimestampTz",
+ .source_type = iceberg::timestamp_tz(),
+ .source = Literal::TimestampTz(
+ TemporalTestHelper::CreateTimestampTz({.year = 2021,
+ .month = 1,
+ .day = 1,
+ .hour = 7,
+ .minute = 43,
+ .second = 20,
+ .tz_offset_minutes =
480})),
+ .expected = Literal::Int(
+ TemporalTestHelper::CreateDate({.year = 2020, .month = 12,
.day = 31}))},
+ TransformParam{.str = "Date",
+ .source_type = iceberg::date(),
+ .source = Literal::Date(30000),
+ .expected = Literal::Int(30000)}),
[](const ::testing::TestParamInfo<TransformParam>& info) { return
info.param.str; });
class HourTransformTest : public ::testing::TestWithParam<TransformParam> {};
diff --git a/src/iceberg/util/temporal_util.cc
b/src/iceberg/util/temporal_util.cc
index 41748c9..0112e49 100644
--- a/src/iceberg/util/temporal_util.cc
+++ b/src/iceberg/util/temporal_util.cc
@@ -20,6 +20,7 @@
#include "iceberg/util/temporal_util.h"
#include <chrono>
+#include <cstdint>
#include <utility>
#include "iceberg/expression/literal.h"