diff --git a/src/parser/SparqlParser.cpp b/src/parser/SparqlParser.cpp index c8be808b3a..17877adf33 100644 --- a/src/parser/SparqlParser.cpp +++ b/src/parser/SparqlParser.cpp @@ -9,15 +9,17 @@ using AntlrParser = SparqlAutomaticParser; // _____________________________________________________________________________ -ParsedQuery SparqlParser::parseQuery(std::string query) { +ParsedQuery SparqlParser::parseQuery( + std::string query, const std::vector& datasets) { // The second argument is the `PrefixMap` for QLever's internal IRIs. using S = std::string; sparqlParserHelpers::ParserAndVisitor p{ std::move(query), - {{S{QLEVER_INTERNAL_PREFIX_NAME}, S{QLEVER_INTERNAL_PREFIX_IRI}}}}; - // Note: `AntlrParser::query` is a method of `AntlrParser` (which is an alias - // for `SparqlAutomaticParser`) that returns the `QueryContext*` for the whole - // query. + {{S{QLEVER_INTERNAL_PREFIX_NAME}, S{QLEVER_INTERNAL_PREFIX_IRI}}}, + parsedQuery::DatasetClauses::fromClauses(datasets)}; + // Note: `AntlrParser::queryOrUpdate` is a method of `AntlrParser` (which is + // an alias for `SparqlAutomaticParser`) that returns the + // `QueryOrUpdateContext*` for the whole query or update. auto resultOfParseAndRemainingText = p.parseTypesafe(&AntlrParser::queryOrUpdate); // The query rule ends with so the parse always has to consume the whole @@ -26,16 +28,3 @@ ParsedQuery SparqlParser::parseQuery(std::string query) { AD_CONTRACT_CHECK(resultOfParseAndRemainingText.remainingText_.empty()); return std::move(resultOfParseAndRemainingText.resultOfParse_); } - -// _____________________________________________________________________________ -ParsedQuery SparqlParser::parseQuery( - std::string operation, const std::vector& datasets) { - auto parsedOperation = parseQuery(std::move(operation)); - // SPARQL Protocol 2.1.4 specifies that the dataset from the query - // parameters overrides the dataset from the query itself. - if (!datasets.empty()) { - parsedOperation.datasetClauses_ = - parsedQuery::DatasetClauses::fromClauses(datasets); - } - return parsedOperation; -} diff --git a/src/parser/SparqlParser.h b/src/parser/SparqlParser.h index b713a7a3bc..4eaa10f06a 100644 --- a/src/parser/SparqlParser.h +++ b/src/parser/SparqlParser.h @@ -13,8 +13,6 @@ // message is given. class SparqlParser { public: - static ParsedQuery parseQuery(std::string query); - // A convenience function for parsing the query and setting the datasets. - static ParsedQuery parseQuery(std::string operation, - const std::vector& datasets); + static ParsedQuery parseQuery( + std::string operation, const std::vector& datasets = {}); }; diff --git a/src/parser/SparqlParserHelpers.cpp b/src/parser/SparqlParserHelpers.cpp index 541725ec39..e7f26e583f 100644 --- a/src/parser/SparqlParserHelpers.cpp +++ b/src/parser/SparqlParserHelpers.cpp @@ -16,10 +16,10 @@ using std::string; // _____________________________________________________________________________ ParserAndVisitor::ParserAndVisitor( - std::string input, + std::string input, ParsedQuery::DatasetClauses datasetClauses, SparqlQleverVisitor::DisableSomeChecksOnlyForTesting disableSomeChecks) : input_{unescapeUnicodeSequences(std::move(input))}, - visitor_{{}, disableSomeChecks} { + visitor_{{}, std::move(datasetClauses), disableSomeChecks} { // The default in ANTLR is to log all errors to the console and to continue // the parsing. We need to turn parse errors into exceptions instead to // propagate them to the user. @@ -32,8 +32,10 @@ ParserAndVisitor::ParserAndVisitor( // _____________________________________________________________________________ ParserAndVisitor::ParserAndVisitor( std::string input, SparqlQleverVisitor::PrefixMap prefixes, + ParsedQuery::DatasetClauses datasetClauses, SparqlQleverVisitor::DisableSomeChecksOnlyForTesting disableSomeChecks) - : ParserAndVisitor{std::move(input), disableSomeChecks} { + : ParserAndVisitor{std::move(input), std::move(datasetClauses), + disableSomeChecks} { visitor_.setPrefixMapManually(std::move(prefixes)); } diff --git a/src/parser/SparqlParserHelpers.h b/src/parser/SparqlParserHelpers.h index 55d597d0eb..3549ff09c6 100644 --- a/src/parser/SparqlParserHelpers.h +++ b/src/parser/SparqlParserHelpers.h @@ -43,11 +43,12 @@ struct ParserAndVisitor { SparqlAutomaticParser parser_{&tokens_}; SparqlQleverVisitor visitor_; explicit ParserAndVisitor( - string input, + string input, ParsedQuery::DatasetClauses datasetClauses = {}, SparqlQleverVisitor::DisableSomeChecksOnlyForTesting disableSomeChecks = SparqlQleverVisitor::DisableSomeChecksOnlyForTesting::False); ParserAndVisitor( string input, SparqlQleverVisitor::PrefixMap prefixes, + ParsedQuery::DatasetClauses datasetClauses = {}, SparqlQleverVisitor::DisableSomeChecksOnlyForTesting disableSomeChecks = SparqlQleverVisitor::DisableSomeChecksOnlyForTesting::False); diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.cpp b/src/parser/sparqlParser/SparqlQleverVisitor.cpp index 04ebc7bb73..0c4ee6cae2 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.cpp +++ b/src/parser/sparqlParser/SparqlQleverVisitor.cpp @@ -318,12 +318,23 @@ parsedQuery::BasicGraphPattern Visitor::toGraphPattern( return pattern; } +// ____________________________________________________________________________________ +parsedQuery::DatasetClauses SparqlQleverVisitor::setAndGetDatasetClauses( + const std::vector& clauses) { + // TODO: is it a good idea to do this implicitly (the fields are nullopt) or + // should this be done explicitly (with a bool flag)? + if (!activeDatasetClauses_.defaultGraphs_.has_value() && + !activeDatasetClauses_.namedGraphs_.has_value()) { + activeDatasetClauses_ = parsedQuery::DatasetClauses::fromClauses(clauses); + } + return activeDatasetClauses_; +} + // ____________________________________________________________________________________ ParsedQuery Visitor::visit(Parser::ConstructQueryContext* ctx) { ParsedQuery query; - query.datasetClauses_ = parsedQuery::DatasetClauses::fromClauses( - visitVector(ctx->datasetClause())); - activeDatasetClauses_ = query.datasetClauses_; + query.datasetClauses_ = + setAndGetDatasetClauses(visitVector(ctx->datasetClause())); if (ctx->constructTemplate()) { query._clause = visit(ctx->constructTemplate()) .value_or(parsedQuery::ConstructClause{}); @@ -367,9 +378,8 @@ ParsedQuery Visitor::visit(Parser::DescribeQueryContext* ctx) { } // Parse the FROM and FROM NAMED clauses. - activeDatasetClauses_ = parsedQuery::DatasetClauses::fromClauses( - visitVector(ctx->datasetClause())); - describeClause.datasetClauses_ = activeDatasetClauses_; + describeClause.datasetClauses_ = + setAndGetDatasetClauses(visitVector(ctx->datasetClause())); // Parse the WHERE clause and construct a SELECT query from it. For `DESCRIBE // *`, add each visible variable as a resource to describe. @@ -414,9 +424,8 @@ ParsedQuery Visitor::visit(Parser::DescribeQueryContext* ctx) { // ____________________________________________________________________________________ ParsedQuery Visitor::visit(Parser::AskQueryContext* ctx) { parsedQuery_._clause = ParsedQuery::AskClause{}; - parsedQuery_.datasetClauses_ = parsedQuery::DatasetClauses::fromClauses( - visitVector(ctx->datasetClause())); - activeDatasetClauses_ = parsedQuery_.datasetClauses_; + parsedQuery_.datasetClauses_ = + setAndGetDatasetClauses(visitVector(ctx->datasetClause())); visitWhereClause(ctx->whereClause(), parsedQuery_); // NOTE: It can make sense to have solution modifiers with an ASK query, for // example, a GROUP BY with a HAVING. @@ -661,9 +670,9 @@ ParsedQuery Visitor::visit(Parser::ModifyContext* ctx) { } }; AD_CORRECTNESS_CHECK(visibleVariables_.empty()); - auto graphPattern = visit(ctx->groupGraphPattern()); parsedQuery_.datasetClauses_ = - parsedQuery::DatasetClauses::fromClauses(visitVector(ctx->usingClause())); + setAndGetDatasetClauses(visitVector(ctx->usingClause())); + auto graphPattern = visit(ctx->groupGraphPattern()); parsedQuery_._rootGraphPattern = std::move(graphPattern); parsedQuery_.registerVariablesVisibleInQueryBody(visibleVariables_); visibleVariables_.clear(); @@ -1263,9 +1272,8 @@ void Visitor::visit(Parser::PrefixDeclContext* ctx) { // ____________________________________________________________________________________ ParsedQuery Visitor::visit(Parser::SelectQueryContext* ctx) { parsedQuery_._clause = visit(ctx->selectClause()); - parsedQuery_.datasetClauses_ = parsedQuery::DatasetClauses::fromClauses( - visitVector(ctx->datasetClause())); - activeDatasetClauses_ = parsedQuery_.datasetClauses_; + parsedQuery_.datasetClauses_ = + setAndGetDatasetClauses(visitVector(ctx->datasetClause())); visitWhereClause(ctx->whereClause(), parsedQuery_); parsedQuery_.addSolutionModifiers(visit(ctx->solutionModifier())); return parsedQuery_; diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.h b/src/parser/sparqlParser/SparqlQleverVisitor.h index 412f2677f6..d189e4d35f 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.h +++ b/src/parser/sparqlParser/SparqlQleverVisitor.h @@ -109,11 +109,15 @@ class SparqlQleverVisitor { public: SparqlQleverVisitor() = default; + // If `datasetOverride` contains datasets, then the datasets in + // the operation itself are ignored. This is used for the datasets from the + // url parameters which override those in the operation. explicit SparqlQleverVisitor( - PrefixMap prefixMap, + PrefixMap prefixMap, ParsedQuery::DatasetClauses datasetOverride, DisableSomeChecksOnlyForTesting disableSomeChecksOnlyForTesting = DisableSomeChecksOnlyForTesting::False) - : prefixMap_{std::move(prefixMap)}, + : activeDatasetClauses_{std::move(datasetOverride)}, + prefixMap_{std::move(prefixMap)}, disableSomeChecksOnlyForTesting_{disableSomeChecksOnlyForTesting} {} const PrefixMap& prefixMap() const { return prefixMap_; } @@ -629,5 +633,10 @@ class SparqlQleverVisitor { static parsedQuery::BasicGraphPattern toGraphPattern( const ad_utility::sparql_types::Triples& triples); + // Set the datasets state of the visitor if no override is defined. Returns + // the currently active datasets. + parsedQuery::DatasetClauses setAndGetDatasetClauses( + const std::vector& clauses); + FRIEND_TEST(SparqlParser, ensureExceptionOnInvalidGraphTerm); }; diff --git a/test/QueryPlannerTest.cpp b/test/QueryPlannerTest.cpp index cbd9e380be..e1534fff11 100644 --- a/test/QueryPlannerTest.cpp +++ b/test/QueryPlannerTest.cpp @@ -2968,6 +2968,10 @@ TEST(QueryPlanner, Exists) { filter); h::expect("Describe ?x FROM { ?x ?y ?z FILTER EXISTS {?a ?b ?c}}", h::Describe(::testing::_, filter)); + h::expect( + "DELETE { ?x } USING WHERE { ?x ?y ?z FILTER EXISTS {?a ?b " + "?c}}", + filter); // Test the interaction of FROM NAMES with EXISTS auto varG = std::vector{Variable{"?g"}}; diff --git a/test/SparqlAntlrParserTest.cpp b/test/SparqlAntlrParserTest.cpp index d902dacdf4..22075e20df 100644 --- a/test/SparqlAntlrParserTest.cpp +++ b/test/SparqlAntlrParserTest.cpp @@ -55,7 +55,8 @@ auto parse = ParsedQuery::DatasetClauses clauses = {}, SparqlQleverVisitor::DisableSomeChecksOnlyForTesting disableSomeChecks = SparqlQleverVisitor::DisableSomeChecksOnlyForTesting::False) { - ParserAndVisitor p{input, std::move(prefixes), disableSomeChecks}; + // TODO: use dataset clauses directly? + ParserAndVisitor p{input, std::move(prefixes), {}, disableSomeChecks}; p.visitor_.setActiveDatasetClausesForTesting(std::move(clauses)); if (testInsideConstructTemplate) { p.visitor_.setParseModeToInsideConstructTemplateForTesting(); @@ -1485,106 +1486,10 @@ TEST(SparqlParser, Query) { RuntimeParameters().set<"throw-on-unbound-variables">(false); } -// Some helper matchers for the `builtInCall` test below. -namespace builtInCallTestHelpers { -using namespace sparqlExpression; - -// Return a matcher that checks whether a given `SparqlExpression::Ptr` actually -// (via `dynamic_cast`) points to an object of type `Expression`, and that this -// `Expression` matches the `matcher`. -template -auto matchPtr(Matcher matcher = Matcher{}) - -> ::testing::Matcher { - return testing::Pointee( - testing::WhenDynamicCastTo(matcher)); -} - -// Return a matcher that matches a `SparqlExpression::Ptr` that stores a -// `VariableExpression` with the given `variable`. -auto variableExpressionMatcher = [](const Variable& variable) { - return matchPtr( - AD_PROPERTY(VariableExpression, value, testing::Eq(variable))); -}; - -// Return a matcher that matches a `SparqlExpression::Ptr`that stores an -// `Expression` (template argument), the children of which match the -// `childrenMatchers`. -template -auto matchPtrWithChildren(auto&&... childrenMatchers) - -> ::testing::Matcher { - return matchPtr( - AD_PROPERTY(SparqlExpression, childrenForTesting, - testing::ElementsAre(childrenMatchers...))); -} - -// Same as `matchPtrWithChildren` above, but the children are all variables. -template -auto matchPtrWithVariables(const std::same_as auto&... children) - -> ::testing::Matcher { - return matchPtrWithChildren( - variableExpressionMatcher(children)...); -} - -// Return a matcher that checks whether a given `SparqlExpression::Ptr` points -// (via `dynamic_cast`) to an object of the same type that a call to the -// `makeFunction` yields. The matcher also checks that the expression's children -// match the `childrenMatchers`. -auto matchNaryWithChildrenMatchers(auto makeFunction, - auto&&... childrenMatchers) - -> ::testing::Matcher { - using namespace sparqlExpression; - auto typeIdLambda = [](const auto& ptr) { - return std::type_index{typeid(*ptr)}; - }; - - [[maybe_unused]] auto makeDummyChild = [](auto&&) -> SparqlExpression::Ptr { - return std::make_unique(Variable{"?x"}); - }; - auto expectedTypeIndex = - typeIdLambda(makeFunction(makeDummyChild(childrenMatchers)...)); - ::testing::Matcher typeIdMatcher = - ::testing::ResultOf(typeIdLambda, ::testing::Eq(expectedTypeIndex)); - return ::testing::AllOf(typeIdMatcher, - ::testing::Pointee(AD_PROPERTY( - SparqlExpression, childrenForTesting, - ::testing::ElementsAre(childrenMatchers...)))); -} - -auto idExpressionMatcher = [](Id id) { - return matchPtr( - AD_PROPERTY(IdExpression, value, testing::Eq(id))); -}; - -// Return a matcher that checks whether a given `SparqlExpression::Ptr` points -// (via `dynamic_cast`) to an object of the same type that a call to the -// `makeFunction` yields. The matcher also checks that the expression's children -// are the `variables`. -auto matchNary( - auto makeFunction, - QL_CONCEPT_OR_NOTHING(ad_utility::SimilarTo) auto&&... variables) - -> ::testing::Matcher { - using namespace sparqlExpression; - return matchNaryWithChildrenMatchers(makeFunction, - variableExpressionMatcher(variables)...); -} -auto matchUnary(auto makeFunction) - -> ::testing::Matcher { - return matchNary(makeFunction, Variable{"?x"}); -} - -template -auto matchLiteralExpression(const T& value) - -> ::testing::Matcher { - using Expr = sparqlExpression::detail::LiteralExpression; - return ::testing::Pointee(::testing::WhenDynamicCastTo( - AD_PROPERTY(Expr, value, ::testing::Eq(value)))); -} -} // namespace builtInCallTestHelpers - // ___________________________________________________________________________ TEST(SparqlParser, primaryExpression) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; auto expectPrimaryExpression = ExpectCompleteParse<&Parser::primaryExpression>{}; auto expectFails = ExpectParseFails<&Parser::primaryExpression>{}; @@ -1598,7 +1503,7 @@ TEST(SparqlParser, primaryExpression) { // ___________________________________________________________________________ TEST(SparqlParser, builtInCall) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; auto expectBuiltInCall = ExpectCompleteParse<&Parser::builtInCall>{}; auto expectFails = ExpectParseFails<&Parser::builtInCall>{}; expectBuiltInCall("StrLEN(?x)", matchUnary(&makeStrlenExpression)); @@ -1700,7 +1605,7 @@ TEST(SparqlParser, builtInCall) { TEST(SparqlParser, unaryExpression) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; auto expectUnary = ExpectCompleteParse<&Parser::unaryExpression>{}; expectUnary("-?x", matchUnary(&makeUnaryMinusExpression)); @@ -1709,7 +1614,7 @@ TEST(SparqlParser, unaryExpression) { TEST(SparqlParser, multiplicativeExpression) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; Variable x{"?x"}; Variable y{"?y"}; Variable z{"?z"}; @@ -1734,7 +1639,7 @@ TEST(SparqlParser, relationalExpression) { Variable y{"?y"}; Variable z{"?z"}; using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; auto expectRelational = ExpectCompleteParse<&Parser::relationalExpression>{}; expectRelational("?x IN (?y, ?z)", matchPtrWithVariables(x, y, z)); @@ -1759,7 +1664,7 @@ matchOperatorAndExpression( TEST(SparqlParser, multiplicativeExpressionLeadingSignButNoSpaceContext) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; Variable x{"?x"}; Variable y{"?y"}; Variable z{"?z"}; @@ -1815,7 +1720,7 @@ TEST(SparqlParser, multiplicativeExpressionLeadingSignButNoSpaceContext) { TEST(SparqlParser, FunctionCall) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; auto expectFunctionCall = ExpectCompleteParse<&Parser::functionCall>{}; auto expectFunctionCallFails = ExpectParseFails<&Parser::functionCall>{}; // These prefixes are currently stored without the leading "<", so we have to @@ -1890,7 +1795,7 @@ TEST(SparqlParser, FunctionCall) { // ______________________________________________________________________________ TEST(SparqlParser, substringExpression) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; using V = Variable; auto expectBuiltInCall = ExpectCompleteParse<&Parser::builtInCall>{}; auto expectBuiltInCallFails = ExpectParseFails<&Parser::builtInCall>{}; @@ -1915,7 +1820,7 @@ TEST(SparqlParser, substringExpression) { // _________________________________________________________ TEST(SparqlParser, binaryStringExpressions) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; using V = Variable; auto expectBuiltInCall = ExpectCompleteParse<&Parser::builtInCall>{}; auto expectBuiltInCallFails = ExpectParseFails<&Parser::builtInCall>{}; @@ -1931,26 +1836,8 @@ TEST(SparqlParser, binaryStringExpressions) { expectBuiltInCall("STRBEFORE(?x, ?y)", makeMatcher(&makeStrBeforeExpression)); } -// Matchers for EXISTS and NOT EXISTS functions. -namespace existsTestHelpers { -using namespace sparqlExpression; -using namespace ::testing; - -// Match an EXISTS function -auto existsMatcher(Matcher pattern) { - return Pointee(WhenDynamicCastTo( - AD_PROPERTY(ExistsExpression, argument, pattern))); -} -// Match a NOT EXISTS function -auto notExistsMatcher(Matcher pattern) { - return builtInCallTestHelpers::matchNaryWithChildrenMatchers( - &makeUnaryNegateExpression, existsMatcher(pattern)); -} -} // namespace existsTestHelpers - // _____________________________________________________________________________ TEST(SparqlParser, Exists) { - using namespace existsTestHelpers; auto expectBuiltInCall = ExpectCompleteParse<&Parser::builtInCall>{}; // A matcher that matches the query `SELECT * { ?x ?foo }`, where the @@ -1965,9 +1852,9 @@ TEST(SparqlParser, Exists) { }; expectBuiltInCall("EXISTS {?a ?foo}", - existsMatcher(selectABarFooMatcher())); + m::Exists(selectABarFooMatcher())); expectBuiltInCall("NOT EXISTS {?a ?foo}", - notExistsMatcher(selectABarFooMatcher())); + m::NotExists(selectABarFooMatcher())); Graphs defaultGraphs{ad_utility::HashSet{iri("")}}; Graphs namedGraphs{ad_utility::HashSet{iri("")}}; @@ -1979,17 +1866,16 @@ TEST(SparqlParser, Exists) { datasetClauses.namedGraphs_ = namedGraphs; datasetClauses.defaultGraphs_.value().insert(iri("")); expectBuiltInCall("EXISTS {?a ?foo}", - existsMatcher(selectABarFooMatcher())); + m::Exists(selectABarFooMatcher())); expectBuiltInCall("NOT EXISTS {?a ?foo}", - notExistsMatcher(selectABarFooMatcher())); + m::NotExists(selectABarFooMatcher())); - expectBuiltInCall( - "EXISTS {?a ?foo}", - existsMatcher(selectABarFooMatcher(defaultGraphs, namedGraphs)), - datasetClauses); + expectBuiltInCall("EXISTS {?a ?foo}", + m::Exists(selectABarFooMatcher(defaultGraphs, namedGraphs)), + datasetClauses); expectBuiltInCall( "NOT EXISTS {?a ?foo}", - notExistsMatcher(selectABarFooMatcher(defaultGraphs, namedGraphs)), + m::NotExists(selectABarFooMatcher(defaultGraphs, namedGraphs)), datasetClauses); } @@ -2004,7 +1890,7 @@ template ::testing::Matcher matchAggregate( bool distinct, const Variable& child, const auto&... additionalMatchers) { using namespace ::testing; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; using Exp = SparqlExpression; auto innerMatcher = [&]() -> Matcher { @@ -2030,7 +1916,7 @@ template ::testing::Matcher matchAggregateWithoutChild( bool distinct) { using namespace ::testing; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; using Exp = SparqlExpression; using enum SparqlExpression::AggregateStatus; @@ -2043,7 +1929,7 @@ ::testing::Matcher matchAggregateWithoutChild( // ___________________________________________________________ TEST(SparqlParser, aggregateExpressions) { using namespace sparqlExpression; - using namespace builtInCallTestHelpers; + using namespace m::builtInCall; using namespace aggregateTestHelpers; using V = Variable; auto expectAggregate = ExpectCompleteParse<&Parser::aggregate>{}; @@ -2419,3 +2305,49 @@ TEST(SparqlParser, SourceSelector) { auto expectDefaultGraph = ExpectCompleteParse<&Parser::defaultGraphClause>{}; expectDefaultGraph("", m::TripleComponentIri("")); } + +TEST(SparqlParser, Datasets) { + auto expectUpdate = ExpectCompleteParse<&Parser::update>{defaultPrefixMap}; + auto expectQuery = ExpectCompleteParse<&Parser::query>{defaultPrefixMap}; + auto expectAsk = ExpectCompleteParse<&Parser::askQuery>{defaultPrefixMap}; + auto expectConstruct = + ExpectCompleteParse<&Parser::constructQuery>{defaultPrefixMap}; + auto expectDescribe = + ExpectCompleteParse<&Parser::describeQuery>{defaultPrefixMap}; + auto Iri = [](std::string_view stringWithBrackets) { + return TripleComponent::Iri::fromIriref(stringWithBrackets); + }; + auto noGraph = std::monostate{}; + ScanSpecificationAsTripleComponent::Graphs datasets{{Iri("")}}; + // Only checks `_filters` on the GraphPattern. We are not concerned with the + // `_graphPatterns` here. + auto filterGraphPattern = m::Filters(m::ExistsFilter( + m::GraphPattern(m::Triples({{Var("?a"), "?b", Var("?c")}})), datasets)); + // Check that datasets are propagated correctly into the different types of + // operations. + expectUpdate( + "DELETE { ?x } USING WHERE { ?x ?y ?z FILTER EXISTS {?a ?b " + "?c} }", + m::UpdateClause( + m::GraphUpdate({{Var("?x"), Iri(""), Iri(""), noGraph}}, {}, + std::nullopt), + filterGraphPattern, m::datasetClausesMatcher(datasets))); + expectQuery( + "SELECT * FROM WHERE { ?x ?y ?z FILTER EXISTS {?a ?b ?c} }", + m::SelectQuery(m::AsteriskSelect(), filterGraphPattern, datasets)); + expectAsk("ASK FROM { ?x ?y ?z FILTER EXISTS {?a ?b ?c}}", + m::AskQuery(filterGraphPattern, datasets)); + expectConstruct( + "CONSTRUCT { } FROM { ?x ?y ?z FILTER EXISTS {?a ?b?c}}", + m::ConstructQuery( + {std::array{::Iri(""), ::Iri(""), ::Iri("")}}, + filterGraphPattern, datasets)); + // See comment in visit function for `DescribeQueryContext`. + expectDescribe( + "Describe ?x FROM { ?x ?y ?z FILTER EXISTS {?a ?b ?c}}", + m::DescribeQuery( + m::Describe({Var("?x")}, {datasets, {}}, + m::SelectQuery(m::VariablesSelect({"?x"}, false, false), + filterGraphPattern, datasets)), + datasets)); +} diff --git a/test/SparqlAntlrParserTestHelpers.h b/test/SparqlAntlrParserTestHelpers.h index b898659acc..3e48137262 100644 --- a/test/SparqlAntlrParserTestHelpers.h +++ b/test/SparqlAntlrParserTestHelpers.h @@ -10,8 +10,10 @@ #include #include +#include #include +#include "engine/sparqlExpressions/ExistsExpression.h" #include "engine/sparqlExpressions/SparqlExpressionPimpl.h" #include "parser/Alias.h" #include "parser/DatasetClauses.h" @@ -677,21 +679,6 @@ inline auto Triples = [](const vector& triples) testing::UnorderedElementsAreArray(triples))); }; -// Match a `Describe` clause. -inline auto Describe = [](const std::vector& resources, - const p::DatasetClauses& datasetClauses, - const Matcher& subquery) - -> Matcher { - using namespace ::testing; - auto getSubquery = [](const p::Subquery& subquery) -> const ParsedQuery& { - return subquery.get(); - }; - return detail::GraphPatternOperation(AllOf( - AD_FIELD(p::Describe, resources_, Eq(resources)), - AD_FIELD(p::Describe, datasetClauses_, Eq(datasetClauses)), - AD_FIELD(p::Describe, whereClause_, ResultOf(getSubquery, subquery)))); -}; - namespace detail { inline auto Optional = [](auto&& subMatcher) -> Matcher { @@ -910,17 +897,31 @@ inline auto ConstructQuery( // A matcher for a `DescribeQuery` inline auto DescribeQuery( - const Matcher& describeMatcher, + const Matcher& describeMatcher, const ScanSpecificationAsTripleComponent::Graphs& defaultGraphs = std::nullopt, const ScanSpecificationAsTripleComponent::Graphs& namedGraphs = std::nullopt) { using Var = ::Variable; return ConstructQuery({{Var{"?subject"}, Var{"?predicate"}, Var{"?object"}}}, - GraphPattern(describeMatcher), defaultGraphs, - namedGraphs); + describeMatcher, defaultGraphs, namedGraphs); } +// Match a `Describe` clause. +inline auto Describe = [](const std::vector& resources, + const p::DatasetClauses& datasetClauses, + const Matcher& subquery) + -> Matcher { + using namespace ::testing; + auto getSubquery = [](const p::Subquery& subquery) -> const ParsedQuery& { + return subquery.get(); + }; + return GraphPattern(detail::GraphPatternOperation(AllOf( + AD_FIELD(p::Describe, resources_, Eq(resources)), + AD_FIELD(p::Describe, datasetClauses_, Eq(datasetClauses)), + AD_FIELD(p::Describe, whereClause_, ResultOf(getSubquery, subquery))))); +}; + // _____________________________________________________________________________ inline auto VisibleVariables = [](const std::vector<::Variable>& elems) -> Matcher { @@ -1041,4 +1042,131 @@ inline auto Quad = [](const TripleComponent& s, const TripleComponent& p, AD_FIELD(SparqlTripleSimpleWithGraph, g_, testing::Eq(g))); }; +// Some helper matchers for testing SparqlExpressions +namespace builtInCall { +using namespace sparqlExpression; + +// Return a matcher that checks whether a given `SparqlExpression::Ptr` actually +// (via `dynamic_cast`) points to an object of type `Expression`, and that this +// `Expression` matches the `matcher`. +template +auto matchPtr(Matcher matcher = Matcher{}) + -> testing::Matcher { + return testing::Pointee( + testing::WhenDynamicCastTo(matcher)); +} + +// Return a matcher that matches a `SparqlExpression::Ptr` that stores a +// `VariableExpression` with the given `variable`. +inline auto variableExpressionMatcher = [](const ::Variable& variable) { + return matchPtr( + AD_PROPERTY(VariableExpression, value, testing::Eq(variable))); +}; + +// Return a matcher that matches a `SparqlExpression::Ptr`that stores an +// `Expression` (template argument), the children of which match the +// `childrenMatchers`. +template +auto matchPtrWithChildren(auto&&... childrenMatchers) + -> Matcher { + return matchPtr( + AD_PROPERTY(SparqlExpression, childrenForTesting, + testing::ElementsAre(childrenMatchers...))); +} + +// Same as `matchPtrWithChildren` above, but the children are all variables. +template +auto matchPtrWithVariables(const std::same_as<::Variable> auto&... children) + -> Matcher { + return matchPtrWithChildren( + variableExpressionMatcher(children)...); +} + +// Return a matcher that checks whether a given `SparqlExpression::Ptr` points +// (via `dynamic_cast`) to an object of the same type that a call to the +// `makeFunction` yields. The matcher also checks that the expression's children +// match the `childrenMatchers`. +auto matchNaryWithChildrenMatchers(auto makeFunction, + auto&&... childrenMatchers) + -> Matcher { + using namespace sparqlExpression; + auto typeIdLambda = [](const auto& ptr) { + return std::type_index{typeid(*ptr)}; + }; + + [[maybe_unused]] auto makeDummyChild = [](auto&&) -> SparqlExpression::Ptr { + return std::make_unique(::Variable{"?x"}); + }; + auto expectedTypeIndex = + typeIdLambda(makeFunction(makeDummyChild(childrenMatchers)...)); + Matcher typeIdMatcher = + ::testing::ResultOf(typeIdLambda, ::testing::Eq(expectedTypeIndex)); + return ::testing::AllOf(typeIdMatcher, + ::testing::Pointee(AD_PROPERTY( + SparqlExpression, childrenForTesting, + ::testing::ElementsAre(childrenMatchers...)))); +} + +inline auto idExpressionMatcher = [](Id id) { + return matchPtr( + AD_PROPERTY(IdExpression, value, testing::Eq(id))); +}; + +// Return a matcher that checks whether a given `SparqlExpression::Ptr` points +// (via `dynamic_cast`) to an object of the same type that a call to the +// `makeFunction` yields. The matcher also checks that the expression's children +// are the `variables`. +auto matchNary(auto makeFunction, + QL_CONCEPT_OR_NOTHING( + ad_utility::SimilarTo<::Variable>) auto&&... variables) + -> Matcher { + using namespace sparqlExpression; + return matchNaryWithChildrenMatchers(makeFunction, + variableExpressionMatcher(variables)...); +} +auto matchUnary(auto makeFunction) -> Matcher { + return matchNary(makeFunction, ::Variable{"?x"}); +} + +template +auto matchLiteralExpression(const T& value) + -> Matcher { + using Expr = sparqlExpression::detail::LiteralExpression; + return testing::Pointee(testing::WhenDynamicCastTo( + AD_PROPERTY(Expr, value, testing::Eq(value)))); +} +} // namespace builtInCall + +// Match an EXISTS function +inline auto Exists(Matcher pattern) { + return testing::Pointee( + testing::WhenDynamicCastTo( + AD_PROPERTY(sparqlExpression::ExistsExpression, argument, pattern))); +} + +// Match a NOT EXISTS function +inline auto NotExists(Matcher pattern) { + return builtInCall::matchNaryWithChildrenMatchers( + &sparqlExpression::makeUnaryNegateExpression, Exists(pattern)); +} + +// Check that the given filters are set on the graph pattern. +inline auto Filters = [](const auto&... filterMatchers) + -> Matcher { + return AD_FIELD(ParsedQuery::GraphPattern, _filters, + testing::ElementsAre(filterMatchers...)); +}; + +// Matcher for `FILTER EXISTS` filters. +inline auto ExistsFilter = + [](const Matcher& m, + ScanSpecificationAsTripleComponent::Graphs defaultGraphs = std::nullopt, + ScanSpecificationAsTripleComponent::Graphs namedGraphs = + std::nullopt) -> Matcher { + return AD_FIELD(SparqlFilter, expression_, + AD_PROPERTY(sparqlExpression::SparqlExpressionPimpl, getPimpl, + Exists(SelectQuery(AsteriskSelect(), m, + defaultGraphs, namedGraphs)))); +}; + } // namespace matchers diff --git a/test/SparqlParserTest.cpp b/test/SparqlParserTest.cpp index d19fc8d196..a844e45f15 100644 --- a/test/SparqlParserTest.cpp +++ b/test/SparqlParserTest.cpp @@ -1386,6 +1386,10 @@ TEST(ParserTest, BaseDeclaration) { } TEST(ParserTest, parseWithDatasets) { + // This test tests the correct behaviour and propagation of override datasets + // (datasets passed as URL parameters overwrite all datasets in the + // operation). `SparqlParser::Datasets` tests that datasets set in the + // operation are propagated correctly. auto Iri = ad_utility::triple_component::Iri::fromIriref; auto query = "SELECT * WHERE { ?s ?p ?o }"; auto queryGraphPatternMatcher = @@ -1406,4 +1410,39 @@ TEST(ParserTest, parseWithDatasets) { DatasetClause{Iri(""), false}}), m::SelectQuery(m::AsteriskSelect(), queryGraphPatternMatcher, {{Iri(""), Iri("")}}, {{Iri("")}})); + ScanSpecificationAsTripleComponent::Graphs datasets{{Iri("")}}; + auto filterGraphPattern = m::Filters(m::ExistsFilter( + m::GraphPattern(m::Triples({{Var("?a"), "?b", Var("?c")}})), datasets)); + EXPECT_THAT( + SparqlParser::parseQuery("DELETE { ?x } USING WHERE { ?x ?y " + "?z FILTER EXISTS {?a ?b ?c} }", + {{Iri(""), false}}), + m::UpdateClause(m::GraphUpdate({{Var("?x"), Iri(""), Iri(""), + std::monostate{}}}, + {}, std::nullopt), + filterGraphPattern, m::datasetClausesMatcher(datasets))); + EXPECT_THAT( + SparqlParser::parseQuery( + "SELECT * FROM WHERE { ?x ?y ?z FILTER EXISTS {?a ?b ?c} }", + {{Iri(""), false}}), + m::SelectQuery(m::AsteriskSelect(), filterGraphPattern, datasets)); + EXPECT_THAT(SparqlParser::parseQuery( + "ASK FROM { ?x ?y ?z FILTER EXISTS {?a ?b ?c}}", + {{Iri(""), false}}), + m::AskQuery(filterGraphPattern, datasets)); + EXPECT_THAT(SparqlParser::parseQuery("CONSTRUCT { } FROM { ?x " + "?y ?z FILTER EXISTS {?a ?b?c}}", + {{Iri(""), false}}), + m::ConstructQuery({std::array{ + ::Iri(""), ::Iri(""), ::Iri("")}}, + filterGraphPattern, datasets)); + EXPECT_THAT( + SparqlParser::parseQuery( + "Describe ?x FROM { ?x ?y ?z FILTER EXISTS {?a ?b ?c}}", + {{Iri(""), false}}), + m::DescribeQuery( + m::Describe({Var("?x")}, {datasets, {}}, + m::SelectQuery(m::VariablesSelect({"?x"}, false, false), + filterGraphPattern, datasets)), + datasets)); }