From 98eab72504e5834baa25e62f47c1097a1509b742 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 13:50:48 +0100 Subject: [PATCH 01/12] checkout util changes from other branch --- src/parser/CMakeLists.txt | 1 + src/parser/GraphPatternAnalysis.cpp | 29 ++++++++++++ src/parser/GraphPatternAnalysis.h | 48 ++++++++++++++++++++ src/parser/GraphPatternOperation.cpp | 30 ++++++++++++ src/parser/GraphPatternOperation.h | 12 +++++ src/parser/MaterializedViewQuery.cpp | 6 +++ src/parser/MaterializedViewQuery.h | 6 ++- src/parser/PropertyPath.cpp | 12 +++++ src/parser/PropertyPath.h | 7 +++ src/util/StringPairHashMap.h | 68 ++++++++++++++++++++++++++++ test/parser/PropertyPathTest.cpp | 30 ++++++++++++ 11 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 src/parser/GraphPatternAnalysis.cpp create mode 100644 src/parser/GraphPatternAnalysis.h create mode 100644 src/util/StringPairHashMap.h diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt index 8c76f41716..1629ea784d 100644 --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -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) diff --git a/src/parser/GraphPatternAnalysis.cpp b/src/parser/GraphPatternAnalysis.cpp new file mode 100644 index 0000000000..f2454ceeea --- /dev/null +++ b/src/parser/GraphPatternAnalysis.cpp @@ -0,0 +1,29 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , 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& values) const { + return + // The `VALUES` doesn't bind to any of the `variables_`. + !std::ranges::any_of( + values._inlineValues._variables, + [this](const auto& var) { return variables_.contains(var); }) && + // There is exactly one row inside the `VALUES`. + values._inlineValues._values.size() == 1; +} + +} // namespace graphPatternAnalysis diff --git a/src/parser/GraphPatternAnalysis.h b/src/parser/GraphPatternAnalysis.h new file mode 100644 index 0000000000..13ed8727f8 --- /dev/null +++ b/src/parser/GraphPatternAnalysis.h @@ -0,0 +1,48 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , 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 of a query that only selects `variables_`. 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 variables_; + + bool operator()(const parsedQuery::Bind& bind) const; + bool operator()(const parsedQuery::Values& values) const; + + template + bool operator()(const T&) const { + // The presence of any of these operations might remove or duplicate rows. + static_assert(ad_utility::SimilarToAny< + T, parsedQuery::Optional, parsedQuery::Union, + parsedQuery::Subquery, parsedQuery::TransPath, + parsedQuery::BasicGraphPattern, parsedQuery::Service, + parsedQuery::PathQuery, parsedQuery::SpatialQuery, + parsedQuery::TextSearchQuery, parsedQuery::Minus, + parsedQuery::GroupGraphPattern, parsedQuery::Describe, + parsedQuery::Load, parsedQuery::NamedCachedResult, + parsedQuery::MaterializedViewQuery>); + return false; + } +}; + +} // namespace graphPatternAnalysis + +#endif // QLEVER_SRC_PARSER_GRAPHPATTERNANALYSIS_H_ diff --git a/src/parser/GraphPatternOperation.cpp b/src/parser/GraphPatternOperation.cpp index b92e2eb46d..0abc32cdd7 100644 --- a/src/parser/GraphPatternOperation.cpp +++ b/src/parser/GraphPatternOperation.cpp @@ -81,4 +81,34 @@ void BasicGraphPattern::appendTriples(BasicGraphPattern other) { auto inner = _expression.getDescriptor(); return "BIND (" + inner + " AS " + _target.name() + ")"; } + +// ____________________________________________________________________________ +void BasicGraphPattern::collectAllContainedVariables( + ad_utility::HashSet& vars) const { + for (const SparqlTriple& t : _triples) { + if (t.s_.isVariable()) { + vars.insert(t.s_.getVariable()); + } + if (auto predicate = t.getPredicateVariable()) { + vars.insert(predicate.value()); + } + if (t.o_.isVariable()) { + vars.insert(t.o_.getVariable()); + } + } +} + +// _____________________________________________________________________________ +ad_utility::HashSet getVariablesPresentInBasicGraphPatterns( + const std::vector& graphPatterns) { + ad_utility::HashSet vars; + for (const auto& graphPattern : graphPatterns) { + if (!std::holds_alternative(graphPattern)) { + continue; + } + graphPattern.getBasic().collectAllContainedVariables(vars); + } + return vars; +} + } // namespace parsedQuery diff --git a/src/parser/GraphPatternOperation.h b/src/parser/GraphPatternOperation.h index 4f16be3f9a..041e357616 100644 --- a/src/parser/GraphPatternOperation.h +++ b/src/parser/GraphPatternOperation.h @@ -80,8 +80,20 @@ struct BasicGraphPattern { std::vector _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& vars) const; }; +// Extract all variables present in a set of `BasicGraphPatterns` contained in +// `GraphPatternOperation`s. +// +// IMPORTANT: This function does not consider variables that are contained in +// other types of `GraphPatternOperation`s. +ad_utility::HashSet getVariablesPresentInBasicGraphPatterns( + const std::vector& graphPatterns); + /// A `Values` clause struct Values { SparqlValues _inlineValues; diff --git a/src/parser/MaterializedViewQuery.cpp b/src/parser/MaterializedViewQuery.cpp index 7ad56493e9..2ee31d2a4d 100644 --- a/src/parser/MaterializedViewQuery.cpp +++ b/src/parser/MaterializedViewQuery.cpp @@ -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 MaterializedViewQuery::getVarsToKeep() const { ad_utility::HashSet varsToKeep; diff --git a/src/parser/MaterializedViewQuery.h b/src/parser/MaterializedViewQuery.h index 4a44b4f9fc..886e239943 100644 --- a/src/parser/MaterializedViewQuery.h +++ b/src/parser/MaterializedViewQuery.h @@ -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 requestedColumns_; + using RequestedColumns = ad_utility::HashMap; + 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 @@ -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 diff --git a/src/parser/PropertyPath.cpp b/src/parser/PropertyPath.cpp index cccfad61fe..b405a70ccc 100644 --- a/src/parser/PropertyPath.cpp +++ b/src/parser/PropertyPath.cpp @@ -130,6 +130,18 @@ bool PropertyPath::isIri() const { return std::holds_alternative(path_); } +// _____________________________________________________________________________ +const std::vector& PropertyPath::getSequence() const { + AD_CONTRACT_CHECK(isSequence()); + return std::get(path_).children_; +} + +// _____________________________________________________________________________ +bool PropertyPath::isSequence() const { + return std::holds_alternative(path_) && + std::get(path_).modifier_ == Modifier::SEQUENCE; +} + // _____________________________________________________________________________ std::optional> PropertyPath::getChildOfInvertedPath() const { diff --git a/src/parser/PropertyPath.h b/src/parser/PropertyPath.h index 08fa199863..0daab58efe 100644 --- a/src/parser/PropertyPath.h +++ b/src/parser/PropertyPath.h @@ -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& 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> diff --git a/src/util/StringPairHashMap.h b/src/util/StringPairHashMap.h new file mode 100644 index 0000000000..0b8ff98965 --- /dev/null +++ b/src/util/StringPairHashMap.h @@ -0,0 +1,68 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , 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. + +// _____________________________________________________________________________ +namespace ad_utility { + +// _____________________________________________________________________________ +namespace detail { + +using StringPair = std::pair; +using StringViewPair = std::pair; + +// _____________________________________________________________________________ +struct StringPairHash { + // Allows looking up values from a hash map with `StringPair` keys also with + // `StringViewPair`. + 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 +using StringPairHashMap = + ad_utility::HashMap; + +} // namespace ad_utility + +#endif // QLEVER_SRC_UTIL_STRINGPAIRHASHMAP_H_ diff --git a/test/parser/PropertyPathTest.cpp b/test/parser/PropertyPathTest.cpp index 0d99b1791b..de2cd749e1 100644 --- a/test/parser/PropertyPathTest.cpp +++ b/test/parser/PropertyPathTest.cpp @@ -288,3 +288,33 @@ TEST(PropertyPath, handlePath) { }), 2); } + +// _____________________________________________________________________________ +TEST(PropertyPath, Getters) { + auto path1 = PropertyPath::fromIri(iri1); + EXPECT_TRUE(path1.isIri()); + EXPECT_FALSE(path1.isSequence()); + EXPECT_EQ(path1.getIri(), iri1); + + auto path2 = PropertyPath::makeInverse(PropertyPath::fromIri(iri1)); + EXPECT_FALSE(path2.isIri()); + EXPECT_FALSE(path2.isSequence()); + + auto path3 = PropertyPath::makeAlternative( + {PropertyPath::fromIri(iri1), PropertyPath::fromIri(iri2)}); + EXPECT_FALSE(path3.isIri()); + EXPECT_FALSE(path3.isSequence()); + + auto path4 = PropertyPath::makeSequence( + {PropertyPath::fromIri(iri1), PropertyPath::fromIri(iri2)}); + EXPECT_FALSE(path4.isIri()); + EXPECT_TRUE(path4.isSequence()); + auto matchIri = [](ad_utility::triple_component::Iri iri) + -> ::testing::Matcher { + return ::testing::AllOf( + ::testing::Property(&PropertyPath::isIri, ::testing::IsTrue()), + ::testing::Property(&PropertyPath::getIri, ::testing::Eq(iri))); + }; + EXPECT_THAT(path4.getSequence(), + ::testing::ElementsAre(matchIri(iri1), matchIri(iri2))); +} From 04d014316c3ca503cfad336b8fedfefed2f4b666 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 14:22:42 +0100 Subject: [PATCH 02/12] apply Johannes' feedback --- src/engine/QueryPlanner.h | 11 ++--------- src/parser/GraphPatternAnalysis.cpp | 13 +++++++------ src/parser/GraphPatternAnalysis.h | 27 ++++++++++++++++----------- src/parser/GraphPatternOperation.cpp | 22 +++++++--------------- src/parser/GraphPatternOperation.h | 7 ++++--- src/parser/SparqlTriple.h | 13 +++++++++++++ src/util/TransparentFunctors.h | 9 +++++++++ 7 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/engine/QueryPlanner.h b/src/engine/QueryPlanner.h index 94c6ce9a30..88d2ca39af 100644 --- a/src/engine/QueryPlanner.h +++ b/src/engine/QueryPlanner.h @@ -65,15 +65,8 @@ class QueryPlanner { Node(size_t id, SparqlTriple t, std::optional 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()); } diff --git a/src/parser/GraphPatternAnalysis.cpp b/src/parser/GraphPatternAnalysis.cpp index f2454ceeea..8969420ae4 100644 --- a/src/parser/GraphPatternAnalysis.cpp +++ b/src/parser/GraphPatternAnalysis.cpp @@ -16,14 +16,15 @@ bool BasicGraphPatternsInvariantTo::operator()( // _____________________________________________________________________________ bool BasicGraphPatternsInvariantTo::operator()( - const parsedQuery::Values& values) const { + const parsedQuery::Values& valuesClause) const { + const auto& [variables, values] = valuesClause._inlineValues; return - // The `VALUES` doesn't bind to any of the `variables_`. - !std::ranges::any_of( - values._inlineValues._variables, - [this](const auto& var) { return variables_.contains(var); }) && // There is exactly one row inside the `VALUES`. - values._inlineValues._values.size() == 1; + 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); + }); } } // namespace graphPatternAnalysis diff --git a/src/parser/GraphPatternAnalysis.h b/src/parser/GraphPatternAnalysis.h index 13ed8727f8..91f7c659c0 100644 --- a/src/parser/GraphPatternAnalysis.h +++ b/src/parser/GraphPatternAnalysis.h @@ -16,8 +16,15 @@ 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 of a query that only selects `variables_`. This is -// currently used for the `MaterializedViewsManager`'s `QueryPatternCache`. +// 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. +// +// 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`. @@ -30,15 +37,13 @@ struct BasicGraphPatternsInvariantTo { template bool operator()(const T&) const { // The presence of any of these operations might remove or duplicate rows. - static_assert(ad_utility::SimilarToAny< - T, parsedQuery::Optional, parsedQuery::Union, - parsedQuery::Subquery, parsedQuery::TransPath, - parsedQuery::BasicGraphPattern, parsedQuery::Service, - parsedQuery::PathQuery, parsedQuery::SpatialQuery, - parsedQuery::TextSearchQuery, parsedQuery::Minus, - parsedQuery::GroupGraphPattern, parsedQuery::Describe, - parsedQuery::Load, parsedQuery::NamedCachedResult, - parsedQuery::MaterializedViewQuery>); + 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; } }; diff --git a/src/parser/GraphPatternOperation.cpp b/src/parser/GraphPatternOperation.cpp index 0abc32cdd7..01c0754362 100644 --- a/src/parser/GraphPatternOperation.cpp +++ b/src/parser/GraphPatternOperation.cpp @@ -86,27 +86,19 @@ void BasicGraphPattern::appendTriples(BasicGraphPattern other) { void BasicGraphPattern::collectAllContainedVariables( ad_utility::HashSet& vars) const { for (const SparqlTriple& t : _triples) { - if (t.s_.isVariable()) { - vars.insert(t.s_.getVariable()); - } - if (auto predicate = t.getPredicateVariable()) { - vars.insert(predicate.value()); - } - if (t.o_.isVariable()) { - vars.insert(t.o_.getVariable()); - } + t.forEachVariable([&vars](const auto& var) { vars.insert(var); }); } } // _____________________________________________________________________________ -ad_utility::HashSet getVariablesPresentInBasicGraphPatterns( +ad_utility::HashSet getVariablesPresentInFirstBasicGraphPattern( const std::vector& graphPatterns) { ad_utility::HashSet vars; - for (const auto& graphPattern : graphPatterns) { - if (!std::holds_alternative(graphPattern)) { - continue; - } - graphPattern.getBasic().collectAllContainedVariables(vars); + auto basicGraphPatterns = + ad_utility::filterRangeOfVariantsByType( + graphPatterns); + if (!ql::ranges::empty(basicGraphPatterns)) { + (*basicGraphPatterns.begin()).collectAllContainedVariables(vars); } return vars; } diff --git a/src/parser/GraphPatternOperation.h b/src/parser/GraphPatternOperation.h index 041e357616..9e6687d58a 100644 --- a/src/parser/GraphPatternOperation.h +++ b/src/parser/GraphPatternOperation.h @@ -86,12 +86,13 @@ struct BasicGraphPattern { void collectAllContainedVariables(ad_utility::HashSet& vars) const; }; -// Extract all variables present in a set of `BasicGraphPatterns` contained in -// `GraphPatternOperation`s. +// 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`. // // IMPORTANT: This function does not consider variables that are contained in // other types of `GraphPatternOperation`s. -ad_utility::HashSet getVariablesPresentInBasicGraphPatterns( +ad_utility::HashSet getVariablesPresentInFirstBasicGraphPattern( const std::vector& graphPatterns); /// A `Values` clause diff --git a/src/parser/SparqlTriple.h b/src/parser/SparqlTriple.h index 2ba815c743..e0212a4a73 100644 --- a/src/parser/SparqlTriple.h +++ b/src/parser/SparqlTriple.h @@ -133,6 +133,19 @@ class SparqlTriple auto ptr = std::get_if(&p_); return (ptr != nullptr && *ptr == variable); } + + // Call a function for every variable contained in the triple. + void forEachVariable(auto function) const { + if (s_.isVariable()) { + function(s_.getVariable()); + } + if (auto predicate = getPredicateVariable()) { + function(predicate.value()); + } + if (o_.isVariable()) { + function(o_.getVariable()); + } + } }; #endif // QLEVER_SRC_PARSER_SPARQLTRIPLE_H diff --git a/src/util/TransparentFunctors.h b/src/util/TransparentFunctors.h index adc6e88673..94bac92192 100644 --- a/src/util/TransparentFunctors.h +++ b/src/util/TransparentFunctors.h @@ -119,6 +119,15 @@ static constexpr detail::HoldsAlternativeImpl holdsAlternative; template static constexpr detail::GetImpl get; +// 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) auto filterRangeOfVariantsByType(const R& + range) { + return range | ql::views::filter(holdsAlternative) | + ql::views::transform(get); +} + // Transparent functor for `std::get_if`. As an extension to `std::get_if`, // `ad_utility::getIf` may also be called with a `variant` object or reference, // not only with a pointer. From 76509c777ee8dd966f21f6c3e6f39ca4dd287a93 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 15:42:58 +0100 Subject: [PATCH 03/12] add todo --- src/util/StringPairHashMap.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util/StringPairHashMap.h b/src/util/StringPairHashMap.h index 0b8ff98965..9f984b364d 100644 --- a/src/util/StringPairHashMap.h +++ b/src/util/StringPairHashMap.h @@ -14,6 +14,9 @@ // values with pairs of string views as keys. This is implemented using custom // hash and equality operators. +// TODO This could be extended to support `std::tuple` or +// `std::array`, not only `std::pair`, and other transparently + // _____________________________________________________________________________ namespace ad_utility { From e8ff99e1da67b8989fa9aa537886c6c51413c917 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 15:47:19 +0100 Subject: [PATCH 04/12] try to fix compiler error --- src/parser/GraphPatternOperation.cpp | 1 + src/util/TransparentFunctors.h | 9 --------- src/util/VariantRangeFilter.h | 26 ++++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/util/VariantRangeFilter.h diff --git a/src/parser/GraphPatternOperation.cpp b/src/parser/GraphPatternOperation.cpp index 01c0754362..027adbbaaa 100644 --- a/src/parser/GraphPatternOperation.cpp +++ b/src/parser/GraphPatternOperation.cpp @@ -16,6 +16,7 @@ #include "parser/TripleComponent.h" #include "util/Exception.h" #include "util/Forward.h" +#include "util/VariantRangeFilter.h" namespace parsedQuery { diff --git a/src/util/TransparentFunctors.h b/src/util/TransparentFunctors.h index 94bac92192..adc6e88673 100644 --- a/src/util/TransparentFunctors.h +++ b/src/util/TransparentFunctors.h @@ -119,15 +119,6 @@ static constexpr detail::HoldsAlternativeImpl holdsAlternative; template static constexpr detail::GetImpl get; -// 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) auto filterRangeOfVariantsByType(const R& - range) { - return range | ql::views::filter(holdsAlternative) | - ql::views::transform(get); -} - // Transparent functor for `std::get_if`. As an extension to `std::get_if`, // `ad_utility::getIf` may also be called with a `variant` object or reference, // not only with a pointer. diff --git a/src/util/VariantRangeFilter.h b/src/util/VariantRangeFilter.h new file mode 100644 index 0000000000..b6a5661488 --- /dev/null +++ b/src/util/VariantRangeFilter.h @@ -0,0 +1,26 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , 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) auto filterRangeOfVariantsByType(const R& + range) { + return range | ql::views::filter(holdsAlternative) | + ql::views::transform(get); +} + +} // namespace ad_utility + +#endif // QLEVER_SRC_UTIL_VARIANTRANGEFILTER_H From 44e4f890ca631d9489d98f3c4b9a30becc7a5383 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 15:49:55 +0100 Subject: [PATCH 05/12] move code to cpp --- src/parser/GraphPatternAnalysis.cpp | 14 ++++++++++++++ src/parser/GraphPatternAnalysis.h | 12 +----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/parser/GraphPatternAnalysis.cpp b/src/parser/GraphPatternAnalysis.cpp index 8969420ae4..55db3a28d7 100644 --- a/src/parser/GraphPatternAnalysis.cpp +++ b/src/parser/GraphPatternAnalysis.cpp @@ -27,4 +27,18 @@ bool BasicGraphPatternsInvariantTo::operator()( }); } +// _____________________________________________________________________________ +template +bool BasicGraphPatternsInvariantTo::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 diff --git a/src/parser/GraphPatternAnalysis.h b/src/parser/GraphPatternAnalysis.h index 91f7c659c0..061dcc2913 100644 --- a/src/parser/GraphPatternAnalysis.h +++ b/src/parser/GraphPatternAnalysis.h @@ -35,17 +35,7 @@ struct BasicGraphPatternsInvariantTo { bool operator()(const parsedQuery::Values& values) const; template - 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; - } + bool operator()(const T&) const; }; } // namespace graphPatternAnalysis From 7d0fc418fa7a343bed385186d46640139d0a8bf4 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 16:20:54 +0100 Subject: [PATCH 06/12] add a test for string pair hash map --- test/CMakeLists.txt | 2 ++ test/StringPairHashMapTest.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 test/StringPairHashMapTest.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 32aa042017..f487f01d73 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -193,6 +193,8 @@ addLinkAndDiscoverTestSerial(QueryPlannerSpatialJoinTest engine) addLinkAndDiscoverTestNoLibs(HashMapTest) +addLinkAndDiscoverTestNoLibs(StringPairHashMapTest) + addLinkAndDiscoverTest(HashSetTest) addLinkAndDiscoverTestSerial(GroupByTest engine) diff --git a/test/StringPairHashMapTest.cpp b/test/StringPairHashMapTest.cpp new file mode 100644 index 0000000000..6ded6fa40b --- /dev/null +++ b/test/StringPairHashMapTest.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , UFR +// +// UFR = University of Freiburg, Chair of Algorithms and Data Structures + +#include + +#include "util/StringPairHashMap.h" + +// _____________________________________________________________________________ +TEST(StringPairHashMapTest, InsertAndLookup) { + ad_utility::StringPairHashMap map; + + using ad_utility::detail::StringPair; + using ad_utility::detail::StringViewPair; + + // Insert using `std::string` pairs. + map[StringPair{"hello", "world"}] = 7; + map[StringPair{"foo", "bar"}] = 42; + + ASSERT_EQ(map.size(), 2u); + + // Lookup using `std::string_view` pairs. + auto it = map.find(StringViewPair{"hello", "world"}); + ASSERT_NE(it, map.end()); + ASSERT_EQ(it->second, 7); + + EXPECT_EQ(map.count(StringViewPair{"foo", "bar"}), 1u); + EXPECT_EQ(map.count(StringViewPair{"doesnot", "exist"}), 0u); +} From 2d34cfc6684e4138dcf6ed8d18b7f96a880a8bfb Mon Sep 17 00:00:00 2001 From: ullingerc Date: Mon, 2 Feb 2026 18:14:39 +0100 Subject: [PATCH 07/12] make codespell happy --- test/StringPairHashMapTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/StringPairHashMapTest.cpp b/test/StringPairHashMapTest.cpp index 6ded6fa40b..aa09b5efc9 100644 --- a/test/StringPairHashMapTest.cpp +++ b/test/StringPairHashMapTest.cpp @@ -27,5 +27,5 @@ TEST(StringPairHashMapTest, InsertAndLookup) { ASSERT_EQ(it->second, 7); EXPECT_EQ(map.count(StringViewPair{"foo", "bar"}), 1u); - EXPECT_EQ(map.count(StringViewPair{"doesnot", "exist"}), 0u); + EXPECT_EQ(map.count(StringViewPair{"does not", "exist"}), 0u); } From a2ea70ba3f357ab853edd7273d32cbbe07f982d8 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Tue, 3 Feb 2026 09:16:57 +0100 Subject: [PATCH 08/12] Improve test coverage --- test/CMakeLists.txt | 2 ++ test/StringPairHashMapTest.cpp | 31 ++++++++++++++++++ test/VariantRangeFilterTest.cpp | 38 +++++++++++++++++++++ test/parser/CMakeLists.txt | 1 + test/parser/GraphPatternOperationTest.cpp | 40 +++++++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 test/VariantRangeFilterTest.cpp create mode 100644 test/parser/GraphPatternOperationTest.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f487f01d73..6653abc2ea 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -495,3 +495,5 @@ addLinkAndDiscoverTest(MaterializedViewsTest qlever engine server) addLinkAndDiscoverTestNoLibs(ConstexprMapTest) addLinkAndDiscoverTestNoLibs(ParallelExecutorTest) + +addLinkAndDiscoverTestNoLibs(VariantRangeFilterTest) diff --git a/test/StringPairHashMapTest.cpp b/test/StringPairHashMapTest.cpp index aa09b5efc9..d6ee1b3dfd 100644 --- a/test/StringPairHashMapTest.cpp +++ b/test/StringPairHashMapTest.cpp @@ -29,3 +29,34 @@ TEST(StringPairHashMapTest, InsertAndLookup) { EXPECT_EQ(map.count(StringViewPair{"foo", "bar"}), 1u); EXPECT_EQ(map.count(StringViewPair{"does not", "exist"}), 0u); } + +// _____________________________________________________________________________ +TEST(StringPairHashMapTest, StringPairEq) { + using ad_utility::detail::StringPair; + using ad_utility::detail::StringViewPair; + ad_utility::detail::StringPairEq eq; + + StringPair a{"a", "b"}; + StringPair b{"x", "y"}; + StringPair c{"x", "g"}; + + EXPECT_TRUE(eq(a, a)); + EXPECT_FALSE(eq(a, b)); + EXPECT_FALSE(eq(a, c)); + + StringViewPair aEq{"a", "b"}; + StringViewPair aNe{"a", "c"}; + StringViewPair bNe{"f", "g"}; + + EXPECT_TRUE(eq(a, aEq)); + EXPECT_FALSE(eq(a, aNe)); + EXPECT_FALSE(eq(b, bNe)); + + EXPECT_TRUE(eq(aEq, a)); + EXPECT_FALSE(eq(aNe, a)); + EXPECT_FALSE(eq(bNe, b)); + + StringViewPair aSv{"a", "b"}; + + EXPECT_TRUE(eq(a, aSv)); +} diff --git a/test/VariantRangeFilterTest.cpp b/test/VariantRangeFilterTest.cpp new file mode 100644 index 0000000000..ca9036fd56 --- /dev/null +++ b/test/VariantRangeFilterTest.cpp @@ -0,0 +1,38 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , UFR +// +// UFR = University of Freiburg, Chair of Algorithms and Data Structures + +#include + +#include "./util/GTestHelpers.h" +#include "util/VariantRangeFilter.h" + +namespace { + +// Helper for testing `filterRangeOfVariantsByType`. +template +void expectFilteredRange( + std::vector input, std::vector expected, + ad_utility::source_location location = AD_CURRENT_SOURCE_LOC()) { + auto l = generateLocationTrace(location); + auto matcher = liftMatcherToElementsAreArray>( + [](auto value) { return ::testing::Eq(value); }); + auto actual = ad_utility::filterRangeOfVariantsByType(input) | + ::ranges::to>; + EXPECT_THAT(actual, matcher(expected)); +} + +// _____________________________________________________________________________ +TEST(VariantRangeFilterTest, Test) { + using V = std::variant; + std::vector vec{1, 'c', true, false, true, 3, 'f'}; + + expectFilteredRange(vec, {1, 3}); + expectFilteredRange(vec, {'c', 'f'}); + expectFilteredRange(vec, {true, false, true}); + expectFilteredRange(vec, {}); +} + +} // namespace diff --git a/test/parser/CMakeLists.txt b/test/parser/CMakeLists.txt index 8810d5f332..45cfc320e7 100644 --- a/test/parser/CMakeLists.txt +++ b/test/parser/CMakeLists.txt @@ -8,6 +8,7 @@ addLinkAndDiscoverTest(BlankNodeExpressionTest engine) addLinkAndDiscoverTest(PropertyPathTest parser) addLinkAndDiscoverTest(UpdateTriplesTest parser) addLinkAndDiscoverTest(NamedCachedResultTest parser) +addLinkAndDiscoverTest(GraphPatternOperationTest parser) addLinkAndDiscoverTestSerial(SparqlAntlrParserTest parser engine) addLinkAndDiscoverTestSerial(SparqlAntlrParserUpdateTest parser engine) addLinkAndDiscoverTestSerial(SparqlAntlrParserExpressionTest parser sparqlExpressions engine) diff --git a/test/parser/GraphPatternOperationTest.cpp b/test/parser/GraphPatternOperationTest.cpp new file mode 100644 index 0000000000..4d456be5e9 --- /dev/null +++ b/test/parser/GraphPatternOperationTest.cpp @@ -0,0 +1,40 @@ +// Copyright 2026 The QLever Authors, in particular: +// +// 2026 Christoph Ullinger , UFR +// +// UFR = University of Freiburg, Chair of Algorithms and Data Structures + +#include + +#include "gmock/gmock.h" +#include "parser/GraphPatternOperation.h" +#include "parser/SparqlTriple.h" +#include "rdfTypes/Iri.h" + +// _____________________________________________________________________________ +TEST(GraphPatternOperationTest, BasicPatternContainedVars) { + SparqlTripleSimple example1{Variable{"?s"}, Variable{"?p"}, Variable{"?o"}}; + SparqlTripleSimple example2{ + ad_utility::triple_component::Iri::fromIriref(""), + ad_utility::triple_component::Iri::fromIriref("

"), Variable{"?o2"}}; + + auto triple1 = SparqlTriple::fromSimple(example1); + auto triple2 = SparqlTriple::fromSimple(example2); + + parsedQuery::BasicGraphPattern bgp{{triple1, triple2}}; + + ad_utility::HashSet vars; + bgp.collectAllContainedVariables(vars); + auto expectedVarsMatcher = ::testing::UnorderedElementsAre( + ::testing::Eq(Variable{"?s"}), ::testing::Eq(Variable{"?p"}), + ::testing::Eq(Variable{"?o"}), ::testing::Eq(Variable{"?o2"})); + EXPECT_THAT(vars, expectedVarsMatcher); + + parsedQuery::Bind bind{ + sparqlExpression::SparqlExpressionPimpl::makeVariableExpression( + Variable{"?x"}), + Variable{"?y"}}; + std::vector graphPatterns{bind, bgp}; + EXPECT_THAT(getVariablesPresentInFirstBasicGraphPattern(graphPatterns), + expectedVarsMatcher); +} From cd263f4173ebff82920b42fcab9ff5b76779ae12 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Tue, 3 Feb 2026 09:17:22 +0100 Subject: [PATCH 09/12] fix includes --- test/parser/GraphPatternOperationTest.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/parser/GraphPatternOperationTest.cpp b/test/parser/GraphPatternOperationTest.cpp index 4d456be5e9..71b11e9aae 100644 --- a/test/parser/GraphPatternOperationTest.cpp +++ b/test/parser/GraphPatternOperationTest.cpp @@ -4,12 +4,10 @@ // // UFR = University of Freiburg, Chair of Algorithms and Data Structures -#include +#include -#include "gmock/gmock.h" #include "parser/GraphPatternOperation.h" #include "parser/SparqlTriple.h" -#include "rdfTypes/Iri.h" // _____________________________________________________________________________ TEST(GraphPatternOperationTest, BasicPatternContainedVars) { From 41b733c5309081b52e6ed66d58e43a8e4e25d78f Mon Sep 17 00:00:00 2001 From: ullingerc Date: Tue, 3 Feb 2026 09:22:18 +0100 Subject: [PATCH 10/12] another test --- test/MaterializedViewsTest.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/MaterializedViewsTest.cpp b/test/MaterializedViewsTest.cpp index 887263df63..6331e0c6b2 100644 --- a/test/MaterializedViewsTest.cpp +++ b/test/MaterializedViewsTest.cpp @@ -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_); From af7416340149a06a1b41b242ebf0aec65501131f Mon Sep 17 00:00:00 2001 From: ullingerc Date: Tue, 3 Feb 2026 09:26:22 +0100 Subject: [PATCH 11/12] part of comment got lost --- src/util/StringPairHashMap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/StringPairHashMap.h b/src/util/StringPairHashMap.h index 9f984b364d..26a0083cd0 100644 --- a/src/util/StringPairHashMap.h +++ b/src/util/StringPairHashMap.h @@ -15,7 +15,7 @@ // hash and equality operators. // TODO This could be extended to support `std::tuple` or -// `std::array`, not only `std::pair`, and other transparently +// `std::array`, not only `std::pair`, and other transparently comparable types. // _____________________________________________________________________________ namespace ad_utility { From 95d02561c18351fa69b8c497ed135491abb94301 Mon Sep 17 00:00:00 2001 From: ullingerc Date: Tue, 3 Feb 2026 10:06:33 +0100 Subject: [PATCH 12/12] fix linker --- src/parser/GraphPatternAnalysis.cpp | 14 -------------- src/parser/GraphPatternAnalysis.h | 12 +++++++++++- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/parser/GraphPatternAnalysis.cpp b/src/parser/GraphPatternAnalysis.cpp index 55db3a28d7..8969420ae4 100644 --- a/src/parser/GraphPatternAnalysis.cpp +++ b/src/parser/GraphPatternAnalysis.cpp @@ -27,18 +27,4 @@ bool BasicGraphPatternsInvariantTo::operator()( }); } -// _____________________________________________________________________________ -template -bool BasicGraphPatternsInvariantTo::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 diff --git a/src/parser/GraphPatternAnalysis.h b/src/parser/GraphPatternAnalysis.h index 061dcc2913..91f7c659c0 100644 --- a/src/parser/GraphPatternAnalysis.h +++ b/src/parser/GraphPatternAnalysis.h @@ -35,7 +35,17 @@ struct BasicGraphPatternsInvariantTo { bool operator()(const parsedQuery::Values& values) const; template - bool operator()(const T&) const; + 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