Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 2 additions & 9 deletions src/engine/QueryPlanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,8 @@ class QueryPlanner {
Node(size_t id, SparqlTriple t,
std::optional<Variable> graphVariable = std::nullopt)
: id_(id), triple_(std::move(t)) {
if (triple_.s_.isVariable()) {
_variables.insert(triple_.s_.getVariable());
}
if (auto predicate = triple_.getPredicateVariable()) {
_variables.insert(predicate.value());
}
if (triple_.o_.isVariable()) {
_variables.insert(triple_.o_.getVariable());
}
triple_.forEachVariable(
[this](const auto& var) { _variables.insert(var); });
if (graphVariable.has_value()) {
_variables.insert(std::move(graphVariable).value());
}
Expand Down
1 change: 1 addition & 0 deletions src/parser/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ add_library(parser
Quads.cpp
UpdateTriples.cpp
MaterializedViewQuery.cpp
GraphPatternAnalysis.cpp
)
qlever_target_link_libraries(parser sparqlParser parserData sparqlExpressions rdfEscaping global re2::re2 util engine index rdfTypes)
30 changes: 30 additions & 0 deletions src/parser/GraphPatternAnalysis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2026 The QLever Authors, in particular:
//
// 2026 Christoph Ullinger <[email protected]>, UFR
//
// UFR = University of Freiburg, Chair of Algorithms and Data Structures

#include "parser/GraphPatternAnalysis.h"

namespace graphPatternAnalysis {

// _____________________________________________________________________________
bool BasicGraphPatternsInvariantTo::operator()(
const parsedQuery::Bind& bind) const {
return !variables_.contains(bind._target);
}

// _____________________________________________________________________________
bool BasicGraphPatternsInvariantTo::operator()(
const parsedQuery::Values& valuesClause) const {
const auto& [variables, values] = valuesClause._inlineValues;
return
// There is exactly one row inside the `VALUES`.
values.size() == 1 &&
// The `VALUES` doesn't bind to any of the `variables_`.
ql::ranges::none_of(variables, [this](const auto& var) {
return variables_.contains(var);
});
Copy link
Member

Choose a reason for hiding this comment

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

Technically this module is currently untested (at least I cannot find the tests, otherwise please point me to them). I am not sure if this is a hard problem for me, but they should be easy to vibecode etc.

}

} // namespace graphPatternAnalysis
53 changes: 53 additions & 0 deletions src/parser/GraphPatternAnalysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2026 The QLever Authors, in particular:
//
// 2026 Christoph Ullinger <[email protected]>, UFR
//
// UFR = University of Freiburg, Chair of Algorithms and Data Structures

#ifndef QLEVER_SRC_PARSER_GRAPHPATTERNANALYSIS_H_
#define QLEVER_SRC_PARSER_GRAPHPATTERNANALYSIS_H_

#include "parser/GraphPatternOperation.h"

// This module contains helpers for analyzing the structure of graph patterns.

// _____________________________________________________________________________
namespace graphPatternAnalysis {

// Check whether certain graph patterns can be ignored when we are only
// interested in the bindings for variables from `variables_` as they do not
// affect the result for these `variables_`.
//
// For example: A basic graph pattern (a list of triples) is invariant to a
// `BIND` statement whose target variable is not contained in the basic graph
// pattern, because the `BIND` only adds its own column, but neither adds nor
// deletes result rows.
//
Comment on lines +18 to +25
Copy link
Member

Choose a reason for hiding this comment

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

Is this statement actually true:

 ?x <is-a> ?y .
 BIND (somethingCompletelyElse as ?z) # seems invariant.
 FILTER (?x > ?y)  upps...

Or will you in this case consider filter the whole thing out, because you see the filter and EVERYTHING has to be invariant? in that case please comment this, that this is only true for one step.

But otherwise: The comment is now great to understand

// This is currently used for the `MaterializedViewsManager`'s
// `QueryPatternCache`.
//
// NOTE: This does not guarantee completeness, so it might return `false` even
// though we could be invariant to a `GraphPatternOperation`.
struct BasicGraphPatternsInvariantTo {
ad_utility::HashSet<Variable> variables_;

bool operator()(const parsedQuery::Bind& bind) const;
bool operator()(const parsedQuery::Values& values) const;

template <typename T>
bool operator()(const T&) const {
// The presence of any of these operations might remove or duplicate rows.
namespace pq = parsedQuery;
static_assert(
ad_utility::SimilarToAny<
T, pq::Optional, pq::Union, pq::Subquery, pq::TransPath,
pq::BasicGraphPattern, pq::Service, pq::PathQuery, pq::SpatialQuery,
pq::TextSearchQuery, pq::Minus, pq::GroupGraphPattern, pq::Describe,
pq::Load, pq::NamedCachedResult, pq::MaterializedViewQuery>);
return false;
}
};

} // namespace graphPatternAnalysis

#endif // QLEVER_SRC_PARSER_GRAPHPATTERNANALYSIS_H_
23 changes: 23 additions & 0 deletions src/parser/GraphPatternOperation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "parser/TripleComponent.h"
#include "util/Exception.h"
#include "util/Forward.h"
#include "util/VariantRangeFilter.h"

namespace parsedQuery {

Expand Down Expand Up @@ -81,4 +82,26 @@ void BasicGraphPattern::appendTriples(BasicGraphPattern other) {
auto inner = _expression.getDescriptor();
return "BIND (" + inner + " AS " + _target.name() + ")";
}

// ____________________________________________________________________________
void BasicGraphPattern::collectAllContainedVariables(
ad_utility::HashSet<Variable>& vars) const {
for (const SparqlTriple& t : _triples) {
t.forEachVariable([&vars](const auto& var) { vars.insert(var); });
}
}

// _____________________________________________________________________________
ad_utility::HashSet<Variable> getVariablesPresentInFirstBasicGraphPattern(
const std::vector<parsedQuery::GraphPatternOperation>& graphPatterns) {
ad_utility::HashSet<Variable> vars;
auto basicGraphPatterns =
ad_utility::filterRangeOfVariantsByType<parsedQuery::BasicGraphPattern>(
graphPatterns);
if (!ql::ranges::empty(basicGraphPatterns)) {
(*basicGraphPatterns.begin()).collectAllContainedVariables(vars);
Copy link
Member

Choose a reason for hiding this comment

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

why not bgp.begin()-> (the start is confusing to read )

}
return vars;
}

} // namespace parsedQuery
13 changes: 13 additions & 0 deletions src/parser/GraphPatternOperation.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,21 @@ struct BasicGraphPattern {
std::vector<SparqlTriple> _triples;
/// Append the triples from `other` to this `BasicGraphPattern`
void appendTriples(BasicGraphPattern other);

// Collect all the `Variable`s present in this `BasicGraphPattern` and add
// them to a `HashSet`.
void collectAllContainedVariables(ad_utility::HashSet<Variable>& vars) const;
};

// Extract all variables present in a the first `BasicGraphPattern` contained in
// a vector of `GraphPatternOperation`s. It is used for skipping some graph
// patterns in `MaterializedViewQueryAnalysis.cpp`.
//
Comment on lines +89 to +92
Copy link
Member

Choose a reason for hiding this comment

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

The comment is a little ambigious:

  1. does this use the first basic pattern in the query, even if the pattern is not at the beginnning of the query (so first subquery, then BGP), or only "the first element if it is a parsed graph pattern". The comment says more the first, but as this is a function with strange uninituitive behavior, better be precise and verbose.

// IMPORTANT: This function does not consider variables that are contained in
// other types of `GraphPatternOperation`s.
ad_utility::HashSet<Variable> getVariablesPresentInFirstBasicGraphPattern(
const std::vector<parsedQuery::GraphPatternOperation>& graphPatterns);

/// A `Values` clause
struct Values {
SparqlValues _inlineValues;
Expand Down
6 changes: 6 additions & 0 deletions src/parser/MaterializedViewQuery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ MaterializedViewQuery::MaterializedViewQuery(const SparqlTriple& triple) {
addRequestedColumn(requestedColumn, simpleTriple.o_);
}

// _____________________________________________________________________________
MaterializedViewQuery::MaterializedViewQuery(std::string name,
RequestedColumns requestedColumns)
: viewName_{std::move(name)},
requestedColumns_{std::move(requestedColumns)} {};

// _____________________________________________________________________________
ad_utility::HashSet<Variable> MaterializedViewQuery::getVarsToKeep() const {
ad_utility::HashSet<Variable> varsToKeep;
Expand Down
6 changes: 5 additions & 1 deletion src/parser/MaterializedViewQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ struct MaterializedViewQuery : MagicServiceQuery {
// column names in the query result or literals/IRIs to restrict the column
// on. This can be used for filtering the results and reading any number of
// payload columns from the materialized view.
ad_utility::HashMap<Variable, TripleComponent> requestedColumns_;
using RequestedColumns = ad_utility::HashMap<Variable, TripleComponent>;
RequestedColumns requestedColumns_;

// This constructor takes an IRI consisting of the magic service IRI for
// materialized views with the view name as a suffix. If this is used, add the
Expand All @@ -58,6 +59,9 @@ struct MaterializedViewQuery : MagicServiceQuery {
// are necessary in this case.
explicit MaterializedViewQuery(const SparqlTriple& triple);

// For query rewriting: Initialize directly using name and requested columns.
MaterializedViewQuery(std::string name, RequestedColumns requestedColumns);

void addParameter(const SparqlTriple& triple) override;

// Return the variables that should be visible from this read on the
Expand Down
12 changes: 12 additions & 0 deletions src/parser/PropertyPath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ bool PropertyPath::isIri() const {
return std::holds_alternative<ad_utility::triple_component::Iri>(path_);
}

// _____________________________________________________________________________
const std::vector<PropertyPath>& PropertyPath::getSequence() const {
AD_CONTRACT_CHECK(isSequence());
return std::get<ModifiedPath>(path_).children_;
}

// _____________________________________________________________________________
bool PropertyPath::isSequence() const {
return std::holds_alternative<ModifiedPath>(path_) &&
std::get<ModifiedPath>(path_).modifier_ == Modifier::SEQUENCE;
}

// _____________________________________________________________________________
std::optional<std::reference_wrapper<const PropertyPath>>
PropertyPath::getChildOfInvertedPath() const {
Expand Down
7 changes: 7 additions & 0 deletions src/parser/PropertyPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ class PropertyPath {
// otherwise.
bool isIri() const;

// If the path is a sequence, return the children (that is, the parts of the
// sequence). If the path is not a sequence this will throw.
const std::vector<PropertyPath>& getSequence() const;

// Check if the path is a sequence.
bool isSequence() const;

// If the path is a modified path with an inverse modifier, return the pointer
// to its only child. Otherwise, return nullptr.
std::optional<std::reference_wrapper<const PropertyPath>>
Expand Down
13 changes: 13 additions & 0 deletions src/parser/SparqlTriple.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ class SparqlTriple
auto ptr = std::get_if<Variable>(&p_);
return (ptr != nullptr && *ptr == variable);
}

// Call a function for every variable contained in the triple.
void forEachVariable(auto function) const {
Copy link
Member

Choose a reason for hiding this comment

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

Add a requirement (invocable with a const Variable&).

if (s_.isVariable()) {
function(s_.getVariable());
Copy link
Member

Choose a reason for hiding this comment

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

consider using std::invoke (for the flex + member pointers of Variable:))

}
if (auto predicate = getPredicateVariable()) {
function(predicate.value());
}
if (o_.isVariable()) {
function(o_.getVariable());
}
}
};

#endif // QLEVER_SRC_PARSER_SPARQLTRIPLE_H
71 changes: 71 additions & 0 deletions src/util/StringPairHashMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2026 The QLever Authors, in particular:
//
// 2026 Christoph Ullinger <[email protected]>, UFR
//
// UFR = University of Freiburg, Chair of Algorithms and Data Structures

#ifndef QLEVER_SRC_UTIL_STRINGPAIRHASHMAP_H_
#define QLEVER_SRC_UTIL_STRINGPAIRHASHMAP_H_

#include "util/HashMap.h"

// This module provides a modified version of `ad_utility::HashMap` that uses
// pairs of strings as keys. Unlike the default hash map it allows looking up
// values with pairs of string views as keys. This is implemented using custom
// hash and equality operators.
Comment on lines +14 to +15
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// values with pairs of string views as keys. This is implemented using custom
// hash and equality operators.
// values with pairs of string views as keys. This is implemented using custom
// transparent hash and equality operators.

(give people something to google:))


// TODO<ullingerc> This could be extended to support `std::tuple` or
// `std::array`, not only `std::pair`, and other transparently comparable types.

// _____________________________________________________________________________
namespace ad_utility {

// _____________________________________________________________________________
namespace detail {

using StringPair = std::pair<std::string, std::string>;
using StringViewPair = std::pair<std::string_view, std::string_view>;

// _____________________________________________________________________________
struct StringPairHash {
// Allows looking up values from a hash map with `StringPair` keys also with
// `StringViewPair`.
Comment on lines +31 to +32
Copy link
Member

Choose a reason for hiding this comment

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

Have you checked that absl doesn't already provide this (e.g. transparent hashing of tuple-like types?), same for the equality, isn't there an implicit and transparent equality for tuples in the STL (haven't checked yet myself, just want to make sure that this is needed).

using is_transparent = void;

size_t operator()(const StringPair& p) const {
return absl::HashOf(p.first, p.second);
}

size_t operator()(const StringViewPair& p) const {
return absl::HashOf(p.first, p.second);
}
};

// _____________________________________________________________________________
struct StringPairEq {
using is_transparent = void;

bool operator()(const StringPair& a, const StringPair& b) const {
return a == b;
}

bool operator()(const StringPair& a, const StringViewPair& b) const {
return a.first == b.first && a.second == b.second;
}

bool operator()(const StringViewPair& a, const StringPair& b) const {
return b.first == a.first && b.second == a.second;
}
};

} // namespace detail

template <typename ValueType>
using StringPairHashMap =
ad_utility::HashMap<ad_utility::detail::StringPair, ValueType,
Comment on lines +63 to +65
Copy link
Member

Choose a reason for hiding this comment

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

Does this support types other than StringPair and StringViewPair?
in that case you can make that clear in comments, and/or requires clauses.

ad_utility::detail::StringPairHash,
ad_utility::detail::StringPairEq>;

} // namespace ad_utility

#endif // QLEVER_SRC_UTIL_STRINGPAIRHASHMAP_H_
26 changes: 26 additions & 0 deletions src/util/VariantRangeFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2026 The QLever Authors, in particular:
//
// 2026 Christoph Ullinger <[email protected]>, UFR
//
// UFR = University of Freiburg, Chair of Algorithms and Data Structures

#ifndef QLEVER_SRC_UTIL_VARIANTRANGEFILTER_H
#define QLEVER_SRC_UTIL_VARIANTRANGEFILTER_H

#include "backports/algorithm.h"
#include "util/TransparentFunctors.h"

namespace ad_utility {

// Helper that filters a range, like `std::vector` which contains `std::variant`
// elements by a certain type `T` and returns a view of the contained values.
CPP_template(typename T, typename R)(
requires ql::ranges::range<R>) auto filterRangeOfVariantsByType(const R&
range) {
Comment on lines +17 to +19
Copy link
Member

Choose a reason for hiding this comment

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

doesn't need to be constrained to const & , can be R&& , then you need a remove_cvref_t or decay for the range constraint, and (as I still haven't patched range-v3 for this) use return ad_utility::allView(AD_FWD(range)) | ....

return range | ql::views::filter(holdsAlternative<T>) |
ql::views::transform(get<T>);
}

} // namespace ad_utility

#endif // QLEVER_SRC_UTIL_VARIANTRANGEFILTER_H
4 changes: 4 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ addLinkAndDiscoverTestSerial(QueryPlannerSpatialJoinTest engine)

addLinkAndDiscoverTestNoLibs(HashMapTest)

addLinkAndDiscoverTestNoLibs(StringPairHashMapTest)

addLinkAndDiscoverTest(HashSetTest)

addLinkAndDiscoverTestSerial(GroupByTest engine)
Expand Down Expand Up @@ -493,3 +495,5 @@ addLinkAndDiscoverTest(MaterializedViewsTest qlever engine server)
addLinkAndDiscoverTestNoLibs(ConstexprMapTest)

addLinkAndDiscoverTestNoLibs(ParallelExecutorTest)

addLinkAndDiscoverTestNoLibs(VariantRangeFilterTest)
10 changes: 10 additions & 0 deletions test/MaterializedViewsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,16 @@ TEST_F(MaterializedViewsTest, ManualConfigurations) {
::testing::Eq(V{"?o"})));
}

// Test internal constructor.
{
ViewQuery query{"testView", ViewQuery::RequestedColumns{
{V{"?s"}, V{"?s2"}}, {V{"?o"}, V{"?o2"}}}};
EXPECT_EQ(query.viewName_, "testView");
EXPECT_THAT(query.getVarsToKeep(),
::testing::UnorderedElementsAre(::testing::Eq(V{"?s2"}),
::testing::Eq(V{"?o2"})));
}

// Unsupported format version.
{
auto plan = qlv().parseAndPlanQuery(simpleWriteQuery_);
Expand Down
Loading
Loading