Skip to content

Commit 4820c20

Browse files
pratikpugaliameta-codesync[bot]
authored andcommitted
fix: Greatest/least custom comparison; add Time/TimeWithTimezone support (facebookincubator#15666)
Summary: Pull Request resolved: facebookincubator#15666 The greatest/least functions were extracting raw values (int64_t, int128_t) early and comparing them with standard operators, bypassing custom comparison logic. This caused incorrect results for types like IPAddress: - IPAddress: Used signed int128_t comparison instead of unsigned byte-by-byte comparison (memcmp), causing addresses with high bit set (e.g., ffff::1) to compare as less than addresses starting with 0x00 The fix compares them directly using their operator< and operator>, which trigger the custom compare() methods. The raw value is extracted only from the winning element at the end and hence fixes [facebookincubator#15620](facebookincubator#15620) Additionally, this diff adds support for Time and TimeWithTimezone types in greatest/least functions (registration + tests), which also benefit from the custom comparison fix. Reviewed By: kgpai Differential Revision: D87961025 fbshipit-source-id: 789ea8fd7bf1ff0cd91ff7de36721aa0158cc82e
1 parent 4182405 commit 4820c20

File tree

3 files changed

+129
-8
lines changed

3 files changed

+129
-8
lines changed

velox/functions/prestosql/GreatestLeast.h

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#pragma once
1717

1818
#include <cmath>
19+
#include <optional>
1920
#include "velox/expression/ComplexViewTypes.h"
2021
#include "velox/functions/Macros.h"
2122

@@ -41,23 +42,32 @@ struct ExtremeValueFunction {
4142
out_type<T>& result,
4243
const arg_type<T>& firstElement,
4344
const arg_type<Variadic<T>>& remainingElement) {
44-
auto currentValue = extractValue(firstElement);
45+
// Track the winner: nullopt means firstElement, a value means index in
46+
// remainingElement
47+
std::optional<vector_size_t> winnerIdx;
4548

46-
for (auto element : remainingElement) {
47-
auto candidateValue = extractValue(element.value());
49+
for (size_t i = 0; i < remainingElement.size(); ++i) {
50+
const auto& cur = remainingElement[i].value();
51+
const auto& best = winnerIdx.has_value()
52+
? remainingElement[*winnerIdx].value()
53+
: firstElement;
4854

4955
if constexpr (isLeast) {
50-
if (smallerThan(candidateValue, currentValue)) {
51-
currentValue = candidateValue;
56+
if (smallerThan(cur, best)) {
57+
winnerIdx = i;
5258
}
5359
} else {
54-
if (greaterThan(candidateValue, currentValue)) {
55-
currentValue = candidateValue;
60+
if (greaterThan(cur, best)) {
61+
winnerIdx = i;
5662
}
5763
}
5864
}
5965

60-
result = currentValue;
66+
if (!winnerIdx.has_value()) {
67+
result = extractValue(firstElement);
68+
} else {
69+
result = extractValue(remainingElement[*winnerIdx].value());
70+
}
6171
}
6272

6373
private:

velox/functions/prestosql/registration/GeneralFunctionsRegistration.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "velox/functions/prestosql/InPredicate.h"
2424
#include "velox/functions/prestosql/Reduce.h"
2525
#include "velox/functions/prestosql/types/IPAddressType.h"
26+
#include "velox/functions/prestosql/types/TimeWithTimezoneType.h"
2627
#include "velox/functions/prestosql/types/TimestampWithTimeZoneType.h"
2728

2829
namespace facebook::velox::functions {
@@ -60,6 +61,8 @@ void registerAllGreatestLeastFunctions(const std::string& prefix) {
6061
registerGreatestLeastFunction<Timestamp>(prefix);
6162
registerGreatestLeastFunction<TimestampWithTimezone>(prefix);
6263
registerGreatestLeastFunction<IPAddress>(prefix);
64+
registerGreatestLeastFunction<Time>(prefix);
65+
registerGreatestLeastFunction<TimeWithTimezone>(prefix);
6366
}
6467
} // namespace
6568

velox/functions/prestosql/tests/GreatestLeastTest.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <optional>
2020
#include "velox/common/base/tests/GTestUtils.h"
2121
#include "velox/functions/prestosql/tests/utils/FunctionBaseTest.h"
22+
#include "velox/functions/prestosql/types/TimeWithTimezoneType.h"
23+
#include "velox/type/Time.h"
2224

2325
using namespace facebook::velox;
2426

@@ -286,6 +288,104 @@ TEST_F(GreatestLeastTest, leastDate) {
286288
DATE());
287289
}
288290

291+
TEST_F(GreatestLeastTest, greatestLeastTime) {
292+
using namespace facebook::velox::util;
293+
294+
// Helper to parse TIME from string
295+
const auto parseTime = [](const std::string& timeStr) -> int64_t {
296+
auto result = fromTimeString(timeStr.c_str(), timeStr.size());
297+
if (result.hasError()) {
298+
throw std::runtime_error("Parse error: " + result.error().message());
299+
}
300+
return result.value();
301+
};
302+
303+
// Test greatest
304+
runTest<int64_t>(
305+
"greatest(c0, c1, c2)",
306+
{
307+
{parseTime("10:00:00"),
308+
parseTime("00:00:00"),
309+
parseTime("12:00:00.001")}, // c0
310+
{parseTime("12:30:00"),
311+
parseTime("12:00:00"),
312+
parseTime("12:00:00.500")}, // c1
313+
{parseTime("15:30:45.100"),
314+
parseTime("23:59:59.999"),
315+
parseTime("12:00:00.999")}, // c2
316+
},
317+
{parseTime("15:30:45.100"),
318+
parseTime("23:59:59.999"),
319+
parseTime("12:00:00.999")},
320+
std::nullopt,
321+
TIME(),
322+
TIME());
323+
324+
// Test least
325+
runTest<int64_t>(
326+
"least(c0, c1, c2)",
327+
{
328+
{parseTime("10:00:00"),
329+
parseTime("00:00:00"),
330+
parseTime("12:00:00.001")}, // c0
331+
{parseTime("12:30:00"),
332+
parseTime("12:00:00"),
333+
parseTime("12:00:00.500")}, // c1
334+
{parseTime("15:30:45.100"),
335+
parseTime("23:59:59.999"),
336+
parseTime("12:00:00.999")}, // c2
337+
},
338+
{parseTime("10:00:00"), parseTime("00:00:00"), parseTime("12:00:00.001")},
339+
std::nullopt,
340+
TIME(),
341+
TIME());
342+
}
343+
344+
TEST_F(GreatestLeastTest, greatestLeastTimeWithTimezone) {
345+
using namespace facebook::velox::util;
346+
347+
// Helper to parse TIME WITH TIME ZONE from string
348+
const auto parseTimeWithTz = [](const std::string& timeStr) -> int64_t {
349+
auto result = fromTimeWithTimezoneString(timeStr.c_str(), timeStr.size());
350+
if (result.hasError()) {
351+
throw std::runtime_error("Parse error: " + result.error().message());
352+
}
353+
return result.value();
354+
};
355+
356+
// Test greatest
357+
runTest<int64_t>(
358+
"greatest(c0, c1, c2)",
359+
{
360+
{parseTimeWithTz("10:00:00+00:00"),
361+
parseTimeWithTz("20:00:00-08:00")}, // c0
362+
{parseTimeWithTz("10:00:00-08:00"),
363+
parseTimeWithTz("23:59:59.999+00:00")}, // c1
364+
{parseTimeWithTz("10:00:00+08:00"),
365+
parseTimeWithTz("00:00:00+08:00")}, // c2
366+
},
367+
{parseTimeWithTz("10:00:00-08:00"), parseTimeWithTz("20:00:00-08:00")},
368+
std::nullopt,
369+
TIME_WITH_TIME_ZONE(),
370+
TIME_WITH_TIME_ZONE());
371+
372+
// Test least
373+
runTest<int64_t>(
374+
"least(c0, c1, c2)",
375+
{
376+
{parseTimeWithTz("10:00:00+00:00"),
377+
parseTimeWithTz("00:00:00-08:00")}, // c0
378+
{parseTimeWithTz("10:00:00-08:00"),
379+
parseTimeWithTz("23:59:59.999+00:00")}, // c1
380+
{parseTimeWithTz("10:00:00+08:00"),
381+
parseTimeWithTz("00:00:00+08:00")}, // c2
382+
},
383+
{parseTimeWithTz("10:00:00+08:00"), parseTimeWithTz("00:00:00+08:00")},
384+
std::nullopt,
385+
TIME_WITH_TIME_ZONE(),
386+
TIME_WITH_TIME_ZONE());
387+
}
388+
289389
TEST_F(GreatestLeastTest, greatestLeastIpAddress) {
290390
auto greatest = [&](const std::optional<std::string>& a,
291391
const std::optional<std::string>& b,
@@ -324,6 +424,14 @@ TEST_F(GreatestLeastTest, greatestLeastIpAddress) {
324424
"255.255.255.255",
325425
"2001:0db8:0000:0000:0000:ff00:0042:832");
326426
EXPECT_FALSE(leastValueWithNulls.has_value());
427+
428+
// Case where raw comparison of values will give
429+
// different result than comparison of IPAddress values byte by byte.
430+
auto greatValue = greatest("ffff::1", "1::1", "1::2");
431+
EXPECT_EQ("ffff::1", greatValue.value());
432+
433+
auto lessValue = least("ffff::1", "1::1", "ffff::2");
434+
EXPECT_EQ("1::1", lessValue.value());
327435
}
328436

329437
TEST_F(GreatestLeastTest, stringBuffersMoved) {

0 commit comments

Comments
 (0)