diff --git a/src/engine/sparqlExpressions/NowDatetimeExpression.h b/src/engine/sparqlExpressions/NowDatetimeExpression.h new file mode 100644 index 0000000000..059e8522f0 --- /dev/null +++ b/src/engine/sparqlExpressions/NowDatetimeExpression.h @@ -0,0 +1,38 @@ +// Copyright 2024, University of Freiburg, +// Chair of Algorithms and Data Structures +// Author: Hannes Baumann + +#include "engine/sparqlExpressions/SparqlExpression.h" +#include "util/ChunkedForLoop.h" +#include "util/Date.h" +#include "util/Random.h" + +namespace sparqlExpression { + +// The expression `NOW()` is evaluated within NowDatetimeExpression.h. +// `NowDatetimeExpression` has to be explicitly constructed from a +// `date-formatted string`, which is for all evaluations within a Sparql +// query the same. +class NowDatetimeExpression : public SparqlExpression { + private: + DateOrLargeYear date_; + + public: + explicit NowDatetimeExpression(const std::string& dateTimeFormat) + : date_(DateOrLargeYear::parseXsdDatetime(dateTimeFormat)) {} + + std::string getCacheKey( + [[maybe_unused]] const VariableToColumnMap& varColMap) const override { + return absl::StrCat("NOW ", date_.toBits()); + } + + ExpressionResult evaluate( + [[maybe_unused]] EvaluationContext* context) const override { + return Id::makeFromDate(date_); + } + + private: + std::span childrenImpl() override { return {}; } +}; + +} // namespace sparqlExpression diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.cpp b/src/parser/sparqlParser/SparqlQleverVisitor.cpp index 7dc9334726..deebe20679 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.cpp +++ b/src/parser/sparqlParser/SparqlQleverVisitor.cpp @@ -13,16 +13,20 @@ #include #include +#include "absl/time/time.h" +#include "engine/sparqlExpressions/GroupConcatExpression.h" #include "engine/sparqlExpressions/LangExpression.h" +#include "engine/sparqlExpressions/LiteralExpression.h" +#include "engine/sparqlExpressions/NowDatetimeExpression.h" #include "engine/sparqlExpressions/RandomExpression.h" #include "engine/sparqlExpressions/RegexExpression.h" #include "engine/sparqlExpressions/RelationalExpressions.h" +#include "engine/sparqlExpressions/SampleExpression.h" #include "engine/sparqlExpressions/UuidExpressions.h" #include "parser/SparqlParser.h" #include "parser/TokenizerCtre.h" #include "parser/TurtleParser.h" #include "parser/data/Variable.h" -#include "util/OnDestructionDontThrowDuringStackUnwinding.h" #include "util/StringUtils.h" #include "util/TransparentFunctors.h" #include "util/antlr/GenerateAntlrExceptionMetadata.h" @@ -55,6 +59,12 @@ std::string Visitor::getOriginalInputForContext( ad_utility::getUTF8Substring(fullInput, posBeg, posEnd - posBeg + 1)}; } +// _____________________________________________________________________________ +std::string Visitor::currentTimeAsXsdString() { + return absl::FormatTime("%Y-%m-%dT%H:%M:%E3S%Ez", absl::Now(), + absl::LocalTimeZone()); +} + // ___________________________________________________________________________ ExpressionPtr Visitor::processIriFunctionCall( const TripleComponent::Iri& iri, std::vector argList, @@ -2001,6 +2011,9 @@ ExpressionPtr Visitor::visit([[maybe_unused]] Parser::BuiltInCallContext* ctx) { return createUnary(&makeDayExpression); } else if (functionName == "tz") { return createUnary(&makeTimezoneStrExpression); + } else if (functionName == "now") { + AD_CONTRACT_CHECK(argList.empty()); + return std::make_unique(startTime_); } else if (functionName == "hours") { return createUnary(&makeHoursExpression); } else if (functionName == "minutes") { diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.h b/src/parser/sparqlParser/SparqlQleverVisitor.h index f54ebecd76..6dfe8328d4 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.h +++ b/src/parser/sparqlParser/SparqlQleverVisitor.h @@ -9,26 +9,11 @@ #include #include "engine/sparqlExpressions/AggregateExpression.h" -#include "engine/sparqlExpressions/GroupConcatExpression.h" -#include "engine/sparqlExpressions/LiteralExpression.h" #include "engine/sparqlExpressions/NaryExpression.h" -#include "engine/sparqlExpressions/SampleExpression.h" -#include "engine/sparqlExpressions/SparqlExpressionPimpl.h" -#include "parser/Alias.h" -#include "parser/ConstructClause.h" -#include "parser/ParsedQuery.h" -#include "parser/RdfEscaping.h" -#include "parser/data/BlankNode.h" #include "parser/data/GraphRef.h" -#include "parser/data/Iri.h" -#include "parser/data/SolutionModifiers.h" -#include "parser/data/Types.h" #undef EOF #include "parser/sparqlParser/generated/SparqlAutomaticVisitor.h" #define EOF std::char_traits::eof() -#include "util/HashMap.h" -#include "util/OverloadCallOperator.h" -#include "util/StringUtils.h" template class Reversed { @@ -472,6 +457,14 @@ class SparqlQleverVisitor { string visit(Parser::PnameNsContext* ctx); private: + // Helper to assign variable `startTime_` a correctly formatted time string. + static std::string currentTimeAsXsdString(); + + // Member starTime_ is needed for the NOW expression. All calls within + // the query execution reference it. The underlying date time format is e.g.: + // 2011-01-10T14:45:13.815-05:00 + std::string startTime_ = currentTimeAsXsdString(); + template static constexpr bool voidWhenVisited = std::is_void_v().visit( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1aace8bca3..b50df7c3d9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -294,6 +294,8 @@ addLinkAndDiscoverTest(ExceptionTest) addLinkAndDiscoverTestSerial(RandomExpressionTest index) +addLinkAndDiscoverTestSerial(NowDatetimeExpressionTest index) + addLinkAndDiscoverTestSerial(SortTest engine) addLinkAndDiscoverTestSerial(OrderByTest engine) diff --git a/test/NowDatetimeExpressionTest.cpp b/test/NowDatetimeExpressionTest.cpp new file mode 100644 index 0000000000..83a5941297 --- /dev/null +++ b/test/NowDatetimeExpressionTest.cpp @@ -0,0 +1,57 @@ +// Copyright 2024, University of Freiburg, +// Chair of Algorithms and Data Structures +// Author: Hannes Baumann + +#include "./SparqlExpressionTestHelpers.h" +#include "engine/sparqlExpressions/NowDatetimeExpression.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace sparqlExpression; + +TEST(NowDatetimeExpression, nowExpressionEvaluate) { + std::string strDate = "2011-01-10T14:45:13.815-05:00"; + TestContext testContext{}; + auto& evaluationContext = testContext.context; + + // technically the evaluation context isn't necessary. + evaluationContext._beginIndex = 43; + evaluationContext._endIndex = 1044; + + // The result should hold an ID (from Date) given that NOW() should return by + // definition a xsd:dateTime: "2011-01-10T14:45:13.815-05:00"^^xsd:dateTime + auto resultAsVariant = + NowDatetimeExpression{strDate}.evaluate(&evaluationContext); + ASSERT_TRUE(std::holds_alternative(resultAsVariant)); + const auto& resultDate = std::get(resultAsVariant); + + DateOrLargeYear dateNowTest = + DateOrLargeYear(DateOrLargeYear::parseXsdDatetime(strDate)); + + ASSERT_EQ(resultDate.getDatatype(), Datatype::Date); + ASSERT_EQ(resultDate.getDate(), dateNowTest); + + evaluationContext._isPartOfGroupBy = true; + auto resultAsVariant2 = + NowDatetimeExpression{strDate}.evaluate(&evaluationContext); + ASSERT_TRUE(std::holds_alternative(resultAsVariant2)); + DateOrLargeYear singleDateNow = std::get(resultAsVariant2).getDate(); + ASSERT_EQ(singleDateNow, dateNowTest); +} + +TEST(NowDatetimeExpression, getCacheKeyNowExpression) { + std::string strDate1 = "2011-01-10T14:45:13.815-05:00"; + std::string strDate2 = "2024-06-18T12:16:33.815-06:00"; + NowDatetimeExpression dateNow1(strDate1); + NowDatetimeExpression dateNow2(strDate2); + ASSERT_TRUE(dateNow1.getUnaggregatedVariables().empty()); + auto cacheKey1 = dateNow1.getCacheKey({}); + ASSERT_THAT(cacheKey1, ::testing::StartsWith("NOW ")); + ASSERT_EQ(cacheKey1, dateNow1.getCacheKey({})); + // Given that these use the same date-time string the key should be equal. + ASSERT_EQ(cacheKey1, NowDatetimeExpression{strDate1}.getCacheKey({})); + // Given that dateNow1 and dateNow2 are constructed from different date-time + // strings, it should be rather unlikely that their cache-keys are equal. + auto cacheKey2 = dateNow2.getCacheKey({}); + ASSERT_NE(cacheKey1, cacheKey2); +} diff --git a/test/SparqlAntlrParserTest.cpp b/test/SparqlAntlrParserTest.cpp index 134840c1e0..3b77b8109f 100644 --- a/test/SparqlAntlrParserTest.cpp +++ b/test/SparqlAntlrParserTest.cpp @@ -18,6 +18,7 @@ #include "SparqlAntlrParserTestHelpers.h" #include "engine/sparqlExpressions/LangExpression.h" #include "engine/sparqlExpressions/LiteralExpression.h" +#include "engine/sparqlExpressions/NowDatetimeExpression.h" #include "engine/sparqlExpressions/RandomExpression.h" #include "engine/sparqlExpressions/RegexExpression.h" #include "engine/sparqlExpressions/UuidExpressions.h" @@ -1385,6 +1386,7 @@ TEST(SparqlParser, builtInCall) { expectBuiltInCall("month(?x)", matchUnary(&makeMonthExpression)); expectBuiltInCall("tz(?x)", matchUnary(&makeTimezoneStrExpression)); expectBuiltInCall("day(?x)", matchUnary(&makeDayExpression)); + expectBuiltInCall("NOW()", matchPtr()); expectBuiltInCall("hours(?x)", matchUnary(&makeHoursExpression)); expectBuiltInCall("minutes(?x)", matchUnary(&makeMinutesExpression)); expectBuiltInCall("seconds(?x)", matchUnary(&makeSecondsExpression));