From 0a23eb11aeb16a3b7a20e9311cd1a7e78db07f51 Mon Sep 17 00:00:00 2001 From: antonio2368 <antonio2368@users.noreply.github.com> Date: Mon, 20 Sep 2021 10:59:30 +0200 Subject: [PATCH] Define procedure API for temporal types (#229) --- include/mg_procedure.h | 396 ++++++++++++++++++ include/mgp.py | 17 +- src/query/procedure/cypher_types.hpp | 40 +- src/query/procedure/mg_procedure_impl.cpp | 477 +++++++++++++++++++++- src/query/procedure/mg_procedure_impl.hpp | 211 ++++++++++ src/query/procedure/py_module.cpp | 140 +++++++ src/utils/temporal.cpp | 72 ++-- src/utils/temporal.hpp | 6 + 8 files changed, 1313 insertions(+), 46 deletions(-) diff --git a/include/mg_procedure.h b/include/mg_procedure.h index 1d09069c1..8ce1d40a5 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -125,6 +125,18 @@ struct mgp_edge; /// Path containing mgp_vertex and mgp_edge instances. struct mgp_path; +/// Date stored in Memgraph. +struct mgp_date; + +/// Local time stored in Memgraph. +struct mgp_local_time; + +/// Local date-time stored in Memgraph. +struct mgp_local_date_time; + +/// Duration stored in Memgraph. +struct mgp_duration; + /// All available types that can be stored in a mgp_value enum mgp_value_type { // NOTE: New types need to be appended, so as not to break ABI. @@ -138,6 +150,10 @@ enum mgp_value_type { MGP_VALUE_TYPE_VERTEX, MGP_VALUE_TYPE_EDGE, MGP_VALUE_TYPE_PATH, + MGP_VALUE_TYPE_DATE, + MGP_VALUE_TYPE_LOCAL_TIME, + MGP_VALUE_TYPE_LOCAL_DATE_TIME, + MGP_VALUE_TYPE_DURATION, }; /// Free the memory used by the given mgp_value instance. @@ -209,6 +225,38 @@ enum mgp_error mgp_value_make_edge(struct mgp_edge *val, struct mgp_value **resu /// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_path(struct mgp_path *val, struct mgp_value **result); +/// Create a mgp_value storing a mgp_date. +/// You need to free the instance through mgp_value_destroy. The ownership of +/// the date is transferred to the created mgp_value and destroying the mgp_value will +/// destroy the mgp_date. Therefore, if a mgp_value is successfully created you +/// must not call mgp_date_destroy on the given date. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_date(struct mgp_date *val, struct mgp_value **result); + +/// Create a mgp_value storing a mgp_local_time. +/// You need to free the instance through mgp_value_destroy. The ownership of +/// the local time is transferred to the created mgp_value and destroying the mgp_value will +/// destroy the mgp_local_time. Therefore, if a mgp_value is successfully created you +/// must not call mgp_local_time_destroy on the given local time. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_local_time(struct mgp_local_time *val, struct mgp_value **result); + +/// Create a mgp_value storing a mgp_local_date_time. +/// You need to free the instance through mgp_value_destroy. The ownership of +/// the local date-time is transferred to the created mgp_value and destroying the mgp_value will +/// destroy the mgp_local_date_time. Therefore, if a mgp_value is successfully created you +/// must not call mgp_local_date_time_destroy on the given local date-time. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_local_date_time(struct mgp_local_date_time *val, struct mgp_value **result); + +/// Create a mgp_value storing a mgp_duration. +/// You need to free the instance through mgp_value_destroy. The ownership of +/// the duration is transferred to the created mgp_value and destroying the mgp_value will +/// destroy the mgp_duration. Therefore, if a mgp_value is successfully created you +/// must not call mgp_duration_destroy on the given duration. +/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +enum mgp_error mgp_value_make_duration(struct mgp_duration *val, struct mgp_value **result); + /// Get the type of the value contained in mgp_value. /// Current implementation always returns without errors. enum mgp_error mgp_value_get_type(struct mgp_value *val, enum mgp_value_type *result); @@ -253,6 +301,22 @@ enum mgp_error mgp_value_is_edge(struct mgp_value *val, int *result); /// Current implementation always returns without errors. enum mgp_error mgp_value_is_path(struct mgp_value *val, int *result); +/// Result is non-zero if the given mgp_value stores a date. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_date(struct mgp_value *val, int *result); + +/// Result is non-zero if the given mgp_value stores a local time. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_local_time(struct mgp_value *val, int *result); + +/// Result is non-zero if the given mgp_value stores a local date-time. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_local_date_time(struct mgp_value *val, int *result); + +/// Result is non-zero if the given mgp_value stores a duration. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_is_duration(struct mgp_value *val, int *result); + /// Get the contained boolean value. /// Non-zero values represent `true`, while zero represents `false`. /// Result is undefined if mgp_value does not contain the expected type. @@ -299,6 +363,26 @@ enum mgp_error mgp_value_get_edge(struct mgp_value *val, struct mgp_edge **resul /// Current implementation always returns without errors. enum mgp_error mgp_value_get_path(struct mgp_value *val, struct mgp_path **result); +/// Get the contained date. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_date(struct mgp_value *val, struct mgp_date **result); + +/// Get the contained local time. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_local_time(struct mgp_value *val, struct mgp_local_time **result); + +/// Get the contained local date-time. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_local_date_time(struct mgp_value *val, struct mgp_local_date_time **result); + +/// Get the contained duration. +/// Result is undefined if mgp_value does not contain the expected type. +/// Current implementation always returns without errors. +enum mgp_error mgp_value_get_duration(struct mgp_value *val, struct mgp_duration **result); + /// Create an empty list with given capacity. /// You need to free the created instance with mgp_list_destroy. /// The created list will have allocated enough memory for `capacity` elements @@ -774,6 +858,302 @@ enum mgp_error mgp_vertices_iterator_underlying_graph_is_mutable(struct mgp_vert /// Result is NULL if the end of the iteration has been reached. enum mgp_error mgp_vertices_iterator_get(struct mgp_vertices_iterator *it, struct mgp_vertex **result); +/// @name Temporal Types +/// +///@{ + +struct mgp_date_parameters { + int year; + int month; + int day; +}; + +/// Create a date from a string following the ISO 8601 format. +/// Resulting date must be freed with mgp_date_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_from_string(const char *string, struct mgp_memory *memory, struct mgp_date **date); + +/// Create a date from mgp_date_parameter. +/// Resulting date must be freed with mgp_date_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_from_parameters(struct mgp_date_parameters *parameters, struct mgp_memory *memory, + struct mgp_date **date); + +/// Copy a mgp_date. +/// Resulting pointer must be freed with mgp_date_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_copy(struct mgp_date *date, struct mgp_memory *memory, struct mgp_date **result); + +/// Free the memory used by a mgp_date. +void mgp_date_destroy(struct mgp_date *date); + +/// Result is non-zero if given dates are equal, otherwise 0. +enum mgp_error mgp_date_equal(struct mgp_date *first, struct mgp_date *second, int *result); + +/// Get the year property of the date. +enum mgp_error mgp_date_get_year(struct mgp_date *date, int *year); + +/// Get the month property of the date. +enum mgp_error mgp_date_get_month(struct mgp_date *date, int *month); + +/// Get the day property of the date. +enum mgp_error mgp_date_get_day(struct mgp_date *date, int *day); + +/// Get the date as microseconds from Unix epoch. +enum mgp_error mgp_date_timestamp(struct mgp_date *date, int64_t *timestamp); + +/// Get the date representing current date. +/// Resulting date must be freed with mgp_date_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_now(struct mgp_memory *memory, struct mgp_date **date); + +/// Add a duration to the date. +/// Resulting date must be freed with mgp_date_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_add_duration(struct mgp_date *date, struct mgp_duration *dur, struct mgp_memory *memory, + struct mgp_date **result); + +/// Subtract a duration from the date. +/// Resulting date must be freed with mgp_date_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_sub_duration(struct mgp_date *date, struct mgp_duration *dur, struct mgp_memory *memory, + struct mgp_date **result); + +/// Get a duration between two dates. +/// Resulting duration must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_date_diff(struct mgp_date *first, struct mgp_date *second, struct mgp_memory *memory, + struct mgp_duration **result); + +struct mgp_local_time_parameters { + int hour; + int minute; + int second; + int millisecond; + int microsecond; +}; + +/// Create a local time from a string following the ISO 8601 format. +/// Resulting local time must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_from_string(const char *string, struct mgp_memory *memory, + struct mgp_local_time **local_time); + +/// Create a local time from mgp_local_time_parameters. +/// Resulting local time must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_from_parameters(struct mgp_local_time_parameters *parameters, struct mgp_memory *memory, + struct mgp_local_time **local_time); + +/// Copy a mgp_local_time. +/// Resulting pointer must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_copy(struct mgp_local_time *local_time, struct mgp_memory *memory, + struct mgp_local_time **result); + +/// Free the memory used by a mgp_local_time. +void mgp_local_time_destroy(struct mgp_local_time *local_time); + +/// Result is non-zero if given local times are equal, otherwise 0. +enum mgp_error mgp_local_time_equal(struct mgp_local_time *first, struct mgp_local_time *second, int *result); + +/// Get the hour property of the local time. +enum mgp_error mgp_local_time_get_hour(struct mgp_local_time *local_time, int *hour); + +/// Get the minute property of the local time. +enum mgp_error mgp_local_time_get_minute(struct mgp_local_time *local_time, int *minute); + +/// Get the second property of the local time. +enum mgp_error mgp_local_time_get_second(struct mgp_local_time *local_time, int *second); + +/// Get the millisecond property of the local time. +enum mgp_error mgp_local_time_get_millisecond(struct mgp_local_time *local_time, int *millisecond); + +/// Get the microsecond property of the local time. +enum mgp_error mgp_local_time_get_microsecond(struct mgp_local_time *local_time, int *microsecond); + +/// Get the local time as microseconds from midnight. +enum mgp_error mgp_local_time_timestamp(struct mgp_local_time *local_time, int64_t *timestamp); + +/// Get the local time representing current time. +/// Resulting pointer must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_now(struct mgp_memory *memory, struct mgp_local_time **local_time); + +/// Add a duration to the local time. +/// Resulting pointer must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_add_duration(struct mgp_local_time *local_time, struct mgp_duration *dur, + struct mgp_memory *memory, struct mgp_local_time **result); + +/// Subtract a duration from the local time. +/// Resulting pointer must be freed with mgp_local_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_sub_duration(struct mgp_local_time *local_time, struct mgp_duration *dur, + struct mgp_memory *memory, struct mgp_local_time **result); + +/// Get a duration between two local times. +/// Resulting pointer must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +enum mgp_error mgp_local_time_diff(struct mgp_local_time *first, struct mgp_local_time *second, + struct mgp_memory *memory, struct mgp_duration **result); + +struct mgp_local_date_time_parameters { + struct mgp_date_parameters *date_parameters; + struct mgp_local_time_parameters *local_time_parameters; +}; + +/// Create a local date-time from a string following the ISO 8601 format. +/// Resulting local date-time must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_from_string(const char *string, struct mgp_memory *memory, + struct mgp_local_date_time **local_date_time); + +/// Create a local date-time from mgp_local_date_time_parameters. +/// Resulting local date-time must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_from_parameters(struct mgp_local_date_time_parameters *parameters, + struct mgp_memory *memory, + struct mgp_local_date_time **local_date_time); + +/// Copy a mgp_local_date_time. +/// Resulting pointer must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_copy(struct mgp_local_date_time *local_date_time, struct mgp_memory *memory, + struct mgp_local_date_time **result); + +/// Free the memory used by a mgp_local_date_time. +void mgp_local_date_time_destroy(struct mgp_local_date_time *local_date_time); + +/// Result is non-zero if given local date-times are equal, otherwise 0. +enum mgp_error mgp_local_date_time_equal(struct mgp_local_date_time *first, struct mgp_local_date_time *second, + int *result); + +/// Get the year property of the local date-time. +enum mgp_error mgp_local_date_time_get_year(struct mgp_local_date_time *local_date_time, int *year); + +/// Get the month property of the local date-time. +enum mgp_error mgp_local_date_time_get_month(struct mgp_local_date_time *local_date_time, int *month); + +/// Get the day property of the local date-time. +enum mgp_error mgp_local_date_time_get_day(struct mgp_local_date_time *local_date_time, int *day); + +/// Get the hour property of the local date-time. +enum mgp_error mgp_local_date_time_get_hour(struct mgp_local_date_time *local_date_time, int *hour); + +/// Get the minute property of the local date-time. +enum mgp_error mgp_local_date_time_get_minute(struct mgp_local_date_time *local_date_time, int *minute); + +/// Get the second property of the local date-time. +enum mgp_error mgp_local_date_time_get_second(struct mgp_local_date_time *local_date_time, int *second); + +/// Get the milisecond property of the local date-time. +enum mgp_error mgp_local_date_time_get_millisecond(struct mgp_local_date_time *local_date_time, int *millisecond); + +/// Get the microsecond property of the local date-time. +enum mgp_error mgp_local_date_time_get_microsecond(struct mgp_local_date_time *local_date_time, int *microsecond); + +/// Get the local date-time as microseconds from Unix epoch. +enum mgp_error mgp_local_date_time_timestamp(struct mgp_local_date_time *local_date_time, int64_t *timestamp); + +/// Get the local date-time representing current date and time. +/// Resulting local date-time must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_now(struct mgp_memory *memory, struct mgp_local_date_time **local_date_time); + +/// Add a duration to the local date-time. +/// Resulting local date-time must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_add_duration(struct mgp_local_date_time *local_date_time, struct mgp_duration *dur, + struct mgp_memory *memory, struct mgp_local_date_time **result); + +/// Subtract a duration from the local date-time. +/// Resulting local date-time must be freed with mgp_local_date_time_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_sub_duration(struct mgp_local_date_time *local_date_time, struct mgp_duration *dur, + struct mgp_memory *memory, struct mgp_local_date_time **result); + +/// Get a duration between two local date-times. +/// Resulting duration must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +enum mgp_error mgp_local_date_time_diff(struct mgp_local_date_time *first, struct mgp_local_date_time *second, + struct mgp_memory *memory, struct mgp_duration **result); + +struct mgp_duration_parameters { + double day; + double hour; + double minute; + double second; + double millisecond; + double microsecond; +}; + +/// Create a duration from a string following the ISO 8601 format. +/// Resulting duration must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_from_string(const char *string, struct mgp_memory *memory, struct mgp_duration **duration); + +/// Create a duration from mgp_duration_parameters. +/// Resulting duration must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_from_parameters(struct mgp_duration_parameters *parameters, struct mgp_memory *memory, + struct mgp_duration **duration); + +/// Create a duration from microseconds. +/// Resulting duration must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_from_microseconds(int64_t microseconds, struct mgp_memory *memory, + struct mgp_duration **duration); + +/// Copy a mgp_duration. +/// Resulting pointer must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_copy(struct mgp_duration *duration, struct mgp_memory *memory, + struct mgp_duration **result); + +/// Free the memory used by a mgp_duration. +void mgp_duration_destroy(struct mgp_duration *duration); + +/// Result is non-zero if given durations are equal, otherwise 0. +enum mgp_error mgp_duration_equal(struct mgp_duration *first, struct mgp_duration *second, int *result); + +/// Get the duration as microseconds. +enum mgp_error mgp_duration_get_microseconds(struct mgp_duration *duration, int64_t *microseconds); + +/// Apply unary minus operator to the duration. +/// Resulting pointer must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_neg(struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_duration **result); + +/// Add two durations. +/// Resulting pointer must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_add(struct mgp_duration *first, struct mgp_duration *second, struct mgp_memory *memory, + struct mgp_duration **result); + +/// Subtract two durations. +/// Resulting pointer must be freed with mgp_duration_destroy. +/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +enum mgp_error mgp_duration_sub(struct mgp_duration *first, struct mgp_duration *second, struct mgp_memory *memory, + struct mgp_duration **result); +///@} + /// Advance the iterator to the next vertex and return it. /// The previous mgp_vertex obtained through mgp_vertices_iterator_get /// will be invalidated, and you must not use its value. @@ -857,6 +1237,22 @@ enum mgp_error mgp_type_path(struct mgp_type **result); /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_list(struct mgp_type *element_type, struct mgp_type **result); +/// Get the type representing a date. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_date(struct mgp_type **result); + +/// Get the type representing a local time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_local_time(struct mgp_type **result); + +/// Get the type representing a local date-time. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_local_date_time(struct mgp_type **result); + +/// Get the type representing a duration. +/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +enum mgp_error mgp_type_duration(struct mgp_type **result); + /// Build a type representing either a `null` value or a value of given `type`. /// /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. diff --git a/include/mgp.py b/include/mgp.py index 119366a26..b15d0d969 100644 --- a/include/mgp.py +++ b/include/mgp.py @@ -21,6 +21,7 @@ from functools import wraps import inspect import sys import typing +import datetime import _mgp @@ -951,7 +952,15 @@ Number = typing.Union[int, float] Map = typing.Union[dict, Edge, Vertex] -Any = typing.Union[bool, str, Number, Map, Path, list] +Date = datetime.date + +LocalTime = datetime.time + +LocalDateTime = datetime.datetime + +Duration = datetime.timedelta + +Any = typing.Union[bool, str, Number, Map, Path, list, Date, LocalTime, LocalDateTime, Duration] List = typing.List @@ -980,7 +989,11 @@ def _typing_to_cypher_type(type_): Map: _mgp.type_map(), Vertex: _mgp.type_node(), Edge: _mgp.type_relationship(), - Path: _mgp.type_path() + Path: _mgp.type_path(), + Date: _mgp.type_date(), + LocalTime: _mgp.type_local_time(), + LocalDateTime: _mgp.type_local_date_time(), + Duration: _mgp.type_duration() } try: return simple_types[type_] diff --git a/src/query/procedure/cypher_types.hpp b/src/query/procedure/cypher_types.hpp index e7a41100d..eb31b3f46 100644 --- a/src/query/procedure/cypher_types.hpp +++ b/src/query/procedure/cypher_types.hpp @@ -129,8 +129,6 @@ class PathType : public CypherType { bool SatisfiesType(const query::TypedValue &value) const override { return value.IsPath(); } }; -// TODO: There's also Temporal Types, but we currently do not support those. - // You'd think that MapType would be a composite type like ListType, but nope. // Why? No-one really knows. It's defined like that in "CIP2015-09-16 Public // Type System and Type Annotations" @@ -149,6 +147,44 @@ class MapType : public CypherType { } }; +// Temporal Types + +class DateType : public CypherType { + public: + std::string_view GetPresentableName() const override { return "DATE"; } + + bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DATE; } + + bool SatisfiesType(const query::TypedValue &value) const override { return value.IsDate(); } +}; + +class LocalTimeType : public CypherType { + public: + std::string_view GetPresentableName() const override { return "LOCAL_TIME"; } + + bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_LOCAL_TIME; } + + bool SatisfiesType(const query::TypedValue &value) const override { return value.IsLocalTime(); } +}; + +class LocalDateTimeType : public CypherType { + public: + std::string_view GetPresentableName() const override { return "LOCAL_DATE_TIME"; } + + bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_LOCAL_DATE_TIME; } + + bool SatisfiesType(const query::TypedValue &value) const override { return value.IsLocalDateTime(); } +}; + +class DurationType : public CypherType { + public: + std::string_view GetPresentableName() const override { return "DURATION"; } + + bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DURATION; } + + bool SatisfiesType(const query::TypedValue &value) const override { return value.IsDuration(); } +}; + // Composite Types class ListType : public CypherType { diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 1f2c6f849..3801067fa 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -23,6 +23,7 @@ #include "utils/math.hpp" #include "utils/memory.hpp" #include "utils/string.hpp" +#include "utils/temporal.hpp" // This file contains implementation of top level C API functions, but this is // all actually part of query::procedure. So use that namespace for simplicity. @@ -163,6 +164,9 @@ template <typename TFunc, typename... Args> } catch (const std::exception &e) { spdlog::error("Unexpected error during mg API call: {}", e.what()); return MGP_ERROR_UNKNOWN_ERROR; + } catch (const utils::temporal::InvalidArgumentException &e) { + spdlog::error("Invalid argument was sent to an mg API call for temporal types: {}", e.what()); + return MGP_ERROR_INVALID_ARGUMENT; } catch (...) { spdlog::error("Unexpected error during mg API call"); return MGP_ERROR_UNKNOWN_ERROR; @@ -262,8 +266,15 @@ mgp_value_type FromTypedValueType(query::TypedValue::Type type) { return MGP_VALUE_TYPE_EDGE; case query::TypedValue::Type::Path: return MGP_VALUE_TYPE_PATH; + case query::TypedValue::Type::Date: + return MGP_VALUE_TYPE_DATE; + case query::TypedValue::Type::LocalTime: + return MGP_VALUE_TYPE_LOCAL_TIME; + case query::TypedValue::Type::LocalDateTime: + return MGP_VALUE_TYPE_LOCAL_DATE_TIME; + case query::TypedValue::Type::Duration: + return MGP_VALUE_TYPE_DURATION; default: - // TODO(antonio2368): Implement this when we add mgp temporal types LOG_FATAL("Unsupported value in the procedures"); } } @@ -312,6 +323,14 @@ query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memo } return query::TypedValue(std::move(tv_path)); } + case MGP_VALUE_TYPE_DATE: + return query::TypedValue(val.date_v->date, memory); + case MGP_VALUE_TYPE_LOCAL_TIME: + return query::TypedValue(val.local_time_v->local_time, memory); + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: + return query::TypedValue(val.local_date_time_v->local_date_time, memory); + case MGP_VALUE_TYPE_DURATION: + return query::TypedValue(val.duration_v->duration, memory); } } @@ -355,6 +374,26 @@ mgp_value::mgp_value(mgp_path *val, utils::MemoryResource *m) noexcept MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator."); } +mgp_value::mgp_value(mgp_date *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_DATE), memory(m), date_v(val) { + MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator."); +} + +mgp_value::mgp_value(mgp_local_time *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_LOCAL_TIME), memory(m), local_time_v(val) { + MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator."); +} + +mgp_value::mgp_value(mgp_local_date_time *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_LOCAL_DATE_TIME), memory(m), local_date_time_v(val) { + MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator."); +} + +mgp_value::mgp_value(mgp_duration *val, utils::MemoryResource *m) noexcept + : type(MGP_VALUE_TYPE_DURATION), memory(m), duration_v(val) { + MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator."); +} + mgp_value::mgp_value(const query::TypedValue &tv, mgp_graph *graph, utils::MemoryResource *m) : type(FromTypedValueType(tv.type())), memory(m) { switch (type) { @@ -427,6 +466,26 @@ mgp_value::mgp_value(const query::TypedValue &tv, mgp_graph *graph, utils::Memor path_v = allocator.new_object<mgp_path>(std::move(tmp_path)); break; } + case MGP_VALUE_TYPE_DATE: { + utils::Allocator<mgp_date> allocator(m); + date_v = allocator.new_object<mgp_date>(tv.ValueDate()); + break; + } + case MGP_VALUE_TYPE_LOCAL_TIME: { + utils::Allocator<mgp_local_time> allocator(m); + local_time_v = allocator.new_object<mgp_local_time>(tv.ValueLocalTime()); + break; + } + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: { + utils::Allocator<mgp_local_date_time> allocator(m); + local_date_time_v = allocator.new_object<mgp_local_date_time>(tv.ValueLocalDateTime()); + break; + } + case MGP_VALUE_TYPE_DURATION: { + utils::Allocator<mgp_duration> allocator(m); + duration_v = allocator.new_object<mgp_duration>(tv.ValueDuration()); + break; + } } } @@ -481,8 +540,33 @@ mgp_value::mgp_value(const storage::PropertyValue &pv, utils::MemoryResource *m) break; } case storage::PropertyValue::Type::TemporalData: { - // TODO(antonio2368): Add support for temporala data types - LOG_FATAL("Unsupported type"); + const auto &temporal_data = pv.ValueTemporalData(); + switch (temporal_data.type) { + case storage::TemporalType::Date: { + type = MGP_VALUE_TYPE_DATE; + utils::Allocator<mgp_date> allocator(m); + date_v = allocator.new_object<mgp_date>(temporal_data.microseconds); + break; + } + case storage::TemporalType::LocalTime: { + type = MGP_VALUE_TYPE_LOCAL_TIME; + utils::Allocator<mgp_local_time> allocator(m); + local_time_v = allocator.new_object<mgp_local_time>(temporal_data.microseconds); + break; + } + case storage::TemporalType::LocalDateTime: { + type = MGP_VALUE_TYPE_LOCAL_DATE_TIME; + utils::Allocator<mgp_local_date_time> allocator(m); + local_date_time_v = allocator.new_object<mgp_local_date_time>(temporal_data.microseconds); + break; + } + case storage::TemporalType::Duration: { + type = MGP_VALUE_TYPE_DURATION; + utils::Allocator<mgp_duration> allocator(m); + duration_v = allocator.new_object<mgp_duration>(temporal_data.microseconds); + break; + } + } } } } @@ -528,6 +612,26 @@ mgp_value::mgp_value(const mgp_value &other, utils::MemoryResource *m) : type(ot path_v = allocator.new_object<mgp_path>(*other.path_v); break; } + case MGP_VALUE_TYPE_DATE: { + utils::Allocator<mgp_date> allocator(m); + date_v = allocator.new_object<mgp_date>(*other.date_v); + break; + } + case MGP_VALUE_TYPE_LOCAL_TIME: { + utils::Allocator<mgp_local_time> allocator(m); + local_time_v = allocator.new_object<mgp_local_time>(*other.local_time_v); + break; + } + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: { + utils::Allocator<mgp_local_date_time> allocator(m); + local_date_time_v = allocator.new_object<mgp_local_date_time>(*other.local_date_time_v); + break; + } + case MGP_VALUE_TYPE_DURATION: { + utils::Allocator<mgp_duration> allocator(m); + duration_v = allocator.new_object<mgp_duration>(*other.duration_v); + break; + } } } @@ -561,6 +665,18 @@ void DeleteValueMember(mgp_value *value) noexcept { case MGP_VALUE_TYPE_PATH: allocator.delete_object(value->path_v); return; + case MGP_VALUE_TYPE_DATE: + allocator.delete_object(value->date_v); + return; + case MGP_VALUE_TYPE_LOCAL_TIME: + allocator.delete_object(value->local_time_v); + return; + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: + allocator.delete_object(value->local_date_time_v); + return; + case MGP_VALUE_TYPE_DURATION: + allocator.delete_object(value->duration_v); + return; } } @@ -632,6 +748,47 @@ mgp_value::mgp_value(mgp_value &&other, utils::MemoryResource *m) : type(other.t path_v = allocator.new_object<mgp_path>(std::move(*other.path_v)); } break; + case MGP_VALUE_TYPE_DATE: + static_assert(std::is_pointer_v<decltype(date_v)>, "Expected to move date_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + date_v = other.date_v; + other.type = MGP_VALUE_TYPE_NULL; + } else { + utils::Allocator<mgp_date> allocator(m); + date_v = allocator.new_object<mgp_date>(*other.date_v); + } + break; + case MGP_VALUE_TYPE_LOCAL_TIME: + static_assert(std::is_pointer_v<decltype(local_time_v)>, "Expected to move local_time_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + local_time_v = other.local_time_v; + other.type = MGP_VALUE_TYPE_NULL; + } else { + utils::Allocator<mgp_local_time> allocator(m); + local_time_v = allocator.new_object<mgp_local_time>(*other.local_time_v); + } + break; + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: + static_assert(std::is_pointer_v<decltype(local_date_time_v)>, + "Expected to move local_date_time_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + local_date_time_v = other.local_date_time_v; + other.type = MGP_VALUE_TYPE_NULL; + } else { + utils::Allocator<mgp_local_date_time> allocator(m); + local_date_time_v = allocator.new_object<mgp_local_date_time>(*other.local_date_time_v); + } + break; + case MGP_VALUE_TYPE_DURATION: + static_assert(std::is_pointer_v<decltype(duration_v)>, "Expected to move duration_v by copying pointers."); + if (*other.GetMemoryResource() == *m) { + duration_v = other.duration_v; + other.type = MGP_VALUE_TYPE_NULL; + } else { + utils::Allocator<mgp_duration> allocator(m); + duration_v = allocator.new_object<mgp_duration>(*other.duration_v); + } + break; } DeleteValueMember(&other); other.type = MGP_VALUE_TYPE_NULL; @@ -675,6 +832,10 @@ DEFINE_MGP_VALUE_MAKE(map) DEFINE_MGP_VALUE_MAKE(vertex) DEFINE_MGP_VALUE_MAKE(edge) DEFINE_MGP_VALUE_MAKE(path) +DEFINE_MGP_VALUE_MAKE(date) +DEFINE_MGP_VALUE_MAKE(local_time) +DEFINE_MGP_VALUE_MAKE(local_date_time) +DEFINE_MGP_VALUE_MAKE(duration) namespace { mgp_value_type MgpValueGetType(const mgp_value &val) noexcept { return val.type; } @@ -704,6 +865,10 @@ DEFINE_MGP_VALUE_IS(map, MAP) DEFINE_MGP_VALUE_IS(vertex, VERTEX) DEFINE_MGP_VALUE_IS(edge, EDGE) DEFINE_MGP_VALUE_IS(path, PATH) +DEFINE_MGP_VALUE_IS(date, DATE) +DEFINE_MGP_VALUE_IS(local_time, LOCAL_TIME) +DEFINE_MGP_VALUE_IS(local_date_time, LOCAL_DATE_TIME) +DEFINE_MGP_VALUE_IS(duration, DURATION) mgp_error mgp_value_get_bool(mgp_value *val, int *result) { *result = val->bool_v ? 1 : 0; @@ -735,6 +900,10 @@ DEFINE_MGP_VALUE_GET(map) DEFINE_MGP_VALUE_GET(vertex) DEFINE_MGP_VALUE_GET(edge) DEFINE_MGP_VALUE_GET(path) +DEFINE_MGP_VALUE_GET(date) +DEFINE_MGP_VALUE_GET(local_time) +DEFINE_MGP_VALUE_GET(local_date_time) +DEFINE_MGP_VALUE_GET(duration) mgp_error mgp_list_make_empty(size_t capacity, mgp_memory *memory, mgp_list **result) { return WrapExceptions( @@ -975,6 +1144,274 @@ mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) { result); } +mgp_error mgp_date_from_string(const char *string, mgp_memory *memory, mgp_date **date) { + return WrapExceptions([string, memory] { return NewRawMgpObject<mgp_date>(memory, string); }, date); +} + +mgp_error mgp_date_from_parameters(mgp_date_parameters *parameters, mgp_memory *memory, mgp_date **date) { + return WrapExceptions([parameters, memory] { return NewRawMgpObject<mgp_date>(memory, parameters); }, date); +} + +mgp_error mgp_date_copy(mgp_date *date, mgp_memory *memory, mgp_date **result) { + return WrapExceptions([date, memory] { return NewRawMgpObject<mgp_date>(memory, *date); }, result); +} + +void mgp_date_destroy(mgp_date *date) { DeleteRawMgpObject(date); } + +mgp_error mgp_date_equal(mgp_date *first, mgp_date *second, int *result) { + return WrapExceptions([first, second] { return first->date == second->date; }, result); +} + +mgp_error mgp_date_get_year(mgp_date *date, int *year) { + return WrapExceptions([date] { return date->date.years; }, year); +} + +mgp_error mgp_date_get_month(mgp_date *date, int *month) { + return WrapExceptions([date] { return date->date.months; }, month); +} + +mgp_error mgp_date_get_day(mgp_date *date, int *day) { + return WrapExceptions([date] { return date->date.days; }, day); +} + +mgp_error mgp_date_timestamp(mgp_date *date, int64_t *timestamp) { + return WrapExceptions([date] { return static_cast<int>(date->date.MicrosecondsSinceEpoch()); }, timestamp); +} + +mgp_error mgp_date_now(mgp_memory *memory, mgp_date **date) { + return WrapExceptions([memory] { return NewRawMgpObject<mgp_date>(memory, utils::UtcToday()); }, date); +} + +mgp_error mgp_date_add_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory, mgp_date **result) { + return WrapExceptions([date, dur, memory] { return NewRawMgpObject<mgp_date>(memory, date->date + dur->duration); }, + result); +} + +mgp_error mgp_date_sub_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory, mgp_date **result) { + return WrapExceptions([date, dur, memory] { return NewRawMgpObject<mgp_date>(memory, date->date - dur->duration); }, + result); +} + +mgp_error mgp_date_diff(mgp_date *first, mgp_date *second, mgp_memory *memory, mgp_duration **result) { + return WrapExceptions( + [first, second, memory] { return NewRawMgpObject<mgp_duration>(memory, first->date - second->date); }, result); +} + +mgp_error mgp_local_time_from_string(const char *string, mgp_memory *memory, mgp_local_time **local_time) { + return WrapExceptions([string, memory] { return NewRawMgpObject<mgp_local_time>(memory, string); }, local_time); +} + +mgp_error mgp_local_time_from_parameters(mgp_local_time_parameters *parameters, mgp_memory *memory, + mgp_local_time **local_time) { + return WrapExceptions([parameters, memory] { return NewRawMgpObject<mgp_local_time>(memory, parameters); }, + local_time); +} + +mgp_error mgp_local_time_copy(mgp_local_time *local_time, mgp_memory *memory, mgp_local_time **result) { + return WrapExceptions([local_time, memory] { return NewRawMgpObject<mgp_local_time>(memory, *local_time); }, result); +} + +void mgp_local_time_destroy(mgp_local_time *local_time) { DeleteRawMgpObject(local_time); } + +mgp_error mgp_local_time_equal(mgp_local_time *first, mgp_local_time *second, int *result) { + return WrapExceptions([first, second] { return first->local_time == second->local_time; }, result); +} + +mgp_error mgp_local_time_get_hour(mgp_local_time *local_time, int *hour) { + return WrapExceptions([local_time] { return local_time->local_time.hours; }, hour); +} + +mgp_error mgp_local_time_get_minue(mgp_local_time *local_time, int *minute) { + return WrapExceptions([local_time] { return local_time->local_time.minutes; }, minute); +} + +mgp_error mgp_local_time_get_second(mgp_local_time *local_time, int *second) { + return WrapExceptions([local_time] { return local_time->local_time.seconds; }, second); +} + +mgp_error mgp_local_time_get_millisecond(mgp_local_time *local_time, int *millisecond) { + return WrapExceptions([local_time] { return local_time->local_time.milliseconds; }, millisecond); +} + +mgp_error mgp_local_time_get_microsecond(mgp_local_time *local_time, int *microsecond) { + return WrapExceptions([local_time] { return local_time->local_time.microseconds; }, microsecond); +} + +mgp_error mgp_local_time_timestamp(mgp_local_time *local_time, int64_t *timestamp) { + return WrapExceptions([local_time] { return static_cast<int>(local_time->local_time.MicrosecondsSinceEpoch()); }, + timestamp); +} + +mgp_error mgp_local_time_now(mgp_memory *memory, mgp_local_time **local_time) { + return WrapExceptions([memory] { return NewRawMgpObject<mgp_local_time>(memory, utils::UtcLocalTime()); }, + local_time); +} + +mgp_error mgp_local_time_add_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory, + mgp_local_time **result) { + return WrapExceptions( + [local_time, dur, memory] { + return NewRawMgpObject<mgp_local_time>(memory, local_time->local_time + dur->duration); + }, + result); +} + +mgp_error mgp_local_time_sub_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory, + mgp_local_time **result) { + return WrapExceptions( + [local_time, dur, memory] { + return NewRawMgpObject<mgp_local_time>(memory, local_time->local_time - dur->duration); + }, + result); +} + +mgp_error mgp_local_time_diff(mgp_local_time *first, mgp_local_time *second, mgp_memory *memory, + mgp_duration **result) { + return WrapExceptions( + [first, second, memory] { return NewRawMgpObject<mgp_duration>(memory, first->local_time - second->local_time); }, + result); +} + +mgp_error mgp_local_date_time_from_string(const char *string, mgp_memory *memory, + mgp_local_date_time **local_date_time) { + return WrapExceptions([string, memory] { return NewRawMgpObject<mgp_local_date_time>(memory, string); }, + local_date_time); +} + +mgp_error mgp_local_date_time_from_parameters(mgp_local_date_time_parameters *parameters, mgp_memory *memory, + mgp_local_date_time **local_date_time) { + return WrapExceptions([parameters, memory] { return NewRawMgpObject<mgp_local_date_time>(memory, parameters); }, + local_date_time); +} + +mgp_error mgp_local_date_time_copy(mgp_local_date_time *local_date_time, mgp_memory *memory, + mgp_local_date_time **result) { + return WrapExceptions( + [local_date_time, memory] { return NewRawMgpObject<mgp_local_date_time>(memory, *local_date_time); }, result); +} + +void mgp_local_date_time_destroy(mgp_local_date_time *local_date_time) { DeleteRawMgpObject(local_date_time); } + +mgp_error mgp_local_date_time_equal(mgp_local_date_time *first, mgp_local_date_time *second, int *result) { + return WrapExceptions([first, second] { return first->local_date_time == second->local_date_time; }, result); +} + +mgp_error mgp_local_date_time_get_year(mgp_local_date_time *local_date_time, int *year) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.date.years; }, year); +} + +mgp_error mgp_local_date_time_get_month(mgp_local_date_time *local_date_time, int *month) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.date.months; }, month); +} + +mgp_error mgp_local_date_time_get_day(mgp_local_date_time *local_date_time, int *day) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.date.days; }, day); +} + +mgp_error mgp_local_date_time_get_hour(mgp_local_date_time *local_date_time, int *hour) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.local_time.hours; }, hour); +} + +mgp_error mgp_local_date_time_get_minute(mgp_local_date_time *local_date_time, int *minute) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.local_time.minutes; }, minute); +} + +mgp_error mgp_local_date_time_get_second(mgp_local_date_time *local_date_time, int *second) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.local_time.seconds; }, second); +} + +mgp_error mgp_local_date_time_get_millisecond(mgp_local_date_time *local_date_time, int *millisecond) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.local_time.milliseconds; }, + millisecond); +} + +mgp_error mgp_local_date_time_get_microsecond(mgp_local_date_time *local_date_time, int *microsecond) { + return WrapExceptions([local_date_time] { return local_date_time->local_date_time.local_time.microseconds; }, + microsecond); +} + +mgp_error mgp_local_date_time_timestamp(mgp_local_date_time *local_date_time, int64_t *timestamp) { + return WrapExceptions( + [local_date_time] { return static_cast<int>(local_date_time->local_date_time.MicrosecondsSinceEpoch()); }, + timestamp); +} + +mgp_error mgp_local_date_time_now(mgp_memory *memory, mgp_local_date_time **local_date_time) { + return WrapExceptions([memory] { return NewRawMgpObject<mgp_local_date_time>(memory, utils::UtcLocalDateTime()); }, + local_date_time); +} + +mgp_error mgp_local_date_time_add_duration(mgp_local_date_time *local_date_time, mgp_duration *dur, mgp_memory *memory, + mgp_local_date_time **result) { + return WrapExceptions( + [local_date_time, dur, memory] { + return NewRawMgpObject<mgp_local_date_time>(memory, local_date_time->local_date_time + dur->duration); + }, + result); +} + +mgp_error mgp_local_date_time_sub_duration(mgp_local_date_time *local_date_time, mgp_duration *dur, mgp_memory *memory, + mgp_local_date_time **result) { + return WrapExceptions( + [local_date_time, dur, memory] { + return NewRawMgpObject<mgp_local_date_time>(memory, local_date_time->local_date_time - dur->duration); + }, + result); +} + +mgp_error mgp_local_date_time_diff(mgp_local_date_time *first, mgp_local_date_time *second, mgp_memory *memory, + mgp_duration **result) { + return WrapExceptions( + [first, second, memory] { + return NewRawMgpObject<mgp_duration>(memory, first->local_date_time - second->local_date_time); + }, + result); +} + +mgp_error mgp_duration_from_string(const char *string, mgp_memory *memory, mgp_duration **duration) { + return WrapExceptions([memory, string] { return NewRawMgpObject<mgp_duration>(memory, string); }, duration); +} + +mgp_error mgp_duration_from_parameters(mgp_duration_parameters *parameters, mgp_memory *memory, + mgp_duration **duration) { + return WrapExceptions([memory, parameters] { return NewRawMgpObject<mgp_duration>(memory, parameters); }, duration); +} + +mgp_error mgp_duration_from_microseconds(int64_t microseconds, mgp_memory *memory, mgp_duration **duration) { + return WrapExceptions([microseconds, memory] { return NewRawMgpObject<mgp_duration>(memory, microseconds); }, + duration); +} + +mgp_error mgp_duration_copy(mgp_duration *duration, mgp_memory *memory, mgp_duration **result) { + return WrapExceptions([duration, memory] { return NewRawMgpObject<mgp_duration>(memory, *duration); }, result); +} + +void mgp_duration_destroy(mgp_duration *duration) { DeleteRawMgpObject(duration); } + +mgp_error mgp_duration_get_microseconds(mgp_duration *duration, int64_t *microseconds) { + return WrapExceptions([duration] { return duration->duration.microseconds; }, microseconds); +} + +mgp_error mgp_duration_equal(mgp_duration *first, mgp_duration *second, int *result) { + return WrapExceptions([first, second] { return first->duration == second->duration; }, result); +} + +mgp_error mgp_duration_neg(mgp_duration *dur, mgp_memory *memory, mgp_duration **result) { + return WrapExceptions([memory, dur] { return NewRawMgpObject<mgp_duration>(memory, -dur->duration); }, result); +} + +mgp_error mgp_duration_add(mgp_duration *first, mgp_duration *second, mgp_memory *memory, mgp_duration **result) { + return WrapExceptions( + [memory, first, second] { return NewRawMgpObject<mgp_duration>(memory, first->duration + second->duration); }, + result); +} + +mgp_error mgp_duration_sub(mgp_duration *first, mgp_duration *second, mgp_memory *memory, mgp_duration **result) { + return WrapExceptions( + [memory, first, second] { return NewRawMgpObject<mgp_duration>(memory, first->duration - second->duration); }, + result); +} + /// Plugin Result mgp_error mgp_result_set_error_msg(mgp_result *res, const char *msg) { @@ -1106,6 +1543,18 @@ storage::PropertyValue ToPropertyValue(const mgp_value &value) { return ToPropertyValue(*value.list_v); case MGP_VALUE_TYPE_MAP: return ToPropertyValue(*value.map_v); + case MGP_VALUE_TYPE_DATE: + return storage::PropertyValue{ + storage::TemporalData{storage::TemporalType::Date, value.date_v->date.MicrosecondsSinceEpoch()}}; + case MGP_VALUE_TYPE_LOCAL_TIME: + return storage::PropertyValue{storage::TemporalData{storage::TemporalType::LocalTime, + value.local_time_v->local_time.MicrosecondsSinceEpoch()}}; + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: + return storage::PropertyValue{storage::TemporalData{ + storage::TemporalType::LocalDateTime, value.local_date_time_v->local_date_time.MicrosecondsSinceEpoch()}}; + case MGP_VALUE_TYPE_DURATION: + return storage::PropertyValue{ + storage::TemporalData{storage::TemporalType::Duration, value.duration_v->duration.microseconds}}; case MGP_VALUE_TYPE_VERTEX: throw ValueConversionException{"A vertex is not a valid property value! "}; case MGP_VALUE_TYPE_EDGE: @@ -1754,6 +2203,10 @@ DEFINE_MGP_TYPE_GETTER(Map, map); DEFINE_MGP_TYPE_GETTER(Node, node); DEFINE_MGP_TYPE_GETTER(Relationship, relationship); DEFINE_MGP_TYPE_GETTER(Path, path); +DEFINE_MGP_TYPE_GETTER(Date, date); +DEFINE_MGP_TYPE_GETTER(LocalTime, local_time); +DEFINE_MGP_TYPE_GETTER(LocalDateTime, local_date_time); +DEFINE_MGP_TYPE_GETTER(Duration, duration); mgp_error mgp_type_list(mgp_type *type, mgp_type **result) { return WrapExceptions( @@ -1850,6 +2303,10 @@ mgp_error mgp_proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, case MGP_VALUE_TYPE_STRING: case MGP_VALUE_TYPE_LIST: case MGP_VALUE_TYPE_MAP: + case MGP_VALUE_TYPE_DATE: + case MGP_VALUE_TYPE_LOCAL_TIME: + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: + case MGP_VALUE_TYPE_DURATION: break; } // Default value must be of required `type`. @@ -1941,16 +2398,18 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) { PrintValue(item.second, &stream); }); return (*stream) << "}"; + case TypedValue::Type::Date: + return (*stream) << value.ValueDate(); + case TypedValue::Type::LocalTime: + return (*stream) << value.ValueLocalTime(); + case TypedValue::Type::LocalDateTime: + return (*stream) << value.ValueLocalDateTime(); + case TypedValue::Type::Duration: + return (*stream) << value.ValueDuration(); case TypedValue::Type::Vertex: case TypedValue::Type::Edge: case TypedValue::Type::Path: LOG_FATAL("value must not be a graph element"); - case TypedValue::Type::Date: - case TypedValue::Type::LocalTime: - case TypedValue::Type::LocalDateTime: - case TypedValue::Type::Duration: - // TODO(antonio2368): Check how to print out nicely temporal types - LOG_FATAL("Temporal types not imlemented yet"); } } diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 1dc436ef1..23c993c30 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -18,6 +18,7 @@ #include "utils/pmr/map.hpp" #include "utils/pmr/string.hpp" #include "utils/pmr/vector.hpp" +#include "utils/temporal.hpp" /// Wraps memory resource used in custom procedures. /// /// This should have been `using mgp_memory = utils::MemoryResource`, but that's @@ -53,6 +54,11 @@ struct mgp_value { /// Take ownership of the mgp_path, MemoryResource must match. mgp_value(mgp_path *, utils::MemoryResource *) noexcept; + mgp_value(mgp_date *, utils::MemoryResource *) noexcept; + mgp_value(mgp_local_time *, utils::MemoryResource *) noexcept; + mgp_value(mgp_local_date_time *, utils::MemoryResource *) noexcept; + mgp_value(mgp_duration *, utils::MemoryResource *) noexcept; + /// Construct by copying query::TypedValue using utils::MemoryResource. /// mgp_graph is needed to construct mgp_vertex and mgp_edge. /// @throw std::bad_alloc @@ -102,9 +108,214 @@ struct mgp_value { mgp_vertex *vertex_v; mgp_edge *edge_v; mgp_path *path_v; + mgp_date *date_v; + mgp_local_time *local_time_v; + mgp_local_date_time *local_date_time_v; + mgp_duration *duration_v; }; }; +inline utils::DateParameters MapDateParameters(const mgp_date_parameters *parameters) { + return {.years = parameters->year, .months = parameters->month, .days = parameters->day}; +} + +struct mgp_date { + /// Allocator type so that STL containers are aware that we need one. + /// We don't actually need this, but it simplifies the C API, because we store + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator<mgp_date>; + + // Hopefully utils::Date copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v<utils::Date>); + + mgp_date(const utils::Date &date, utils::MemoryResource *memory) noexcept : memory(memory), date(date) {} + + mgp_date(const std::string_view string, utils::MemoryResource *memory) noexcept + : memory(memory), date(utils::ParseDateParameters(string).first) {} + + mgp_date(const mgp_date_parameters *parameters, utils::MemoryResource *memory) noexcept + : memory(memory), date(MapDateParameters(parameters)) {} + + mgp_date(const int64_t microseconds, utils::MemoryResource *memory) noexcept : memory(memory), date(microseconds) {} + + mgp_date(const mgp_date &other, utils::MemoryResource *memory) noexcept : memory(memory), date(other.date) {} + + mgp_date(mgp_date &&other, utils::MemoryResource *memory) noexcept : memory(memory), date(other.date) {} + + mgp_date(mgp_date &&other) noexcept : memory(other.memory), date(other.date) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_date(const mgp_date &) = delete; + + mgp_date &operator=(const mgp_date &) = delete; + mgp_date &operator=(mgp_date &&) = delete; + + ~mgp_date() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + utils::Date date; +}; + +inline utils::LocalTimeParameters MapLocalTimeParameters(const mgp_local_time_parameters *parameters) { + return {.hours = parameters->hour, + .minutes = parameters->minute, + .seconds = parameters->second, + .milliseconds = parameters->millisecond, + .microseconds = parameters->microsecond}; +} + +struct mgp_local_time { + /// Allocator type so that STL containers are aware that we need one. + /// We don't actually need this, but it simplifies the C API, because we store + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator<mgp_local_time>; + + // Hopefully utils::LocalTime copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v<utils::LocalTime>); + + mgp_local_time(const std::string_view string, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(utils::ParseLocalTimeParameters(string).first) {} + + mgp_local_time(const mgp_local_time_parameters *parameters, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(MapLocalTimeParameters(parameters)) {} + + mgp_local_time(const utils::LocalTime &local_time, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(local_time) {} + + mgp_local_time(const int64_t microseconds, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(microseconds) {} + + mgp_local_time(const mgp_local_time &other, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(other.local_time) {} + + mgp_local_time(mgp_local_time &&other, utils::MemoryResource *memory) noexcept + : memory(memory), local_time(other.local_time) {} + + mgp_local_time(mgp_local_time &&other) noexcept : memory(other.memory), local_time(other.local_time) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_local_time(const mgp_local_time &) = delete; + + mgp_local_time &operator=(const mgp_local_time &) = delete; + mgp_local_time &operator=(mgp_local_time &&) = delete; + + ~mgp_local_time() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + utils::LocalTime local_time; +}; + +inline utils::LocalDateTime CreateLocalDateTimeFromString(const std::string_view string) { + const auto &[date_parameters, local_time_parameters] = utils::ParseLocalDateTimeParameters(string); + return utils::LocalDateTime{date_parameters, local_time_parameters}; +} + +struct mgp_local_date_time { + /// Allocator type so that STL containers are aware that we need one. + /// We don't actually need this, but it simplifies the C API, because we store + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator<mgp_local_date_time>; + + // Hopefully utils::LocalDateTime copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v<utils::LocalDateTime>); + + mgp_local_date_time(const utils::LocalDateTime &local_date_time, utils::MemoryResource *memory) noexcept + : memory(memory), local_date_time(local_date_time) {} + + mgp_local_date_time(const std::string_view string, utils::MemoryResource *memory) noexcept + : memory(memory), local_date_time(CreateLocalDateTimeFromString(string)) {} + + mgp_local_date_time(const mgp_local_date_time_parameters *parameters, utils::MemoryResource *memory) noexcept + : memory(memory), + local_date_time(MapDateParameters(parameters->date_parameters), + MapLocalTimeParameters(parameters->local_time_parameters)) {} + + mgp_local_date_time(const int64_t microseconds, utils::MemoryResource *memory) noexcept + : memory(memory), local_date_time(microseconds) {} + + mgp_local_date_time(const mgp_local_date_time &other, utils::MemoryResource *memory) noexcept + : memory(memory), local_date_time(other.local_date_time) {} + + mgp_local_date_time(mgp_local_date_time &&other, utils::MemoryResource *memory) noexcept + : memory(memory), local_date_time(other.local_date_time) {} + + mgp_local_date_time(mgp_local_date_time &&other) noexcept + : memory(other.memory), local_date_time(other.local_date_time) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_local_date_time(const mgp_local_date_time &) = delete; + + mgp_local_date_time &operator=(const mgp_local_date_time &) = delete; + mgp_local_date_time &operator=(mgp_local_date_time &&) = delete; + + ~mgp_local_date_time() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + utils::LocalDateTime local_date_time; +}; + +inline utils::DurationParameters MapDurationParameters(const mgp_duration_parameters *parameters) { + return {.days = parameters->day, + .hours = parameters->hour, + .minutes = parameters->minute, + .seconds = parameters->second, + .milliseconds = parameters->millisecond, + .microseconds = parameters->microsecond}; +} + +struct mgp_duration { + /// Allocator type so that STL containers are aware that we need one. + /// We don't actually need this, but it simplifies the C API, because we store + /// the allocator which was used to allocate `this`. + using allocator_type = utils::Allocator<mgp_duration>; + + // Hopefully utils::Duration copy constructor remains noexcept, so that we can + // have everything noexcept here. + static_assert(std::is_nothrow_copy_constructible_v<utils::Duration>); + + mgp_duration(const std::string_view string, utils::MemoryResource *memory) noexcept + : memory(memory), duration(utils::ParseDurationParameters(string)) {} + + mgp_duration(const mgp_duration_parameters *parameters, utils::MemoryResource *memory) noexcept + : memory(memory), duration(MapDurationParameters(parameters)) {} + + mgp_duration(const utils::Duration &duration, utils::MemoryResource *memory) noexcept + : memory(memory), duration(duration) {} + + mgp_duration(const int64_t microseconds, utils::MemoryResource *memory) noexcept + : memory(memory), duration(microseconds) {} + + mgp_duration(const mgp_duration &other, utils::MemoryResource *memory) noexcept + : memory(memory), duration(other.duration) {} + + mgp_duration(mgp_duration &&other, utils::MemoryResource *memory) noexcept + : memory(memory), duration(other.duration) {} + + mgp_duration(mgp_duration &&other) noexcept : memory(other.memory), duration(other.duration) {} + + /// Copy construction without utils::MemoryResource is not allowed. + mgp_duration(const mgp_duration &) = delete; + + mgp_duration &operator=(const mgp_duration &) = delete; + mgp_duration &operator=(mgp_duration &&) = delete; + + ~mgp_duration() = default; + + utils::MemoryResource *GetMemoryResource() const noexcept { return memory; } + + utils::MemoryResource *memory; + utils::Duration duration; +}; + struct mgp_list { /// Allocator type so that STL containers are aware that we need one. using allocator_type = utils::Allocator<mgp_list>; diff --git a/src/query/procedure/py_module.cpp b/src/query/procedure/py_module.cpp index e41c987e1..ab97d15a6 100644 --- a/src/query/procedure/py_module.cpp +++ b/src/query/procedure/py_module.cpp @@ -1,5 +1,6 @@ #include "query/procedure/py_module.hpp" +#include <datetime.h> #include <pyerrors.h> #include <array> #include <sstream> @@ -9,6 +10,7 @@ #include "query/procedure/mg_procedure_helpers.hpp" #include "query/procedure/mg_procedure_impl.hpp" +#include "utils/memory.hpp" #include "utils/on_scope_exit.hpp" #include "utils/pmr/vector.hpp" @@ -1127,6 +1129,10 @@ DEFINE_PY_MGP_MODULE_TYPE(Map, map); DEFINE_PY_MGP_MODULE_TYPE(Node, node); DEFINE_PY_MGP_MODULE_TYPE(Relationship, relationship); DEFINE_PY_MGP_MODULE_TYPE(Path, path); +DEFINE_PY_MGP_MODULE_TYPE(Date, date); +DEFINE_PY_MGP_MODULE_TYPE(LocalTime, local_time); +DEFINE_PY_MGP_MODULE_TYPE(LocalDateTime, local_date_time); +DEFINE_PY_MGP_MODULE_TYPE(Duration, duration); static PyMethodDef PyMgpModuleMethods[] = { {"type_nullable", PyMgpModuleTypeNullable, METH_O, @@ -1145,6 +1151,10 @@ static PyMethodDef PyMgpModuleMethods[] = { "Get the type representing graph relationship values."}, {"type_path", PyMgpModuleTypePath, METH_NOARGS, "Get the type representing a graph path (walk) from one node to another."}, + {"type_date", PyMgpModuleTypeDate, METH_NOARGS, "Get the type representing a Date."}, + {"type_local_time", PyMgpModuleTypeLocalTime, METH_NOARGS, "Get the type representing a LocalTime."}, + {"type_local_date_time", PyMgpModuleTypeLocalDateTime, METH_NOARGS, "Get the type representing a LocalDateTime."}, + {"type_duration", PyMgpModuleTypeDuration, METH_NOARGS, "Get the type representing a Duration."}, {nullptr}, }; @@ -1956,6 +1966,10 @@ PyObject *PyInitMgpModule() { } } clean_up.Disable(); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + PyDateTime_IMPORT; + return mgp; } @@ -2055,6 +2069,30 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) { py::Object py_path(reinterpret_cast<PyObject *>(MakePyPath(*p, py_graph))); return py_mgp.CallMethod("Path", py_path); } + case MGP_VALUE_TYPE_DATE: { + const auto &date = value.date_v->date; + py::Object py_date(PyDate_FromDate(date.years, date.months, date.days)); + return py_date; + } + case MGP_VALUE_TYPE_LOCAL_TIME: { + const auto &local_time = value.local_time_v->local_time; + py::Object py_local_time(PyTime_FromTime(local_time.hours, local_time.minutes, local_time.seconds, + local_time.milliseconds * 1000 + local_time.microseconds)); + return py_local_time; + } + case MGP_VALUE_TYPE_LOCAL_DATE_TIME: { + const auto &local_time = value.local_date_time_v->local_date_time.local_time; + const auto &date = value.local_date_time_v->local_date_time.date; + py::Object py_local_date_time( + PyDateTime_FromDateAndTime(date.years, date.months, date.days, local_time.hours, local_time.minutes, + local_time.seconds, local_time.milliseconds * 1000 + local_time.microseconds)); + return py_local_date_time; + } + case MGP_VALUE_TYPE_DURATION: { + const auto &duration = value.duration_v->duration; + py::Object py_duration(PyDelta_FromDSU(0, 0, duration.microseconds)); + return py_duration; + } } } @@ -2247,6 +2285,108 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) { throw std::invalid_argument("'mgp.Path' is missing '_path' attribute"); } return PyObjectToMgpValue(path.Ptr(), memory); + } else if (PyDate_CheckExact(o)) { + mgp_date_parameters parameters{ + .year = PyDateTime_GET_YEAR(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .month = PyDateTime_GET_MONTH(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .day = PyDateTime_GET_DAY(o)}; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + MgpUniquePtr<mgp_date> date{nullptr, mgp_date_destroy}; + + if (const auto err = CreateMgpObject(date, mgp_date_from_parameters, ¶meters, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_date"}; + } + if (const auto err = mgp_value_make_date(date.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_value"}; + } + static_cast<void>(date.release()); + } else if (PyTime_CheckExact(o)) { + mgp_local_time_parameters parameters{ + .hour = PyDateTime_TIME_GET_HOUR(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .minute = PyDateTime_TIME_GET_MINUTE(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .second = PyDateTime_TIME_GET_SECOND(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .millisecond = + PyDateTime_TIME_GET_MICROSECOND(o) / // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + 1000, + .microsecond = + PyDateTime_TIME_GET_MICROSECOND(o) % // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + 1000}; + MgpUniquePtr<mgp_local_time> local_time{nullptr, mgp_local_time_destroy}; + + if (const auto err = CreateMgpObject(local_time, mgp_local_time_from_parameters, ¶meters, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_local_time"}; + } + if (const auto err = mgp_value_make_local_time(local_time.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_value"}; + } + static_cast<void>(local_time.release()); + } else if (PyDateTime_CheckExact(o)) { + mgp_date_parameters date_parameters{ + .year = PyDateTime_GET_YEAR(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .month = PyDateTime_GET_MONTH(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .day = PyDateTime_GET_DAY(o)}; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + mgp_local_time_parameters local_time_parameters{ + .hour = PyDateTime_DATE_GET_HOUR(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .minute = PyDateTime_DATE_GET_MINUTE(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .second = PyDateTime_DATE_GET_SECOND(o), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + .millisecond = + PyDateTime_DATE_GET_MICROSECOND(o) / // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + 1000, + .microsecond = + PyDateTime_DATE_GET_MICROSECOND(o) % // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + 1000}; + + mgp_local_date_time_parameters parameters{&date_parameters, &local_time_parameters}; + + MgpUniquePtr<mgp_local_date_time> local_date_time{nullptr, mgp_local_date_time_destroy}; + + if (const auto err = CreateMgpObject(local_date_time, mgp_local_date_time_from_parameters, ¶meters, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_local_date_time"}; + } + if (const auto err = mgp_value_make_local_date_time(local_date_time.get(), &mgp_v); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_value"}; + } + static_cast<void>(local_date_time.release()); + } else if (PyDelta_CheckExact(o)) { + constexpr int64_t microseconds_in_days = static_cast<std::chrono::microseconds>(std::chrono::days{1}).count(); + const auto days = + PyDateTime_DELTA_GET_DAYS(o); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + auto microseconds = + std::abs(days) * microseconds_in_days + + PyDateTime_DELTA_GET_SECONDS(o) * 1000 * // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + 1000 + + PyDateTime_DELTA_GET_MICROSECONDS(o); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,hicpp-signed-bitwise) + microseconds *= days < 0 ? -1 : 1; + + MgpUniquePtr<mgp_duration> duration{nullptr, mgp_duration_destroy}; + + if (const auto err = CreateMgpObject(duration, mgp_duration_from_microseconds, microseconds, memory); + err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_duration"}; + } + if (const auto err = mgp_value_make_duration(duration.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) { + throw std::bad_alloc{}; + } else if (err != MGP_ERROR_NO_ERROR) { + throw std::runtime_error{"Unexpected error while creating mgp_value"}; + } + static_cast<void>(duration.release()); } else { throw std::invalid_argument("Unsupported PyObject conversion"); } diff --git a/src/utils/temporal.cpp b/src/utils/temporal.cpp index 438596bfa..ab3842967 100644 --- a/src/utils/temporal.cpp +++ b/src/utils/temporal.cpp @@ -47,18 +47,18 @@ Date::Date(const int64_t microseconds) { Date::Date(const DateParameters &date_parameters) { if (!IsInBounds(0, 9999, date_parameters.years)) { - throw utils::BasicException( + throw temporal::InvalidArgumentException( "Creating a Date with invalid year parameter. The value should be an integer between 0 and 9999."); } if (!IsInBounds(1, 12, date_parameters.months)) { - throw utils::BasicException( + throw temporal::InvalidArgumentException( "Creating a Date with invalid month parameter. The value should be an integer between 1 and 12."); } if (!IsInBounds(1, 31, date_parameters.days) || !IsValidDay(date_parameters.days, date_parameters.months, date_parameters.years)) { - throw utils::BasicException( + throw temporal::InvalidArgumentException( "Creating a Date with invalid day parameter. The value should be an integer between 1 and 31, depending on the " "month and year."); } @@ -74,7 +74,7 @@ tm GetUtcFromSystemClockOrThrow() { 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"); + throw temporal::InvalidArgumentException("Can't access clock's UTC time"); } return utc_today; } @@ -132,13 +132,13 @@ std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string if (!std::any_of( valid_sizes.begin(), valid_sizes.end(), [date_string_size = date_string.size()](const auto valid_size) { return valid_size == date_string_size; })) { - throw utils::BasicException("Invalid string for date. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid string for date. {}", kSupportedDateFormatsHelpMessage); } DateParameters date_parameters; auto maybe_year = ParseNumber<int64_t>(date_string, 4); if (!maybe_year) { - throw utils::BasicException("Invalid year in the string. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid year in the string. {}", kSupportedDateFormatsHelpMessage); } date_parameters.years = *maybe_year; date_string.remove_prefix(4); @@ -151,7 +151,7 @@ std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string auto maybe_month = ParseNumber<int64_t>(date_string, 2); if (!maybe_month) { - throw utils::BasicException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage); } date_parameters.months = *maybe_month; date_string.remove_prefix(2); @@ -159,21 +159,21 @@ std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string if (!date_string.empty()) { if (date_string.front() == '-') { if (!is_extended_format) { - throw utils::BasicException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage); } date_string.remove_prefix(1); } auto maybe_day = ParseNumber<int64_t>(date_string, 2); if (!maybe_day) { - throw utils::BasicException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage); } date_parameters.days = *maybe_day; date_string.remove_prefix(2); } if (!date_string.empty()) { - throw utils::BasicException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage); } return {date_parameters, is_extended_format}; @@ -242,7 +242,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l } if (*using_colon ^ has_colon) { - throw utils::BasicException( + throw temporal::InvalidArgumentException( "Invalid format for the local time. A separator should be used consistently or not at all. {}", kSupportedTimeFormatsHelpMessage); } @@ -252,7 +252,8 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l } if (local_time_string.empty()) { - throw utils::BasicException("Invalid format for the local time. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid format for the local time. {}", + kSupportedTimeFormatsHelpMessage); } }; @@ -260,7 +261,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l const auto maybe_hour = ParseNumber<int64_t>(local_time_string, 2); if (!maybe_hour) { - throw utils::BasicException("Invalid hour in the string. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid hour in the string. {}", kSupportedTimeFormatsHelpMessage); } local_time_parameters.hours = *maybe_hour; local_time_string.remove_prefix(2); @@ -273,7 +274,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l const auto maybe_minute = ParseNumber<int64_t>(local_time_string, 2); if (!maybe_minute) { - throw utils::BasicException("Invalid minutes in the string. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid minutes in the string. {}", kSupportedTimeFormatsHelpMessage); } local_time_parameters.minutes = *maybe_minute; local_time_string.remove_prefix(2); @@ -286,7 +287,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l const auto maybe_seconds = ParseNumber<int64_t>(local_time_string, 2); if (!maybe_seconds) { - throw utils::BasicException("Invalid seconds in the string. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid seconds in the string. {}", kSupportedTimeFormatsHelpMessage); } local_time_parameters.seconds = *maybe_seconds; local_time_string.remove_prefix(2); @@ -296,13 +297,14 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l } if (local_time_string.front() != '.') { - throw utils::BasicException("Invalid format for local time. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid format for local time. {}", kSupportedTimeFormatsHelpMessage); } local_time_string.remove_prefix(1); const auto maybe_milliseconds = ParseNumber<int64_t>(local_time_string, 3); if (!maybe_milliseconds) { - throw utils::BasicException("Invalid milliseconds in the string. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid milliseconds in the string. {}", + kSupportedTimeFormatsHelpMessage); } local_time_parameters.milliseconds = *maybe_milliseconds; local_time_string.remove_prefix(3); @@ -313,13 +315,14 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l const auto maybe_microseconds = ParseNumber<int64_t>(local_time_string, 3); if (!maybe_microseconds) { - throw utils::BasicException("Invalid microseconds in the string. {}", kSupportedTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid microseconds in the string. {}", + kSupportedTimeFormatsHelpMessage); } local_time_parameters.microseconds = *maybe_microseconds; local_time_string.remove_prefix(3); if (!local_time_string.empty()) { - throw utils::BasicException("Extra characters present at the end of the string."); + throw temporal::InvalidArgumentException("Extra characters present at the end of the string."); } return {local_time_parameters, *using_colon}; @@ -328,12 +331,12 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l LocalTime::LocalTime(const int64_t microseconds) { auto chrono_microseconds = std::chrono::microseconds(microseconds); if (chrono_microseconds.count() < 0) { - throw utils::BasicException("Negative LocalTime specified in microseconds"); + throw temporal::InvalidArgumentException("Negative LocalTime specified in microseconds"); } const auto parsed_hours = GetAndSubtractDuration<std::chrono::hours>(chrono_microseconds); if (parsed_hours > 23) { - throw utils::BasicException("Invalid LocalTime specified in microseconds"); + throw temporal::InvalidArgumentException("Invalid LocalTime specified in microseconds"); } hours = parsed_hours; @@ -345,24 +348,24 @@ LocalTime::LocalTime(const int64_t microseconds) { LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) { if (!IsInBounds(0, 23, local_time_parameters.hours)) { - throw utils::BasicException("Creating a LocalTime with invalid hour parameter."); + throw temporal::InvalidArgumentException("Creating a LocalTime with invalid hour parameter."); } if (!IsInBounds(0, 59, local_time_parameters.minutes)) { - throw utils::BasicException("Creating a LocalTime with invalid minutes parameter."); + throw temporal::InvalidArgumentException("Creating a LocalTime with invalid minutes parameter."); } // ISO 8601 supports leap seconds, but we ignore it for now to simplify the implementation if (!IsInBounds(0, 59, local_time_parameters.seconds)) { - throw utils::BasicException("Creating a LocalTime with invalid seconds parameter."); + throw temporal::InvalidArgumentException("Creating a LocalTime with invalid seconds parameter."); } if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) { - throw utils::BasicException("Creating a LocalTime with invalid milliseconds parameter."); + throw temporal::InvalidArgumentException("Creating a LocalTime with invalid milliseconds parameter."); } if (!IsInBounds(0, 999, local_time_parameters.microseconds)) { - throw utils::BasicException("Creating a LocalTime with invalid microseconds parameter."); + throw temporal::InvalidArgumentException("Creating a LocalTime with invalid microseconds parameter."); } hours = local_time_parameters.hours; @@ -433,7 +436,8 @@ or both parts should be written in their basic forms without the separators.)hel std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std::string_view string) { auto t_position = string.find('T'); if (t_position == std::string_view::npos) { - throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}", + kSupportedLocalDateTimeFormatsHelpMessage); } try { @@ -444,20 +448,22 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std: // which denotes the basic format. The opposite case also aplies. auto local_time_substring = string.substr(t_position + 1); if (local_time_substring.empty()) { - throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage); + throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}", + kSupportedLocalDateTimeFormatsHelpMessage); } auto [local_time_parameters, extended_time_format] = ParseLocalTimeParameters(local_time_substring); if (extended_date_format ^ extended_time_format) { - throw utils::BasicException( + throw temporal::InvalidArgumentException( "Invalid LocalDateTime format. Both date and time should be in the basic or extended format. {}", kSupportedLocalDateTimeFormatsHelpMessage); } return {date_parameters, local_time_parameters}; - } catch (const utils::BasicException &e) { - throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage); + } catch (const temporal::InvalidArgumentException &e) { + throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}", + kSupportedLocalDateTimeFormatsHelpMessage); } } @@ -630,7 +636,7 @@ DurationParameters ParseDurationParameters(std::string_view string) { // The string needs to start with P followed by one of the two options: // - string in a duration specific format if (string.empty() || string.front() != 'P') { - throw utils::BasicException("Duration string is empty."); + throw temporal::InvalidArgumentException("Duration string is empty."); } if (auto maybe_duration_parameters = TryParseDurationString(string); maybe_duration_parameters) { @@ -705,7 +711,7 @@ int64_t Duration::SubSecondsAsNanoseconds() const { Duration Duration::operator-() const { if (microseconds == std::numeric_limits<decltype(microseconds)>::min()) [[unlikely]] { - throw utils::BasicException("Duration arithmetic overflows"); + throw temporal::InvalidArgumentException("Duration arithmetic overflows"); } Duration result{-microseconds}; return result; diff --git a/src/utils/temporal.hpp b/src/utils/temporal.hpp index 082db6121..8a14d0dd0 100644 --- a/src/utils/temporal.hpp +++ b/src/utils/temporal.hpp @@ -40,6 +40,12 @@ bool Underflows(const TType &lhs, const TType &rhs) { return false; } +namespace temporal { +struct InvalidArgumentException : public utils::BasicException { + using utils::BasicException::BasicException; +}; +} // namespace temporal + struct DurationParameters { double days{0}; double hours{0};