Skip to content

Commit c9050af

Browse files
committed
1
1 parent 483fa0f commit c9050af

File tree

2 files changed

+128
-32
lines changed

2 files changed

+128
-32
lines changed

src/iceberg/expression/literal.cc

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include <chrono>
2323
#include <cmath>
2424
#include <concepts>
25+
#include <cstdint>
26+
#include <iomanip>
2527

2628
#include "iceberg/exception.h"
2729

@@ -35,6 +37,14 @@ int32_t MicrosToDays(int64_t micros_since_epoch) {
3537
return static_cast<int32_t>(days_duration.count());
3638
}
3739

40+
time_t timegm_custom(std::tm* tm) {
41+
#if defined(_WIN32)
42+
return _mkgmtime(tm);
43+
#else
44+
return timegm(tm);
45+
#endif
46+
}
47+
3848
} // namespace
3949

4050
/// \brief LiteralCaster handles type casting operations for Literal.
@@ -111,6 +121,7 @@ Result<Literal> LiteralCaster::CastFromInt(
111121
return Literal::Double(static_cast<double>(int_val));
112122
case TypeId::kDate:
113123
return Literal::Date(int_val);
124+
// TODO(Li Feiyang): Implement cast from Int to decimal
114125
default:
115126
return NotSupported("Cast from Int to {} is not implemented",
116127
target_type->ToString());
@@ -137,10 +148,10 @@ Result<Literal> LiteralCaster::CastFromLong(
137148
case TypeId::kDouble:
138149
return Literal::Double(static_cast<double>(long_val));
139150
case TypeId::kDate: {
140-
if (long_val > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) {
151+
if (long_val > std::numeric_limits<int32_t>::max()) {
141152
return AboveMaxLiteral(target_type);
142153
}
143-
if (long_val < static_cast<int64_t>(std::numeric_limits<int32_t>::min())) {
154+
if (long_val < std::numeric_limits<int32_t>::min()) {
144155
return BelowMinLiteral(target_type);
145156
}
146157
return Literal::Date(static_cast<int32_t>(long_val));
@@ -151,6 +162,7 @@ Result<Literal> LiteralCaster::CastFromLong(
151162
return Literal::Timestamp(long_val);
152163
case TypeId::kTimestampTz:
153164
return Literal::TimestampTz(long_val);
165+
// TODO(Li Feiyang): Implement cast from Long to decimal, TimestampNs and
154166
default:
155167
return NotSupported("Cast from Long to {} is not supported",
156168
target_type->ToString());
@@ -164,6 +176,7 @@ Result<Literal> LiteralCaster::CastFromFloat(
164176
switch (target_type->type_id()) {
165177
case TypeId::kDouble:
166178
return Literal::Double(static_cast<double>(float_val));
179+
// TODO(Li Feiyang): Implement cast from Float to decimal
167180
default:
168181
return NotSupported("Cast from Float to {} is not supported",
169182
target_type->ToString());
@@ -192,30 +205,129 @@ Result<Literal> LiteralCaster::CastFromDouble(
192205

193206
Result<Literal> LiteralCaster::CastFromString(
194207
const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
208+
const auto& str_val = std::get<std::string>(literal.value_);
209+
std::istringstream in{str_val};
210+
std::tm tm = {};
211+
195212
switch (target_type->type_id()) {
196213
case TypeId::kDate: {
197-
// TODO(Li Feiyang): Implement parsing for "YYYY-MM-DD" using std::chrono::parse
198-
// once it becomes available in the target libc++.
199-
return NotImplemented("Cast from String to Date is not yet implemented.");
214+
// Parse "YYYY-MM-DD" into days since 1970-01-01 epoch.
215+
in >> std::get_time(&tm, "%Y-%m-%d");
216+
217+
if (in.fail() || in.peek() != EOF) {
218+
return NotSupported("Failed to parse '{}' as a valid Date (expected YYYY-MM-DD)",
219+
str_val);
220+
}
221+
222+
auto time_point = std::chrono::system_clock::from_time_t(timegm_custom(&tm));
223+
auto days_since_epoch = std::chrono::floor<std::chrono::days>(time_point);
224+
return Literal::Date(
225+
static_cast<int32_t>(days_since_epoch.time_since_epoch().count()));
200226
}
201227

202228
case TypeId::kTime: {
203-
// TODO(Li Feiyang): Implement parsing for "HH:MM:SS.ffffff" using
204-
// std::chrono::parse once it becomes available in the target libc++.
205-
return NotImplemented("Cast from String to Time is not yet implemented.");
206-
}
229+
// Parse "HH:MM:SS.ffffff" into microseconds since midnight.
230+
in >> std::get_time(&tm, "%H:%M:%S");
207231

208-
case TypeId::kTimestamp: {
209-
// TODO(Li Feiyang): Implement parsing for "YYYY-MM-DDTHH:MM:SS.ffffff" using
210-
// std::chrono::parse once it becomes available in the target libc++.
211-
return NotImplemented("Cast from String to Timestamp is not yet implemented.");
232+
if (in.fail()) {
233+
return NotSupported(
234+
"Failed to parse '{}' as a valid Time (expected HH:MM:SS.ffffff)", str_val);
235+
}
236+
237+
int64_t total_micros =
238+
(tm.tm_hour * 3600LL + tm.tm_min * 60LL + tm.tm_sec) * 1000000LL;
239+
240+
if (in.peek() == '.') {
241+
in.ignore();
242+
std::string fractional_str;
243+
char c;
244+
while (in.get(c) && isdigit(c)) {
245+
fractional_str += c;
246+
}
247+
if (in) {
248+
in.unget();
249+
}
250+
251+
if (fractional_str.length() > 6) {
252+
fractional_str.resize(6);
253+
}
254+
try {
255+
if (!fractional_str.empty()) {
256+
fractional_str.append(6 - fractional_str.length(), '0');
257+
total_micros += std::stoll(fractional_str);
258+
}
259+
} catch (const std::exception&) {
260+
return NotSupported("Failed to parse fractional part of Time '{}'", str_val);
261+
}
262+
}
263+
264+
if (in.peek() != EOF) {
265+
return NotSupported("Unconsumed characters found after parsing Time '{}'",
266+
str_val);
267+
}
268+
269+
return Literal::Time(total_micros);
212270
}
213271

272+
case TypeId::kTimestamp:
214273
case TypeId::kTimestampTz: {
215-
// TODO(Li Feiyang): Implement parsing for "YYYY-MM-DDTHH:MM:SS.ffffffZ" using
216-
// std::chrono::parse once it becomes available in the target libc++.
217-
return NotImplemented("Cast from String to TimestampTz is not yet implemented.");
274+
// Parse "YYYY-MM-DDTHH:MM:SS.ffffff" and optional 'Z'
275+
in >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
276+
277+
if (in.fail()) {
278+
return NotSupported(
279+
"Failed to parse '{}' as a valid Timestamp (expected YYYY-MM-DDTHH:MM:SS...)",
280+
str_val);
281+
}
282+
283+
auto seconds_since_epoch = timegm_custom(&tm);
284+
int64_t total_micros = seconds_since_epoch * 1000000LL;
285+
286+
if (in.peek() == '.') {
287+
in.ignore();
288+
std::string fractional_str;
289+
char c;
290+
while (in.get(c) && isdigit(c)) {
291+
fractional_str += c;
292+
}
293+
if (in) {
294+
in.unget();
295+
}
296+
297+
if (fractional_str.length() > 6) {
298+
fractional_str.resize(6);
299+
}
300+
try {
301+
if (!fractional_str.empty()) {
302+
fractional_str.append(6 - fractional_str.length(), '0');
303+
total_micros += std::stoll(fractional_str);
304+
}
305+
} catch (const std::exception&) {
306+
return NotSupported("Failed to parse fractional part of Timestamp '{}'",
307+
str_val);
308+
}
309+
}
310+
311+
if (target_type->type_id() == TypeId::kTimestampTz) {
312+
// NOTE: This implementation DOES NOT support timezone offsets like
313+
// '+08:00' or '-07:00'. It only supports the UTC designator 'Z'.
314+
if (in.peek() == 'Z') {
315+
in.ignore(); // Consume 'Z'
316+
}
317+
}
318+
319+
if (in.peek() != EOF) {
320+
return NotSupported("Unconsumed characters found after parsing Timestamp '{}'",
321+
str_val);
322+
}
323+
324+
if (target_type->type_id() == TypeId::kTimestamp) {
325+
return Literal::Timestamp(total_micros);
326+
} else {
327+
return Literal::TimestampTz(total_micros);
328+
}
218329
}
330+
// TODO(Li Feiyang): Implement cast from String to uuid and decimal
219331

220332
default:
221333
return NotSupported("Cast from String to {} is not supported",
@@ -281,14 +393,6 @@ Result<Literal> LiteralCaster::CastFromFixed(
281393
case TypeId::kBinary: {
282394
return Literal::Binary(fixed_val);
283395
}
284-
case TypeId::kFixed: {
285-
auto target_fixed_type = std::dynamic_pointer_cast<FixedType>(target_type);
286-
if (fixed_val.size() == target_fixed_type->length()) {
287-
return literal;
288-
}
289-
return NotSupported("Cannot cast Fixed({}) to Fixed({}) due to mismatched lengths",
290-
fixed_val.size(), target_fixed_type->length());
291-
}
292396
default:
293397
return NotSupported("Cast from Fixed to {} is not supported",
294398
target_type->ToString());

test/literal_test.cc

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -534,14 +534,6 @@ TEST(LiteralTest, CrossTypeComparison) {
534534
EXPECT_EQ(int_literal <=> string_literal, std::partial_ordering::unordered);
535535
}
536536

537-
// Special value tests
538-
TEST(LiteralTest, SpecialValues) {
539-
auto int_literal = Literal::Int(42);
540-
541-
EXPECT_FALSE(int_literal.IsAboveMax());
542-
EXPECT_FALSE(int_literal.IsBelowMin());
543-
}
544-
545537
// Same type cast test
546538
TEST(LiteralTest, SameTypeCast) {
547539
auto int_literal = Literal::Int(42);

0 commit comments

Comments
 (0)