Functions for defining temporals (#197)
This commit is contained in:
parent
6913feacc7
commit
e2112faff0
@ -6,11 +6,14 @@
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
namespace query {
|
||||
namespace {
|
||||
@ -1009,6 +1012,126 @@ TypedValue FromByteString(const TypedValue *args, int64_t nargs, const FunctionC
|
||||
return TypedValue(std::move(str));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept IsNumberOrInteger = utils::SameAsAnyOf<T, Number, Integer>;
|
||||
|
||||
template <IsNumberOrInteger ArgType>
|
||||
void MapNumericParameters(auto ¶meter_mappings, const auto &input_parameters) {
|
||||
for (const auto &[key, value] : input_parameters) {
|
||||
if (auto it = parameter_mappings.find(key); it != parameter_mappings.end()) {
|
||||
if (value.IsInt()) {
|
||||
*it->second = value.ValueInt();
|
||||
} else if (std::is_same_v<ArgType, Number> && value.IsDouble()) {
|
||||
*it->second = value.ValueDouble();
|
||||
} else {
|
||||
std::string_view error = std::is_same_v<ArgType, Integer> ? "an integer." : "a numeric value.";
|
||||
throw QueryRuntimeException("Invalid value for key '{}'. Expected {}", key, error);
|
||||
}
|
||||
} else {
|
||||
throw QueryRuntimeException("Unknown key '{}'.", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Date(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Optional<Or<String, Map>>>("date", args, nargs);
|
||||
if (nargs == 0) {
|
||||
return TypedValue(utils::UtcToday(), ctx.memory);
|
||||
}
|
||||
|
||||
if (args[0].IsString()) {
|
||||
const auto &[date_parameters, is_extended] = utils::ParseDateParameters(args[0].ValueString());
|
||||
return TypedValue(utils::Date(date_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
utils::DateParameters date_parameters;
|
||||
|
||||
using namespace std::literals;
|
||||
std::unordered_map parameter_mappings = {std::pair{"year"sv, &date_parameters.years},
|
||||
std::pair{"month"sv, &date_parameters.months},
|
||||
std::pair{"day"sv, &date_parameters.days}};
|
||||
|
||||
MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
|
||||
return TypedValue(utils::Date(date_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue LocalTime(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Optional<Or<String, Map>>>("localtime", args, nargs);
|
||||
|
||||
if (nargs == 0) {
|
||||
return TypedValue(utils::UtcLocalTime(), ctx.memory);
|
||||
}
|
||||
|
||||
if (args[0].IsString()) {
|
||||
const auto &[local_time_parameters, is_extended] = utils::ParseLocalTimeParameters(args[0].ValueString());
|
||||
return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
utils::LocalTimeParameters local_time_parameters;
|
||||
|
||||
using namespace std::literals;
|
||||
std::unordered_map parameter_mappings{
|
||||
std::pair{"hour"sv, &local_time_parameters.hours},
|
||||
std::pair{"minute"sv, &local_time_parameters.minutes},
|
||||
std::pair{"second"sv, &local_time_parameters.seconds},
|
||||
std::pair{"millisecond"sv, &local_time_parameters.milliseconds},
|
||||
std::pair{"microsecond"sv, &local_time_parameters.microseconds},
|
||||
};
|
||||
|
||||
MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
|
||||
return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue LocalDateTime(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Optional<Or<String, Map>>>("localdatetime", args, nargs);
|
||||
|
||||
if (nargs == 0) {
|
||||
return TypedValue(utils::UtcLocalDateTime(), ctx.memory);
|
||||
}
|
||||
|
||||
if (args[0].IsString()) {
|
||||
const auto &[date_parameters, local_time_parameters] = ParseLocalDateTimeParameters(args[0].ValueString());
|
||||
return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
utils::DateParameters date_parameters;
|
||||
utils::LocalTimeParameters local_time_parameters;
|
||||
using namespace std::literals;
|
||||
std::unordered_map parameter_mappings{
|
||||
std::pair{"year"sv, &date_parameters.years},
|
||||
std::pair{"month"sv, &date_parameters.months},
|
||||
std::pair{"day"sv, &date_parameters.days},
|
||||
std::pair{"hour"sv, &local_time_parameters.hours},
|
||||
std::pair{"minute"sv, &local_time_parameters.minutes},
|
||||
std::pair{"second"sv, &local_time_parameters.seconds},
|
||||
std::pair{"millisecond"sv, &local_time_parameters.milliseconds},
|
||||
std::pair{"microsecond"sv, &local_time_parameters.microseconds},
|
||||
};
|
||||
|
||||
MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
|
||||
return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<String, Map>>("duration", args, nargs);
|
||||
|
||||
if (args[0].IsString()) {
|
||||
return TypedValue(utils::Duration(ParseDurationParameters(args[0].ValueString())), ctx.memory);
|
||||
}
|
||||
|
||||
utils::DurationParameters duration_parameters;
|
||||
using namespace std::literals;
|
||||
std::unordered_map parameter_mappings{std::pair{"year"sv, &duration_parameters.years},
|
||||
std::pair{"month"sv, &duration_parameters.months},
|
||||
std::pair{"day"sv, &duration_parameters.days},
|
||||
std::pair{"hour"sv, &duration_parameters.hours},
|
||||
std::pair{"minute"sv, &duration_parameters.minutes},
|
||||
std::pair{"second"sv, &duration_parameters.seconds},
|
||||
std::pair{"millisecond"sv, &duration_parameters.milliseconds},
|
||||
std::pair{"microsecond"sv, &duration_parameters.microseconds}};
|
||||
MapNumericParameters<Number>(parameter_mappings, args[0].ValueMap());
|
||||
return TypedValue(utils::Duration(duration_parameters), ctx.memory);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction(
|
||||
@ -1088,6 +1211,12 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
|
||||
if (function_name == "TOBYTESTRING") return ToByteString;
|
||||
if (function_name == "FROMBYTESTRING") return FromByteString;
|
||||
|
||||
// Functions for temporal types
|
||||
if (function_name == "DATE") return Date;
|
||||
if (function_name == "LOCALTIME") return LocalTime;
|
||||
if (function_name == "LOCALDATETIME") return LocalDateTime;
|
||||
if (function_name == "DURATION") return Duration;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
@ -66,6 +67,38 @@ Date::Date(const DateParameters &date_parameters) {
|
||||
days = date_parameters.days;
|
||||
}
|
||||
|
||||
namespace {
|
||||
tm GetUtcFromSystemClockOrThrow() {
|
||||
namespace chrono = std::chrono;
|
||||
const auto today = chrono::system_clock::to_time_t(chrono::system_clock::now());
|
||||
tm utc_today;
|
||||
if (!gmtime_r(&today, &utc_today)) {
|
||||
throw utils::BasicException("Can't access clock's UTC time");
|
||||
}
|
||||
return utc_today;
|
||||
}
|
||||
|
||||
int64_t TMYearToUtcYear(int year) { return year + 1900; }
|
||||
|
||||
int64_t TMMonthToUtcMonth(int month) { return month + 1; }
|
||||
} // namespace
|
||||
|
||||
Date UtcToday() {
|
||||
const auto utc_today = GetUtcFromSystemClockOrThrow();
|
||||
return Date({TMYearToUtcYear(utc_today.tm_year), TMMonthToUtcMonth(utc_today.tm_mon), utc_today.tm_mday});
|
||||
}
|
||||
|
||||
LocalTime UtcLocalTime() {
|
||||
const auto utc_today = GetUtcFromSystemClockOrThrow();
|
||||
return LocalTime({utc_today.tm_hour, utc_today.tm_min, utc_today.tm_sec});
|
||||
}
|
||||
|
||||
LocalDateTime UtcLocalDateTime() {
|
||||
const auto utc_today = GetUtcFromSystemClockOrThrow();
|
||||
return LocalDateTime({TMYearToUtcYear(utc_today.tm_year), TMMonthToUtcMonth(utc_today.tm_mon), utc_today.tm_mday},
|
||||
{utc_today.tm_hour, utc_today.tm_min, utc_today.tm_sec});
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr auto *kSupportedDateFormatsHelpMessage = R"help(
|
||||
String representing the date should be in one of the following formats:
|
||||
@ -459,6 +492,8 @@ int64_t LocalDateTime::SubSecondsAsNanoseconds() const {
|
||||
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
|
||||
: date(date_parameters), local_time(local_time_parameters) {}
|
||||
|
||||
LocalDateTime::LocalDateTime(const Date &date, const LocalTime &local_time) : date(date), local_time(local_time) {}
|
||||
|
||||
size_t LocalDateTimeHash::operator()(const LocalDateTime &local_date_time) const {
|
||||
utils::HashCombine<uint64_t, uint64_t> hasher;
|
||||
size_t result = hasher(0, LocalTimeHash{}(local_date_time.local_time));
|
||||
|
@ -111,8 +111,7 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
|
||||
struct LocalDateTime {
|
||||
explicit LocalDateTime(int64_t microseconds);
|
||||
explicit LocalDateTime(DateParameters date_parameters, const LocalTimeParameters &local_time_parameters);
|
||||
|
||||
LocalDateTime(const Date &dt, const LocalTime <) : date(dt), local_time(lt) {}
|
||||
explicit LocalDateTime(const Date &date, const LocalTime &local_time);
|
||||
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t SecondsSinceEpoch() const; // seconds since epoch
|
||||
@ -185,4 +184,7 @@ constexpr std::chrono::days DaysSinceEpoch(uint16_t years, uint8_t months, uint8
|
||||
return chrono::sys_days{ymd}.time_since_epoch();
|
||||
}
|
||||
|
||||
Date UtcToday();
|
||||
LocalTime UtcLocalTime();
|
||||
LocalDateTime UtcLocalDateTime();
|
||||
} // namespace utils
|
||||
|
@ -15,9 +15,11 @@
|
||||
#include "query/interpret/frame.hpp"
|
||||
#include "query/path.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
#include "query_common.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
using namespace query;
|
||||
using query::test_common::ToIntList;
|
||||
@ -1732,4 +1734,95 @@ TEST_F(FunctionTest, FromByteString) {
|
||||
EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", std::string("\x00\x42", 2)).ValueString(), "0x0042");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, Date) {
|
||||
const auto unix_epoch = utils::Date({1970, 1, 1});
|
||||
EXPECT_EQ(EvaluateFunction("DATE", "1970-01-01").ValueDate(), unix_epoch);
|
||||
const auto map_param = TypedValue(
|
||||
std::map<std::string, TypedValue>{{"year", TypedValue(1970)}, {"month", TypedValue(1)}, {"day", TypedValue(1)}});
|
||||
EXPECT_EQ(EvaluateFunction("DATE", map_param).ValueDate(), unix_epoch);
|
||||
const auto today = utils::UtcToday();
|
||||
EXPECT_EQ(EvaluateFunction("DATE").ValueDate(), today);
|
||||
|
||||
EXPECT_THROW(EvaluateFunction("DATE", "{}"), utils::BasicException);
|
||||
EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"years", TypedValue(1970)}}),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"mnths", TypedValue(1970)}}),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"dayz", TypedValue(1970)}}),
|
||||
QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, LocalTime) {
|
||||
const auto local_time = utils::LocalTime({13, 3, 2, 0, 0});
|
||||
EXPECT_EQ(EvaluateFunction("LOCALTIME", "130302").ValueLocalTime(), local_time);
|
||||
const auto one_sec_in_microseconds = 1000000;
|
||||
const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"hour", TypedValue(1)},
|
||||
{"minute", TypedValue(2)},
|
||||
{"second", TypedValue(3)},
|
||||
{"millisecond", TypedValue(4)},
|
||||
{"microsecond", TypedValue(5)}});
|
||||
EXPECT_EQ(EvaluateFunction("LOCALTIME", map_param).ValueLocalTime(), utils::LocalTime({1, 2, 3, 4, 5}));
|
||||
const auto today = utils::UtcLocalTime();
|
||||
EXPECT_NEAR(EvaluateFunction("LOCALTIME").ValueLocalTime().MicrosecondsSinceEpoch(), today.MicrosecondsSinceEpoch(),
|
||||
one_sec_in_microseconds);
|
||||
|
||||
EXPECT_THROW(EvaluateFunction("LOCALTIME", "{}"), utils::BasicException);
|
||||
EXPECT_THROW(EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"hous", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(
|
||||
EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"minut", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(
|
||||
EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, LocalDateTime) {
|
||||
const auto local_date_time = utils::LocalDateTime({1970, 1, 1}, {13, 3, 2, 0, 0});
|
||||
EXPECT_EQ(EvaluateFunction("LOCALDATETIME", "1970-01-01T13:03:02").ValueLocalDateTime(), local_date_time);
|
||||
const auto today = utils::UtcLocalDateTime();
|
||||
const auto one_sec_in_microseconds = 1000000;
|
||||
const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"year", TypedValue(1972)},
|
||||
{"month", TypedValue(2)},
|
||||
{"day", TypedValue(3)},
|
||||
{"hour", TypedValue(4)},
|
||||
{"minute", TypedValue(5)},
|
||||
{"second", TypedValue(6)},
|
||||
{"millisecond", TypedValue(7)},
|
||||
{"microsecond", TypedValue(8)}});
|
||||
|
||||
EXPECT_EQ(EvaluateFunction("LOCALDATETIME", map_param).ValueLocalDateTime(),
|
||||
utils::LocalDateTime({1972, 2, 3}, {4, 5, 6, 7, 8}));
|
||||
EXPECT_NEAR(EvaluateFunction("LOCALDATETIME").ValueLocalDateTime().MicrosecondsSinceEpoch(),
|
||||
today.MicrosecondsSinceEpoch(), one_sec_in_microseconds);
|
||||
EXPECT_THROW(EvaluateFunction("LOCALDATETIME", "{}"), utils::BasicException);
|
||||
EXPECT_THROW(
|
||||
EvaluateFunction("LOCALDATETIME", TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(
|
||||
EvaluateFunction("LOCALDATETIME", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, Duration) {
|
||||
constexpr size_t microseconds_in_a_year = 31556952000000;
|
||||
const auto dur = utils::Duration(microseconds_in_a_year);
|
||||
EXPECT_EQ(EvaluateFunction("DURATION", "P1Y").ValueDuration(), dur);
|
||||
const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"year", TypedValue(1972)},
|
||||
{"month", TypedValue(2)},
|
||||
{"day", TypedValue(3)},
|
||||
{"hour", TypedValue(4)},
|
||||
{"minute", TypedValue(5)},
|
||||
{"second", TypedValue(6)},
|
||||
{"millisecond", TypedValue(7)},
|
||||
{"microsecond", TypedValue(8)}});
|
||||
|
||||
EXPECT_EQ(EvaluateFunction("DURATION", map_param).ValueDuration(), utils::Duration({1972, 2, 3, 4, 5, 6, 7, 8}));
|
||||
EXPECT_THROW(EvaluateFunction("DURATION", "{}"), utils::BasicException);
|
||||
EXPECT_THROW(EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
EXPECT_THROW(
|
||||
EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})),
|
||||
QueryRuntimeException);
|
||||
}
|
||||
} // namespace
|
||||
|
Loading…
Reference in New Issue
Block a user