Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement Transitive Paths using binary search instead of a hash map #1313

Merged
merged 98 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
fd1bdf8
Added graphblas dependecies
JoBuRo Jan 30, 2024
285528b
Added wrapper for graphblas matrix
JoBuRo Jan 30, 2024
935bc0d
Replaced transitiveHull computation
JoBuRo Jan 30, 2024
069819f
Added extern keyword around include
JoBuRo Jan 31, 2024
4b257fe
Replaced std map with abseil map
JoBuRo Jan 31, 2024
d4ba6f6
Added graphblas dependency for GH action
JoBuRo Jan 31, 2024
1b20a1e
Added library for mac build
JoBuRo Jan 31, 2024
92621d0
Removed finalize()
JoBuRo Jan 31, 2024
b3d71bd
Added fallback for GraphBLAS
JoBuRo Jan 31, 2024
d1de484
Reworks GrBMatrix
JoBuRo Feb 1, 2024
303ff2f
More reworks on GrbMatrix
JoBuRo Feb 1, 2024
958b4f7
Renamed getMatrix -> matrix
JoBuRo Feb 1, 2024
87bc650
Added documentation to GrbMatrix
JoBuRo Feb 2, 2024
370ad43
Reworked build function
JoBuRo Feb 2, 2024
0018c84
Reworked extractRow and extractCol
JoBuRo Feb 2, 2024
622dddd
Reworked use of C arrays in GrbMatrix
JoBuRo Feb 2, 2024
7c7d5b6
Additional reworks for GrbMatrix
JoBuRo Feb 2, 2024
21a5994
Reworked extractTuples
JoBuRo Feb 5, 2024
6f0f8cd
Added a quick fix for GrB_init issue
JoBuRo Feb 5, 2024
7269e4c
Reworked IdMapping
JoBuRo Feb 5, 2024
97d766a
Reworks on TransitivePath
JoBuRo Feb 6, 2024
c8c6526
Merge branch 'master' into use-graphblas
joka921 Feb 7, 2024
a06ed71
A tiny bugfix and make the stuff configurable.
joka921 Feb 7, 2024
6e84e8a
Fix build error, add move assignment to GrbMatrix
JoBuRo Feb 7, 2024
005dc9c
Simplifications
JoBuRo Feb 8, 2024
e7dbf4d
Reworks
JoBuRo Feb 9, 2024
714744a
Added timer to transitive path computation
JoBuRo Feb 9, 2024
0fd9f92
**WIP** Refactor of TransitivePath into Fallback and Graphblas
JoBuRo Feb 12, 2024
60a37b6
Fixed timing conversion
JoBuRo Feb 15, 2024
d07401e
Added singleton class for GraphBLAS global context
JoBuRo Feb 15, 2024
1f424de
Added some checkCancellation
JoBuRo Feb 15, 2024
d64f4b6
Build fix
JoBuRo Feb 20, 2024
816ba9a
Removed dead code
JoBuRo Feb 20, 2024
ecdf2a7
Sonar fixes
JoBuRo Feb 21, 2024
12f0e3d
Add timings to runtime info
JoBuRo Feb 21, 2024
ed6dc70
Some fixes.
joka921 Feb 21, 2024
dd8c841
Added binary search, updated tests
JoBuRo Mar 8, 2024
50e26ba
Merge branch 'master' into use-graphblas
joka921 Mar 13, 2024
4e64224
Fix merge conflicts.
joka921 Mar 13, 2024
b79470d
Moved sort to constructor
JoBuRo Mar 13, 2024
f6e2429
Merge remote-tracking branch 'origin/master' into use-graphblas
Mar 14, 2024
965d25a
Removed GraphBlas references and implementation
JoBuRo Mar 16, 2024
9eea0f6
Use TransitivePathFallback by default, so tests pass
JoBuRo Mar 16, 2024
7852bc3
Updated TransitiveFallback computeResult
JoBuRo Mar 19, 2024
2c9b117
Moved Map and Set definition
JoBuRo Mar 25, 2024
2fea8d7
Added checkCancellation to BinSearch
JoBuRo Mar 25, 2024
8039acc
Removed suite-sparse from git workflows
JoBuRo Mar 25, 2024
95f6dad
Style fix
JoBuRo Mar 25, 2024
75f4593
Rebased against current master
JoBuRo Mar 25, 2024
f580604
Merge branch 'master' into improve-transitive-path
JoBuRo Mar 25, 2024
c532162
Style fix
JoBuRo Mar 25, 2024
a65f6c5
Merge branch 'improve-transitive-path' of https://github.com/JoBuRo/q…
JoBuRo Mar 25, 2024
34beef3
Removed unnecessary include
JoBuRo Mar 25, 2024
f9811f9
Removed code duplication
JoBuRo Apr 8, 2024
1692d40
Fixed linker error
JoBuRo Apr 10, 2024
19d1c2e
Added TransitivePathImpl and added it to TransitivePathBinSearch
JoBuRo Apr 10, 2024
1c37ce4
Added TransitivePathImpl to TransitivePathFallback
JoBuRo Apr 10, 2024
d0f9330
Fixed build error
JoBuRo Apr 11, 2024
b17701a
Removed debug print statement
JoBuRo Apr 11, 2024
077f1fb
Added move statement
JoBuRo Apr 11, 2024
42f3878
Moved getChildren definition to cpp file
JoBuRo Apr 11, 2024
036c6fc
Removed unnecessary include
JoBuRo Apr 11, 2024
4ba0cab
Removed unused file
JoBuRo Apr 11, 2024
1511e35
Fixed copyright strings
JoBuRo Apr 11, 2024
c17477f
Moved computeResult to TransitivePathImpl
JoBuRo Apr 11, 2024
3e6330e
Renamed TransitivePathFallback to TransitivePathHashMap
JoBuRo Apr 11, 2024
c52e16e
Renamed runtime constant "use-binsearch" to "use-binsearch-transitive…
JoBuRo Apr 12, 2024
56a06f2
Paramteterized TransitivePathTests
JoBuRo Apr 12, 2024
b175cce
Added unit tests for bound cases of transitive paths
JoBuRo Apr 12, 2024
f395516
Merge branch 'master' into improve-transitive-path
JoBuRo Apr 12, 2024
05f5894
Fixed merge errors
JoBuRo Apr 12, 2024
669c29c
Move values in TransitivePath ctors
JoBuRo Apr 12, 2024
5580e14
Changed return type to auto on BinSearchMap.successors
JoBuRo Apr 12, 2024
dff2aa4
Added comment for C++23: use ranges::to
JoBuRo Apr 12, 2024
e9b63ec
Added docs
JoBuRo Apr 12, 2024
ee12697
Declared TransitivePathBase dtor as pure virtual
JoBuRo Apr 12, 2024
47c19ee
Simplified setupEdges in TransitivePathBinSearch
JoBuRo Apr 12, 2024
da97798
Fixed an issue with move semantics and ctors
JoBuRo Apr 12, 2024
e31afd7
Implemented HashMapWrapper
JoBuRo Apr 15, 2024
0a33997
Moved transitiveHull function to TransitiveHullImpl
JoBuRo Apr 15, 2024
6398363
const auto& in transitiveHull
JoBuRo Apr 15, 2024
8391366
Replaced assertSameUnorderedContent with gmock function
JoBuRo Apr 15, 2024
9821e28
Changed RuntimeParameter for transitivePath to true
JoBuRo Apr 15, 2024
0d6977f
Format fix
JoBuRo Apr 15, 2024
6cf3af1
Added some docs
JoBuRo Apr 15, 2024
d2ea890
Revert "Format fix"
JoBuRo Apr 15, 2024
7f70dd3
Format fix
JoBuRo Apr 16, 2024
199d390
Sonar Fixes
JoBuRo Apr 16, 2024
c139244
Added unit test for exception
JoBuRo Apr 16, 2024
5a48b5f
Added tests for 'zero or more' transitive path
JoBuRo Apr 16, 2024
79f5420
HashMapWrapper successors function returns const Set&
JoBuRo Apr 16, 2024
555aa6f
Use shorthand for QueryPlanner sort matcher
JoBuRo Apr 16, 2024
0faa6d8
Simplified tests a bit V(x) -> x
JoBuRo Apr 16, 2024
4eb14ed
Added a todo
JoBuRo Apr 16, 2024
1d496a3
Added doc for HashMapWrapper
JoBuRo Apr 16, 2024
9d6eaf3
Format fix
JoBuRo Apr 16, 2024
a768eb0
Try to fix the MacOS build
joka921 Apr 16, 2024
8a0bee4
Format fix
JoBuRo Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions src/engine/TransitivePathBinSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,53 +24,6 @@ TransitivePathBinSearch::TransitivePathBinSearch(
subtree_, {startSide.subCol_, targetSide.subCol_});
}

// _____________________________________________________________________________
Map TransitivePathBinSearch::transitiveHull(const BinSearchMap& edges,
const std::vector<Id>& startNodes,
std::optional<Id> target) const {
// For every node do a dfs on the graph
Map hull{allocator()};

std::vector<std::pair<Id, size_t>> stack;
ad_utility::HashSetWithMemoryLimit<Id> marks{
getExecutionContext()->getAllocator()};
for (auto startNode : startNodes) {
if (hull.contains(startNode)) {
// We have already computed the hull for this node
continue;
}

marks.clear();
stack.clear();
stack.push_back({startNode, 0});

if (minDist_ == 0 && (!target.has_value() || startNode == target.value())) {
insertIntoMap(hull, startNode, startNode);
}

while (stack.size() > 0) {
checkCancellation();
auto [node, steps] = stack.back();
stack.pop_back();

if (steps <= maxDist_ && marks.count(node) == 0) {
if (steps >= minDist_) {
marks.insert(node);
if (!target.has_value() || node == target.value()) {
insertIntoMap(hull, startNode, node);
}
}

auto successors = edges.successors(node);
for (auto successor : successors) {
stack.push_back({successor, steps + 1});
}
}
}
}
return hull;
}

// _____________________________________________________________________________
BinSearchMap TransitivePathBinSearch::setupEdgesMap(
const IdTable& dynSub, const TransitivePathSide& startSide,
Expand Down
23 changes: 6 additions & 17 deletions src/engine/TransitivePathBinSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ struct BinSearchMap {
}
};

/**
* @class TransitivePathBinSearch
* @brief This class implements the transitive path operation. The
* implementation represents the graph as adjacency lists and uses binary search
* to find successors of given nodes.
*/
class TransitivePathBinSearch : public TransitivePathImpl<BinSearchMap> {
public:
TransitivePathBinSearch(QueryExecutionContext* qec,
Expand All @@ -64,23 +70,6 @@ class TransitivePathBinSearch : public TransitivePathImpl<BinSearchMap> {
size_t maxDist);

private:
/**
* @brief Compute the transitive hull starting at the given nodes,
* using the given Map.
*
* @param edges Adjacency lists, mapping Ids (nodes) to their connected
* Ids.
* @param nodes A list of Ids. These Ids are used as starting points for the
* transitive hull. Thus, this parameter guides the performance of this
* algorithm.
* @param target Optional target Id. If supplied, only paths which end
* in this Id are added to the hull.
* @return Map Maps each Id to its connected Ids in the transitive hull
*/
Map transitiveHull(const BinSearchMap& edges,
const std::vector<Id>& startNodes,
std::optional<Id> target) const override;

// initialize the map from the subresult
BinSearchMap setupEdgesMap(
const IdTable& dynSub, const TransitivePathSide& startSide,
Expand Down
90 changes: 7 additions & 83 deletions src/engine/TransitivePathHashMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,89 +16,12 @@ TransitivePathHashMap::TransitivePathHashMap(
QueryExecutionContext* qec, std::shared_ptr<QueryExecutionTree> child,
TransitivePathSide leftSide, TransitivePathSide rightSide, size_t minDist,
size_t maxDist)
: TransitivePathImpl<Map>(qec, std::move(child), std::move(leftSide),
std::move(rightSide), minDist, maxDist) {}
: TransitivePathImpl<HashMapWrapper>(
qec, std::move(child), std::move(leftSide), std::move(rightSide),
minDist, maxDist) {}

// _____________________________________________________________________________
Map TransitivePathHashMap::transitiveHull(const Map& edges,
const std::vector<Id>& startNodes,
std::optional<Id> target) const {
using MapIt = Map::const_iterator;
// For every node do a dfs on the graph
Map hull{allocator()};

// Stores nodes we already have a path to. This avoids cycles.
ad_utility::HashSetWithMemoryLimit<Id> marks{
getExecutionContext()->getAllocator()};

// The stack used to store the dfs' progress
std::vector<Set::const_iterator> positions;

// Used to store all edges leading away from a node for every level.
// Reduces access to the hashmap, and is safe as the map will not
// be modified after this point.
std::vector<const Set*> edgeCache;

for (Id currentStartNode : startNodes) {
if (hull.contains(currentStartNode)) {
// We have already computed the hull for this node
continue;
}

// Reset for this iteration
marks.clear();

MapIt rootEdges = edges.find(currentStartNode);
if (rootEdges != edges.end()) {
positions.push_back(rootEdges->second.begin());
edgeCache.push_back(&rootEdges->second);
}
if (minDist_ == 0 &&
(!target.has_value() || currentStartNode == target.value())) {
insertIntoMap(hull, currentStartNode, currentStartNode);
}

// While we have not found the entire transitive hull and have not reached
// the max step limit
while (!positions.empty()) {
checkCancellation();
size_t stackIndex = positions.size() - 1;
// Process the next child of the node at the top of the stack
Set::const_iterator& pos = positions[stackIndex];
const Set* nodeEdges = edgeCache.back();

if (pos == nodeEdges->end()) {
// We finished processing this node
positions.pop_back();
edgeCache.pop_back();
continue;
}

Id child = *pos;
++pos;
size_t childDepth = positions.size();
if (childDepth <= maxDist_ && marks.count(child) == 0) {
// process the child
if (childDepth >= minDist_) {
marks.insert(child);
if (!target.has_value() || child == target.value()) {
insertIntoMap(hull, currentStartNode, child);
}
}
// Add the child to the stack
MapIt it = edges.find(child);
if (it != edges.end()) {
positions.push_back(it->second.begin());
edgeCache.push_back(&it->second);
}
}
}
}
return hull;
}

// _____________________________________________________________________________
Map TransitivePathHashMap::setupEdgesMap(
HashMapWrapper TransitivePathHashMap::setupEdgesMap(
const IdTable& dynSub, const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const {
return CALL_FIXED_SIZE((std::array{dynSub.numColumns()}),
Expand All @@ -108,7 +31,7 @@ Map TransitivePathHashMap::setupEdgesMap(

// _____________________________________________________________________________
template <size_t SUB_WIDTH>
Map TransitivePathHashMap::setupEdgesMap(
HashMapWrapper TransitivePathHashMap::setupEdgesMap(
const IdTable& dynSub, const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const {
const IdTableView<SUB_WIDTH> sub = dynSub.asStaticView<SUB_WIDTH>();
Expand All @@ -120,5 +43,6 @@ Map TransitivePathHashMap::setupEdgesMap(
checkCancellation();
insertIntoMap(edges, startCol[i], targetCol[i]);
}
return edges;
auto wrapper = HashMapWrapper(edges);
return wrapper;
}
49 changes: 28 additions & 21 deletions src/engine/TransitivePathHashMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,28 @@
#include "engine/TransitivePathImpl.h"
#include "engine/idTable/IdTable.h"

class TransitivePathHashMap : public TransitivePathImpl<Map> {
struct HashMapWrapper {
Map map_;

auto successors(const Id node) const {
auto iterator = map_.find(node);
if (iterator == map_.end()) {
return std::vector<Id>();
}
std::vector<Id> result(iterator->second.begin(), iterator->second.end());
return result;
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't copy to a vector, but return a const Set&.
You can make an empty set another member of the HashMapWrapper, so that you have something to return in the case of not found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I solved this, but I was not sure where to get the ad_utility::AllocatorWithLimit<Id>. There may be a smarter solution.


/**
* @class TransitivePathHashMap
* @brief This class implements the transitive path operation. The
* implementation uses a hash map to represent the graph and find successors
* of given nodes.
*
*
*/
class TransitivePathHashMap : public TransitivePathImpl<HashMapWrapper> {
public:
TransitivePathHashMap(QueryExecutionContext* qec,
std::shared_ptr<QueryExecutionTree> child,
Expand All @@ -21,22 +42,6 @@ class TransitivePathHashMap : public TransitivePathImpl<Map> {
size_t maxDist);

private:
/**
* @brief Compute the transitive hull starting at the given nodes,
* using the given Map.
*
* @param edges Adjacency lists, mapping Ids (nodes) to their connected
* Ids.
* @param nodes A list of Ids. These Ids are used as starting points for the
* transitive hull. Thus, this parameter guides the performance of this
* algorithm.
* @param target Optional target Id. If supplied, only paths which end
* in this Id are added to the hull.
* @return Map Maps each Id to its connected Ids in the transitive hull
*/
Map transitiveHull(const Map& edges, const std::vector<Id>& startNodes,
std::optional<Id> target) const override;

/**
* @brief Prepare a Map and a nodes vector for the transitive hull
* computation.
Expand Down Expand Up @@ -73,10 +78,12 @@ class TransitivePathHashMap : public TransitivePathImpl<Map> {
const TransitivePathSide& targetSide) const;

// initialize the map from the subresult
Map setupEdgesMap(const IdTable& dynSub, const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const override;
HashMapWrapper setupEdgesMap(
const IdTable& dynSub, const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const override;

template <size_t SUB_WIDTH>
Map setupEdgesMap(const IdTable& dynSub, const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const;
HashMapWrapper setupEdgesMap(const IdTable& dynSub,
const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const;
};
75 changes: 59 additions & 16 deletions src/engine/TransitivePathImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@
ResultTable computeResult() override {
if (minDist_ == 0 && !isBoundOrId() && lhs_.isVariable() &&
rhs_.isVariable()) {
AD_THROW(
"This query might have to evalute the empty path, which is currently "
"not supported");
}

Check warning on line 151 in src/engine/TransitivePathImpl.h

View check run for this annotation

Codecov / codecov/patch

src/engine/TransitivePathImpl.h#L148-L151

Added lines #L148 - L151 were not covered by tests
auto [startSide, targetSide] = decideDirection();
shared_ptr<const ResultTable> subRes = subtree_->getResult();

Expand Down Expand Up @@ -183,6 +183,65 @@
subRes->getSharedLocalVocab()};
};

/**
* @brief Compute the transitive hull starting at the given nodes,
* using the given Map.
*
* @param edges Adjacency lists, mapping Ids (nodes) to their connected
* Ids.
* @param nodes A list of Ids. These Ids are used as starting points for the
* transitive hull. Thus, this parameter guides the performance of this
* algorithm.
* @param target Optional target Id. If supplied, only paths which end
* in this Id are added to the hull.
* @return Map Maps each Id to its connected Ids in the transitive hull
*/
Map transitiveHull(const T& edges, const std::vector<Id>& startNodes,
std::optional<Id> target) const {
// For every node do a dfs on the graph
Map hull{allocator()};

std::vector<std::pair<Id, size_t>> stack;
ad_utility::HashSetWithMemoryLimit<Id> marks{
getExecutionContext()->getAllocator()};
for (auto startNode : startNodes) {
if (hull.contains(startNode)) {
// We have already computed the hull for this node
continue;
}

marks.clear();
stack.clear();
stack.push_back({startNode, 0});

if (minDist_ == 0 &&
(!target.has_value() || startNode == target.value())) {
insertIntoMap(hull, startNode, startNode);
}

while (stack.size() > 0) {
checkCancellation();
auto [node, steps] = stack.back();
stack.pop_back();

if (steps <= maxDist_ && marks.count(node) == 0) {
if (steps >= minDist_) {
marks.insert(node);
if (!target.has_value() || node == target.value()) {
insertIntoMap(hull, startNode, node);
}
}

const auto& successors = edges.successors(node);
for (auto successor : successors) {
stack.push_back({successor, steps + 1});
}
}
}
}
return hull;
}

/**
* @brief Prepare a Map and a nodes vector for the transitive hull
* computation.
Expand Down Expand Up @@ -210,9 +269,9 @@
// TODO<C++23> Use ranges::to.
nodes.insert(nodes.end(), startNodes.begin(), startNodes.end());
if (minDist_ == 0) {
std::span<const Id> targetNodes = sub.getColumn(targetSide.subCol_);
nodes.insert(nodes.end(), targetNodes.begin(), targetNodes.end());
}

Check warning on line 274 in src/engine/TransitivePathImpl.h

View check run for this annotation

Codecov / codecov/patch

src/engine/TransitivePathImpl.h#L272-L274

Added lines #L272 - L274 were not covered by tests
}

return {std::move(edges), std::move(nodes)};
Expand Down Expand Up @@ -248,22 +307,6 @@
return {std::move(edges), std::move(nodes)};
};

/**
* @brief Compute the transitive hull starting at the given nodes,
* using the given Map.
*
* @param edges Adjacency lists, mapping Ids (nodes) to their connected
* Ids.
* @param nodes A list of Ids. These Ids are used as starting points for the
* transitive hull. Thus, this parameter guides the performance of this
* algorithm.
* @param target Optional target Id. If supplied, only paths which end
* in this Id are added to the hull.
* @return Map Maps each Id to its connected Ids in the transitive hull
*/
virtual Map transitiveHull(const T& edges, const std::vector<Id>& startNodes,
std::optional<Id> target) const = 0;

virtual T setupEdgesMap(const IdTable& dynSub,
const TransitivePathSide& startSide,
const TransitivePathSide& targetSide) const = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/global/RuntimeParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ inline auto& RuntimeParameters() {
DurationParameter<std::chrono::seconds, "default-query-timeout">{
30s}),
SizeT<"lazy-index-scan-max-size-materialization">{1'000'000},
Bool<"use-binsearch-transitive-path">{false},
Bool<"use-binsearch-transitive-path">{true},
Bool<"group-by-hash-map-enabled">{false}};
}();
return params;
Expand Down
8 changes: 4 additions & 4 deletions test/QueryPlannerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,8 @@ TEST(QueryPlanner, TransitivePathRightId) {

auto getId = ad_utility::testing::makeGetId(qec->getIndex());

TransitivePathSide left{std::nullopt, 0, Variable("?x"), 0};
TransitivePathSide right{std::nullopt, 1, getId("<o>"), 1};
TransitivePathSide left{std::nullopt, 1, Variable("?x"), 0};
TransitivePathSide right{std::nullopt, 0, getId("<o>"), 1};
h::expect(
"SELECT ?y WHERE {"
"?x <p>+ <o> }",
Expand Down Expand Up @@ -765,8 +765,8 @@ TEST(QueryPlanner, TransitivePathBindRight) {
h::TransitivePath(
left, right, 0, std::numeric_limits<size_t>::max(),
scan("?y", "<p>", "<o>"),
scan("?_qlever_internal_variable_query_planner_0", "<p>",
"?_qlever_internal_variable_query_planner_1")));
h::Sort(scan("?_qlever_internal_variable_query_planner_0", "<p>",
"?_qlever_internal_variable_query_planner_1"))));
}

// __________________________________________________________________________
Expand Down
Loading
Loading