diff --git a/src/engine/CountConnectedSubgraphs.cpp b/src/engine/CountConnectedSubgraphs.cpp index 529b5ac463..effd22adf8 100644 --- a/src/engine/CountConnectedSubgraphs.cpp +++ b/src/engine/CountConnectedSubgraphs.cpp @@ -67,7 +67,7 @@ static uint64_t subsetIndexToBitmap(size_t i, // `13` (`1101` as bits) will be converted to `[0, 2, 3]`; static std::vector bitsetToVector(uint64_t bitset) { std::vector result; - for (uint64_t i = 0; i < 64; ++i) { + for (uint8_t i = 0; i < 64; ++i) { if (bitset & (1ULL << i)) { result.push_back(i); } diff --git a/src/engine/QueryPlanner.cpp b/src/engine/QueryPlanner.cpp index f77998b44b..c54f3be174 100644 --- a/src/engine/QueryPlanner.cpp +++ b/src/engine/QueryPlanner.cpp @@ -1254,12 +1254,13 @@ void QueryPlanner::applyTextLimitsIfPossible( size_t QueryPlanner::findUniqueNodeIds( const std::vector& connectedComponent) { ad_utility::HashSet uniqueNodeIds; - // TODO Assert that all the node IDs only consist of a single - // element. Or even better: Do a bitwise or and then count the number of set - // bits... - std::ranges::copy(connectedComponent | std::views::transform( - &SubtreePlan::_idsOfIncludedNodes), - std::inserter(uniqueNodeIds, uniqueNodeIds.end())); + auto nodeIds = connectedComponent | + std::views::transform(&SubtreePlan::_idsOfIncludedNodes); + // Check that all the `_idsOfIncludedNodes` are one-hot encodings of a single + // value, i.e. they have exactly one bit set. + AD_CORRECTNESS_CHECK(std::ranges::all_of( + nodeIds, [](auto nodeId) { return std::popcount(nodeId) == 1; })); + std::ranges::copy(nodeIds, std::inserter(uniqueNodeIds, uniqueNodeIds.end())); return uniqueNodeIds.size(); } @@ -1300,19 +1301,18 @@ QueryPlanner::runDynamicProgrammingOnConnectedComponent( size_t QueryPlanner::countSubgraphs( std::vector graph, size_t budget) { // Remove duplicate plans from `graph`. - auto getId = [](const SubtreePlan* v) -> uint64_t { - return v->_idsOfIncludedNodes; - }; + auto getId = [](const SubtreePlan* v) { return v->_idsOfIncludedNodes; }; std::ranges::sort(graph, std::ranges::less{}, getId); graph.erase( std::ranges::unique(graph, std::ranges::equal_to{}, getId).begin(), graph.end()); - // NOTE: Not sure if we can have this many plans here, but if we can, assume - // that we have more subgraphs than `budget` allows. - if (graph.size() > 64) { - return budget + 1; - } + // Qlever currently limits the number of triples etc. per group to be <= 64 + // anyway, so we can simply assert here. + AD_CORRECTNESS_CHECK(graph.size() <= 64, + "Should qlever ever support more than 64 elements per " + "group graph pattern, then the `countSubgraphs` " + "functionality also has to be changed"); // Compute the bit representation needed for the call to // `countConnectedSubgraphs::countSubgraphs` below. @@ -1322,7 +1322,7 @@ size_t QueryPlanner::countSubgraphs( for (size_t k = 0; k < graph.size(); ++k) { if ((k != i) && !QueryPlanner::getJoinColumns(*graph.at(k), *graph.at(i)).empty()) { - v.neighbors_ |= (1ull << k); + v.neighbors_ |= (1ULL << k); } } g.push_back(v); @@ -1385,23 +1385,18 @@ vector> QueryPlanner::fillDpTab( for (const auto& plan : component) { g.push_back(&plan); } - ad_utility::Timer timer{ad_utility::Timer::Started}; - bool alwaysGreedy = RuntimeParameters().get<"use-greedy-planning">(); - const size_t budget = RuntimeParameters().get<"greedy-planning-budget">(); - bool useGreedyPlanning = alwaysGreedy || countSubgraphs(g, budget) > budget; - if (!alwaysGreedy && useGreedyPlanning) { + const size_t budget = RuntimeParameters().get<"query-planning-budget">(); + bool useGreedyPlanning = countSubgraphs(g, budget) > budget; + if (useGreedyPlanning) { LOG(INFO) << "Using the greedy query planner for a large connected component" << std::endl; } - timer.start(); auto impl = useGreedyPlanning ? &QueryPlanner::runGreedyPlanningOnConnectedComponent : &QueryPlanner::runDynamicProgrammingOnConnectedComponent; lastDpRowFromComponents.push_back( std::invoke(impl, this, std::move(component), filters, textLimits, tg)); - // LOG(INFO) << "time for planning of connected component" << - // timer.msecs().count() << "ms" << std::endl; checkCancellation(); } size_t numConnectedComponents = lastDpRowFromComponents.size(); diff --git a/src/global/RuntimeParameters.h b/src/global/RuntimeParameters.h index 973ac38e11..9c39ffb28e 100644 --- a/src/global/RuntimeParameters.h +++ b/src/global/RuntimeParameters.h @@ -49,8 +49,7 @@ inline auto& RuntimeParameters() { Bool<"group-by-hash-map-enabled">{false}, Bool<"group-by-disable-index-scan-optimizations">{false}, SizeT<"service-max-value-rows">{100}, - Bool<"use-greedy-planning">{false}, - SizeT<"greedy-planning-budget">{1500}}; + SizeT<"query-planning-budget">{1500}}; }(); return params; } diff --git a/test/QueryPlannerTest.cpp b/test/QueryPlannerTest.cpp index f393799326..4863d60192 100644 --- a/test/QueryPlannerTest.cpp +++ b/test/QueryPlannerTest.cpp @@ -207,11 +207,11 @@ TEST(QueryPlanner, testBFSLeaveOut) { TEST(QueryPlanner, indexScanZeroVariables) { auto scan = h::IndexScanFromStrings; using enum Permutation::Enum; - h::expectDifferentPlanners( + h::expect( "SELECT * \n " "WHERE \t { }", scan("", "", "")); - h::expectDifferentPlanners( + h::expect( "SELECT * \n " "WHERE \t { . ?z}", h::CartesianProductJoin(scan("", "", ""), @@ -221,14 +221,14 @@ TEST(QueryPlanner, indexScanZeroVariables) { TEST(QueryPlanner, indexScanOneVariable) { auto scan = h::IndexScanFromStrings; using enum Permutation::Enum; - h::expectDifferentPlanners( + h::expect( "PREFIX : \n" "SELECT ?x \n " "WHERE \t {?x :myrel :obj}", scan("?x", "", "", {POS})); - h::expectDifferentPlanners( + h::expect( "PREFIX : \n" "SELECT ?x \n " "WHERE \t {:subj :myrel ?x}", @@ -240,7 +240,7 @@ TEST(QueryPlanner, indexScanTwoVariables) { auto scan = h::IndexScanFromStrings; using enum Permutation::Enum; - h::expectDifferentPlanners( + h::expect( "PREFIX : \n" "SELECT ?x \n " "WHERE \t {?x :myrel ?y}", @@ -250,20 +250,20 @@ TEST(QueryPlanner, indexScanTwoVariables) { TEST(QueryPlanner, joinOfTwoScans) { auto scan = h::IndexScanFromStrings; using enum Permutation::Enum; - h::expectDifferentPlanners( + h::expect( "PREFIX :
\n"
       "SELECT ?x \n "
       "WHERE \t {:s1 :r ?x. :s2 :r ?x}",
       h::Join(scan("
", "
", "?x"),
               scan("
", "
", "?x")));
 
-  h::expectDifferentPlanners(
+  h::expect(
       "PREFIX : 
\n"
       "SELECT ?x ?y \n "
       "WHERE  {?y :r ?x . :s2 :r ?x}",
       h::Join(scan("?y", "
", "?x"), scan("
", "
", "?x")));
 
-  h::expectDifferentPlanners(
+  h::expect(
       "PREFIX : 
\n"
       "SELECT ?x ?y ?z \n "
       "WHERE {?y :r ?x. ?z :r ?x}",
@@ -273,7 +273,7 @@ TEST(QueryPlanner, joinOfTwoScans) {
 TEST(QueryPlanner, testActorsBornInEurope) {
   auto scan = h::IndexScanFromStrings;
   using enum ::OrderBy::AscOrDesc;
-  h::expectDifferentPlanners(
+  h::expect(
       "PREFIX : 
\n"
       "SELECT ?a \n "
       "WHERE {?a :profession :Actor . ?a :born-in ?c. ?c :in :Europe}\n"
@@ -287,7 +287,7 @@ TEST(QueryPlanner, testActorsBornInEurope) {
 
 TEST(QueryPlanner, testStarTwoFree) {
   auto scan = h::IndexScanFromStrings;
-  h::expectDifferentPlanners(
+  h::expect(
       "PREFIX : \n"
       "PREFIX ns: \n"
       "PREFIX xxx: \n"
@@ -311,10 +311,11 @@ TEST(QueryPlanner, testFilterAfterSeed) {
       "SELECT ?x ?y ?z WHERE {"
       "?x  ?y . ?y  ?z . "
       "FILTER(?x != ?y) }";
-  h::expect(query,
-            h::Filter("?x != ?y", h::Join(scan("?x", "", "?y"),
-                                          scan("?y", "", "?z"))),
-            qec);
+  h::expectDynamicProgramming(
+      query,
+      h::Filter("?x != ?y",
+                h::Join(scan("?x", "", "?y"), scan("?y", "", "?z"))),
+      qec);
   h::expectGreedy(query,
                   h::Join(h::Filter("?x != ?y", scan("?x", "", "?y")),
                           scan("?y", "", "?z")),
@@ -324,7 +325,7 @@ TEST(QueryPlanner, testFilterAfterSeed) {
 TEST(QueryPlanner, testFilterAfterJoin) {
   auto scan = h::IndexScanFromStrings;
   auto qec = ad_utility::testing::getQec("  ");
-  h::expectDifferentPlanners(
+  h::expect(
       "SELECT ?x ?y ?z WHERE {"
       "?x  ?y . ?y  ?z . "
       "FILTER(?x != ?z) }",
@@ -337,19 +338,19 @@ TEST(QueryPlanner, threeVarTriples) {
   auto scan = h::IndexScanFromStrings;
   using enum Permutation::Enum;
 
-  h::expectDifferentPlanners(
+  h::expect(
       "SELECT ?x ?p ?o WHERE {"
       " 

?x . ?x ?p ?o }", h::Join(scan("", "

", "?x", {SPO, PSO}), scan("?x", "?p", "?o", {SPO, SOP}))); - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?p ?o WHERE {" " ?x . ?x ?p ?o }", h::Join(scan("", "?x", "", {SOP, OSP}), scan("?x", "?p", "?o", {SPO, SOP}))); - h::expectDifferentPlanners( + h::expect( "SELECT ?s ?p ?o WHERE {" "

?p . ?s ?p ?o }", h::Join(scan("", "

", "?p", {SPO, PSO}), @@ -359,12 +360,12 @@ TEST(QueryPlanner, threeVarTriples) { TEST(QueryPlanner, threeVarTriplesTCJ) { auto qec = ad_utility::testing::getQec("

"); auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?p ?o WHERE {" " ?p ?x . ?x ?p ?o }", h::MultiColumnJoin(scan("", "?p", "?x"), scan("?x", "?p", "?o")), qec); - h::expectDifferentPlanners( + h::expect( "SELECT ?s ?p ?o WHERE {" "?s ?p ?o . ?s ?p }", h::MultiColumnJoin(scan("?s", "?p", "?o"), scan("?s", "?p", "")), qec); @@ -372,7 +373,7 @@ TEST(QueryPlanner, threeVarTriplesTCJ) { TEST(QueryPlanner, threeVarXthreeVarException) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?s ?s2 WHERE {" "?s ?p ?o . ?s2 ?p ?o }", h::MultiColumnJoin(scan("?s", "?p", "?o"), scan("?s2", "?p", "?o"))); @@ -380,7 +381,7 @@ TEST(QueryPlanner, threeVarXthreeVarException) { TEST(QueryExecutionTreeTest, testBooksbyNewman) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?x WHERE { ?x . " "?x }", h::Join(scan("?x", "", ""), @@ -389,7 +390,7 @@ TEST(QueryExecutionTreeTest, testBooksbyNewman) { TEST(QueryExecutionTreeTest, testBooksGermanAwardNomAuth) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?y WHERE { " "?x . " "?x . " @@ -405,7 +406,7 @@ TEST(QueryExecutionTreeTest, testPlantsEdibleLeaves) { auto scan = h::IndexScanFromStrings; auto wordScan = h::TextIndexScanForWord; auto entityScan = h::TextIndexScanForEntity; - h::expectDifferentPlanners( + h::expect( "SELECT ?a WHERE {?a . ?c ql:contains-entity ?a. ?c " "ql:contains-word \"edible leaves\"}", h::UnorderedJoins(scan("?a", "", ""), @@ -418,7 +419,7 @@ TEST(QueryExecutionTreeTest, testCoOccFreeVar) { auto scan = h::IndexScanFromStrings; auto wordScan = h::TextIndexScanForWord; auto entityScan = h::TextIndexScanForEntity; - h::expectDifferentPlanners( + h::expect( "PREFIX : <> SELECT ?x ?y WHERE { ?x :is-a :Politician . ?c " "ql:contains-entity ?x . ?c ql:contains-word \"friend*\" . ?c " "ql:contains-entity ?y }", @@ -432,7 +433,7 @@ TEST(QueryExecutionTreeTest, testPoliticiansFriendWithScieManHatProj) { auto scan = h::IndexScanFromStrings; auto wordScan = h::TextIndexScanForWord; auto entityScan = h::TextIndexScanForEntity; - h::expectDifferentPlanners( + h::expect( "SELECT ?p ?s" "WHERE {" "?a . " @@ -585,11 +586,11 @@ TEST(QueryExecutionTreeTest, testFormerSegfaultTriFilter) { TEST(QueryPlanner, testSimpleOptional) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?a ?b \n " "WHERE {?a ?b . OPTIONAL { ?a ?c }}", h::OptionalJoin(scan("?a", "", "?b"), scan("?a", "", "?c"))); - h::expectDifferentPlanners( + h::expect( "SELECT ?a ?b \n " "WHERE {?a ?b . " "OPTIONAL { ?a ?c }} ORDER BY ?b", @@ -605,12 +606,9 @@ TEST(QueryPlanner, SimpleTripleOneVariable) { // With only one variable, there are always two permutations that will yield // exactly the same result. The query planner consistently chooses one of // them. - h::expectDifferentPlanners("SELECT * WHERE { ?s

}", - scan("?s", "

", "", {POS})); - h::expectDifferentPlanners("SELECT * WHERE { ?p }", - scan("", "?p", "", {SOP})); - h::expectDifferentPlanners("SELECT * WHERE {

?o }", - scan("", "

", "?o", {PSO})); + h::expect("SELECT * WHERE { ?s

}", scan("?s", "

", "", {POS})); + h::expect("SELECT * WHERE { ?p }", scan("", "?p", "", {SOP})); + h::expect("SELECT * WHERE {

?o }", scan("", "

", "?o", {PSO})); } TEST(QueryPlanner, SimpleTripleTwoVariables) { @@ -628,29 +626,29 @@ TEST(QueryPlanner, SimpleTripleTwoVariables) { // Fixed predicate. // Without `Order By`, two orderings are possible, both are fine. - h::expectDifferentPlanners("SELECT * WHERE { ?s

?o }", - scan("?s", "

", "?o", {POS, PSO}), qec); + h::expect("SELECT * WHERE { ?s

?o }", scan("?s", "

", "?o", {POS, PSO}), + qec); // Must always be a single index scan, never index scan + sorting. - h::expectDifferentPlanners("SELECT * WHERE { ?s

?o } INTERNAL SORT BY ?o", - scan("?s", "

", "?o", {POS}), qec); - h::expectDifferentPlanners("SELECT * WHERE { ?s

?o } INTERNAL SORT BY ?s", - scan("?s", "

", "?o", {PSO}), qec); + h::expect("SELECT * WHERE { ?s

?o } INTERNAL SORT BY ?o", + scan("?s", "

", "?o", {POS}), qec); + h::expect("SELECT * WHERE { ?s

?o } INTERNAL SORT BY ?s", + scan("?s", "

", "?o", {PSO}), qec); // Fixed subject. - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o }", - scan("", "?p", "?o", {SOP, SPO}), qec); - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?o", - scan("", "?p", "?o", {SOP}), qec); - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?p", - scan("", "?p", "?o", {SPO}), qec); + h::expect("SELECT * WHERE { ?p ?o }", scan("", "?p", "?o", {SOP, SPO}), + qec); + h::expect("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?o", + scan("", "?p", "?o", {SOP}), qec); + h::expect("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?p", + scan("", "?p", "?o", {SPO}), qec); // Fixed object. - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o }", - scan("", "?p", "?o", {SOP, SPO}), qec); - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?o", - scan("", "?p", "?o", {SOP}), qec); - h::expectDifferentPlanners("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?p", - scan("", "?p", "?o", {SPO}), qec); + h::expect("SELECT * WHERE { ?p ?o }", scan("", "?p", "?o", {SOP, SPO}), + qec); + h::expect("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?o", + scan("", "?p", "?o", {SOP}), qec); + h::expect("SELECT * WHERE { ?p ?o } INTERNAL SORT BY ?p", + scan("", "?p", "?o", {SPO}), qec); } TEST(QueryPlanner, SimpleTripleThreeVariables) { @@ -658,55 +656,46 @@ TEST(QueryPlanner, SimpleTripleThreeVariables) { // Fixed predicate. // Don't care about the sorting. - h::expectDifferentPlanners("SELECT * WHERE { ?s ?p ?o }", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, - {SPO, SOP, PSO, POS, OSP, OPS})); + h::expect("SELECT * WHERE { ?s ?p ?o }", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, + {SPO, SOP, PSO, POS, OSP, OPS})); // Sorted by one variable, two possible permutations remain. - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SPO, SOP})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {POS, PSO})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OSP, OPS})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SPO, SOP})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {POS, PSO})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OSP, OPS})); // Sorted by two variables, this makes the permutation unique. - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s ?o", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SOP})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s ?p", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SPO})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o ?s", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OSP})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o ?p", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OPS})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p ?s", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {PSO})); - h::expectDifferentPlanners( - "SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p ?o", - h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {POS})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s ?o", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SOP})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?s ?p", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {SPO})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o ?s", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OSP})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?o ?p", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {OPS})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p ?s", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {PSO})); + h::expect("SELECT * WHERE { ?s ?p ?o } INTERNAL SORT BY ?p ?o", + h::IndexScan(Var{"?s"}, Var{"?p"}, Var{"?o"}, {POS})); } TEST(QueryPlanner, CartesianProductJoin) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?p ?o WHERE {" "

?o . ?a }", h::CartesianProductJoin(scan("", "

", "?o"), scan("?a", "", ""))); // This currently fails because of a bug, we have to fix the bug... - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?p ?o WHERE {" " ?p ?o . ?a ?b ?c }", h::CartesianProductJoin(scan("", "?p", "?o"), scan("?a", "?b", "?c"))); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE {" "?s

. ?s ?o2 . ?x ?c }", h::CartesianProductJoin( @@ -726,7 +715,7 @@ TEST(QueryPlanner, TransitivePathUnbound) { auto scan = h::IndexScanFromStrings; TransitivePathSide left{std::nullopt, 0, Variable("?x"), 0}; TransitivePathSide right{std::nullopt, 1, Variable("?y"), 1}; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?y WHERE {" "?x

+ ?y }", h::TransitivePath(left, right, 1, std::numeric_limits::max(), @@ -741,7 +730,7 @@ TEST(QueryPlanner, TransitivePathLeftId) { TransitivePathSide left{std::nullopt, 0, getId(""), 0}; TransitivePathSide right{std::nullopt, 1, Variable("?y"), 1}; - h::expectDifferentPlanners( + h::expect( "SELECT ?y WHERE {" "

+ ?y }", h::TransitivePath(left, right, 1, std::numeric_limits::max(), @@ -757,7 +746,7 @@ TEST(QueryPlanner, TransitivePathRightId) { TransitivePathSide left{std::nullopt, 1, Variable("?x"), 0}; TransitivePathSide right{std::nullopt, 0, getId(""), 1}; - h::expectDifferentPlanners( + h::expect( "SELECT ?y WHERE {" "?x

+ }", h::TransitivePath(left, right, 1, std::numeric_limits::max(), @@ -769,7 +758,7 @@ TEST(QueryPlanner, TransitivePathBindLeft) { auto scan = h::IndexScanFromStrings; TransitivePathSide left{std::nullopt, 0, Variable("?x"), 0}; TransitivePathSide right{std::nullopt, 1, Variable("?y"), 1}; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?y WHERE {" "

?x." "?x

* ?y }", @@ -782,7 +771,7 @@ TEST(QueryPlanner, TransitivePathBindRight) { auto scan = h::IndexScanFromStrings; TransitivePathSide left{std::nullopt, 1, Variable("?x"), 0}; TransitivePathSide right{std::nullopt, 0, Variable("?y"), 1}; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?y WHERE {" "?x

* ?y." "?y

}", @@ -795,7 +784,7 @@ TEST(QueryPlanner, TransitivePathBindRight) { TEST(QueryPlanner, SpatialJoin) { auto scan = h::IndexScanFromStrings; - h::expectDifferentPlanners( + h::expect( "SELECT ?x ?y WHERE {" "?x

?y." "?a

?b." @@ -803,57 +792,57 @@ TEST(QueryPlanner, SpatialJoin) { h::SpatialJoin(1, scan("?x", "

", "?y"), scan("?a", "

", "?b"))); AD_EXPECT_THROW_WITH_MESSAGE( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?x

?y." - "?a

?b." - "?y ?b ." - "?y ?b}", - ::testing::_), + h::expect("SELECT ?x ?y WHERE {" + "?x

?y." + "?a

?b." + "?y ?b ." + "?y ?b}", + ::testing::_), ::testing::ContainsRegex( "Currently, if both sides of a SpatialJoin are variables, then the" "SpatialJoin must be the only connection between these variables")); AD_EXPECT_THROW_WITH_MESSAGE( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?y

?b." - "?y ?b }", - ::testing::_), + h::expect("SELECT ?x ?y WHERE {" + "?y

?b." + "?y ?b }", + ::testing::_), ::testing::ContainsRegex( "Currently, if both sides of a SpatialJoin are variables, then the" "SpatialJoin must be the only connection between these variables")); EXPECT_ANY_THROW( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?x

?y." - "?y }", - ::testing::_)); + h::expect("SELECT ?x ?y WHERE {" + "?x

?y." + "?y }", + ::testing::_)); EXPECT_ANY_THROW( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?x

?y." - " ?y }", - ::testing::_)); + h::expect("SELECT ?x ?y WHERE {" + "?x

?y." + " ?y }", + ::testing::_)); AD_EXPECT_THROW_WITH_MESSAGE( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?y ?b }", - ::testing::_), + h::expect("SELECT ?x ?y WHERE {" + "?y ?b }", + ::testing::_), ::testing::ContainsRegex( "SpatialJoin needs two children, but at least one is missing")); AD_EXPECT_THROW_WITH_MESSAGE( - h::expectDifferentPlanners("SELECT ?x ?y WHERE {" - "?x

?y." - "?a

?b." - "?y ?b }", - ::testing::_), + h::expect("SELECT ?x ?y WHERE {" + "?x

?y." + "?a

?b." + "?y ?b }", + ::testing::_), ::testing::ContainsRegex("parsing of the maximum distance for the " "SpatialJoin operation was not possible")); } // __________________________________________________________________________ TEST(QueryPlanner, BindAtBeginningOfQuery) { - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE {" " BIND (3 + 5 AS ?x) }", h::Bind(h::NeutralElement(), "3 + 5", Variable{"?x"})); @@ -869,15 +858,13 @@ TEST(QueryPlanner, TextIndexScanForWord) { true, true, true, 16_B, true); auto wordScan = h::TextIndexScanForWord; - h::expectDifferentPlanners( - "SELECT * WHERE { ?text ql:contains-word \"test*\" }", - wordScan(Var{"?text"}, "test*"), qec); + h::expect("SELECT * WHERE { ?text ql:contains-word \"test*\" }", + wordScan(Var{"?text"}, "test*"), qec); - h::expectDifferentPlanners( - "SELECT * WHERE { ?text2 ql:contains-word \"test\" }", - wordScan(Var{"?text2"}, "test"), qec); + h::expect("SELECT * WHERE { ?text2 ql:contains-word \"test\" }", + wordScan(Var{"?text2"}, "test"), qec); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text2 ql:contains-word \"multiple words* test\" }", h::UnorderedJoins(wordScan(Var{"?text2"}, "test"), wordScan(Var{"?text2"}, "words*"), @@ -902,14 +889,14 @@ TEST(QueryPlanner, TextIndexScanForEntity) { auto wordScan = h::TextIndexScanForWord; auto entityScan = h::TextIndexScanForEntity; - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity ?scientist . ?text " "ql:contains-word \"test*\" }", h::Join(wordScan(Var{"?text"}, "test*"), entityScan(Var{"?text"}, Var{"?scientist"}, "test*")), qec); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity . ?text " "ql:contains-word \"test\" }", h::Join(wordScan(Var{"?text"}, "test"), @@ -917,7 +904,7 @@ TEST(QueryPlanner, TextIndexScanForEntity) { qec); // Test case sensitivity - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity . ?text " "ql:contains-word \"TeST\" }", h::Join(wordScan(Var{"?text"}, "test"), @@ -927,7 +914,7 @@ TEST(QueryPlanner, TextIndexScanForEntity) { // NOTE: It is important that the TextIndexScanForEntity uses "opti", because // we also want to test here if the QueryPlanner assigns the optimal word to // the Operation. - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-word \"picking*\" . ?text " "ql:contains-entity . ?text ql:contains-word " "\"opti\" . ?text ql:contains-word \"testi*\"}", @@ -959,12 +946,11 @@ TEST(QueryPlanner, TextLimit) { auto entityScan = h::TextIndexScanForEntity; // Only contains word - h::expectDifferentPlanners( - "SELECT * WHERE { ?text ql:contains-word \"test*\" } TEXTLIMIT 10", - wordScan(Var{"?text"}, "test*"), qec); + h::expect("SELECT * WHERE { ?text ql:contains-word \"test*\" } TEXTLIMIT 10", + wordScan(Var{"?text"}, "test*"), qec); // Contains fixed entity - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-word \"test*\" . ?text " "ql:contains-entity } TEXTLIMIT 10", h::TextLimit( @@ -976,7 +962,7 @@ TEST(QueryPlanner, TextLimit) { qec); // Contains entity - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity ?scientist . ?text " "ql:contains-word \"test*\" } TEXTLIMIT 10", h::TextLimit( @@ -988,7 +974,7 @@ TEST(QueryPlanner, TextLimit) { qec); // Contains entity and fixed entity - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity ?scientist . ?text " "ql:contains-word \"test*\" . ?text ql:contains-entity } " "TEXTLIMIT 5", @@ -1004,7 +990,7 @@ TEST(QueryPlanner, TextLimit) { qec); // Contains two entities - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text ql:contains-entity ?scientist . ?text " "ql:contains-word \"test*\" . ?text ql:contains-entity ?scientist2} " "TEXTLIMIT 5", @@ -1021,7 +1007,7 @@ TEST(QueryPlanner, TextLimit) { // Contains two text variables. Also checks if the textlimit at an efficient // place in the query - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?text1 ql:contains-entity ?scientist1 . ?text1 " "ql:contains-word \"test*\" . ?text2 ql:contains-word \"test*\" . ?text2 " "ql:contains-entity ?author1 . ?text2 ql:contains-entity ?author2 } " @@ -1052,50 +1038,40 @@ TEST(QueryPlanner, NonDistinctVariablesInTriple) { return absl::StrCat(l, "=", r); }; - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?p ?s}", - h::Filter(eq(internalVar(0), "?s"), - h::IndexScanFromStrings(internalVar(0), "?p", "?s"))); - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?s ?o}", - h::Filter(eq(internalVar(0), "?s"), - h::IndexScanFromStrings(internalVar(0), "?s", "?o"))); - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?p ?p}", - h::Filter(eq(internalVar(0), "?p"), - h::IndexScanFromStrings("?s", "?p", internalVar(0)))); - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?s ?s}", - h::Filter(eq(internalVar(1), "?s"), - h::Filter(eq(internalVar(0), "?s"), - h::IndexScanFromStrings(internalVar(1), "?s", - internalVar(0))))); - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?s}", - h::Filter(eq(internalVar(0), "?s"), - h::IndexScanFromStrings("?s", "", internalVar(0)))); - h::expectDifferentPlanners( - "SELECT * WHERE { ?p ?p}", - h::Filter(eq(internalVar(0), "?p"), - h::IndexScanFromStrings("", "?p", internalVar(0)))); - h::expectDifferentPlanners( - "SELECT * WHERE {?s ?s }", - h::Filter(eq(internalVar(0), "?s"), - h::IndexScanFromStrings(internalVar(0), "?s", ""))); + h::expect("SELECT * WHERE {?s ?p ?s}", + h::Filter(eq(internalVar(0), "?s"), + h::IndexScanFromStrings(internalVar(0), "?p", "?s"))); + h::expect("SELECT * WHERE {?s ?s ?o}", + h::Filter(eq(internalVar(0), "?s"), + h::IndexScanFromStrings(internalVar(0), "?s", "?o"))); + h::expect("SELECT * WHERE {?s ?p ?p}", + h::Filter(eq(internalVar(0), "?p"), + h::IndexScanFromStrings("?s", "?p", internalVar(0)))); + h::expect("SELECT * WHERE {?s ?s ?s}", + h::Filter(eq(internalVar(1), "?s"), + h::Filter(eq(internalVar(0), "?s"), + h::IndexScanFromStrings(internalVar(1), "?s", + internalVar(0))))); + h::expect("SELECT * WHERE {?s ?s}", + h::Filter(eq(internalVar(0), "?s"), + h::IndexScanFromStrings("?s", "", internalVar(0)))); + h::expect("SELECT * WHERE { ?p ?p}", + h::Filter(eq(internalVar(0), "?p"), + h::IndexScanFromStrings("", "?p", internalVar(0)))); + h::expect("SELECT * WHERE {?s ?s }", + h::Filter(eq(internalVar(0), "?s"), + h::IndexScanFromStrings(internalVar(0), "?s", ""))); } TEST(QueryPlanner, emptyGroupGraphPattern) { - h::expectDifferentPlanners("SELECT * WHERE {}", h::NeutralElement()); - h::expectDifferentPlanners("SELECT * WHERE { {} }", h::NeutralElement()); - h::expectDifferentPlanners( - "SELECT * WHERE { {} {} }", - h::CartesianProductJoin(h::NeutralElement(), h::NeutralElement())); - h::expectDifferentPlanners( - "SELECT * WHERE { {} UNION {} }", - h::Union(h::NeutralElement(), h::NeutralElement())); - h::expectDifferentPlanners( - "SELECT * WHERE { {} { SELECT * WHERE {}}}", - h::CartesianProductJoin(h::NeutralElement(), h::NeutralElement())); + h::expect("SELECT * WHERE {}", h::NeutralElement()); + h::expect("SELECT * WHERE { {} }", h::NeutralElement()); + h::expect("SELECT * WHERE { {} {} }", + h::CartesianProductJoin(h::NeutralElement(), h::NeutralElement())); + h::expect("SELECT * WHERE { {} UNION {} }", + h::Union(h::NeutralElement(), h::NeutralElement())); + h::expect("SELECT * WHERE { {} { SELECT * WHERE {}}}", + h::CartesianProductJoin(h::NeutralElement(), h::NeutralElement())); } // __________________________________________________________________________ @@ -1114,12 +1090,12 @@ TEST(QueryPlanner, TooManyTriples) { // ___________________________________________________________________________ TEST(QueryPlanner, CountAvailablePredicates) { - h::expectDifferentPlanners( + h::expect( "SELECT ?p (COUNT(DISTINCT ?s) as ?cnt) WHERE { ?s ?p ?o} GROUP BY ?p", h::CountAvailablePredicates( 0, Var{"?p"}, Var{"?cnt"}, h::IndexScanFromStrings("?s", HAS_PATTERN_PREDICATE, "?p"))); - h::expectDifferentPlanners( + h::expect( "SELECT ?p (COUNT(DISTINCT ?s) as ?cnt) WHERE { ?s ql:has-predicate " "?p} " "GROUP BY ?p", @@ -1133,11 +1109,9 @@ TEST(QueryPlanner, CountAvailablePredicates) { // Check that a MINUS operation that only refers to unbound variables is deleted // by the query planner. TEST(QueryPlanner, UnboundMinusIgnored) { - h::expectDifferentPlanners("SELECT * WHERE {MINUS{?x ?y}}", - h::NeutralElement()); - h::expectDifferentPlanners( - "SELECT * WHERE { ?a ?b MINUS{?x ?y}}", - h::IndexScanFromStrings("?a", "", "?b")); + h::expect("SELECT * WHERE {MINUS{?x ?y}}", h::NeutralElement()); + h::expect("SELECT * WHERE { ?a ?b MINUS{?x ?y}}", + h::IndexScanFromStrings("?a", "", "?b")); } // ___________________________________________________________________________ @@ -1161,17 +1135,17 @@ TEST(QueryPlanner, JoinWithService) { auto sibling = scan("?x", "", "?y"); std::string_view graphPatternAsString = "{ ?x ?z . }"; - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE {" "SERVICE { ?x ?z . ?y ?a . }}", h::Service(std::nullopt, "{ ?x ?z . ?y ?a . }")); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?x ?y ." "SERVICE { ?x ?z . }}", h::UnorderedJoins(sibling, h::Service(sibling, graphPatternAsString))); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?x ?y . " "SERVICE { ?x ?z . ?y ?a . }}", h::MultiColumnJoin( @@ -1184,13 +1158,13 @@ TEST(QueryPlanner, SubtreeWithService) { auto scan = h::IndexScanFromStrings; auto sibling = scan("?x", "", "?y"); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?x ?y ." "OPTIONAL{SERVICE { ?x ?z . }}}", h::OptionalJoin(sibling, h::Sort(h::Service(sibling, "{ ?x ?z . }")))); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?x ?y . " "OPTIONAL{" "SERVICE { ?x ?z . ?y ?a . }}}", @@ -1198,7 +1172,7 @@ TEST(QueryPlanner, SubtreeWithService) { sibling, h::Sort(h::Service(sibling, "{ ?x ?z . ?y ?a . }")))); - h::expectDifferentPlanners( + h::expect( "SELECT * WHERE { ?x ?y MINUS{SERVICE { ?x " " ?z . }}}", h::Minus(sibling, h::Sort(h::Service(sibling, "{ ?x ?z . }")))); @@ -1208,19 +1182,18 @@ TEST(QueryPlanner, SubtreeWithService) { TEST(QueryPlanner, DatasetClause) { auto scan = h::IndexScanFromStrings; using Graphs = ad_utility::HashSet; - h::expectDifferentPlanners("SELECT * FROM FROM WHERE { ?x ?y ?z}", - scan("?x", "?y", "?z", {}, Graphs{"", ""})); + h::expect("SELECT * FROM FROM WHERE { ?x ?y ?z}", + scan("?x", "?y", "?z", {}, Graphs{"", ""})); - h::expectDifferentPlanners( - "SELECT * FROM FROM { SELECT * {?x ?y ?z}}", - scan("?x", "?y", "?z", {}, Graphs{"", ""})); + h::expect("SELECT * FROM FROM { SELECT * {?x ?y ?z}}", + scan("?x", "?y", "?z", {}, Graphs{"", ""})); - h::expectDifferentPlanners("SELECT * FROM WHERE { GRAPH {?x ?y ?z}}", - scan("?x", "?y", "?z", {}, Graphs{""})); + h::expect("SELECT * FROM WHERE { GRAPH {?x ?y ?z}}", + scan("?x", "?y", "?z", {}, Graphs{""})); auto g1 = Graphs{""}; auto g2 = Graphs{""}; - h::expectDifferentPlanners( + h::expect( "SELECT * FROM { ?p . { ?p } GRAPH { ?p " "{SELECT * { ?p }}} ?p }", h::UnorderedJoins( @@ -1231,19 +1204,18 @@ TEST(QueryPlanner, DatasetClause) { auto g12 = Graphs{"", ""}; auto varG = std::vector{Variable{"?g"}}; std::vector graphCol{ADDITIONAL_COLUMN_GRAPH_ID}; - h::expectDifferentPlanners( + h::expect( "SELECT * FROM FROM NAMED FROM NAMED WHERE { GRAPH ?g { " " }}", scan("", "", "", {}, g12, varG, graphCol)); - h::expectDifferentPlanners( - "SELECT * FROM WHERE { GRAPH ?g { }}", - scan("", "", "", {}, std::nullopt, varG, graphCol)); + h::expect("SELECT * FROM WHERE { GRAPH ?g { }}", + scan("", "", "", {}, std::nullopt, varG, graphCol)); // `GROUP BY` inside a `GRAPH ?g` clause. // We use the `UnorderedJoins` matcher, because the index scan has to be // resorted by the graph column. - h::expectDifferentPlanners( + h::expect( "SELECT * FROM FROM NAMED { GRAPH ?g " "{ " "{SELECT ?p { ?p } GROUP BY ?p}" @@ -1253,7 +1225,7 @@ TEST(QueryPlanner, DatasetClause) { scan("", "?p", "", {}, g2, varG, graphCol)))); // A complex example with graph variables. - h::expectDifferentPlanners( + h::expect( "SELECT * FROM FROM NAMED { ?p . { ?p } GRAPH ?g " "{ ?p " "{SELECT * { ?p }}" @@ -1270,8 +1242,7 @@ TEST(QueryPlanner, DatasetClause) { // We currently don't support repeating the graph variable inside the // graph clause AD_EXPECT_THROW_WITH_MESSAGE( - h::expectDifferentPlanners("SELECT * { GRAPH ?x {?x }}", - ::testing::_), + h::expect("SELECT * { GRAPH ?x {?x }}", ::testing::_), AllOf(HasSubstr("used as the graph specifier"), HasSubstr("may not appear in the body"))); } diff --git a/test/QueryPlannerTestHelpers.h b/test/QueryPlannerTestHelpers.h index 3c2c9a07f6..6c75438445 100644 --- a/test/QueryPlannerTestHelpers.h +++ b/test/QueryPlannerTestHelpers.h @@ -375,14 +375,16 @@ QueryExecutionTree parseAndPlan(std::string query, QueryExecutionContext* qec) { // Check that the `QueryExecutionTree` that is obtained by parsing and planning // the `query` matches the `matcher`. The query planning budget can be // controlled to choose between the greedy and the dynamic programming planner. -void expect(std::string query, auto matcher, - std::optional optQec = std::nullopt, - size_t queryPlanningBudget = 100'000, - source_location l = source_location::current()) { - auto budgetBackup = RuntimeParameters().get<"greedy-planning-budget">(); - RuntimeParameters().set<"greedy-planning-budget">(queryPlanningBudget); +// This function only serves as a common implementation, for the actual tests +// the three functions below should be used. +void expectWithGivenBudget(std::string query, auto matcher, + std::optional optQec, + size_t queryPlanningBudget, + source_location l = source_location::current()) { + auto budgetBackup = RuntimeParameters().get<"query-planning-budget">(); + RuntimeParameters().set<"query-planning-budget">(queryPlanningBudget); auto cleanup = absl::Cleanup{[budgetBackup]() { - RuntimeParameters().set<"greedy-planning-budget">(budgetBackup); + RuntimeParameters().set<"query-planning-budget">(budgetBackup); }}; auto trace = generateLocationTrace( l, absl::StrCat("expect with budget ", queryPlanningBudget)); @@ -393,21 +395,32 @@ void expect(std::string query, auto matcher, EXPECT_THAT(qet, matcher); } -// Same as `expect` above, but always use the greedy query planner. +// Same as `expectWithGivenBudget` above, but always use the greedy query +// planner. void expectGreedy(std::string query, auto matcher, std::optional optQec = std::nullopt, source_location l = source_location::current()) { - expect(std::move(query), std::move(matcher), optQec, 0, l); + expectWithGivenBudget(std::move(query), std::move(matcher), optQec, 0, l); } - -// Same as `expect` above, but run the test for different query planning -// budgets. This is guaranteed to run with both the greedy query planner and the -// dynamic-programming based query planner. -void expectDifferentPlanners( +// Same as `expectWithGivenBudget` above, but always use the dyanmic programming +// query planner. +void expectDynamicProgramming( std::string query, auto matcher, std::optional optQec = std::nullopt, source_location l = source_location::current()) { - auto e = [&](size_t budget) { expect(query, matcher, optQec, budget, l); }; + expectWithGivenBudget(std::move(query), std::move(matcher), optQec, + std::numeric_limits::max(), l); +} + +// Same as `expectWithGivenBudget` above, but run the test for different query +// planning budgets. This is guaranteed to run with both the greedy query +// planner and the dynamic-programming based query planner. +void expect(std::string query, auto matcher, + std::optional optQec = std::nullopt, + source_location l = source_location::current()) { + auto e = [&](size_t budget) { + expectWithGivenBudget(query, matcher, optQec, budget, l); + }; e(0); e(1); e(4);