Functions for defining temporals (#197)

This commit is contained in:
antonio2368 2021-08-16 14:09:53 +02:00 committed by Antonio Andelic
parent 6913feacc7
commit e2112faff0
5 changed files with 261 additions and 3 deletions

View File

@ -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 &parameter_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;
}

View File

@ -1,4 +1,3 @@
/// @file
#pragma once
#include <functional>

View File

@ -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));

View File

@ -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 &lt) : 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

View File

@ -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