Skip to content

Commit 033fa2d

Browse files
authored
test: construct temporal values from structural inputs (#267)
1 parent dc73627 commit 033fa2d

File tree

4 files changed

+258
-51
lines changed

4 files changed

+258
-51
lines changed

src/iceberg/test/bucket_util_test.cc

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "iceberg/util/decimal.h"
2727
#include "iceberg/util/uuid.h"
28+
#include "temporal_test_helper.h"
2829

2930
namespace iceberg {
3031

@@ -41,27 +42,55 @@ TEST(BucketUtilsTest, HashHelper) {
4142
EXPECT_EQ(BucketUtils::HashBytes(decimal->ToBigEndian()), -500754589);
4243

4344
// date hash
44-
std::chrono::sys_days sd = std::chrono::year{2017} / 11 / 16;
45-
std::chrono::sys_days epoch{std::chrono::year{1970} / 1 / 1};
46-
int32_t days = (sd - epoch).count();
47-
EXPECT_EQ(BucketUtils::HashInt(days), -653330422);
45+
EXPECT_EQ(BucketUtils::HashInt(
46+
TemporalTestHelper::CreateDate({.year = 2017, .month = 11, .day = 16})),
47+
-653330422);
4848

4949
// time
50-
// 22:31:08 in microseconds
51-
int64_t time_micros = (22 * 3600 + 31 * 60 + 8) * 1000000LL;
52-
EXPECT_EQ(BucketUtils::HashLong(time_micros), -662762989);
50+
EXPECT_EQ(BucketUtils::HashLong(
51+
TemporalTestHelper::CreateTime({.hour = 22, .minute = 31, .second = 8})),
52+
-662762989);
5353

5454
// timestamp
5555
// 2017-11-16T22:31:08 in microseconds
56-
std::chrono::system_clock::time_point tp =
57-
std::chrono::sys_days{std::chrono::year{2017} / 11 / 16} + std::chrono::hours{22} +
58-
std::chrono::minutes{31} + std::chrono::seconds{8};
59-
int64_t timestamp_micros =
60-
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch())
61-
.count();
62-
EXPECT_EQ(BucketUtils::HashLong(timestamp_micros), -2047944441);
56+
EXPECT_EQ(
57+
BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp(
58+
{.year = 2017, .month = 11, .day = 16, .hour = 22, .minute = 31, .second = 8})),
59+
-2047944441);
60+
6361
// 2017-11-16T22:31:08.000001 in microseconds
64-
EXPECT_EQ(BucketUtils::HashLong(timestamp_micros + 1), -1207196810);
62+
EXPECT_EQ(
63+
BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp({.year = 2017,
64+
.month = 11,
65+
.day = 16,
66+
.hour = 22,
67+
.minute = 31,
68+
.second = 8,
69+
.microsecond = 1})),
70+
-1207196810);
71+
72+
// 2017-11-16T14:31:08-08:00 in microseconds
73+
EXPECT_EQ(BucketUtils::HashLong(
74+
TemporalTestHelper::CreateTimestampTz({.year = 2017,
75+
.month = 11,
76+
.day = 16,
77+
.hour = 14,
78+
.minute = 31,
79+
.second = 8,
80+
.tz_offset_minutes = -480})),
81+
-2047944441);
82+
83+
// 2017-11-16T14:31:08.000001-08:00 in microseconds
84+
EXPECT_EQ(BucketUtils::HashLong(
85+
TemporalTestHelper::CreateTimestampTz({.year = 2017,
86+
.month = 11,
87+
.day = 16,
88+
.hour = 14,
89+
.minute = 31,
90+
.second = 8,
91+
.microsecond = 1,
92+
.tz_offset_minutes = -480})),
93+
-1207196810);
6594

6695
// string
6796
std::string str = "iceberg";
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <chrono>
23+
#include <cstdint>
24+
25+
namespace iceberg {
26+
27+
using namespace std::chrono; // NOLINT
28+
29+
struct DateParts {
30+
int32_t year{0};
31+
uint8_t month{0};
32+
uint8_t day{0};
33+
};
34+
35+
struct TimeParts {
36+
int32_t hour{0};
37+
int32_t minute{0};
38+
int32_t second{0};
39+
int32_t microsecond{0};
40+
};
41+
42+
struct TimestampParts {
43+
int32_t year{0};
44+
uint8_t month{0};
45+
uint8_t day{0};
46+
int32_t hour{0};
47+
int32_t minute{0};
48+
int32_t second{0};
49+
int32_t microsecond{0};
50+
// e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
51+
int32_t tz_offset_minutes{0};
52+
};
53+
54+
struct TimestampNanosParts {
55+
int32_t year{0};
56+
uint8_t month{0};
57+
uint8_t day{0};
58+
int32_t hour{0};
59+
int32_t minute{0};
60+
int32_t second{0};
61+
int32_t nanosecond{0};
62+
// e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
63+
int32_t tz_offset_minutes{0};
64+
};
65+
66+
class TemporalTestHelper {
67+
static constexpr auto kEpochDays = sys_days(year{1970} / January / 1);
68+
69+
public:
70+
/// \brief Construct a Calendar date without timezone or time
71+
static int32_t CreateDate(const DateParts& parts) {
72+
return static_cast<int32_t>(
73+
(sys_days(year{parts.year} / month{parts.month} / day{parts.day}) - kEpochDays)
74+
.count());
75+
}
76+
77+
/// \brief Construct a time-of-day, microsecond precision, without date, timezone
78+
static int64_t CreateTime(const TimeParts& parts) {
79+
return duration_cast<microseconds>(hours(parts.hour) + minutes(parts.minute) +
80+
seconds(parts.second) +
81+
microseconds(parts.microsecond))
82+
.count();
83+
}
84+
85+
/// \brief Construct a timestamp, microsecond precision, without timezone
86+
static int64_t CreateTimestamp(const TimestampParts& parts) {
87+
year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
88+
auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
89+
minutes{parts.minute} + seconds{parts.second} +
90+
microseconds{parts.microsecond})
91+
.time_since_epoch()};
92+
return tp.time_since_epoch().count();
93+
}
94+
95+
/// \brief Construct a timestamp, microsecond precision, with timezone
96+
static int64_t CreateTimestampTz(const TimestampParts& parts) {
97+
year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
98+
auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
99+
minutes{parts.minute} + seconds{parts.second} +
100+
microseconds{parts.microsecond} -
101+
minutes{parts.tz_offset_minutes})
102+
.time_since_epoch()};
103+
return tp.time_since_epoch().count();
104+
}
105+
106+
/// \brief Construct a timestamp, nanosecond precision, without timezone
107+
static int64_t CreateTimestampNanos(const TimestampNanosParts& parts) {
108+
year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
109+
auto tp =
110+
sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} + minutes{parts.minute} +
111+
seconds{parts.second} + nanoseconds{parts.nanosecond})
112+
.time_since_epoch()};
113+
return tp.time_since_epoch().count();
114+
}
115+
116+
/// \brief Construct a timestamp, nanosecond precision, with timezone
117+
static int64_t CreateTimestampTzNanos(const TimestampNanosParts& parts) {
118+
year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
119+
auto tp =
120+
sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} + minutes{parts.minute} +
121+
seconds{parts.second} + nanoseconds{parts.nanosecond} -
122+
minutes{parts.tz_offset_minutes})
123+
.time_since_epoch()};
124+
return tp.time_since_epoch().count();
125+
}
126+
};
127+
128+
} // namespace iceberg

src/iceberg/test/transform_test.cc

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
#include <gtest/gtest.h>
2828

2929
#include "iceberg/expression/literal.h"
30-
#include "iceberg/transform_function.h"
3130
#include "iceberg/type.h"
3231
#include "iceberg/util/formatter.h" // IWYU pragma: keep
3332
#include "matchers.h"
33+
#include "temporal_test_helper.h"
3434

3535
namespace iceberg {
3636

@@ -315,25 +315,40 @@ INSTANTIATE_TEST_SUITE_P(
315315
.source = Literal::Decimal(1420, 4, 2),
316316
.expected = Literal::Int(3)},
317317
TransformParam{.str = "Date",
318-
// 2017-11-16
319318
.source_type = iceberg::date(),
320-
.source = Literal::Date(17486),
319+
.source = Literal::Date(TemporalTestHelper::CreateDate(
320+
{.year = 2017, .month = 11, .day = 16})),
321321
.expected = Literal::Int(2)},
322322
TransformParam{.str = "Time",
323-
// 22:31:08 in microseconds
324323
.source_type = iceberg::time(),
325-
.source = Literal::Time(81068000000),
324+
.source = Literal::Time(TemporalTestHelper::CreateTime(
325+
{.hour = 22, .minute = 31, .second = 8})),
326326
.expected = Literal::Int(3)},
327327
TransformParam{.str = "Timestamp",
328328
// 2017-11-16T22:31:08 in microseconds
329329
.source_type = iceberg::timestamp(),
330-
.source = Literal::Timestamp(1510871468000000),
330+
.source = Literal::Timestamp(
331+
TemporalTestHelper::CreateTimestamp({.year = 2017,
332+
.month = 11,
333+
.day = 16,
334+
.hour = 22,
335+
.minute = 31,
336+
.second = 8})),
331337
.expected = Literal::Int(3)},
332-
TransformParam{.str = "TimestampTz",
333-
// 2017-11-16T22:31:08.000001 in microseconds
334-
.source_type = iceberg::timestamp_tz(),
335-
.source = Literal::TimestampTz(1510871468000001),
336-
.expected = Literal::Int(2)},
338+
TransformParam{
339+
.str = "TimestampTz",
340+
// 2017-11-16T14:31:08.000001-08:00 in microseconds
341+
.source_type = iceberg::timestamp_tz(),
342+
.source = Literal::TimestampTz(
343+
TemporalTestHelper::CreateTimestampTz({.year = 2017,
344+
.month = 11,
345+
.day = 16,
346+
.hour = 14,
347+
.minute = 31,
348+
.second = 8,
349+
.microsecond = 1,
350+
.tz_offset_minutes = -480})),
351+
.expected = Literal::Int(2)},
337352
TransformParam{.str = "String",
338353
.source_type = iceberg::string(),
339354
.source = Literal::String("iceberg"),
@@ -428,19 +443,36 @@ TEST_P(YearTransformTest, YearTransform) {
428443

429444
INSTANTIATE_TEST_SUITE_P(
430445
YearTransformTests, YearTransformTest,
431-
::testing::Values(TransformParam{.str = "Timestamp",
432-
// 2021-06-01T11:43:20Z
433-
.source_type = iceberg::timestamp(),
434-
.source = Literal::Timestamp(1622547800000000),
435-
.expected = Literal::Int(2021)},
436-
TransformParam{.str = "TimestampTz",
437-
.source_type = iceberg::timestamp_tz(),
438-
.source = Literal::TimestampTz(1622547800000000),
439-
.expected = Literal::Int(2021)},
440-
TransformParam{.str = "Date",
441-
.source_type = iceberg::date(),
442-
.source = Literal::Date(30000),
443-
.expected = Literal::Int(2052)}),
446+
::testing::Values(
447+
TransformParam{.str = "Timestamp",
448+
// 2021-06-01T11:43:20Z
449+
.source_type = iceberg::timestamp(),
450+
.source = Literal::Timestamp(
451+
TemporalTestHelper::CreateTimestamp({.year = 2021,
452+
.month = 6,
453+
.day = 1,
454+
.hour = 11,
455+
.minute = 43,
456+
.second = 20})),
457+
.expected = Literal::Int(2021)},
458+
TransformParam{
459+
.str = "TimestampTz",
460+
// 2021-01-01T07:43:20+08:00, which is 2020-12-31T23:43:20Z
461+
.source_type = iceberg::timestamp_tz(),
462+
.source = Literal::TimestampTz(
463+
TemporalTestHelper::CreateTimestampTz({.year = 2021,
464+
.month = 1,
465+
.day = 1,
466+
.hour = 7,
467+
.minute = 43,
468+
.second = 20,
469+
.tz_offset_minutes = 480})),
470+
.expected = Literal::Int(2020)},
471+
TransformParam{.str = "Date",
472+
.source_type = iceberg::date(),
473+
.source = Literal::Date(TemporalTestHelper::CreateDate(
474+
{.year = 2052, .month = 2, .day = 20})),
475+
.expected = Literal::Int(2052)}),
444476
[](const ::testing::TestParamInfo<TransformParam>& info) { return info.param.str; });
445477

446478
class MonthTransformTest : public ::testing::TestWithParam<TransformParam> {};
@@ -495,18 +527,35 @@ TEST_P(DayTransformTest, DayTransform) {
495527

496528
INSTANTIATE_TEST_SUITE_P(
497529
DayTransformTests, DayTransformTest,
498-
::testing::Values(TransformParam{.str = "Timestamp",
499-
.source_type = iceberg::timestamp(),
500-
.source = Literal::Timestamp(1622547800000000),
501-
.expected = Literal::Int(18779)},
502-
TransformParam{.str = "TimestampTz",
503-
.source_type = iceberg::timestamp_tz(),
504-
.source = Literal::TimestampTz(1622547800000000),
505-
.expected = Literal::Int(18779)},
506-
TransformParam{.str = "Date",
507-
.source_type = iceberg::date(),
508-
.source = Literal::Date(30000),
509-
.expected = Literal::Int(30000)}),
530+
::testing::Values(
531+
TransformParam{.str = "Timestamp",
532+
.source_type = iceberg::timestamp(),
533+
.source = Literal::Timestamp(
534+
TemporalTestHelper::CreateTimestamp({.year = 2021,
535+
.month = 6,
536+
.day = 1,
537+
.hour = 11,
538+
.minute = 43,
539+
.second = 20})),
540+
.expected = Literal::Int(TemporalTestHelper::CreateDate(
541+
{.year = 2021, .month = 6, .day = 1}))},
542+
TransformParam{
543+
.str = "TimestampTz",
544+
.source_type = iceberg::timestamp_tz(),
545+
.source = Literal::TimestampTz(
546+
TemporalTestHelper::CreateTimestampTz({.year = 2021,
547+
.month = 1,
548+
.day = 1,
549+
.hour = 7,
550+
.minute = 43,
551+
.second = 20,
552+
.tz_offset_minutes = 480})),
553+
.expected = Literal::Int(
554+
TemporalTestHelper::CreateDate({.year = 2020, .month = 12, .day = 31}))},
555+
TransformParam{.str = "Date",
556+
.source_type = iceberg::date(),
557+
.source = Literal::Date(30000),
558+
.expected = Literal::Int(30000)}),
510559
[](const ::testing::TestParamInfo<TransformParam>& info) { return info.param.str; });
511560

512561
class HourTransformTest : public ::testing::TestWithParam<TransformParam> {};

src/iceberg/util/temporal_util.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "iceberg/util/temporal_util.h"
2121

2222
#include <chrono>
23+
#include <cstdint>
2324
#include <utility>
2425

2526
#include "iceberg/expression/literal.h"

0 commit comments

Comments
 (0)