From 155718d049df93f31862006c05ec5845c72ac561 Mon Sep 17 00:00:00 2001 From: Hannes Baumann <116301375+realHannes@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:35:11 +0100 Subject: [PATCH] Get `PrefilterExpression` from `SparqlExpression` (#1613) The `SparqlExpression` base class has been extended with the method `getPrefilterExpressionForMetadata`. This method constructs for suitable (logical) expressions which are used inside a `FILTER` a corresponding `PrefilterExpression` (see PR #1503). These `PrefilterExpression`s can be used to prefilter the blocks of an `IndexScan` by only looking at their metadata. At the moment, the following expressions provide an overriden implementation of `getPrefilterExpressionForMetadata`: `strstarts` (preliminary), `logical-or` and `logical-and` (binary), `logical-not` (unary) and the standard `RelationalExpressions (<, ==, >, <=, >=)`. --- src/engine/sparqlExpressions/CMakeLists.txt | 3 +- .../sparqlExpressions/LiteralExpression.h | 10 +- .../NumericBinaryExpressions.cpp | 295 ++++++++- .../NumericUnaryExpressions.cpp | 66 +- .../PrefilterExpressionIndex.cpp} | 179 +++++- .../PrefilterExpressionIndex.h} | 71 ++- .../RelationalExpressions.cpp | 76 +++ .../sparqlExpressions/RelationalExpressions.h | 7 + .../sparqlExpressions/SparqlExpression.cpp | 14 + .../sparqlExpressions/SparqlExpression.h | 19 + .../SparqlExpressionPimpl.cpp | 6 + .../sparqlExpressions/SparqlExpressionPimpl.h | 12 + .../sparqlExpressions/StringExpressions.cpp | 60 +- src/index/CMakeLists.txt | 1 - src/index/CompressedRelation.h | 12 + src/parser/data/Variable.h | 3 + test/CMakeLists.txt | 4 +- ...lterExpressionFromSparqlExpressionTest.cpp | 592 ++++++++++++++++++ ...t.cpp => PrefilterExpressionIndexTest.cpp} | 161 +++-- test/PrefilterExpressionTestHelpers.h | 70 +++ 20 files changed, 1552 insertions(+), 109 deletions(-) rename src/{index/CompressedBlockPrefiltering.cpp => engine/sparqlExpressions/PrefilterExpressionIndex.cpp} (68%) rename src/{index/CompressedBlockPrefiltering.h => engine/sparqlExpressions/PrefilterExpressionIndex.h} (66%) create mode 100644 test/GetPrefilterExpressionFromSparqlExpressionTest.cpp rename test/{CompressedBlockPrefilteringTest.cpp => PrefilterExpressionIndexTest.cpp} (83%) create mode 100644 test/PrefilterExpressionTestHelpers.h diff --git a/src/engine/sparqlExpressions/CMakeLists.txt b/src/engine/sparqlExpressions/CMakeLists.txt index dfe34fa146..17a96d689e 100644 --- a/src/engine/sparqlExpressions/CMakeLists.txt +++ b/src/engine/sparqlExpressions/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(sparqlExpressions ConvertToNumericExpression.cpp RdfTermExpressions.cpp LangExpression.cpp - CountStarExpression.cpp) + CountStarExpression.cpp + PrefilterExpressionIndex.cpp) qlever_target_link_libraries(sparqlExpressions util index Boost::url) diff --git a/src/engine/sparqlExpressions/LiteralExpression.h b/src/engine/sparqlExpressions/LiteralExpression.h index 9bd27b3406..9e913a0b30 100644 --- a/src/engine/sparqlExpressions/LiteralExpression.h +++ b/src/engine/sparqlExpressions/LiteralExpression.h @@ -24,7 +24,7 @@ class LiteralExpression : public SparqlExpression { mutable std::atomic cachedResult_ = nullptr; public: - // _________________________________________________________________________ + // ___________________________________________________________________________ explicit LiteralExpression(T _value) : _value{std::move(_value)} {} ~LiteralExpression() override { delete cachedResult_.load(std::memory_order_relaxed); @@ -83,7 +83,7 @@ class LiteralExpression : public SparqlExpression { } } - // _________________________________________________________________________ + // ___________________________________________________________________________ vector getUnaggregatedVariables() const override { if constexpr (std::is_same_v) { return {_value}; @@ -92,7 +92,7 @@ class LiteralExpression : public SparqlExpression { } } - // ______________________________________________________________________ + // ___________________________________________________________________________ string getCacheKey(const VariableToColumnMap& varColMap) const override { if constexpr (std::is_same_v) { if (!varColMap.contains(_value)) { @@ -118,13 +118,13 @@ class LiteralExpression : public SparqlExpression { } } - // ______________________________________________________________________ + // ___________________________________________________________________________ bool isConstantExpression() const override { return !std::is_same_v; } protected: - // _________________________________________________________________________ + // ___________________________________________________________________________ std::optional<::Variable> getVariableOrNullopt() const override { if constexpr (std::is_same_v) { return _value; diff --git a/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp b/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp index e5852a6add..e8bf9a450f 100644 --- a/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp +++ b/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp @@ -34,6 +34,7 @@ inline auto subtract = makeNumericExpression>(); NARY_EXPRESSION(SubtractExpression, 2, FV); +// _____________________________________________________________________________ // Power. inline auto powImpl = [](double base, double exp) { return std::pow(base, exp); @@ -41,7 +42,8 @@ inline auto powImpl = [](double base, double exp) { inline auto pow = makeNumericExpression(); NARY_EXPRESSION(PowExpression, 2, FV); -// Or +// OR and AND +// _____________________________________________________________________________ inline auto orLambda = [](TernaryBool a, TernaryBool b) { using enum TernaryBool; if (a == True || b == True) { @@ -53,11 +55,7 @@ inline auto orLambda = [](TernaryBool a, TernaryBool b) { return Id::makeUndefined(); }; -NARY_EXPRESSION(OrExpression, 2, - FV, - SET); - -// And +// _____________________________________________________________________________ inline auto andLambda = [](TernaryBool a, TernaryBool b) { using enum TernaryBool; if (a == True && b == True) { @@ -68,9 +66,286 @@ inline auto andLambda = [](TernaryBool a, TernaryBool b) { } return Id::makeUndefined(); }; -NARY_EXPRESSION(AndExpression, 2, - FV, - SET); + +namespace constructPrefilterExpr { +namespace { + +// `BinaryOperator` defines the operations `AND` and `OR`. +using BinaryOperator = prefilterExpressions::LogicalOperator; + +// MERGE +// _____________________________________________________________________________ +// For our pre-filtering logic over index scans, we need exactly one +// corresponding PrefilterExpression for each relevant Variable. Thus, if the +// left and right child contain a PrefilterExpression w.r.t. the same Variable, +// combine them here for an AND conjunction. In the following, three examples +// are given on how the following function merges the content of the left and +// right child. +// +// EXAMPLE 1 +// left child: {<(>= 10), ?x>, <(!= 5), ?y>}; (?x >= 10 && ?y != 5) +// right child: {}; (std::nullopt) +// The AND conjunction can only evaluate to true, if both child expressions +// evaluate to true. For the given example, we have no PrefilterExpression +// for the right child, but we can certainly assume that the left child +// expression must evaluate to true. +// Thus, return {<(>= 10), ?x>, <(!= 5), ?y>}. +// +// EXAMPLE 2 +// left child: {<(= 5), ?x>}; (?x = 5) +// right child: {<(= VocabId(10)), ?y>}; (?y = VocabId(10)) +// Returns {<(= 5), ?x>, <(= VocabId(10)), ?y>}. +// +// EXAMPLE 3 +// left child: {<(>= 10 AND <= 20), ?x>}; (?x >= 10 && ?x <= 20) +// right child: {<(!= 15), ?x>, <(= 10), ?y>}; (?x != 15 && ?y = 10) +// Returns {<((>= 10 AND <= 20) AND != 15), ?x>, <(= 10), ?y>} +// +// +// MERGE +// (Partial De-Morgan applied w.r.t. Or-conjunction) +//______________________________________________________________________________ +// Merge the pair values of leftChild and rightChild with the combine logic for +// AND. Given we have a respective pair from leftChild and rightChild for which the Variable value is +// equal, conjunct the two PrefilterExpressions over an OrExpression. For the +// resulting pairs, NOT (NotExpression) is applied later on. (As a result we +// have fully applied De-Morgan on the available PrefilterExpression) +// +// EXAMPLE 1 +// !...(?x = 5 OR ?y = VocabId(10)) +// partial De-Morgan: ...(?x = 5 AND ?y = VocabId(10)) +// (complete De-Morgan later on with NotExpression: +// ...(?x != 5 AND ?y != VocabId(10))) (in NumericUnaryExpression.cpp) +// The merge function implements the following: +// left child: {<(= 5), ?x>} +// right child: {<(= VocabId(10)), ?y>} +// merged: {<(= 5), ?x>, <(= VocabId(10)), ?y>} +// +// EXAMPLE 2 +// left child: {<(>= 10 OR <= 20), ?x>} +// right child: {<(!= 15), ?x>, <(= 10), ?y>} +// merged: {<((>= 10 OR <= 20) OR ?x != 15), ?x>, <(= 10), ?y>} +// +// MERGE +//______________________________________________________________________________ +// Four examples on how this function (OR) merges the content of two children. +// +// EXAMPLE 1 +// left child: {} (std::nullopt) +// right child: {<(<= 10), ?y>} +// Given that it is not possible to make a fixed value assumption (true or +// false) for the left (empty) child, it is also unclear if the right child +// must evaluate to true for this OR expression to become true. +// Hence, no PrefilterExpression (std::nullopt) will be returned. +// +// EXAMPLE 2 +// left child: {<(= 5), ?y>} +// right child: {<(<= 3), ?x>} +// We want to pre-filter the blocks for the expression: ?y >= 5 || ?x <= 3 +// No PrefilterExpression will be returned. +// +// EXAMPLE 3 +// left child: {<(>= 5), ?x>} +// right child: {<(= 0), ?x>} +// We want to pre-filter the blocks to the expression: ?x >= 5 || ?x = 0 +// The resulting PrefilterExpression is {<(>= 5 OR = 0), ?x>} +// +// EXAMPLE 4 +// left child: {<(= 10), ?x), <(!= 0), ?y>} +// right child: {<(<= 0), ?x>} +// We have to construct a PrefilterExpression for (?x >= 10 && ?y != 0) || +// ?x <= 0. If this OR expression yields true, at least ?x >= 10 || ?x <= 0 +// must be staisifed; for this objective we can construct a +// PrefiterExpression. We can't make a distinct prediction w.r.t. ?y != 0 +// => not relevant for the PrefilterExpression. Thus, we return the +// PrefilterExpresion {<(>= 10 OR <= 0), ?x>}. +// +// +// MERGE +// (Partial De-Morgan applied w.r.t. And-conjunction) +//______________________________________________________________________________ +// Merge the pair values of leftChild and rightChild with the combine logic for +// OR. The difference, given we have a respective pair from leftChild and rightChild for which the Variable value is +// equal, conjunct the two PrefilterExpressions over an AndExpression. For the +// resulting pairs, NOT (NotExpression) is applied later on. As a result we +// have fully applied De-Morgan on the available PrefilterExpression. +// +// EXAMPLE 1 +// !...(?y = 5 AND ?x <= 3); apply partial De-Morgan given NOT(!): Thus OR +// instead of AND merge (here). +// left child: {<(= 5), ?y>} +// right child: {<(<= 3), ?x>} +// merged result: {} +// +// EXAMPLE 2 +// !...((?x = 10 OR ?y != 0) AND ?x <= 0); apply partial De-Morgan given NOT(!): +// Thus OR instead of AND merge (here). +// left child: {<(= 10), ?x), <(!= 0), ?y>}; +// right child: {<(<= 0), ?x>} +// merged result: {<((= 10) AND (<= 0)), ?x>} +// (with NOT applied later on: {<((!= 10) OR (> 0)), ?x>}) +//______________________________________________________________________________ +template +std::vector mergeChildrenForBinaryOpExpressionImpl( + std::vector&& leftChild, + std::vector&& rightChild) { + using enum BinaryOperator; + namespace pd = prefilterExpressions::detail; + pd::checkPropertiesForPrefilterConstruction(leftChild); + pd::checkPropertiesForPrefilterConstruction(rightChild); + // Merge the PrefilterExpression vectors from the left and right child. + // Remark: The vectors contain std::pairs, sorted by their respective + // Variable to the PrefilterExpression. + auto itLeft = leftChild.begin(); + auto itRight = rightChild.begin(); + std::vector resPairs; + while (itLeft != leftChild.end() && itRight != rightChild.end()) { + auto& [exprLeft, varLeft] = *itLeft; + auto& [exprRight, varRight] = *itRight; + if (varLeft == varRight) { + resPairs.emplace_back(std::make_unique( + std::move(exprLeft), std::move(exprRight)), + varLeft); + ++itLeft; + ++itRight; + } else if (varLeft < varRight) { + if constexpr (binOp == AND) { + resPairs.emplace_back(std::move(*itLeft)); + } + ++itLeft; + } else { + if constexpr (binOp == AND) { + resPairs.emplace_back(std::move(*itRight)); + } + ++itRight; + } + } + if constexpr (binOp == AND) { + std::ranges::move(itLeft, leftChild.end(), std::back_inserter(resPairs)); + std::ranges::move(itRight, rightChild.end(), std::back_inserter(resPairs)); + } + pd::checkPropertiesForPrefilterConstruction(resPairs); + return resPairs; +} + +// TEMPLATED STANDARD MERGE +//______________________________________________________________________________ +constexpr auto makeAndMergeWithAndConjunction = + mergeChildrenForBinaryOpExpressionImpl; +constexpr auto makeOrMergeWithOrConjunction = + mergeChildrenForBinaryOpExpressionImpl; +// TEMPLATED (PARTIAL) DE-MORGAN MERGE +//______________________________________________________________________________ +constexpr auto makeAndMergeWithOrConjunction = + mergeChildrenForBinaryOpExpressionImpl; +constexpr auto makeOrMergeWithAndConjunction = + mergeChildrenForBinaryOpExpressionImpl; + +// GET TEMPLATED MERGE FUNCTION (CONJUNCTION AND / OR) +//______________________________________________________________________________ +// getMergeFunction (get the respective merging function) follows the +// principles of De Morgan's law. If this is a child of a NotExpression +// (NOT(!)), we have to swap the combination procedure (swap AND(&&) and +// OR(||) respectively). This equals a partial application of De Morgan's +// law. On the resulting PrefilterExpressions, NOT is applied in +// UnaryNegateExpression(Impl). For more details take a look at the +// implementation in NumericUnaryExpressions.cpp (see +// UnaryNegateExpressionImpl). +// +// EXAMPLE +// - How De-Morgan's law is applied in steps +// !((?y != 10 || ?x = 0) || (?x = 5 || ?x >= 10)) is the complete (Sparql +// expression). With De-Morgan's law applied: +// (?y = 10 && ?x != 0) && (?x != 5 && ?x < 10) +// +// {, ....} represents the PrefilterExpressions +// with their corresponding Variable. +// +// At the current step we have: isNegated = true; because of !(...) (NOT) +// left child: {<(!= 10), ?y>, <(!= 0), ?x>}; (?y != 10 || ?x != 0) +// +// Remark: The (child) expressions of the left and right child have been +// combined with the merging procedure AND (makeAndMergeWithOrConjunction), +// because with isNegated = true, it is implied that De-Morgan's law is applied. +// Thus, the OR is logically an AND conjunction with its application. +// +// right child: {<((= 5) OR (>= 10)), ?x>} (?x = 5 || ?x >= 10) +// +// isNegated is true, hence we know that we have to partially apply +// De-Morgan's law. Given that, OR switches to an AND. Therefore we use +// mergeChildrenForBinaryOpExpressionImpl with OrExpression +// (PrefilterExpression) as a template value (BinaryPrefilterExpr). Call +// makeAndMergeWithOrConjunction with left and right child as arguments, we get +// their merged combination: +// {<(!= 10), ?y>, <((!= 0) OR ((= 5) OR (>= 10))), ?x>} +// +// Next their merged value is returned to UnaryNegateExpressionImpl +// (defined in NumericUnaryExpressions.cpp), where for each expression of +// the pairs, NOT (NotExpression) is +// applied. +// {<(!= 10), ?y>, <((!= 0) OR ((= 5) OR (>= 10))), ?x>} +// apply NotExpr.: {, = 10))), ?x>} +// This procedure yields: {<(= 10), ?y>, <((= 0) AND ((!= 5) AND (< 10))), ?x>} +//______________________________________________________________________________ +template +constexpr auto getMergeFunction(bool isNegated) { + if constexpr (std::is_same_v) { + return !isNegated ? makeAndMergeWithAndConjunction + // negated, partially apply De-Morgan: + // change AND to OR + : makeOrMergeWithAndConjunction; + } else { + static_assert(std::is_same_v); + return !isNegated ? makeOrMergeWithOrConjunction + // negated, partially apply De-Morgan: + // change OR to AND + : makeAndMergeWithOrConjunction; + } +} + +} // namespace + +//______________________________________________________________________________ +template +requires isOperation +class LogicalBinaryExpressionImpl : public NaryExpression { + public: + using NaryExpression::NaryExpression; + + std::vector getPrefilterExpressionForMetadata( + bool isNegated) const override { + AD_CORRECTNESS_CHECK(this->N == 2); + auto leftChild = + this->getNthChild(0).value()->getPrefilterExpressionForMetadata( + isNegated); + auto rightChild = + this->getNthChild(1).value()->getPrefilterExpressionForMetadata( + isNegated); + return constructPrefilterExpr::getMergeFunction( + isNegated)(std::move(leftChild), std::move(rightChild)); + } +}; + +} // namespace constructPrefilterExpr + +//______________________________________________________________________________ +using AndExpression = constructPrefilterExpr::LogicalBinaryExpressionImpl< + prefilterExpressions::AndExpression, + Operation<2, FV, + SET>>; + +using OrExpression = constructPrefilterExpr::LogicalBinaryExpressionImpl< + prefilterExpressions::OrExpression, + Operation<2, FV, + SET>>; } // namespace detail @@ -100,6 +375,7 @@ SparqlExpression::Ptr makeAndExpression(SparqlExpression::Ptr child1, SparqlExpression::Ptr child2) { return std::make_unique(std::move(child1), std::move(child2)); } + SparqlExpression::Ptr makeOrExpression(SparqlExpression::Ptr child1, SparqlExpression::Ptr child2) { return std::make_unique(std::move(child1), std::move(child2)); @@ -109,4 +385,5 @@ SparqlExpression::Ptr makePowExpression(SparqlExpression::Ptr child1, SparqlExpression::Ptr child2) { return std::make_unique(std::move(child1), std::move(child2)); } + } // namespace sparqlExpression diff --git a/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp b/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp index fdc464b666..a9d46d645e 100644 --- a/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp +++ b/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp @@ -2,11 +2,14 @@ // Chair of Algorithms and Data Structures. // Author: Johannes Kalmbach +#include + #include "engine/sparqlExpressions/NaryExpressionImpl.h" namespace sparqlExpression { namespace detail { +// _____________________________________________________________________________ // Unary negation. inline auto unaryNegate = [](TernaryBool a) { using enum TernaryBool; @@ -20,10 +23,67 @@ inline auto unaryNegate = [](TernaryBool a) { } AD_FAIL(); }; -NARY_EXPRESSION(UnaryNegateExpression, 1, - FV, - SET); +template +requires(isOperation) +class UnaryNegateExpressionImpl : public NaryExpression { + public: + using NaryExpression::NaryExpression; + + std::vector getPrefilterExpressionForMetadata( + bool isNegated) const override { + AD_CORRECTNESS_CHECK(this->N == 1); + namespace p = prefilterExpressions; + // The bool flag isNegated (by default false) acts as decision variable + // to select the correct merging procedure while constructing the + // PrefilterExpression(s) for a binary expression (AND or OR). For the + // negation procedure, we apply (partially over multiple Variables) + // De-Morgans law w.r.t. the affected (lower) expression parts, and + // isNegated indicates if we should apply it (or not). For more detailed + // information see NumericBinaryExpressions.cpp. For UnaryNegate we have to + // toggle the value of isNegated to pass the respective negation information + // down the expression tree. + // Remark: + // - Expression sub-tree has an even number of NOT expressions as + // parents: the negation cancels out (isNegated = false). + // - For an uneven number of NOT expressions as parent nodes: the + // sub-tree is actually negated (isNegated = true). + // + // Example - Apply De-Morgans law in two steps (see (1) and (2)) on + // expression !(?x >= IntId(10) || ?y >= IntId(10)) (SparqlExpression) + // With De-Morgan's rule we retrieve: ?x < IntId(10) && ?y < IntId(10) + // + // (1) Merge {<(>= IntId(10)), ?x>} and {<(>= IntId(10)), ?y>} + // with mergeChildrenForBinaryOpExpressionImpl (defined in + // NumericBinaryExpressions.cpp), which we select based on isNegated = true + // (first part of De-Morgans law). + // Result (1): {<(>= IntId(10)), ?x>, <(>= IntId(10)), ?y>} + // + // (2) On each pair given the result from + // (1), apply NotExpression (see the following implementation part). + // Step by step for the given example: + // {<(>= IntId(10)), ?x>, <(>= IntId(10)), ?y>} (apply NotExpression) => + // {<(!(>= IntId(10))), ?x>, <(!(>= IntId(10))), ?y>} + // => Result (2): {<(< IntId(10)), ?x>, <(< IntId(10)), ?y>} + auto child = + this->getNthChild(0).value()->getPrefilterExpressionForMetadata( + !isNegated); + std::ranges::for_each( + child | std::views::keys, + [](std::unique_ptr& expression) { + expression = + std::make_unique(std::move(expression)); + }); + p::detail::checkPropertiesForPrefilterConstruction(child); + return child; + } +}; + +using UnaryNegateExpression = UnaryNegateExpressionImpl< + Operation<1, FV, + SET>>; + +// _____________________________________________________________________________ // Unary Minus. inline auto unaryMinus = makeNumericExpression>(); NARY_EXPRESSION(UnaryMinusExpression, 1, diff --git a/src/index/CompressedBlockPrefiltering.cpp b/src/engine/sparqlExpressions/PrefilterExpressionIndex.cpp similarity index 68% rename from src/index/CompressedBlockPrefiltering.cpp rename to src/engine/sparqlExpressions/PrefilterExpressionIndex.cpp index a2af112c77..b7a7c2fd0e 100644 --- a/src/index/CompressedBlockPrefiltering.cpp +++ b/src/engine/sparqlExpressions/PrefilterExpressionIndex.cpp @@ -2,7 +2,9 @@ // Chair of Algorithms and Data Structures // Author: Hannes Baumann -#include "index/CompressedBlockPrefiltering.h" +#include "engine/sparqlExpressions/PrefilterExpressionIndex.h" + +#include #include "global/ValueIdComparators.h" @@ -121,6 +123,42 @@ static auto getSetUnion(const std::vector& blocks1, return mergedVectors; } +//______________________________________________________________________________ +// Return `CompOp`s as string. +static std::string getRelationalOpStr(const CompOp relOp) { + using enum CompOp; + switch (relOp) { + case LT: + return "LT(<)"; + case LE: + return "LE(<=)"; + case EQ: + return "EQ(=)"; + case NE: + return "NE(!=)"; + case GE: + return "GE(>=)"; + case GT: + return "GT(>)"; + default: + AD_FAIL(); + } +} + +//______________________________________________________________________________ +// Return `LogicalOperator`s as string. +static std::string getLogicalOpStr(const LogicalOperator logOp) { + using enum LogicalOperator; + switch (logOp) { + case AND: + return "AND(&&)"; + case OR: + return "OR(||)"; + default: + AD_FAIL(); + } +} + // SECTION PREFILTER EXPRESSION (BASE CLASS) //______________________________________________________________________________ std::vector PrefilterExpression::evaluate( @@ -227,12 +265,41 @@ std::vector RelationalExpression::evaluateImpl( return getSetUnion(relevantBlocks, mixedDatatypeBlocks); }; +//______________________________________________________________________________ +template +bool RelationalExpression::operator==( + const PrefilterExpression& other) const { + const auto* otherRelational = + dynamic_cast*>(&other); + if (!otherRelational) { + return false; + } + return referenceId_ == otherRelational->referenceId_; +}; + +//______________________________________________________________________________ +template +std::unique_ptr RelationalExpression::clone() + const { + return std::make_unique>(referenceId_); +}; + +//______________________________________________________________________________ +template +std::string RelationalExpression::asString( + [[maybe_unused]] size_t depth) const { + std::stringstream stream; + stream << "Prefilter RelationalExpression<" << getRelationalOpStr(Comparison) + << ">\nValueId: " << referenceId_ << std::endl; + return stream.str(); +}; + // SECTION LOGICAL OPERATIONS //______________________________________________________________________________ -template +template std::unique_ptr LogicalExpression::logicalComplement() const { - using enum LogicalOperators; + using enum LogicalOperator; // Source De-Morgan's laws: De Morgan's laws, Wikipedia. // Reference: https://en.wikipedia.org/wiki/De_Morgan%27s_laws if constexpr (Operation == OR) { @@ -248,18 +315,10 @@ LogicalExpression::logicalComplement() const { }; //______________________________________________________________________________ -std::unique_ptr NotExpression::logicalComplement() const { - // Logically we complement (negate) a NOT here => NOT cancels out. - // Therefore, we can simply return the child of the respective NOT - // expression after undoing its previous complementation. - return child_->logicalComplement(); -}; - -//______________________________________________________________________________ -template +template std::vector LogicalExpression::evaluateImpl( const std::vector& input, size_t evaluationColumn) const { - using enum LogicalOperators; + using enum LogicalOperator; if constexpr (Operation == AND) { auto resultChild1 = child1_->evaluate(input, evaluationColumn); return child2_->evaluate(resultChild1, evaluationColumn); @@ -270,6 +329,51 @@ std::vector LogicalExpression::evaluateImpl( } }; +//______________________________________________________________________________ +template +bool LogicalExpression::operator==( + const PrefilterExpression& other) const { + const auto* otherlogical = + dynamic_cast*>(&other); + if (!otherlogical) { + return false; + } + return *child1_ == *otherlogical->child1_ && + *child2_ == *otherlogical->child2_; +}; + +//______________________________________________________________________________ +template +std::unique_ptr LogicalExpression::clone() + const { + return std::make_unique>(child1_->clone(), + child2_->clone()); +}; + +//______________________________________________________________________________ +template +std::string LogicalExpression::asString(size_t depth) const { + std::string child1Info = + depth < maxInfoRecursion ? child1_->asString(depth + 1) : "MAX_DEPTH"; + std::string child2Info = + depth < maxInfoRecursion ? child2_->asString(depth + 1) : "MAX_DEPTH"; + std::stringstream stream; + stream << "Prefilter LogicalExpression<" << getLogicalOpStr(Operation) + << ">\n" + << "child1 {" << child1Info << "}" + << "child2 {" << child2Info << "}" << std::endl; + return stream.str(); +}; + +// SECTION NOT-EXPRESSION +//______________________________________________________________________________ +std::unique_ptr NotExpression::logicalComplement() const { + // Logically we complement (negate) a NOT here => NOT cancels out. + // Therefore, we can simply return the child of the respective NOT + // expression after undoing its previous complementation. + return child_->logicalComplement(); +}; + //______________________________________________________________________________ std::vector NotExpression::evaluateImpl( const std::vector& input, size_t evaluationColumn) const { @@ -277,7 +381,31 @@ std::vector NotExpression::evaluateImpl( }; //______________________________________________________________________________ -// Necessary instantiation of template specializations +bool NotExpression::operator==(const PrefilterExpression& other) const { + const auto* otherNotExpression = dynamic_cast(&other); + if (!otherNotExpression) { + return false; + } + return *child_ == *otherNotExpression->child_; +} + +//______________________________________________________________________________ +std::unique_ptr NotExpression::clone() const { + return std::make_unique((child_->clone()), true); +}; + +//______________________________________________________________________________ +std::string NotExpression::asString(size_t depth) const { + std::string childInfo = + depth < maxInfoRecursion ? child_->asString(depth + 1) : "MAX_DEPTH"; + std::stringstream stream; + stream << "Prefilter NotExpression:\n" + << "child {" << childInfo << "}" << std::endl; + return stream.str(); +} + +//______________________________________________________________________________ +// Instanitate templates with specializations (for linking accessibility) template class RelationalExpression; template class RelationalExpression; template class RelationalExpression; @@ -285,7 +413,26 @@ template class RelationalExpression; template class RelationalExpression; template class RelationalExpression; -template class LogicalExpression; -template class LogicalExpression; +template class LogicalExpression; +template class LogicalExpression; + +namespace detail { +//______________________________________________________________________________ +void checkPropertiesForPrefilterConstruction( + const std::vector& vec) { + auto viewVariable = vec | std::views::values; + if (!std::ranges::is_sorted(viewVariable, std::less<>{})) { + throw std::runtime_error( + "The vector must contain the pairs in " + "sorted order w.r.t. Variable value."); + } + if (auto it = std::ranges::adjacent_find(viewVariable); + it != std::ranges::end(viewVariable)) { + throw std::runtime_error( + "For each relevant Variable must exist exactly one " + " pair."); + } +} +} // namespace detail } // namespace prefilterExpressions diff --git a/src/index/CompressedBlockPrefiltering.h b/src/engine/sparqlExpressions/PrefilterExpressionIndex.h similarity index 66% rename from src/index/CompressedBlockPrefiltering.h rename to src/engine/sparqlExpressions/PrefilterExpressionIndex.h index 28db52c0a7..a5ca2c8d8d 100644 --- a/src/index/CompressedBlockPrefiltering.h +++ b/src/engine/sparqlExpressions/PrefilterExpressionIndex.h @@ -11,8 +11,24 @@ #include "global/ValueIdComparators.h" #include "index/CompressedRelation.h" +// For certain SparqlExpressions it is possible to perform a prefiltering +// procedure w.r.t. relevant data blocks / ValueId values by making use of the +// available metadata (see CompressedBlockMetadata in CompressedRelation.h) +// while performing the index scan. As a result, the actual SparqlExpression +// evaluation is performed for a smaller IdTable if a PrefilterExpression +// (declared in this file) for the respective SparqlExpression is available and +// compatible with the IndexScan. The following SparqlExpressions construct a +// PrefilterExpression if possible: logical-or, logical-and, logical-negate +// (unary), relational-ops. and strstarts. + namespace prefilterExpressions { +//______________________________________________________________________________ +// The maximum recursion depth for `info()` / `operator<<()`. A depth of `3` +// should be sufficient for most `PrefilterExpressions` in our use case. +constexpr size_t maxInfoRecursion = 3; + +//______________________________________________________________________________ // The compressed block metadata (see `CompressedRelation.h`) that we use to // filter out the non-relevant blocks by checking their content of // `firstTriple_` and `lastTriple_` (`PermutedTriple`) @@ -37,6 +53,14 @@ class PrefilterExpression { public: virtual ~PrefilterExpression() = default; + virtual bool operator==(const PrefilterExpression& other) const = 0; + + // Create a copy of this `PrefilterExpression`. + virtual std::unique_ptr clone() const = 0; + + // Format content for debugging. + virtual std::string asString(size_t depth) const = 0; + // Needed for implementing the `NotExpression`. This method is required, // because we logically operate on `BlockMetadata` values which define ranges // given the `ValueIds` from last and first triple. @@ -61,6 +85,13 @@ class PrefilterExpression { std::vector evaluate(const std::vector& input, size_t evaluationColumn) const; + // Format for debugging + friend std::ostream& operator<<(std::ostream& str, + const PrefilterExpression& expression) { + str << expression.asString(0) << "." << std::endl; + return str; + } + private: virtual std::vector evaluateImpl( const std::vector& input, @@ -87,6 +118,9 @@ class RelationalExpression : public PrefilterExpression { : referenceId_(referenceId) {} std::unique_ptr logicalComplement() const override; + bool operator==(const PrefilterExpression& other) const override; + std::unique_ptr clone() const override; + std::string asString(size_t depth) const override; private: std::vector evaluateImpl( @@ -98,10 +132,10 @@ class RelationalExpression : public PrefilterExpression { // Helper struct for a compact class implementation regarding the logical // operations `AND` and `OR`. `NOT` is implemented separately given that the // expression is unary (single child expression). -enum struct LogicalOperators { AND, OR }; +enum struct LogicalOperator { AND, OR }; //______________________________________________________________________________ -template +template class LogicalExpression : public PrefilterExpression { private: std::unique_ptr child1_; @@ -114,6 +148,9 @@ class LogicalExpression : public PrefilterExpression { : child1_(std::move(child1)), child2_(std::move(child2)) {} std::unique_ptr logicalComplement() const override; + bool operator==(const PrefilterExpression& other) const override; + std::unique_ptr clone() const override; + std::string asString(size_t depth) const override; private: std::vector evaluateImpl( @@ -127,10 +164,18 @@ class NotExpression : public PrefilterExpression { std::unique_ptr child_; public: - explicit NotExpression(std::unique_ptr child) - : child_(child->logicalComplement()) {} + // `makeCopy` should always be set to `false`, except when it is called within + // the implementation of the `clone()` method. For the copy construction, + // the `logicalComplement` for the child is omitted because it has + // already been complemented for the original expression. + explicit NotExpression(std::unique_ptr child, + bool makeCopy = false) + : child_(makeCopy ? std::move(child) : child->logicalComplement()) {} std::unique_ptr logicalComplement() const override; + bool operator==(const PrefilterExpression& other) const override; + std::unique_ptr clone() const override; + std::string asString(size_t depth) const override; private: std::vector evaluateImpl( @@ -156,8 +201,22 @@ using GreaterThanExpression = prefilterExpressions::RelationalExpression< //______________________________________________________________________________ // Definition of the LogicalExpression for AND and OR. using AndExpression = prefilterExpressions::LogicalExpression< - prefilterExpressions::LogicalOperators::AND>; + prefilterExpressions::LogicalOperator::AND>; using OrExpression = prefilterExpressions::LogicalExpression< - prefilterExpressions::LogicalOperators::OR>; + prefilterExpressions::LogicalOperator::OR>; +namespace detail { +//______________________________________________________________________________ +// Pair containing a `PrefilterExpression` and its corresponding `Variable`. +using PrefilterExprVariablePair = + std::pair, Variable>; +//______________________________________________________________________________ +// Helper function to perform a check regarding the following conditions. +// (1) For each relevant Variable, the vector contains exactly one pair. +// (2) The vector contains those pairs (``) in +// sorted order w.r.t. the Variable value. +void checkPropertiesForPrefilterConstruction( + const std::vector& vec); + +} // namespace detail } // namespace prefilterExpressions diff --git a/src/engine/sparqlExpressions/RelationalExpressions.cpp b/src/engine/sparqlExpressions/RelationalExpressions.cpp index 91effef243..bb349878da 100644 --- a/src/engine/sparqlExpressions/RelationalExpressions.cpp +++ b/src/engine/sparqlExpressions/RelationalExpressions.cpp @@ -409,6 +409,82 @@ ExpressionResult InExpression::evaluate( return result; } +// _____________________________________________________________________________ +template +std::vector +RelationalExpression::getPrefilterExpressionForMetadata( + [[maybe_unused]] bool isNegated) const { + namespace p = prefilterExpressions; + AD_CORRECTNESS_CHECK(children_.size() == 2); + const SparqlExpression* child0 = children_.at(0).get(); + const SparqlExpression* child1 = children_.at(1).get(); + + const auto mirroredExpression = + [](const ValueId valueId) -> std::unique_ptr { + using enum Comparison; + switch (comp) { + case LT: + // Id < ?var -> ?var > Id + return std::make_unique(valueId); + case LE: + // Id <= ?var -> ?var >= Id + return std::make_unique(valueId); + case GE: + // Id >= ?var -> ?var <= Id + return std::make_unique(valueId); + case GT: + // Id > ?var -> ?var < Id + return std::make_unique(valueId); + case EQ: + case NE: + // EQ(==) or NE(!=) + // Given that these two relations are symmetric w.r.t. ?var and Id, + // no swap regarding the relational operator is necessary. + return std::make_unique>(valueId); + default: + // Unchecked / new valueIdComparators::Comparison case. + AD_FAIL(); + } + }; + + const auto tryGetPrefilterExprVariablePairVec = + [&mirroredExpression](const SparqlExpression* child0, + const SparqlExpression* child1, bool reversed) { + std::vector resVec{}; + if (const auto* variable = + dynamic_cast(child0)) { + if (const auto* valueId = dynamic_cast(child1)) { + resVec.emplace_back( + reversed ? mirroredExpression(valueId->value()) + : std::make_unique>( + valueId->value()), + variable->value()); + } + } + return resVec; + }; + // Option 1: + // RelationalExpression containing a VariableExpression as the first child + // and an IdExpression as the second child. + // E.g. for ?x >= 10 (RelationalExpression Sparql), we obtain the following + // pair with PrefilterExpression and Variable: <(>= 10), ?x> + auto resVec = tryGetPrefilterExprVariablePairVec(child0, child1, false); + if (!resVec.empty()) { + return resVec; + } + // Option 2: + // RelationalExpression containing an IdExpression as the first child and a + // VariableExpression as the second child. + // (1) 10 >= ?x (RelationalExpression Sparql), we obtain the following + // pair with PrefilterExpression and Variable: <(<= 10), ?x> + // (2) 10 != ?x (RelationalExpression Sparql), we obtain the following + // pair with PrefilterExpression and Variable: <(!= 10), ?x>; + // Option 3: + // If no PrefilterExpressions could be constructed for this + // RelationalExpression, just return the empty vector. + return tryGetPrefilterExprVariablePairVec(child1, child0, true); +} + // _____________________________________________________________________________ std::span InExpression::childrenImpl() { return children_; diff --git a/src/engine/sparqlExpressions/RelationalExpressions.h b/src/engine/sparqlExpressions/RelationalExpressions.h index f8364f0137..d406afc936 100644 --- a/src/engine/sparqlExpressions/RelationalExpressions.h +++ b/src/engine/sparqlExpressions/RelationalExpressions.h @@ -38,6 +38,13 @@ class RelationalExpression : public SparqlExpression { // the appropriate data. std::optional getLanguageFilterExpression() const override; + // If this `RelationalExpression` is binary evaluable, return the + // corresponding `PrefilterExpression` for the pre-filtering procedure on + // `CompressedBlockMetadata`. In addition we return the `Variable` that + // corresponds to the sorted column. + std::vector getPrefilterExpressionForMetadata( + [[maybe_unused]] bool isNegated) const override; + // These expressions are typically used inside `FILTER` clauses, so we need // proper estimates. Estimates getEstimatesForFilterExpression( diff --git a/src/engine/sparqlExpressions/SparqlExpression.cpp b/src/engine/sparqlExpressions/SparqlExpression.cpp index 7d1ad5dcf6..7e8fa1648c 100644 --- a/src/engine/sparqlExpressions/SparqlExpression.cpp +++ b/src/engine/sparqlExpressions/SparqlExpression.cpp @@ -108,6 +108,20 @@ Estimates SparqlExpression::getEstimatesForFilterExpression( return {inputSizeEstimate, inputSizeEstimate}; } +// _____________________________________________________________________________ +// The default implementation returns an empty vector given that for most +// `SparqlExpressions` no pre-filter procedure is available. Only specific +// expressions that yield boolean values support the construction of +// <`PrefilterExpression`, `Variable`> pairs. For more information, refer to the +// declaration of this method in SparqlExpression.h. `SparqlExpression`s for +// which pre-filtering over the `IndexScan` is supported, override the virtual +// `getPrefilterExpressionForMetadata` method declared there. +std::vector +SparqlExpression::getPrefilterExpressionForMetadata( + [[maybe_unused]] bool isNegated) const { + return {}; +}; + // _____________________________________________________________________________ bool SparqlExpression::isConstantExpression() const { return false; } diff --git a/src/engine/sparqlExpressions/SparqlExpression.h b/src/engine/sparqlExpressions/SparqlExpression.h index 6a64c4551e..1378f10520 100644 --- a/src/engine/sparqlExpressions/SparqlExpression.h +++ b/src/engine/sparqlExpressions/SparqlExpression.h @@ -96,6 +96,25 @@ class SparqlExpression { [[maybe_unused]] const std::optional& primarySortKeyVariable) const; + // Returns a vector of pairs, each containing a `PrefilterExpression` and its + // corresponding `Variable`. The `Variable` corresponds to the column (index + // column) for which we want to perform the pre-filter procedure. + // For the following SparqlExpressions, a pre-filter procedure can be + // performed given a suitable PrefilterExpression can be constructed: + // `logical-or`, `logical-and`, `logical-negate` (unary), `relational` and + // `strstarts`. + // `isNegated` is set to `false` by default. This boolean flag is toggled to + // `true` if a `logical-negate` (!) expression (see + // `UnaryNegateExpressionImpl` in NumericUnaryExpressions.cpp) is visited, + // allowing this negation information to be passed to the children of the + // respective expression tree. `isNegated` is used to select the suitable + // merge procedure on the children's `PrefilterExpression`s for `logical-and` + // and `logical-or` when constructing their corresponding vector of + // <`PrefilterExpression`, `Variable`> pairs (see `getMergeFunction` in + // NumericBinaryExpression.cpp). + virtual std::vector + getPrefilterExpressionForMetadata(bool isNegated = false) const; + // Returns true iff this expression is a simple constant. Default // implementation returns `false`. virtual bool isConstantExpression() const; diff --git a/src/engine/sparqlExpressions/SparqlExpressionPimpl.cpp b/src/engine/sparqlExpressions/SparqlExpressionPimpl.cpp index ed1e004bd0..99d3f19e3f 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionPimpl.cpp +++ b/src/engine/sparqlExpressions/SparqlExpressionPimpl.cpp @@ -92,6 +92,12 @@ auto SparqlExpressionPimpl::getEstimatesForFilterExpression( primarySortKeyVariable); } +//_____________________________________________________________________________ +std::vector +SparqlExpressionPimpl::getPrefilterExpressionForMetadata() const { + return _pimpl->getPrefilterExpressionForMetadata(); +} + // _____________________________________________________________________________ bool SparqlExpressionPimpl::containsLangExpression() const { return _pimpl->containsLangExpression(); diff --git a/src/engine/sparqlExpressions/SparqlExpressionPimpl.h b/src/engine/sparqlExpressions/SparqlExpressionPimpl.h index c9b023c020..0b4b7b8f48 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionPimpl.h +++ b/src/engine/sparqlExpressions/SparqlExpressionPimpl.h @@ -9,6 +9,7 @@ #include #include "engine/VariableToColumnMap.h" +#include "engine/sparqlExpressions/PrefilterExpressionIndex.h" #include "parser/data/Variable.h" #include "util/HashMap.h" #include "util/HashSet.h" @@ -18,6 +19,12 @@ namespace sparqlExpression { class SparqlExpression; struct EvaluationContext; +// Improve return type readability. +// Pair containing `PrefilterExpression` pointer and a `Variable`. +using PrefilterExprVariablePair = + std::pair, + Variable>; + // Hide the `SparqlExpression` implementation in a Pimpl class, so that code // using this implementation only has to include the (small and therefore cheap // to include) `SparqlExpressionPimpl.h` @@ -111,6 +118,11 @@ class SparqlExpressionPimpl { uint64_t inputSizeEstimate, const std::optional& primarySortKeyVariable); + // For a concise description of this method and its functionality, refer to + // the corresponding declaration in SparqlExpression.h. + std::vector getPrefilterExpressionForMetadata() + const; + SparqlExpression* getPimpl() { return _pimpl.get(); } [[nodiscard]] const SparqlExpression* getPimpl() const { return _pimpl.get(); diff --git a/src/engine/sparqlExpressions/StringExpressions.cpp b/src/engine/sparqlExpressions/StringExpressions.cpp index 47ee97bd23..2392ec2b18 100644 --- a/src/engine/sparqlExpressions/StringExpressions.cpp +++ b/src/engine/sparqlExpressions/StringExpressions.cpp @@ -4,6 +4,7 @@ #include +#include "engine/sparqlExpressions/LiteralExpression.h" #include "engine/sparqlExpressions/NaryExpressionImpl.h" #include "engine/sparqlExpressions/VariadicExpression.h" @@ -231,9 +232,62 @@ using SubstrExpression = return Id::makeFromBool(text.starts_with(pattern)); }; -using StrStartsExpression = - StringExpressionImpl<2, LiftStringFunction, - StringValueGetter>; +namespace { + +template +requires(isOperation) +class StrStartsExpressionImpl : public NaryExpression { + public: + using NaryExpression::NaryExpression; + std::vector getPrefilterExpressionForMetadata( + [[maybe_unused]] bool isNegated) const override { + AD_CORRECTNESS_CHECK(this->N == 2); + const SparqlExpression* child0 = this->getNthChild(0).value(); + const SparqlExpression* child1 = this->getNthChild(1).value(); + const auto getPrefilterExprVariableVec = [](const SparqlExpression* child0, + const SparqlExpression* child1, + bool startsWithVar) { + using namespace prefilterExpressions; + std::vector resVec{}; + if (const auto* variable = + dynamic_cast(child0)) { + if (const auto* valueId = dynamic_cast(child1)) { + !startsWithVar + // Will return: {<(>= VocabId(n)), ?var>} (Option 1) + ? resVec.emplace_back( + std::make_unique(valueId->value()), + variable->value()) + // Will return: {<(<= VocabId(n)), ?var>} (Option 2) + : resVec.emplace_back( + std::make_unique(valueId->value()), + variable->value()); + } + } + return resVec; + }; + // Remark: With the current implementation we only prefilter w.r.t. one + // bound. + // TODO: It is technically possible to pre-filter more precisely by + // introducing a second bound. + // + // Option 1: STRSTARTS(?var, VocabId(n)); startsWithVar = false + // Return PrefilterExpression vector: {<(>= VocabId(n)), ?var>} + auto resVec = getPrefilterExprVariableVec(child0, child1, false); + if (!resVec.empty()) { + return resVec; + } + // Option 2: STRTSTARTS(VocabId(n), ?var); startsWithVar = true + // Return PrefilterExpression vector: {<(<= VocabId(n)), ?var>} + // Option 3: + // child0 or/and child1 are unsuitable SparqlExpression types, return {}. + return getPrefilterExprVariableVec(child1, child0, true); + } +}; + +} // namespace + +using StrStartsExpression = StrStartsExpressionImpl, StringValueGetter>>>; // STRENDS [[maybe_unused]] auto strEndsImpl = [](std::string_view text, diff --git a/src/index/CMakeLists.txt b/src/index/CMakeLists.txt index 9b46467bc6..1fc8773721 100644 --- a/src/index/CMakeLists.txt +++ b/src/index/CMakeLists.txt @@ -6,6 +6,5 @@ add_library(index DocsDB.cpp FTSAlgorithms.cpp PrefixHeuristic.cpp CompressedRelation.cpp PatternCreator.cpp ScanSpecification.cpp - CompressedBlockPrefiltering.cpp DeltaTriples.cpp LocalVocabEntry.cpp) qlever_target_link_libraries(index util parser vocabulary ${STXXL_LIBRARIES}) diff --git a/src/index/CompressedRelation.h b/src/index/CompressedRelation.h index 00cdb10dae..763a86fd34 100644 --- a/src/index/CompressedRelation.h +++ b/src/index/CompressedRelation.h @@ -126,6 +126,18 @@ struct CompressedBlockMetadata : CompressedBlockMetadataNoBlockIndex { // the corresponding block from the `LocatedTriples` when only a subset of // blocks is being used. size_t blockIndex_; + + // Two of these are equal if all members are equal. + bool operator==(const CompressedBlockMetadata&) const = default; + + // Format BlockMetadata contents for debugging. + friend std::ostream& operator<<( + std::ostream& str, const CompressedBlockMetadata& blockMetadata) { + str << "#BlockMetadata\n(first) " << blockMetadata.firstTriple_ << "(last) " + << blockMetadata.lastTriple_ << "num. rows: " << blockMetadata.numRows_ + << "." << std::endl; + return str; + } }; // Serialization of the `OffsetAndcompressedSize` subclass. diff --git a/src/parser/data/Variable.h b/src/parser/data/Variable.h index 32a1ee99a9..a378cfd903 100644 --- a/src/parser/data/Variable.h +++ b/src/parser/data/Variable.h @@ -56,6 +56,9 @@ class Variable { bool operator==(const Variable&) const = default; + // The construction of PrefilterExpressions requires a defined < order. + bool operator<(const Variable& other) const { return _name < other._name; }; + // Make the type hashable for absl, see // https://abseil.io/docs/cpp/guides/hash. template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e9b2cf8347..e540b448eb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -291,7 +291,9 @@ addLinkAndDiscoverTest(AlgorithmTest) addLinkAndDiscoverTestSerial(CompressedRelationsTest index) -addLinkAndDiscoverTestSerial(CompressedBlockPrefilteringTest index) +addLinkAndDiscoverTestSerial(PrefilterExpressionIndexTest sparqlExpressions index) + +addLinkAndDiscoverTestSerial(GetPrefilterExpressionFromSparqlExpressionTest sparqlExpressions index) addLinkAndDiscoverTest(ExceptionTest) diff --git a/test/GetPrefilterExpressionFromSparqlExpressionTest.cpp b/test/GetPrefilterExpressionFromSparqlExpressionTest.cpp new file mode 100644 index 0000000000..8a476db000 --- /dev/null +++ b/test/GetPrefilterExpressionFromSparqlExpressionTest.cpp @@ -0,0 +1,592 @@ +// Copyright 2024, University of Freiburg, +// Chair of Algorithms and Data Structures +// Author: Hannes Baumann + +#include "./PrefilterExpressionTestHelpers.h" +#include "./SparqlExpressionTestHelpers.h" +#include "util/GTestHelpers.h" + +using ad_utility::testing::BlankNodeId; +using ad_utility::testing::BoolId; +using ad_utility::testing::DoubleId; +using ad_utility::testing::IntId; +using ad_utility::testing::UndefId; +using ad_utility::testing::VocabId; + +using Literal = ad_utility::triple_component::Literal; +using Iri = ad_utility::triple_component::Iri; +using RelValues = std::variant; + +namespace { + +using namespace sparqlExpression; + +namespace makeSparqlExpression { + +//______________________________________________________________________________ +const auto makeLiteralSparqlExpr = + [](const auto& child) -> std::unique_ptr { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_unique(child); + } else if constexpr (std::is_same_v) { + return std::make_unique(child); + } else if constexpr (std::is_same_v) { + return std::make_unique(child); + } else if constexpr (std::is_same_v) { + return std::make_unique(child); + } else { + throw std::runtime_error( + "Can't create a LiteralExpression from provided (input) type."); + } +}; + +//______________________________________________________________________________ +auto getExpr = [](const auto& variantVal) -> std::unique_ptr { + return makeLiteralSparqlExpr(variantVal); +}; + +//______________________________________________________________________________ +template +std::unique_ptr makeRelationalSparqlExprImpl( + const RelValues& child0, const RelValues& child1) { + return std::make_unique(std::array{ + std::visit(getExpr, child0), std::visit(getExpr, child1)}); +}; + +//______________________________________________________________________________ +std::unique_ptr makeStringStartsWithSparqlExpression( + const RelValues& child0, const RelValues& child1) { + return makeStrStartsExpression(std::visit(getExpr, child0), + std::visit(getExpr, child1)); +}; + +//______________________________________________________________________________ +// LESS THAN (`<`, `SparqlExpression`) +constexpr auto ltSprql = &makeRelationalSparqlExprImpl; +// LESS EQUAL (`<=`, `SparqlExpression`) +constexpr auto leSprql = &makeRelationalSparqlExprImpl; +// EQUAL (`==`, `SparqlExpression`) +constexpr auto eqSprql = &makeRelationalSparqlExprImpl; +// NOT EQUAL (`!=`, `SparqlExpression`) +constexpr auto neqSprql = &makeRelationalSparqlExprImpl; +// GREATER EQUAL (`>=`, `SparqlExpression`) +constexpr auto geSprql = &makeRelationalSparqlExprImpl; +// GREATER THAN (`>`, `SparqlExpression`) +constexpr auto gtSprql = &makeRelationalSparqlExprImpl; +// AND (`&&`, `SparqlExpression`) +constexpr auto andSprqlExpr = &makeAndExpression; +// OR (`||`, `SparqlExpression`) +constexpr auto orSprqlExpr = &makeOrExpression; +// NOT (`!`, `SparqlExpression`) +constexpr auto notSprqlExpr = &makeUnaryNegateExpression; + +//______________________________________________________________________________ +// Create SparqlExpression `STRSTARTS`. +constexpr auto strStartsSprql = &makeStringStartsWithSparqlExpression; + +} // namespace makeSparqlExpression + +//______________________________________________________________________________ +// make `Literal` +const auto L = [](const std::string& content) -> Literal { + return Literal::fromStringRepresentation(content); +}; + +//______________________________________________________________________________ +// make `Iri` +const auto I = [](const std::string& content) -> Iri { + return Iri::fromIriref(content); +}; + +//______________________________________________________________________________ +struct TestDates { + const Id referenceDate1 = DateId(DateParser, "1999-11-11"); + const Id referenceDate2 = DateId(DateParser, "2005-02-27"); +}; + +//______________________________________________________________________________ +// ASSERT EQUALITY +//______________________________________________________________________________ +const auto equalityCheckPrefilterVectors = + [](const std::vector& result, + const std::vector& expected) -> void { + ASSERT_EQ(result.size(), expected.size()); + const auto isEqualImpl = [](const PrefilterExprVariablePair& resPair, + const PrefilterExprVariablePair& expPair) { + if (*resPair.first != *expPair.first || resPair.second != expPair.second) { + std::stringstream stream; + stream << "The following value pairs don't match:" + << "\nRESULT: " << *resPair.first << "EXPECTED: " << *expPair.first + << "RESULT: VARIABLE" << resPair.second.name() + << "\nEXPECTED: VARIABLE" << expPair.second.name() << std::endl; + ADD_FAILURE() << stream.str(); + return false; + } + return true; + }; + ASSERT_TRUE( + std::equal(result.begin(), result.end(), expected.begin(), isEqualImpl)); +}; + +//______________________________________________________________________________ +// `evalAndEqualityCheck` evaluates the provided `SparqlExpression` and checks +// in the following if the resulting vector contains the same +// `` pairs in the correct order. If no +// `` pair is provided, the expected value for +// the `SparqlExpression` is an empty vector. +const auto evalAndEqualityCheck = + [](std::unique_ptr sparqlExpr, + std::convertible_to auto&&... prefilterArgs) { + std::vector prefilterVarPair = {}; + if constexpr (sizeof...(prefilterArgs) > 0) { + (prefilterVarPair.emplace_back( + std::forward(prefilterArgs)), + ...); + } + equalityCheckPrefilterVectors( + sparqlExpr->getPrefilterExpressionForMetadata(), + std::move(prefilterVarPair)); + }; + +//______________________________________________________________________________ +// Construct a `PAIR` with the given `PrefilterExpression` and `Variable` value. +auto pr = + [](std::unique_ptr expr, + const Variable& var) -> sparqlExpression::PrefilterExprVariablePair { + return {std::move(expr), var}; +}; + +} // namespace + +using namespace makeSparqlExpression; +using namespace makeFilterExpression; +using namespace makeSparqlExpression; + +//______________________________________________________________________________ +// Test coverage for the default implementation of +// getPrefilterExpressionForMetadata. +TEST(GetPrefilterExpressionFromSparqlExpression, + testGetPrefilterExpressionDefault) { + evalAndEqualityCheck( + makeUnaryMinusExpression(makeLiteralSparqlExpr(IntId(0)))); + evalAndEqualityCheck(makeMultiplyExpression( + makeLiteralSparqlExpr(DoubleId(11)), makeLiteralSparqlExpr(DoubleId(3)))); + evalAndEqualityCheck( + makeStrEndsExpression(makeLiteralSparqlExpr(L("\"Freiburg\"")), + makeLiteralSparqlExpr(L("\"burg\"")))); + evalAndEqualityCheck( + makeIsIriExpression(makeLiteralSparqlExpr(I("")))); + evalAndEqualityCheck(makeLogExpression(makeLiteralSparqlExpr(DoubleId(8)))); + evalAndEqualityCheck( + makeStrIriDtExpression(makeLiteralSparqlExpr(L("\"test\"")), + makeLiteralSparqlExpr(I("")))); +} + +//______________________________________________________________________________ +// Check that the (Sparql) RelationalExpression returns the expected +// PrefilterExpression. +TEST(GetPrefilterExpressionFromSparqlExpression, + getPrefilterExpressionFromSparqlRelational) { + const TestDates dt{}; + const Variable var = Variable{"?x"}; + // ?x == BooldId(true) (RelationalExpression Sparql) + // expected: <(== BoolId(true)), ?x> (PrefilterExpression, Variable) + evalAndEqualityCheck(eqSprql(var, BoolId(true)), pr(eq(BoolId(true)), var)); + // For BoolId(true) == ?x we expect the same PrefilterExpression pair. + evalAndEqualityCheck(eqSprql(BoolId(true), var), pr(eq(BoolId(true)), var)); + // ?x != BooldId(true) (RelationalExpression Sparql) + // expected: <(!= BoolId(true)), ?x> (PrefilterExpression, Variable) + evalAndEqualityCheck(neqSprql(var, BoolId(false)), + pr(neq(BoolId(false)), var)); + // Same expected value for BoolId(true) != ?x. + evalAndEqualityCheck(neqSprql(BoolId(false), var), + pr(neq(BoolId(false)), var)); + // ?x >= IntId(1) + // expected: <(>= IntId(1)), ?x> + evalAndEqualityCheck(geSprql(var, IntId(1)), pr(ge(IntId(1)), var)); + // IntId(1) <= ?x + // expected: <(>= IntId(1)), ?x> + evalAndEqualityCheck(leSprql(IntId(1), var), pr(ge(IntId(1)), var)); + // ?x > IntId(1) + // expected: <(> IntId(1)), ?x> + evalAndEqualityCheck(gtSprql(var, IntId(1)), pr(gt(IntId(1)), var)); + // VocabId(10) != ?x + // expected: <(!= VocabId(10)), ?x> + evalAndEqualityCheck(neqSprql(VocabId(10), var), pr(neq(VocabId(10)), var)); + // BlankNodeId(1) > ?x + // expected: <(< BlankNodeId(1)), ?x> + evalAndEqualityCheck(geSprql(BlankNodeId(1), var), + pr(le(BlankNodeId(1)), var)); + // ?x < BlankNodeId(1) + // expected: <(< BlankNodeId(1)), ?x> + evalAndEqualityCheck(ltSprql(var, BlankNodeId(1)), + pr(lt(BlankNodeId(1)), var)); + // ?x <= referenceDate1 + // expected: <(<= referenceDate1), ?x> + evalAndEqualityCheck(leSprql(var, dt.referenceDate1), + pr(le(dt.referenceDate1), var)); + // referenceDate1 >= ?x + // expected: <(<= referenceDate1), ?x> + evalAndEqualityCheck(geSprql(dt.referenceDate1, var), + pr(le(dt.referenceDate1), var)); + // DoubleId(10.2) < ?x + // expected: <(> DoubleId(10.2)), ?x> + evalAndEqualityCheck(ltSprql(DoubleId(10.2), var), + pr(gt(DoubleId(10.2)), var)); + // ?x > DoubleId(10.2) + // expected: <(> DoubleId(10.2)), ?x> + evalAndEqualityCheck(gtSprql(var, DoubleId(10.2)), + pr(gt(DoubleId(10.2)), var)); +} + +//______________________________________________________________________________ +// More complex relational SparqlExpressions for which +// getPrefilterExpressionForMetadata should yield a vector containing the actual +// corresponding PrefilterExpression values. +TEST(GetPrefilterExpressionFromSparqlExpression, + getPrefilterExpressionsToComplexSparqlExpressions) { + const Variable varX = Variable{"?x"}; + const Variable varY = Variable{"?y"}; + const Variable varZ = Variable{"?z"}; + // ?x >= 10 AND ?x != 20 + // expected prefilter pairs: + // {<((>= 10) AND (!= 20)), ?x>} + evalAndEqualityCheck( + andSprqlExpr(geSprql(varX, IntId(10)), neqSprql(varX, IntId(20))), + pr(andExpr(ge(IntId(10)), neq(IntId(20))), varX)); + // ?z > VocabId(0) AND ?y > 0 AND ?x < 30.00 + // expected prefilter pairs + // {<(< 30.00), ?x>, <(> 0), ?y>, <(> VocabId(0)), ?z>} + evalAndEqualityCheck(andSprqlExpr(andSprqlExpr(gtSprql(varZ, VocabId(0)), + gtSprql(varY, IntId(0))), + ltSprql(varX, DoubleId(30.00))), + pr(lt(DoubleId(30.00)), varX), pr(gt(IntId(0)), varY), + pr(gt(VocabId(0)), varZ)); + + // ?x == VocabId(10) AND ?y >= VocabId(10) + // expected prefilter pairs: + // {<(== VocabId(10)), ?x>, <(>= VocabId(10)), ?y>} + evalAndEqualityCheck( + andSprqlExpr(eqSprql(varX, VocabId(10)), geSprql(varY, VocabId(10))), + pr(eq(VocabId(10)), varX), pr(ge(VocabId(10)), varY)); + // !(?x >= 10 OR ?x <= 0) + // expected prefilter pairs: + // {= 10 OR ?x <= 0), ?x>} + evalAndEqualityCheck(notSprqlExpr(orSprqlExpr(geSprql(varX, IntId(10)), + leSprql(varX, IntId(0)))), + pr(notExpr(orExpr(ge(IntId(10)), le(IntId(0)))), varX)); + // !(?z == VocabId(10) AND ?z >= VocabId(20)) + // expected prefilter pairs: + // {= VocabId(20)) , ?z>} + evalAndEqualityCheck( + notSprqlExpr( + andSprqlExpr(eqSprql(varZ, VocabId(10)), geSprql(varZ, VocabId(20)))), + pr(notExpr(andExpr(eq(VocabId(10)), ge(VocabId(20)))), varZ)); + // (?x == VocabId(10) AND ?z == VocabId(0)) AND ?y != DoubleId(22.1) + // expected prefilter pairs: + // {<(==VocabId(10)) , ?x>, <(!=DoubleId(22.1)), ?y>, <(==VocabId(0)), ?z>} + evalAndEqualityCheck(andSprqlExpr(andSprqlExpr(eqSprql(VocabId(10), varX), + eqSprql(varZ, VocabId(0))), + neqSprql(DoubleId(22.1), varY)), + pr(eq(VocabId(10)), varX), pr(neq(DoubleId(22.1)), varY), + pr(eq(VocabId(0)), varZ)); + // (?z >= 1000 AND ?x == VocabId(10)) OR ?z >= 10000 + // expected prefilter pairs: + // {<((>=1000) OR (>= 10000)), ?z>} + evalAndEqualityCheck(orSprqlExpr(andSprqlExpr(geSprql(varZ, IntId(1000)), + eqSprql(varX, VocabId(10))), + geSprql(varZ, IntId(10000))), + pr(orExpr(ge(IntId(1000)), ge(IntId(10000))), varZ)); + // !((?z <= VocabId(10) OR ?y <= VocabId(10)) OR ?x <= VocabId(10)) + // expected prefilter pairs: + // {, , } + evalAndEqualityCheck( + notSprqlExpr(orSprqlExpr( + orSprqlExpr(leSprql(varZ, VocabId(10)), leSprql(varY, VocabId(10))), + leSprql(varX, VocabId(10)))), + pr(notExpr(le(VocabId(10))), varX), pr(notExpr(le(VocabId(10))), varY), + pr(notExpr(le(VocabId(10))), varZ)); + // ?x >= 10 AND ?y >= 10 + // expected prefilter pairs: + // {<(>= 10), ?x>, <(>= 10), ?y>} + evalAndEqualityCheck( + andSprqlExpr(geSprql(varX, IntId(10)), geSprql(varY, IntId(10))), + pr(ge(IntId(10)), varX), pr(ge(IntId(10)), varY)); + // ?x <= 0 AND ?y <= 0 + // expected prefilter pairs: + // {<(<= 0), ?x>, <(<= 0), ?y>} + evalAndEqualityCheck( + andSprqlExpr(leSprql(varX, IntId(0)), leSprql(varY, IntId(0))), + pr(le(IntId(0)), varX), pr(le(IntId(0)), varY)); + // (?x >= 10 AND ?y >= 10) OR (?x <= 0 AND ?y <= 0) + // expected prefilter pairs: + // {<((>= 10) OR (<= 0)), ?x> <(>= 10) OR (<= 0)), ?y>} + evalAndEqualityCheck( + orSprqlExpr( + andSprqlExpr(geSprql(varX, IntId(10)), geSprql(varY, IntId(10))), + andSprqlExpr(leSprql(varX, IntId(0)), leSprql(varY, IntId(0)))), + pr(orExpr(ge(IntId(10)), le(IntId(0))), varX), + pr(orExpr(ge(IntId(10)), le(IntId(0))), varY)); + // !(?x >= 10 OR ?y >= 10) OR !(?x <= 0 OR ?y <= 0) + // expected prefilter pairs: + // {<((!(>= 10) OR !(<= 0))), ?x> <(!(>= 10) OR !(<= 0))), ?y>} + evalAndEqualityCheck( + orSprqlExpr(notSprqlExpr(orSprqlExpr(geSprql(varX, IntId(10)), + geSprql(varY, IntId(10)))), + notSprqlExpr(orSprqlExpr(leSprql(varX, IntId(0)), + leSprql(varY, IntId(0))))), + pr(orExpr(notExpr(ge(IntId(10))), notExpr(le(IntId(0)))), varX), + pr(orExpr(notExpr(ge(IntId(10))), notExpr(le(IntId(0)))), varY)); + // !(?x == VocabId(10) OR ?x == VocabId(20)) AND !(?z >= 10.00 OR ?y == false) + // expected prefilter pairs: + // {, , + // = 10), ?z>} + evalAndEqualityCheck( + andSprqlExpr(notSprqlExpr(orSprqlExpr(eqSprql(varX, VocabId(10)), + eqSprql(varX, VocabId(20)))), + notSprqlExpr(orSprqlExpr(geSprql(varZ, DoubleId(10)), + eqSprql(varY, BoolId(false))))), + pr(notExpr(orExpr(eq(VocabId(10)), eq(VocabId(20)))), varX), + pr(notExpr(eq(BoolId(false))), varY), + pr(notExpr(ge(DoubleId(10))), varZ)); + // !(!(?x >= 10 AND ?y >= 10)) OR !(!(?x <= 0 AND ?y <= 0)) + // expected prefilter pairs: + // {<(!!(>= 10) OR !!(<= 0)), ?x>, <(!!(>= 10) OR !!(<= 0)) ,?y>} + evalAndEqualityCheck( + orSprqlExpr(notSprqlExpr(notSprqlExpr(andSprqlExpr( + geSprql(varX, IntId(10)), geSprql(varY, IntId(10))))), + notSprqlExpr(notSprqlExpr(andSprqlExpr( + leSprql(varX, IntId(0)), leSprql(varY, IntId(0)))))), + pr(orExpr(notExpr(notExpr(ge(IntId(10)))), + notExpr(notExpr(le(IntId(0))))), + varX), + pr(orExpr(notExpr(notExpr(ge(IntId(10)))), + notExpr(notExpr(le(IntId(0))))), + varY)); + // !((?x >= VocabId(0) AND ?x <= VocabId(10)) OR !(?x != VocabId(99))) + // expected prefilter pairs: + // {= VocabId(0)) AND (<= VocabId(10))) OR !(!= VocabId(99))) , ?x>} + evalAndEqualityCheck( + notSprqlExpr(orSprqlExpr( + andSprqlExpr(geSprql(varX, VocabId(0)), leSprql(varX, VocabId(10))), + notSprqlExpr(neqSprql(varX, VocabId(99))))), + pr(notExpr(orExpr(andExpr(ge(VocabId(0)), le(VocabId(10))), + notExpr(neq(VocabId(99))))), + varX)); + // !((?y >= 10 AND ?y <= 100) OR !(?x >= VocabId(99))) + // expected prefilter pairs: + // {= VocabId(0)) AND (<= VocabId(10)), ?y>, = VocabId(99))), ?x>} + evalAndEqualityCheck( + notSprqlExpr(orSprqlExpr( + andSprqlExpr(geSprql(varY, VocabId(0)), leSprql(varY, VocabId(10))), + notSprqlExpr(geSprql(varX, VocabId(99))))), + pr(notExpr(notExpr(ge(VocabId(99)))), varX), + pr(notExpr(andExpr(ge(VocabId(0)), le(VocabId(10)))), varY)); + // ?z >= 10 AND ?z <= 100 AND ?x >= 10 AND ?x != 50 AND !(?y <= 10) AND + // !(?city <= VocabId(1000) OR ?city == VocabId(1005)) + // expected prefilter pairs: + // {, <((>= 10) AND (!= + // 50)), ?x>, , <((>= 10) AND (<= 100)), ?z>} + evalAndEqualityCheck( + andSprqlExpr( + andSprqlExpr( + andSprqlExpr(geSprql(varZ, IntId(10)), leSprql(varZ, IntId(100))), + andSprqlExpr(andSprqlExpr(geSprql(varX, IntId(10)), + neqSprql(varX, IntId(50))), + notSprqlExpr(leSprql(varY, IntId(10))))), + notSprqlExpr(orSprqlExpr(leSprql(Variable{"?city"}, VocabId(1000)), + eqSprql(Variable{"?city"}, VocabId(1005))))), + pr(notExpr(orExpr(le(VocabId(1000)), eq(VocabId(1005)))), + Variable{"?city"}), + pr(andExpr(ge(IntId(10)), neq(IntId(50))), varX), + pr(notExpr(le(IntId(10))), varY), + pr(andExpr(ge(IntId(10)), le(IntId(100))), varZ)); + // ?x >= 10 OR (?x >= -10 AND ?x < 0.00) + // expected prefilter pairs: + // {<((>= 10) OR ((>= -10) AND (< 0.00))), ?x>} + evalAndEqualityCheck( + orSprqlExpr(geSprql(varX, IntId(10)), + andSprqlExpr(geSprql(varX, IntId(-10)), + ltSprql(varX, DoubleId(0.00)))), + pr(orExpr(ge(IntId(10)), andExpr(ge(IntId(-10)), lt(DoubleId(0.00)))), + varX)); + // !(!(?x >= 10) OR !!(?x >= -10 AND ?x < 0.00)) + // expected prefilter pairs: + // {= 10) OR !!((>= -10) AND (< 0.00))), ?x>} + evalAndEqualityCheck( + notSprqlExpr(orSprqlExpr( + notSprqlExpr(geSprql(varX, IntId(10))), + notSprqlExpr(notSprqlExpr(andSprqlExpr( + geSprql(varX, IntId(-10)), ltSprql(varX, DoubleId(0.00))))))), + pr(notExpr(orExpr( + notExpr(ge(IntId(10))), + notExpr(notExpr(andExpr(ge(IntId(-10)), lt(DoubleId(0.00))))))), + varX)); + // ?y != ?x AND ?x >= 10 + // expected prefilter pairs: + // {<(>= 10), ?x>} + evalAndEqualityCheck( + andSprqlExpr(neqSprql(varY, varX), geSprql(varX, IntId(10))), + pr(ge(IntId(10)), varX)); + evalAndEqualityCheck( + andSprqlExpr(geSprql(varX, IntId(10)), neqSprql(varY, varX)), + pr(ge(IntId(10)), varX)); +} + +//______________________________________________________________________________ +// For this test we expect that no PrefilterExpression is available. +TEST(GetPrefilterExpressionFromSparqlExpression, + getEmptyPrefilterFromSparqlRelational) { + const Variable var = Variable{"?x"}; + const Iri iri = I(""); + const Literal lit = L("\"lit\""); + evalAndEqualityCheck(leSprql(var, var)); + evalAndEqualityCheck(neqSprql(iri, var)); + evalAndEqualityCheck(eqSprql(var, iri)); + evalAndEqualityCheck(neqSprql(IntId(10), DoubleId(23.3))); + evalAndEqualityCheck(gtSprql(DoubleId(10), lit)); + evalAndEqualityCheck(ltSprql(VocabId(10), BoolId(10))); + evalAndEqualityCheck(geSprql(lit, lit)); + evalAndEqualityCheck(eqSprql(iri, iri)); + evalAndEqualityCheck(orSprqlExpr(eqSprql(var, var), gtSprql(var, IntId(0)))); + evalAndEqualityCheck(orSprqlExpr(eqSprql(var, var), gtSprql(var, var))); + evalAndEqualityCheck(andSprqlExpr(eqSprql(var, var), gtSprql(var, var))); +} + +//______________________________________________________________________________ +// For the following more complex SparqlExpression trees, we also expect an +// empty PrefilterExpression vector. +TEST(GetPrefilterExpressionFromSparqlExpression, + getEmptyPrefilterForMoreComplexSparqlExpressions) { + const Variable varX = Variable{"?x"}; + const Variable varY = Variable{"?y"}; + const Variable varZ = Variable{"?z"}; + // ?x <= 10.00 OR ?y > 10 + evalAndEqualityCheck( + orSprqlExpr(leSprql(DoubleId(10), varX), gtSprql(IntId(10), varY))); + // ?x >= VocabId(23) OR ?z == VocabId(1) + evalAndEqualityCheck( + orSprqlExpr(geSprql(varX, VocabId(23)), eqSprql(varZ, VocabId(1)))); + // (?x < VocabId(10) OR ?z <= VocabId(4)) OR ?z != 5.00 + evalAndEqualityCheck(orSprqlExpr( + orSprqlExpr(ltSprql(varX, VocabId(10)), leSprql(VocabId(4), varZ)), + neqSprql(varZ, DoubleId(5)))); + // !(?z > 10.20 AND ?x < 0.001) + // is equal to + // ?z <= 10.20 OR ?x >= 0.001 + evalAndEqualityCheck(notSprqlExpr(andSprqlExpr( + gtSprql(DoubleId(10.2), varZ), ltSprql(DoubleId(0.001), varX)))); + // !(?x > 10.20 AND ?z != VocabId(22)) + // is equal to + // ?x <= 10.20 OR ?z == VocabId(22) + evalAndEqualityCheck(notSprqlExpr(andSprqlExpr(gtSprql(DoubleId(10.2), varX), + neqSprql(VocabId(22), varZ)))); + // !(!((?x < VocabId(10) OR ?x <= VocabId(4)) OR ?z != 5.00)) + // is equal to + // (?x < VocabId(10) OR ?x <= VocabId(4)) OR ?z != 5.00 + evalAndEqualityCheck(notSprqlExpr(notSprqlExpr(orSprqlExpr( + orSprqlExpr(ltSprql(varX, VocabId(10)), leSprql(VocabId(4), varX)), + neqSprql(varZ, DoubleId(5)))))); + // !(?x != 10 AND !(?y >= 10.00 OR ?z <= 10)) + // is equal to + // ?x == 10 OR ?y >= 10.00 OR ?z <= 10 + evalAndEqualityCheck(notSprqlExpr( + andSprqlExpr(neqSprql(varX, IntId(10)), + notSprqlExpr(orSprqlExpr(geSprql(varY, DoubleId(10.00)), + leSprql(varZ, IntId(10))))))); + // !((?x != 10 AND ?z != 10) AND (?y == 10 AND ?x >= 20)) + // is equal to + //?x == 10 OR ?z == 10 OR ?y != 10 OR ?x < 20 + evalAndEqualityCheck(notSprqlExpr(andSprqlExpr( + andSprqlExpr(neqSprql(varX, IntId(10)), neqSprql(varZ, IntId(10))), + andSprqlExpr(eqSprql(varY, IntId(10)), geSprql(varX, DoubleId(20)))))); + // !(?z >= 40 AND (?z != 10.00 AND ?y != VocabId(1))) + // is equal to + // ?z <= 40 OR ?z == 10.00 OR ?y == VocabId(1) + evalAndEqualityCheck(notSprqlExpr(andSprqlExpr( + geSprql(varZ, IntId(40)), andSprqlExpr(neqSprql(varZ, DoubleId(10.00)), + neqSprql(varY, VocabId(1)))))); + // ?z <= true OR !(?x == 10 AND ?y == 10) + // is equal to + // ?z <= true OR ?x != 10 OR ?y != 10 + evalAndEqualityCheck( + orSprqlExpr(leSprql(varZ, BoolId(true)), + notSprqlExpr(andSprqlExpr(eqSprql(varX, IntId(10)), + eqSprql(IntId(10), varY))))); + // !(!(?z <= true OR !(?x == 10 AND ?y == 10))) + // is equal to + // ?z <= true OR ?x != 10 OR ?y != 10 + evalAndEqualityCheck(notSprqlExpr(notSprqlExpr( + orSprqlExpr(leSprql(varZ, BoolId(true)), + notSprqlExpr(andSprqlExpr(eqSprql(varX, IntId(10)), + eqSprql(IntId(10), varY))))))); + // !(!(?x != 10 OR !(?y >= 10.00 AND ?z <= 10))) + // is equal to + // ?x != 10 OR ?y < 10.00 OR ?z > 10 + evalAndEqualityCheck(notSprqlExpr(notSprqlExpr( + orSprqlExpr(neqSprql(varX, IntId(10)), + notSprqlExpr(andSprqlExpr(geSprql(varY, DoubleId(10.00)), + leSprql(varZ, IntId(10)))))))); + // !(!(?x == VocabId(10) OR ?y >= 25) AND !(!(?z == true AND ?country == + // VocabId(20)))) + // is equal to + // ?x == VocabId(10) OR ?y >= 25 OR ?z == true AND ?country == VocabId(20) + evalAndEqualityCheck(notSprqlExpr(andSprqlExpr( + notSprqlExpr( + orSprqlExpr(eqSprql(varX, VocabId(10)), geSprql(varY, IntId(25)))), + notSprqlExpr(notSprqlExpr( + andSprqlExpr(eqSprql(varZ, BoolId(true)), + eqSprql(Variable{"?country"}, VocabId(20)))))))); +} + +// Test PrefilterExpression creation for SparqlExpression STRSTARTS +//______________________________________________________________________________ +TEST(GetPrefilterExpressionFromSparqlExpression, + getPrefilterExprForStrStartsExpr) { + const auto varX = Variable{"?x"}; + const auto varY = Variable{"?y"}; + evalAndEqualityCheck(strStartsSprql(varX, VocabId(0)), + pr(ge(VocabId(0)), varX)); + evalAndEqualityCheck(strStartsSprql(VocabId(0), varX), + pr(le(VocabId(0)), varX)); + evalAndEqualityCheck(strStartsSprql(varX, varY)); + evalAndEqualityCheck(strStartsSprql(VocabId(0), VocabId(10))); +} + +// Test that the conditions required for a correct merge of child +// PrefilterExpressions are properly checked during the PrefilterExpression +// construction procedure. This check is applied in the SparqlExpression (for +// NOT, AND and OR) counter-expressions, while constructing their corresponding +// PrefilterExpression. +//______________________________________________________________________________ +TEST(GetPrefilterExpressionFromSparqlExpression, + checkPropertiesForPrefilterConstruction) { + namespace pd = prefilterExpressions::detail; + const Variable varX = Variable{"?x"}; + const Variable varY = Variable{"?y"}; + const Variable varZ = Variable{"?z"}; + const Variable varW = Variable{"?w"}; + std::vector vec{}; + vec.push_back(pr(andExpr(lt(IntId(5)), gt(DoubleId(-0.01))), varX)); + vec.push_back(pr(gt(VocabId(0)), varY)); + EXPECT_NO_THROW(pd::checkPropertiesForPrefilterConstruction(vec)); + vec.push_back(pr(eq(VocabId(33)), varZ)); + EXPECT_NO_THROW(pd::checkPropertiesForPrefilterConstruction(vec)); + // Add a pair with duplicate Variable. + vec.push_back(pr(gt(VocabId(0)), varZ)); + AD_EXPECT_THROW_WITH_MESSAGE( + pd::checkPropertiesForPrefilterConstruction(vec), + ::testing::HasSubstr("For each relevant Variable must exist exactly " + "one pair.")); + // Remove the last two pairs and add a pair + // which violates the order on Variable(s). + vec.pop_back(); + vec.pop_back(); + vec.push_back(pr(eq(VocabId(0)), varW)); + AD_EXPECT_THROW_WITH_MESSAGE( + pd::checkPropertiesForPrefilterConstruction(vec), + ::testing::HasSubstr( + "The vector must contain the " + "pairs in sorted order w.r.t. Variable value.")); +} diff --git a/test/CompressedBlockPrefilteringTest.cpp b/test/PrefilterExpressionIndexTest.cpp similarity index 83% rename from test/CompressedBlockPrefilteringTest.cpp rename to test/PrefilterExpressionIndexTest.cpp index 864906fbce..a941afab6a 100644 --- a/test/CompressedBlockPrefilteringTest.cpp +++ b/test/PrefilterExpressionIndexTest.cpp @@ -6,67 +6,21 @@ #include +#include "./PrefilterExpressionTestHelpers.h" #include "./SparqlExpressionTestHelpers.h" -#include "index/CompressedBlockPrefiltering.h" -#include "util/DateYearDuration.h" #include "util/GTestHelpers.h" -#include "util/IdTestHelpers.h" -namespace { using ad_utility::testing::BlankNodeId; using ad_utility::testing::BoolId; -using ad_utility::testing::DateId; using ad_utility::testing::DoubleId; using ad_utility::testing::IntId; using ad_utility::testing::UndefId; using ad_utility::testing::VocabId; -constexpr auto DateParser = &DateYearOrDuration::parseXsdDate; -using namespace prefilterExpressions; - -namespace makeFilterExpr { -//______________________________________________________________________________ -// Make RelationalExpression -template -auto relExpr = - [](const ValueId& referenceId) -> std::unique_ptr { - return std::make_unique(referenceId); -}; -// Make AndExpression or OrExpression -template -auto logExpr = [](std::unique_ptr child1, - std::unique_ptr child2) - -> std::unique_ptr { - return std::make_unique(std::move(child1), std::move(child2)); -}; - -// Make NotExpression -auto notExpr = [](std::unique_ptr child) - -> std::unique_ptr { - return std::make_unique(std::move(child)); -}; +namespace { -} // namespace makeFilterExpr -//______________________________________________________________________________ -// instantiation relational -// LESS THAN (`<`) -constexpr auto lt = makeFilterExpr::relExpr; -// LESS EQUAL (`<=`) -constexpr auto le = makeFilterExpr::relExpr; -// GREATER EQUAL (`>=`) -constexpr auto ge = makeFilterExpr::relExpr; -// GREATER THAN (`>`) -constexpr auto gt = makeFilterExpr::relExpr; -// EQUAL (`==`) -constexpr auto eq = makeFilterExpr::relExpr; -// NOT EQUAL (`!=`) -constexpr auto neq = makeFilterExpr::relExpr; -// AND (`&&`) -constexpr auto andExpr = makeFilterExpr::logExpr; -// OR (`||`) -constexpr auto orExpr = makeFilterExpr::logExpr; -// NOT (`!`) -constexpr auto notExpr = makeFilterExpr::notExpr; +using namespace prefilterExpressions; +using namespace makeFilterExpression; //______________________________________________________________________________ /* @@ -87,7 +41,7 @@ that is also checked during the pre-filtering procedure. The actual evaluation column (we filter w.r.t. values of COLUMN 0) contains mixed types. */ //______________________________________________________________________________ -class TestPrefilterExprOnBlockMetadata : public ::testing::Test { +class PrefilterExpressionOnMetadataTest : public ::testing::Test { public: const Id referenceDate1 = DateId(DateParser, "1999-11-11"); const Id referenceDate2 = DateId(DateParser, "2005-02-27"); @@ -178,6 +132,12 @@ class TestPrefilterExprOnBlockMetadata : public ::testing::Test { ::testing::HasSubstr(expected)); } + // Assert that the PrefilterExpression tree is properly copied when calling + // method clone. + auto makeTestClone(std::unique_ptr expr) { + ASSERT_EQ(*expr, *expr->clone()); + } + // Check that the provided expression prefilters the correct blocks. auto makeTest(std::unique_ptr expr, std::vector&& expected) { @@ -196,7 +156,7 @@ class TestPrefilterExprOnBlockMetadata : public ::testing::Test { } // namespace //______________________________________________________________________________ -TEST_F(TestPrefilterExprOnBlockMetadata, testBlockFormatForDebugging) { +TEST_F(PrefilterExpressionOnMetadataTest, testBlockFormatForDebugging) { EXPECT_EQ( "#BlockMetadata\n(first) Triple: I:0 V:10 D:33.000000 V:0\n(last) " "Triple: I:0 V:10 D:33.000000 V:0\nnum. rows: 0.\n", @@ -216,7 +176,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testBlockFormatForDebugging) { // Test LessThanExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testLessThanExpressions) { +TEST_F(PrefilterExpressionOnMetadataTest, testLessThanExpressions) { makeTest(lt(IntId(5)), {b5, b6, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18}); makeTest(lt(IntId(-12)), {b18}); @@ -243,7 +203,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testLessThanExpressions) { // Test LessEqualExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testLessEqualExpressions) { +TEST_F(PrefilterExpressionOnMetadataTest, testLessEqualExpressions) { makeTest(le(IntId(0)), {b5, b6, b9, b10, b11, b15, b16, b17, b18}); makeTest(le(IntId(-6)), {b9, b11, b15, b16, b17, b18}); makeTest(le(IntId(7)), @@ -269,7 +229,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testLessEqualExpressions) { // Test GreaterThanExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testGreaterThanExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testGreaterThanExpression) { makeTest(gt(DoubleId(5.5375)), {b7, b8, b11, b14, b18}); makeTest(gt(DoubleId(9.9994)), {b14}); makeTest(gt(IntId(-5)), {b5, b6, b7, b8, b10, b11, b12, b13, b14, b15}); @@ -297,7 +257,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testGreaterThanExpression) { // Test GreaterEqualExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testGreaterEqualExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testGreaterEqualExpression) { makeTest(ge(IntId(0)), {b5, b6, b7, b8, b11, b12, b13, b14}); makeTest(ge(IntId(8)), {b8, b11, b14}); makeTest(ge(DoubleId(9.98)), {b11, b14}); @@ -325,7 +285,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testGreaterEqualExpression) { // Test EqualExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testEqualExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testEqualExpression) { makeTest(eq(IntId(0)), {b4, b5, b6, b11}); makeTest(eq(IntId(5)), {b6, b7, b11, b14}); makeTest(eq(IntId(22)), {}); @@ -353,7 +313,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testEqualExpression) { // Test NotEqualExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testNotEqualExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testNotEqualExpression) { makeTest(neq(DoubleId(0.00)), {b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18}); makeTest(neq(IntId(-4)), @@ -383,7 +343,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testNotEqualExpression) { // Test AndExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testAndExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testAndExpression) { makeTest(andExpr(ge(VocabId(10)), gt(VocabId(10))), {b19, b20, b21, b22}); makeTest(andExpr(ge(VocabId(10)), ge(VocabId(10))), {b19, b20, b21, b22}); makeTest(andExpr(ge(VocabId(12)), gt(VocabId(17))), {b22}); @@ -421,7 +381,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testAndExpression) { // Test OrExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testOrExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testOrExpression) { makeTest(orExpr(lt(VocabId(22)), le(VocabId(0))), {b18, b19, b20, b21}); makeTest(orExpr(le(VocabId(0)), ge(VocabId(16))), {b18, b21, b22}); makeTest(orExpr(gt(VocabId(17)), ge(VocabId(17))), {b21, b22}); @@ -458,7 +418,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testOrExpression) { // Test NotExpression // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testNotExpression) { +TEST_F(PrefilterExpressionOnMetadataTest, testNotExpression) { makeTest(notExpr(eq(VocabId(2))), {b18, b19, b20, b21, b22}); makeTest(notExpr(eq(VocabId(14))), {b18, b19, b21, b22}); makeTest(notExpr(neq(VocabId(14))), {b19, b20, b21}); @@ -505,7 +465,8 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testNotExpression) { // Test PrefilterExpressions mixed // Note: the `makeTest` function automatically adds the blocks with mixed // datatypes to the expected result. -TEST_F(TestPrefilterExprOnBlockMetadata, testGeneralPrefilterExprCombinations) { +TEST_F(PrefilterExpressionOnMetadataTest, + testGeneralPrefilterExprCombinations) { makeTest(andExpr(notExpr(gt(DoubleId(-14.01))), lt(IntId(0))), {b18}); makeTest( orExpr(andExpr(gt(DoubleId(8.25)), le(IntId(10))), eq(DoubleId(-6.25))), @@ -533,7 +494,7 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testGeneralPrefilterExprCombinations) { //______________________________________________________________________________ // Test that correct errors are thrown for invalid input (condition) -TEST_F(TestPrefilterExprOnBlockMetadata, testInputConditionCheck) { +TEST_F(PrefilterExpressionOnMetadataTest, testInputConditionCheck) { makeTestErrorCheck(le(IntId(5)), blocksWithDuplicate1, "The provided data blocks must be unique."); makeTestErrorCheck(andExpr(gt(VocabId(10)), le(VocabId(20))), @@ -558,10 +519,82 @@ TEST_F(TestPrefilterExprOnBlockMetadata, testInputConditionCheck) { //______________________________________________________________________________ // Check for correctness given only one BlockMetadata value is provided. -TEST_F(TestPrefilterExprOnBlockMetadata, testWithOneBlockMetadataValue) { +TEST_F(PrefilterExpressionOnMetadataTest, testWithOneBlockMetadataValue) { auto expr = orExpr(eq(DoubleId(-6.25)), eq(IntId(0))); std::vector input = {b16}; EXPECT_EQ(expr->evaluate(input, 0), input); EXPECT_EQ(expr->evaluate(input, 1), std::vector{}); EXPECT_EQ(expr->evaluate(input, 2), std::vector{}); } + +//______________________________________________________________________________ +// Test method clone. clone() creates a copy of the complete PrefilterExpression +// tree. +TEST_F(PrefilterExpressionOnMetadataTest, testMethodClonePrefilterExpression) { + makeTestClone(lt(VocabId(10))); + makeTestClone(gt(referenceDate2)); + makeTestClone(andExpr(lt(VocabId(20)), gt(VocabId(10)))); + makeTestClone(neq(IntId(10))); + makeTestClone(orExpr(eq(IntId(10)), neq(DoubleId(10)))); + makeTestClone(notExpr(ge(referenceDate1))); + makeTestClone(notExpr(notExpr(neq(VocabId(0))))); + makeTestClone(notExpr(andExpr(eq(IntId(10)), neq(DoubleId(10))))); + makeTestClone(orExpr(orExpr(eq(VocabId(101)), lt(IntId(100))), + andExpr(gt(referenceDate1), lt(referenceDate2)))); + makeTestClone(andExpr(andExpr(neq(IntId(10)), neq(DoubleId(100.23))), + orExpr(gt(DoubleId(0.001)), lt(IntId(250))))); + makeTestClone(orExpr(orExpr(eq(VocabId(101)), lt(IntId(100))), + notExpr(andExpr(lt(VocabId(0)), neq(IntId(100)))))); +} + +//______________________________________________________________________________ +// Test PrefilterExpression equality operator. +TEST_F(PrefilterExpressionOnMetadataTest, testEqualityOperator) { + // Relational PrefilterExpressions + ASSERT_FALSE(*ge(referenceDate1) == *ge(referenceDate2)); + ASSERT_FALSE(*neq(BoolId(true)) == *eq(BoolId(true))); + ASSERT_TRUE(*eq(IntId(1)) == *eq(IntId(1))); + ASSERT_TRUE(*ge(referenceDate1) == *ge(referenceDate1)); + // NotExpression + ASSERT_TRUE(*notExpr(eq(IntId(0))) == *notExpr(eq(IntId(0)))); + ASSERT_TRUE(*notExpr(notExpr(ge(VocabId(0)))) == + *notExpr(notExpr(ge(VocabId(0))))); + ASSERT_FALSE(*notExpr(gt(IntId(0))) == *eq(IntId(0))); + ASSERT_FALSE(*notExpr(andExpr(eq(IntId(1)), eq(IntId(0)))) == + *notExpr(ge(VocabId(0)))); + // Binary PrefilterExpressions (AND and OR) + ASSERT_TRUE(*orExpr(eq(IntId(0)), le(IntId(0))) == + *orExpr(eq(IntId(0)), le(IntId(0)))); + ASSERT_TRUE(*andExpr(le(VocabId(1)), le(IntId(0))) == + *andExpr(le(VocabId(1)), le(IntId(0)))); + ASSERT_FALSE(*orExpr(eq(IntId(0)), le(IntId(0))) == + *andExpr(le(VocabId(1)), le(IntId(0)))); + ASSERT_FALSE(*notExpr(orExpr(eq(IntId(0)), le(IntId(0)))) == + *orExpr(eq(IntId(0)), le(IntId(0)))); +} + +//______________________________________________________________________________ +// Test PrefilterExpression content formatting for debugging. +TEST(PrefilterExpressionExpressionOnMetadataTest, + checkPrintFormattedPrefilterExpression) { + auto expr = lt(IntId(10)); + EXPECT_EQ((std::stringstream() << *expr).str(), + "Prefilter RelationalExpression\nValueId: I:10\n.\n"); + expr = neq(DoubleId(8.21)); + EXPECT_EQ((std::stringstream() << *expr).str(), + "Prefilter RelationalExpression\nValueId: D:8.210000\n.\n"); + expr = notExpr(eq(VocabId(0))); + EXPECT_EQ((std::stringstream() << *expr).str(), + "Prefilter NotExpression:\nchild {Prefilter " + "RelationalExpression\nValueId: V:0\n}\n.\n"); + expr = orExpr(le(IntId(0)), ge(IntId(5))); + EXPECT_EQ((std::stringstream() << *expr).str(), + "Prefilter LogicalExpression\nchild1 {Prefilter " + "RelationalExpression\nValueId: I:0\n}child2 {Prefilter " + "RelationalExpression=)>\nValueId: I:5\n}\n.\n"); + expr = andExpr(lt(IntId(20)), gt(IntId(10))); + EXPECT_EQ((std::stringstream() << *expr).str(), + "Prefilter LogicalExpression\nchild1 {Prefilter " + "RelationalExpression\nValueId: I:20\n}child2 {Prefilter " + "RelationalExpression)>\nValueId: I:10\n}\n.\n"); +} diff --git a/test/PrefilterExpressionTestHelpers.h b/test/PrefilterExpressionTestHelpers.h new file mode 100644 index 0000000000..d148e8dc57 --- /dev/null +++ b/test/PrefilterExpressionTestHelpers.h @@ -0,0 +1,70 @@ +// Copyright 2024, University of Freiburg, +// Chair of Algorithms and Data Structures +// Author: Hannes Baumann + +#pragma once + +#include + +#include "./engine/sparqlExpressions/LiteralExpression.h" +#include "./engine/sparqlExpressions/NaryExpression.h" +#include "./engine/sparqlExpressions/PrefilterExpressionIndex.h" +#include "./engine/sparqlExpressions/RelationalExpressions.h" +#include "./engine/sparqlExpressions/SparqlExpression.h" +#include "util/DateYearDuration.h" +#include "util/IdTestHelpers.h" + +using ad_utility::testing::DateId; + +constexpr auto DateParser = &DateYearOrDuration::parseXsdDate; + +namespace makeFilterExpression { +using namespace prefilterExpressions; + +namespace { +//______________________________________________________________________________ +// Make RelationalExpression +template +auto relExpr = + [](const ValueId& referenceId) -> std::unique_ptr { + return std::make_unique(referenceId); +}; + +// Make AndExpression or OrExpression +template +auto logExpr = [](std::unique_ptr child1, + std::unique_ptr child2) + -> std::unique_ptr { + return std::make_unique(std::move(child1), std::move(child2)); +}; + +// Make NotExpression +auto notPrefilterExpression = [](std::unique_ptr child) + -> std::unique_ptr { + return std::make_unique(std::move(child)); +}; +} // namespace + +// Make PrefilterExpression +//______________________________________________________________________________ +// instantiation relational +// LESS THAN (`<`) +constexpr auto lt = relExpr; +// LESS EQUAL (`<=`) +constexpr auto le = relExpr; +// GREATER EQUAL (`>=`) +constexpr auto ge = relExpr; +// GREATER THAN (`>`) +constexpr auto gt = relExpr; +// EQUAL (`==`) +constexpr auto eq = relExpr; +// NOT EQUAL (`!=`) +constexpr auto neq = relExpr; +// AND (`&&`) +constexpr auto andExpr = logExpr; +// OR (`||`) +constexpr auto orExpr = logExpr; +// NOT (`!`) +constexpr auto notExpr = notPrefilterExpression; + +} // namespace makeFilterExpression