Skip to content

Commit

Permalink
Merge pull request #11861 from hawkfish/timetz-compare
Browse files Browse the repository at this point in the history
Internal #1888 TIMETZ Collation Keys
  • Loading branch information
Mytherin committed Apr 29, 2024
2 parents 7414afc + b484537 commit 1bdf93c
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/core_functions/function_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ static const StaticFunctionDefinition internal_functions[] = {
DUCKDB_AGGREGATE_FUNCTION_ALIAS(SumkahanFun),
DUCKDB_SCALAR_FUNCTION(TanFun),
DUCKDB_SCALAR_FUNCTION_SET(TimeBucketFun),
DUCKDB_SCALAR_FUNCTION(TimeTZSortKeyFun),
DUCKDB_SCALAR_FUNCTION_SET(TimezoneFun),
DUCKDB_SCALAR_FUNCTION_SET(TimezoneHourFun),
DUCKDB_SCALAR_FUNCTION_SET(TimezoneMinuteFun),
Expand Down
17 changes: 17 additions & 0 deletions src/core_functions/scalar/date/epoch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,21 @@ ScalarFunction ToTimestampFun::GetFunction() {
return ScalarFunction({LogicalType::DOUBLE}, LogicalType::TIMESTAMP_TZ, EpochSecFunction);
}

struct TimeTZSortKeyOperator {
template <typename INPUT_TYPE, typename RESULT_TYPE>
static RESULT_TYPE Operation(INPUT_TYPE input) {
return input.sort_key();
}
};

static void TimeTZSortKeyFunction(DataChunk &input, ExpressionState &state, Vector &result) {
D_ASSERT(input.ColumnCount() == 1);

UnaryExecutor::Execute<dtime_tz_t, uint64_t, TimeTZSortKeyOperator>(input.data[0], result, input.size());
}

ScalarFunction TimeTZSortKeyFun::GetFunction() {
return ScalarFunction({LogicalType::TIME_TZ}, LogicalType::UBIGINT, TimeTZSortKeyFunction);
}

} // namespace duckdb
8 changes: 8 additions & 0 deletions src/core_functions/scalar/date/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,14 @@
"example": "timezone_minute(timestamp '2021-08-03 11:59:44.123456')",
"type": "scalar_function_set"
},
{
"struct": "TimeTZSortKeyFun",
"name": "timetz_byte_comparable",
"parameters": "time_tz",
"description": "Converts a TIME WITH TIME ZONE to an integer sort key",
"example": "timetz_byte_comparable('18:18:16.21-07:00'::TIME_TZ)",
"type": "scalar_function"
},
{
"name": "to_centuries",
"parameters": "integer",
Expand Down
41 changes: 33 additions & 8 deletions src/include/duckdb/common/types/datetime.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "duckdb/common/common.hpp"
#include "duckdb/common/numeric_utils.hpp"

#include <functional>

Expand Down Expand Up @@ -92,23 +93,47 @@ struct dtime_tz_t { // NOLINT
static constexpr const uint64_t OFFSET_MASK = ~uint64_t(0) >> TIME_BITS;
static constexpr const int32_t MAX_OFFSET = 16 * 60 * 60 - 1; // ±15:59:59
static constexpr const int32_t MIN_OFFSET = -MAX_OFFSET;
static constexpr const uint64_t OFFSET_MICROS = 1000000;

uint64_t bits;

// Offsets are reverse ordered e.g., 13:00:00+01 < 12:00:00+00 < 11:00:00-01
// Because we encode them as the low order bits,
// they are also biased into an unsigned integer: (-16, 16) => (32, 0)
static inline uint64_t encode_offset(int32_t offset) { // NOLINT
return uint64_t(MAX_OFFSET - offset);
}
static inline int32_t decode_offset(uint64_t bits) { // NOLINT
return MAX_OFFSET - int32_t(bits & OFFSET_MASK);
}

static inline uint64_t encode_micros(int64_t micros) { // NOLINT
return uint64_t(micros) << OFFSET_BITS;
}
static inline int64_t decode_micros(uint64_t bits) { // NOLINT
return int64_t(bits >> OFFSET_BITS);
}

dtime_tz_t() = default;

inline dtime_tz_t(dtime_t t, int32_t offset)
: bits((uint64_t(t.micros) << OFFSET_BITS) | uint64_t(MAX_OFFSET - offset)) {
inline dtime_tz_t(dtime_t t, int32_t offset) : bits(encode_micros(t.micros) | encode_offset(offset)) {
}
explicit inline dtime_tz_t(uint64_t bits_p) : bits(bits_p) {
}

inline dtime_t time() const { // NOLINT
return dtime_t(bits >> OFFSET_BITS);
return dtime_t(decode_micros(bits));
}

inline int32_t offset() const { // NOLINT
return MAX_OFFSET - int32_t(bits & OFFSET_MASK);
return decode_offset(bits);
}

// Times are compared after adjusting to offset +00:00:00, e.g., 13:01:00+01 > 12:00:00+00
// Because we encode them as the high order bits,
// they are biased by the maximum offset: (0, 24) => (0, 56)
inline uint64_t sort_key() const { // NOLINT
return bits + encode_micros((bits & OFFSET_MASK) * OFFSET_MICROS);
}

// comparison operators
Expand All @@ -119,16 +144,16 @@ struct dtime_tz_t { // NOLINT
return bits != rhs.bits;
};
inline bool operator<=(const dtime_tz_t &rhs) const {
return bits <= rhs.bits;
return sort_key() <= rhs.sort_key();
};
inline bool operator<(const dtime_tz_t &rhs) const {
return bits < rhs.bits;
return sort_key() < rhs.sort_key();
};
inline bool operator>(const dtime_tz_t &rhs) const {
return bits > rhs.bits;
return sort_key() > rhs.sort_key();
};
inline bool operator>=(const dtime_tz_t &rhs) const {
return bits >= rhs.bits;
return sort_key() >= rhs.sort_key();
};
};

Expand Down
9 changes: 9 additions & 0 deletions src/include/duckdb/core_functions/scalar/date_functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,15 @@ struct TimezoneMinuteFun {
static ScalarFunctionSet GetFunctions();
};

struct TimeTZSortKeyFun {
static constexpr const char *Name = "timetz_byte_comparable";
static constexpr const char *Parameters = "time_tz";
static constexpr const char *Description = "Converts a TIME WITH TIME ZONE to an integer sort key";
static constexpr const char *Example = "timetz_byte_comparable('18:18:16.21-07:00'::TIME_TZ)";

static ScalarFunction GetFunction();
};

struct ToCenturiesFun {
static constexpr const char *Name = "to_centuries";
static constexpr const char *Parameters = "integer";
Expand Down
245 changes: 245 additions & 0 deletions test/sql/types/time/test_time_tz_collate.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# name: test/sql/types/time/test_time_tz_collate.test
# description: Test Time With Time Zone
# group: [time]

statement ok
PRAGMA enable_verification

# Comparison order is unintuitive: Positive offsets are before negative ones.
# This is similar to how a clock time east of Greenwich is earlier
# than the same clock time further west.
# Moreover, the time portions are compared by applying the offsets
# before comparing them as times
query I
SELECT timetz_byte_comparable('09:00:00+00:00'::TIMETZ) >timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
True

query I
SELECT timetz_byte_comparable('08:00:00+01:00'::TIMETZ) >timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
False

query I
SELECT timetz_byte_comparable('08:00:00+01:00'::TIMETZ) = timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
False

query I
SELECT timetz_byte_comparable('08:01:00+00:00'::TIMETZ) = timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
False

query I
SELECT timetz_byte_comparable('08:01:00+00:00'::TIMETZ) > timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
True

query I
SELECT timetz_byte_comparable('08:02:00+00:00'::TIMETZ) > timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
True

query I
SELECT timetz_byte_comparable('08:00:00+00:00'::TIMETZ) > timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
False

query I
SELECT timetz_byte_comparable('10:01:00+02:00'::TIMETZ) > timetz_byte_comparable('09:01:00+01:00'::TIMETZ);
----
False

statement ok
CREATE TABLE timetzs (ttz TIMETZ);

statement ok
INSERT INTO timetzs VALUES
(NULL),
('00:00:00+1559'),
('00:00:00+1558'),
('02:30:00'),
('02:30:00+04'),
('02:30:00+04:30'),
('02:30:00+04:30:45'),
('16:15:03.123456'),
('02:30:00+1200'),
('02:30:00-1200'),
('24:00:00-1558'),
('24:00:00-1559'),
;

query I
SELECT * FROM timetzs ORDER BY ALL
----
00:00:00+15:59
00:00:00+15:58
02:30:00+12
02:30:00+04:30:45
02:30:00+04:30
02:30:00+04
02:30:00+00
02:30:00-12
16:15:03.123456+00
24:00:00-15:58
24:00:00-15:59
NULL

query IIIIIIII
SELECT
lhs.ttz,
rhs.ttz,
timetz_byte_comparable(lhs.ttz) < timetz_byte_comparable(rhs.ttz),
timetz_byte_comparable(lhs.ttz) <= timetz_byte_comparable(rhs.ttz),
timetz_byte_comparable(lhs.ttz) = timetz_byte_comparable(rhs.ttz),
timetz_byte_comparable(lhs.ttz) >= timetz_byte_comparable(rhs.ttz),
timetz_byte_comparable(lhs.ttz) > timetz_byte_comparable(rhs.ttz),
timetz_byte_comparable(lhs.ttz) <> timetz_byte_comparable(rhs.ttz),
FROM timetzs lhs, timetzs rhs
ORDER BY timetz_byte_comparable(lhs.ttz), timetz_byte_comparable(rhs.ttz)
----
00:00:00+15:59 00:00:00+15:59 false true true true false false
00:00:00+15:59 00:00:00+15:58 true true false false false true
00:00:00+15:59 02:30:00+12 true true false false false true
00:00:00+15:59 02:30:00+04:30:45 true true false false false true
00:00:00+15:59 02:30:00+04:30 true true false false false true
00:00:00+15:59 02:30:00+04 true true false false false true
00:00:00+15:59 02:30:00+00 true true false false false true
00:00:00+15:59 02:30:00-12 true true false false false true
00:00:00+15:59 16:15:03.123456+00 true true false false false true
00:00:00+15:59 24:00:00-15:58 true true false false false true
00:00:00+15:59 24:00:00-15:59 true true false false false true
00:00:00+15:59 NULL NULL NULL NULL NULL NULL NULL
00:00:00+15:58 00:00:00+15:59 false false false true true true
00:00:00+15:58 00:00:00+15:58 false true true true false false
00:00:00+15:58 02:30:00+12 true true false false false true
00:00:00+15:58 02:30:00+04:30:45 true true false false false true
00:00:00+15:58 02:30:00+04:30 true true false false false true
00:00:00+15:58 02:30:00+04 true true false false false true
00:00:00+15:58 02:30:00+00 true true false false false true
00:00:00+15:58 02:30:00-12 true true false false false true
00:00:00+15:58 16:15:03.123456+00 true true false false false true
00:00:00+15:58 24:00:00-15:58 true true false false false true
00:00:00+15:58 24:00:00-15:59 true true false false false true
00:00:00+15:58 NULL NULL NULL NULL NULL NULL NULL
02:30:00+12 00:00:00+15:59 false false false true true true
02:30:00+12 00:00:00+15:58 false false false true true true
02:30:00+12 02:30:00+12 false true true true false false
02:30:00+12 02:30:00+04:30:45 true true false false false true
02:30:00+12 02:30:00+04:30 true true false false false true
02:30:00+12 02:30:00+04 true true false false false true
02:30:00+12 02:30:00+00 true true false false false true
02:30:00+12 02:30:00-12 true true false false false true
02:30:00+12 16:15:03.123456+00 true true false false false true
02:30:00+12 24:00:00-15:58 true true false false false true
02:30:00+12 24:00:00-15:59 true true false false false true
02:30:00+12 NULL NULL NULL NULL NULL NULL NULL
02:30:00+04:30:45 00:00:00+15:59 false false false true true true
02:30:00+04:30:45 00:00:00+15:58 false false false true true true
02:30:00+04:30:45 02:30:00+12 false false false true true true
02:30:00+04:30:45 02:30:00+04:30:45 false true true true false false
02:30:00+04:30:45 02:30:00+04:30 true true false false false true
02:30:00+04:30:45 02:30:00+04 true true false false false true
02:30:00+04:30:45 02:30:00+00 true true false false false true
02:30:00+04:30:45 02:30:00-12 true true false false false true
02:30:00+04:30:45 16:15:03.123456+00 true true false false false true
02:30:00+04:30:45 24:00:00-15:58 true true false false false true
02:30:00+04:30:45 24:00:00-15:59 true true false false false true
02:30:00+04:30:45 NULL NULL NULL NULL NULL NULL NULL
02:30:00+04:30 00:00:00+15:59 false false false true true true
02:30:00+04:30 00:00:00+15:58 false false false true true true
02:30:00+04:30 02:30:00+12 false false false true true true
02:30:00+04:30 02:30:00+04:30:45 false false false true true true
02:30:00+04:30 02:30:00+04:30 false true true true false false
02:30:00+04:30 02:30:00+04 true true false false false true
02:30:00+04:30 02:30:00+00 true true false false false true
02:30:00+04:30 02:30:00-12 true true false false false true
02:30:00+04:30 16:15:03.123456+00 true true false false false true
02:30:00+04:30 24:00:00-15:58 true true false false false true
02:30:00+04:30 24:00:00-15:59 true true false false false true
02:30:00+04:30 NULL NULL NULL NULL NULL NULL NULL
02:30:00+04 00:00:00+15:59 false false false true true true
02:30:00+04 00:00:00+15:58 false false false true true true
02:30:00+04 02:30:00+12 false false false true true true
02:30:00+04 02:30:00+04:30:45 false false false true true true
02:30:00+04 02:30:00+04:30 false false false true true true
02:30:00+04 02:30:00+04 false true true true false false
02:30:00+04 02:30:00+00 true true false false false true
02:30:00+04 02:30:00-12 true true false false false true
02:30:00+04 16:15:03.123456+00 true true false false false true
02:30:00+04 24:00:00-15:58 true true false false false true
02:30:00+04 24:00:00-15:59 true true false false false true
02:30:00+04 NULL NULL NULL NULL NULL NULL NULL
02:30:00+00 00:00:00+15:59 false false false true true true
02:30:00+00 00:00:00+15:58 false false false true true true
02:30:00+00 02:30:00+12 false false false true true true
02:30:00+00 02:30:00+04:30:45 false false false true true true
02:30:00+00 02:30:00+04:30 false false false true true true
02:30:00+00 02:30:00+04 false false false true true true
02:30:00+00 02:30:00+00 false true true true false false
02:30:00+00 02:30:00-12 true true false false false true
02:30:00+00 16:15:03.123456+00 true true false false false true
02:30:00+00 24:00:00-15:58 true true false false false true
02:30:00+00 24:00:00-15:59 true true false false false true
02:30:00+00 NULL NULL NULL NULL NULL NULL NULL
02:30:00-12 00:00:00+15:59 false false false true true true
02:30:00-12 00:00:00+15:58 false false false true true true
02:30:00-12 02:30:00+12 false false false true true true
02:30:00-12 02:30:00+04:30:45 false false false true true true
02:30:00-12 02:30:00+04:30 false false false true true true
02:30:00-12 02:30:00+04 false false false true true true
02:30:00-12 02:30:00+00 false false false true true true
02:30:00-12 02:30:00-12 false true true true false false
02:30:00-12 16:15:03.123456+00 true true false false false true
02:30:00-12 24:00:00-15:58 true true false false false true
02:30:00-12 24:00:00-15:59 true true false false false true
02:30:00-12 NULL NULL NULL NULL NULL NULL NULL
16:15:03.123456+00 00:00:00+15:59 false false false true true true
16:15:03.123456+00 00:00:00+15:58 false false false true true true
16:15:03.123456+00 02:30:00+12 false false false true true true
16:15:03.123456+00 02:30:00+04:30:45 false false false true true true
16:15:03.123456+00 02:30:00+04:30 false false false true true true
16:15:03.123456+00 02:30:00+04 false false false true true true
16:15:03.123456+00 02:30:00+00 false false false true true true
16:15:03.123456+00 02:30:00-12 false false false true true true
16:15:03.123456+00 16:15:03.123456+00 false true true true false false
16:15:03.123456+00 24:00:00-15:58 true true false false false true
16:15:03.123456+00 24:00:00-15:59 true true false false false true
16:15:03.123456+00 NULL NULL NULL NULL NULL NULL NULL
24:00:00-15:58 00:00:00+15:59 false false false true true true
24:00:00-15:58 00:00:00+15:58 false false false true true true
24:00:00-15:58 02:30:00+12 false false false true true true
24:00:00-15:58 02:30:00+04:30:45 false false false true true true
24:00:00-15:58 02:30:00+04:30 false false false true true true
24:00:00-15:58 02:30:00+04 false false false true true true
24:00:00-15:58 02:30:00+00 false false false true true true
24:00:00-15:58 02:30:00-12 false false false true true true
24:00:00-15:58 16:15:03.123456+00 false false false true true true
24:00:00-15:58 24:00:00-15:58 false true true true false false
24:00:00-15:58 24:00:00-15:59 true true false false false true
24:00:00-15:58 NULL NULL NULL NULL NULL NULL NULL
24:00:00-15:59 00:00:00+15:59 false false false true true true
24:00:00-15:59 00:00:00+15:58 false false false true true true
24:00:00-15:59 02:30:00+12 false false false true true true
24:00:00-15:59 02:30:00+04:30:45 false false false true true true
24:00:00-15:59 02:30:00+04:30 false false false true true true
24:00:00-15:59 02:30:00+04 false false false true true true
24:00:00-15:59 02:30:00+00 false false false true true true
24:00:00-15:59 02:30:00-12 false false false true true true
24:00:00-15:59 16:15:03.123456+00 false false false true true true
24:00:00-15:59 24:00:00-15:58 false false false true true true
24:00:00-15:59 24:00:00-15:59 false true true true false false
24:00:00-15:59 NULL NULL NULL NULL NULL NULL NULL
NULL 00:00:00+15:59 NULL NULL NULL NULL NULL NULL
NULL 00:00:00+15:58 NULL NULL NULL NULL NULL NULL
NULL 02:30:00+12 NULL NULL NULL NULL NULL NULL
NULL 02:30:00+04:30:45 NULL NULL NULL NULL NULL NULL
NULL 02:30:00+04:30 NULL NULL NULL NULL NULL NULL
NULL 02:30:00+04 NULL NULL NULL NULL NULL NULL
NULL 02:30:00+00 NULL NULL NULL NULL NULL NULL
NULL 02:30:00-12 NULL NULL NULL NULL NULL NULL
NULL 16:15:03.123456+00 NULL NULL NULL NULL NULL NULL
NULL 24:00:00-15:58 NULL NULL NULL NULL NULL NULL
NULL 24:00:00-15:59 NULL NULL NULL NULL NULL NULL
NULL NULL NULL NULL NULL NULL NULL NULL

0 comments on commit 1bdf93c

Please sign in to comment.