Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement casting/mapping towards datatype xsd:dateTime and xsd:date. #1825

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 22 additions & 32 deletions src/engine/sparqlExpressions/ConvertToDtypeConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,44 +130,34 @@ namespace detail::to_datetime {

// Cast to xsd:dateTime or xsd:date (ValueId)
template <bool ToJustXsdDate>
inline auto convertStringToDateTimeValueId =
[](OptIdOrString input) -> ValueId {
if (!input.has_value()) {
return Id::makeUndefined();
}
const auto& inputValue = input.value();

// Remark: If the parsing procedure for datetime/date string values with
// parseXsdDatetimeGetOptDate/parseXsdDateGetOptDate fails,
// return Id::makeUndefined().
const auto retrieveValueId = [](std::optional<DateYearOrDuration> optValue) {
if (optValue.has_value()) {
return Id::makeFromDate(optValue.value());
}
return Id::makeUndefined();
};

if (auto* valueId = std::get_if<ValueId>(&inputValue)) {
return *valueId;
}

auto* str = std::get_if<std::string>(&inputValue);
AD_CORRECTNESS_CHECK(str != nullptr);
if constexpr (ToJustXsdDate) {
return retrieveValueId(DateYearOrDuration::parseXsdDateGetOptDate(*str));
} else {
return retrieveValueId(
DateYearOrDuration::parseXsdDatetimeGetOptDate(*str));
}
inline const auto castStringToDateTimeValueId = [](OptIdOrString input) {
if (!input.has_value()) return Id::makeUndefined();

using DYD = DateYearOrDuration;
std::optional<DYD> optValueId = std::visit(
[&]<typename T>(const T& value) {
if constexpr (std::is_same_v<std::decay_t<T>, ValueId>) {
return ToJustXsdDate ? DYD::convertToXsdDate(value.getDate())
: DYD::convertToXsdDatetime(value.getDate());
} else if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
return ToJustXsdDate ? DYD::parseXsdDateGetOptDate(value)
: DYD::parseXsdDatetimeGetOptDate(value);
} else {
throw std::runtime_error(
"OptIdOrString variant contains unexpected value type.");
}
},
input.value());
return optValueId.has_value() ? Id::makeFromDate(optValueId.value())
: Id::makeUndefined();
};

NARY_EXPRESSION(ToXsdDateTime, 1,
FV<decltype(convertStringToDateTimeValueId<false>),
FV<decltype(castStringToDateTimeValueId<false>),
DateIdOrLiteralValueGetter>);
NARY_EXPRESSION(ToXsdDate, 1,
FV<decltype(convertStringToDateTimeValueId<true>),
FV<decltype(castStringToDateTimeValueId<true>),
DateIdOrLiteralValueGetter>);

} // namespace detail::to_datetime

using namespace detail::to_numeric;
Expand Down
11 changes: 3 additions & 8 deletions src/engine/sparqlExpressions/SparqlExpressionValueGetters.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,20 +353,15 @@ struct IriOrUriValueGetter : Mixin<IriOrUriValueGetter> {
// Defines the return type for value-getter `DateIdOrLiteralValueGetter`.
using OptIdOrString = std::optional<std::variant<ValueId, std::string>>;

// This value-getter returns a `Date` related `ValueId` or `std::string` (from
// literal).
// This value-getter returns a `DateYearOrDuration` related `ValueId` or
// `std::string` (from literal).
struct DateIdOrLiteralValueGetter : Mixin<DateIdOrLiteralValueGetter> {
using Mixin<DateIdOrLiteralValueGetter>::operator();
// Remark: We use only LiteralFromIdGetter because Iri values should never
// contain date-related string values.
OptIdOrString operator()(ValueId id, const EvaluationContext* context) const {
if (id.getDatatype() == Datatype::Date) {
// Additionally check that `DateYearOrDuration` doesn't hold a
// `DayTimeDuration` value.
if (id.getDate().isDate()) {
return id;
}
return std::nullopt;
return id;
}
return LiteralFromIdGetter{}(id, context);
}
Expand Down
80 changes: 35 additions & 45 deletions src/parser/sparqlParser/SparqlQleverVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,85 +109,76 @@ ExpressionPtr Visitor::processIriFunctionCall(
}
};

using namespace sparqlExpression;
// Create `SparqlExpression` with one child.
auto createUnary =
CPP_template_lambda(&argList, &checkNumArgs)(typename F)(F function)(
requires std::is_invocable_r_v<ExpressionPtr, F, ExpressionPtr>) {
checkNumArgs(1); // Check is unary.
return function(std::move(argList[0]));
};
// Create `SparqlExpression` with two children.
auto createBinary =
CPP_template_lambda(&argList, &checkNumArgs)(typename F)(F function)(
requires std::is_invocable_r_v<ExpressionPtr, F, ExpressionPtr,
ExpressionPtr>) {
checkNumArgs(2); // Check is binary.
return function(std::move(argList[0]), std::move(argList[1]));
};

// Geo functions.
if (checkPrefix(GEOF_PREFIX)) {
if (functionName == "distance") {
checkNumArgs(2);
return sparqlExpression::makeDistExpression(std::move(argList[0]),
std::move(argList[1]));
return createBinary(&makeDistExpression);
} else if (functionName == "longitude") {
checkNumArgs(1);
return sparqlExpression::makeLongitudeExpression(std::move(argList[0]));
return createUnary(&makeLongitudeExpression);
} else if (functionName == "latitude") {
checkNumArgs(1);
return sparqlExpression::makeLatitudeExpression(std::move(argList[0]));
return createUnary(&makeLatitudeExpression);
}
}

// Math functions.
if (checkPrefix(MATH_PREFIX)) {
if (functionName == "log") {
checkNumArgs(1);
return sparqlExpression::makeLogExpression(std::move(argList[0]));
return createUnary(&makeLogExpression);
} else if (functionName == "exp") {
checkNumArgs(1);
return sparqlExpression::makeExpExpression(std::move(argList[0]));
return createUnary(&makeExpExpression);
} else if (functionName == "sqrt") {
checkNumArgs(1);
return sparqlExpression::makeSqrtExpression(std::move(argList[0]));
return createUnary(&makeSqrtExpression);
} else if (functionName == "sin") {
checkNumArgs(1);
return sparqlExpression::makeSinExpression(std::move(argList[0]));
return createUnary(&makeSinExpression);
} else if (functionName == "cos") {
checkNumArgs(1);
return sparqlExpression::makeCosExpression(std::move(argList[0]));
return createUnary(&makeCosExpression);
} else if (functionName == "tan") {
checkNumArgs(1);
return sparqlExpression::makeTanExpression(std::move(argList[0]));
return createUnary(&makeTanExpression);
} else if (functionName == "pow") {
checkNumArgs(2);
return sparqlExpression::makePowExpression(std::move(argList[0]),
std::move(argList[1]));
return createBinary(&makePowExpression);
}
}

// XSD conversion functions.
if (checkPrefix(XSD_PREFIX)) {
if (functionName == "integer" || functionName == "int") {
checkNumArgs(1);
return sparqlExpression::makeConvertToIntExpression(
std::move(argList[0]));
return createUnary(&makeConvertToIntExpression);
}
if (functionName == "decimal") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDecimalExpression(
std::move(argList[0]));
return createUnary(&makeConvertToDecimalExpression);
}
// We currently don't have a float type, so we just convert to double.
if (functionName == "double" || functionName == "float") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDoubleExpression(
std::move(argList[0]));
return createUnary(&makeConvertToDoubleExpression);
}
if (functionName == "boolean") {
checkNumArgs(1);
return sparqlExpression::makeConvertToBooleanExpression(
std::move(argList[0]));
return createUnary(&makeConvertToBooleanExpression);
}
if (functionName == "string") {
checkNumArgs(1);
return sparqlExpression::makeConvertToStringExpression(
std::move(argList[0]));
return createUnary(&makeConvertToStringExpression);
}
if (functionName == "dateTime") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDateTimeExpression(
std::move(argList[0]));
return createUnary(&makeConvertToDateTimeExpression);
}
if (functionName == "date") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDateExpression(
std::move(argList[0]));
return createUnary(&makeConvertToDateExpression);
}
}

Expand All @@ -196,8 +187,7 @@ ExpressionPtr Visitor::processIriFunctionCall(
// NOTE: Predicates like `ql:has-predicate` etc. are handled elsewhere.
if (checkPrefix(QL_PREFIX)) {
if (functionName == "isGeoPoint") {
checkNumArgs(1);
return sparqlExpression::makeIsGeoPointExpression(std::move(argList[0]));
return createUnary(&makeIsGeoPointExpression);
}
}

Expand Down
48 changes: 31 additions & 17 deletions src/util/DateYearDuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ static DateYearOrDuration makeDateOrLargeYear(std::string_view fullInput,
}

// _____________________________________________________________________________
static std::optional<DateYearOrDuration> parseXsdDatetimeImpl(
std::string_view dateString) {
std::optional<DateYearOrDuration>
DateYearOrDuration::parseXsdDatetimeGetOptDate(std::string_view dateString) {
constexpr static ctll::fixed_string dateTime =
dateRegex + "T" + timeRegex + grp(timeZoneRegex) + "?";
auto match = ctre::match<dateTime>(dateString);
Expand All @@ -151,21 +151,15 @@ static std::optional<DateYearOrDuration> parseXsdDatetimeImpl(
// _____________________________________________________________________________
DateYearOrDuration DateYearOrDuration::parseXsdDatetime(
std::string_view dateString) {
if (auto optDate = parseXsdDatetimeImpl(dateString); optDate) {
if (auto optDate = parseXsdDatetimeGetOptDate(dateString); optDate) {
return optDate.value();
}
throw DateParseException{absl::StrCat(
"The value ", dateString, " cannot be parsed as an `xsd:dateTime`.")};
}

// _____________________________________________________________________________
std::optional<DateYearOrDuration>
DateYearOrDuration::parseXsdDatetimeGetOptDate(std::string_view dateString) {
return parseXsdDatetimeImpl(dateString);
}

// _____________________________________________________________________________
static std::optional<DateYearOrDuration> parseXsdDateImpl(
std::optional<DateYearOrDuration> DateYearOrDuration::parseXsdDateGetOptDate(
std::string_view dateString) {
constexpr static ctll::fixed_string dateTime =
dateRegex + grp(timeZoneRegex) + "?";
Expand All @@ -183,19 +177,13 @@ static std::optional<DateYearOrDuration> parseXsdDateImpl(
// _____________________________________________________________________________
DateYearOrDuration DateYearOrDuration::parseXsdDate(
std::string_view dateString) {
if (auto optDate = parseXsdDateImpl(dateString); optDate) {
if (auto optDate = parseXsdDateGetOptDate(dateString); optDate) {
return optDate.value();
}
throw DateParseException{absl::StrCat("The value ", dateString,
" cannot be parsed as an `xsd:date`.")};
}

// _____________________________________________________________________________
std::optional<DateYearOrDuration> DateYearOrDuration::parseXsdDateGetOptDate(
std::string_view dateString) {
return parseXsdDateImpl(dateString);
}

// _____________________________________________________________________________
DateYearOrDuration DateYearOrDuration::parseGYear(std::string_view dateString) {
constexpr static ctll::fixed_string yearRegex = "(?<year>-?\\d{4,})";
Expand Down Expand Up @@ -308,3 +296,29 @@ std::string DateYearOrDuration::getStrTimezone() const {
return "";
}
}

// _____________________________________________________________________________
std::optional<DateYearOrDuration> DateYearOrDuration::convertToXsdDatetime(
const DateYearOrDuration& dateValue) {
if (dateValue.isDayTimeDuration() || !dateValue.isDate()) {
return std::nullopt;
}
const Date& date = dateValue.getDate();
if (date.hasTime()) {
// Is already xsd:dateTime value.
return dateValue;
}
return DateYearOrDuration(
Date(date.getYear(), date.getMonth(), date.getDay(), 0, 0, 0.0));
}

// _____________________________________________________________________________
std::optional<DateYearOrDuration> DateYearOrDuration::convertToXsdDate(
const DateYearOrDuration& dateValue) {
if (dateValue.isDayTimeDuration() || !dateValue.isDate()) {
return std::nullopt;
}
const Date& date = dateValue.getDate();
return DateYearOrDuration(
Date(date.getYear(), date.getMonth(), date.getDay()));
}
15 changes: 15 additions & 0 deletions src/util/DateYearDuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@ class DateYearOrDuration {
// Parse `xsd:dayTimeDuration` from a `DateYearOrDuration`.
static std::optional<DateYearOrDuration> xsdDayTimeDurationFromDate(
const DateYearOrDuration& dateOrLargeYear);

// If the provided `DateYearOrDuration` holds an actual `Date` value,
// transform it to `xsd:dateTime` by filling the missing date-components with
// `0`. If `DateYearOrDuration` holds a `xsd:dayTimeDuration` or `LargeYear`
// related value, return std::nullopt.
static std::optional<DateYearOrDuration> convertToXsdDatetime(
const DateYearOrDuration& dateValue);

// If the provided `DateYearOrDuration` holds an actual `Date` value,
// transform it to `xsd:dateTime` by filling the missing date-components with
// `0` or dropping the additional date-components. If `DateYearOrDuration`
// holds a `xsd:dayTimeDuration` or `LargeYear` related value, return
// std::nullopt.
static std::optional<DateYearOrDuration> convertToXsdDate(
const DateYearOrDuration& dateValue);
};

#endif // QLEVER_DATES_AND_DURATION_H
32 changes: 20 additions & 12 deletions test/SparqlExpressionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1166,26 +1166,34 @@ TEST(SparqlExpression, testToDateOrDateTimeExpression) {
Id G = Id::makeFromGeoPoint(GeoPoint(50.0, 50.0));
auto parserDate = DateYearOrDuration::parseXsdDate;
auto parserDateTime = DateYearOrDuration::parseXsdDatetime;
auto parserDuration = DateYearOrDuration::parseXsdDayTimeDuration;
auto checkGetDate = testUnaryExpression<&makeConvertToDateExpression>;
auto checkGetDateTime = testUnaryExpression<&makeConvertToDateTimeExpression>;

checkGetDate(idOrLitOrStringVec({"---", T, F, G, "2025-02", I(10), D(0.01),
"-2025-02-20", "2025-02-20", "2025-1-1",
DateId(parserDate, "0000-01-01")}),
Ids{U, U, U, U, U, U, U, DateId(parserDate, "-2025-02-20"),
DateId(parserDate, "2025-02-20"), U,
DateId(parserDate, "0000-01-01")});
checkGetDate(
idOrLitOrStringVec(
{"---", T, F, G, "2025-02", I(10), D(0.01), "-2025-02-20",
"2025-02-20", "2025-1-1", DateId(parserDate, "0000-01-01"),
DateId(parserDuration, "-PT5H"),
DateId(parserDateTime, "1900-12-13T03:12:00.33Z"),
DateId(parserDateTime, "2025-02-20T17:12:00.01-05:00")}),
Ids{U, U, U, U, U, U, U, DateId(parserDate, "-2025-02-20"),
DateId(parserDate, "2025-02-20"), U, DateId(parserDate, "0000-01-01"),
U, DateId(parserDate, "1900-12-13"),
DateId(parserDate, "2025-02-20")});
checkGetDateTime(
idOrLitOrStringVec({"---", T, F, G, "2025-02", I(10), D(0.01),
"-2025-02-20", "2025-02-20", "2025-1-1",
"1900-12-13T03:12:00.33Z", "-1900-12-13T03:12:00.33Z",
"2025-02-20T17:12:00.01-05:00",
DateId(parserDateTime, "2025-02-20T17:12:00.01Z")}),
idOrLitOrStringVec(
{"---", T, F, G, "2025-02", I(10), D(0.01), "-2025-02-20",
"2025-02-20", "2025-1-1", "1900-12-13T03:12:00.33Z",
"-1900-12-13T03:12:00.33Z", "2025-02-20T17:12:00.01-05:00",
DateId(parserDateTime, "2025-02-20T17:12:00.01Z"),
DateId(parserDuration, "PT1H4M"), DateId(parserDate, "0000-01-01")}),
Ids{U, U, U, U, U, U, U, U, U, U,
DateId(parserDateTime, "1900-12-13T03:12:00.33Z"),
DateId(parserDateTime, "-1900-12-13T03:12:00.33Z"),
DateId(parserDateTime, "2025-02-20T17:12:00.01-05:00"),
DateId(parserDateTime, "2025-02-20T17:12:00.01Z")});
DateId(parserDateTime, "2025-02-20T17:12:00.01Z"), U,
DateId(parserDateTime, "0000-01-01T00:00:00.00")});
}

// ____________________________________________________________________________
Expand Down
Loading