diff --git a/src/engine/ExportQueryExecutionTrees.cpp b/src/engine/ExportQueryExecutionTrees.cpp index bae73af258..6e11be10eb 100644 --- a/src/engine/ExportQueryExecutionTrees.cpp +++ b/src/engine/ExportQueryExecutionTrees.cpp @@ -117,7 +117,9 @@ nlohmann::json ExportQueryExecutionTrees::idTableToQLeverJSONArray( nlohmann::json json = nlohmann::json::array(); for (size_t rowIndex : getRowIndices(limitAndOffset, data)) { - json.emplace_back(); + // We need the explicit `array` constructor for the special case of zero + // variables. + json.push_back(nlohmann::json::array()); auto& row = json.back(); for (const auto& opt : columns) { if (!opt) { @@ -390,12 +392,6 @@ nlohmann::json ExportQueryExecutionTrees::selectQueryResultBindingsToQLeverJSON( QueryExecutionTree::ColumnIndicesAndTypes selectedColumnIndices = qet.selectedVariablesToColumnIndices(selectClause, true); - // This can never happen, because empty SELECT clauses are not supported by - // QLever. Should we ever support triples without variables then this might - // theoretically happen in combination with `SELECT *`, but then this still - // can be changed. - AD_CORRECTNESS_CHECK(!selectedColumnIndices.empty()); - return ExportQueryExecutionTrees::idTableToQLeverJSONArray( qet, limitAndOffset, selectedColumnIndices, std::move(resultTable), std::move(cancellationHandle)); @@ -425,11 +421,6 @@ ExportQueryExecutionTrees::selectQueryResultToStream( << std::endl; auto selectedColumnIndices = qet.selectedVariablesToColumnIndices(selectClause, true); - // This case should only fail if we have no variables selected at all. - // This case should be handled earlier by the parser. - // TODO What do we want to do for variables that don't - // appear in the query body? - AD_CONTRACT_CHECK(!selectedColumnIndices.empty()); const auto& idTable = resultTable->idTable(); // special case : binary export of IdTable diff --git a/src/engine/QueryPlanner.cpp b/src/engine/QueryPlanner.cpp index eb0f47b2ec..a9318a7ebf 100644 --- a/src/engine/QueryPlanner.cpp +++ b/src/engine/QueryPlanner.cpp @@ -180,6 +180,10 @@ QueryExecutionTree QueryPlanner::createExecutionTree(ParsedQuery& pq) { std::vector QueryPlanner::optimize( ParsedQuery::GraphPattern* rootPattern) { + // Handle the empty pattern + if (rootPattern->_graphPatterns.empty()) { + return {makeSubtreePlan(_qec)}; + } // here we collect a set of possible plans for each of our children. // always only holds plans for children that can be joined in an // arbitrary order @@ -215,11 +219,6 @@ std::vector QueryPlanner::optimize( // find a single best candidate for a given graph pattern auto optimizeSingle = [this](const auto pattern) -> SubtreePlan { auto v = optimize(pattern); - if (v.empty()) { - throw std::runtime_error( - "grandchildren or lower of a Plan to be optimized may never be " - "empty"); - } auto idx = findCheapestExecutionTree(v); return std::move(v[idx]); }; @@ -279,11 +278,8 @@ std::vector QueryPlanner::optimize( } else { static_assert( std::is_same_v, std::decay_t>); - if (v.empty()) { - throw std::runtime_error( - "grandchildren or lower of a Plan to be optimized may never be " - "empty. Please report this"); - } + // Empty group graph patterns should have been handled previously. + AD_CORRECTNESS_CHECK(!v.empty()); // optionals that occur before any of their variables have been bound // actually behave like ordinary (Group)GraphPatterns @@ -382,6 +378,7 @@ std::vector QueryPlanner::optimize( makeSubtreePlan(_qec, left._qet, right._qet); joinCandidates(std::vector{std::move(candidate)}); } else if constexpr (std::is_same_v) { + ParsedQuery& subquery = arg.get(); // TODO We currently do not optimize across subquery borders // but abuse them as "optimization hints". In theory, one could even // remove the ORDER BY clauses of a subquery if we can prove that @@ -389,7 +386,7 @@ std::vector QueryPlanner::optimize( // For a subquery, make sure that one optimal result for each ordering // of the result (by a single column) is contained. - auto candidatesForSubquery = createExecutionTrees(arg.get()); + auto candidatesForSubquery = createExecutionTrees(subquery); // Make sure that variables that are not selected by the subquery are // not visible. auto setSelectedVariables = [&](SubtreePlan& plan) { diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.cpp b/src/parser/sparqlParser/SparqlQleverVisitor.cpp index 8ba0f12752..52a8e23fe1 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.cpp +++ b/src/parser/sparqlParser/SparqlQleverVisitor.cpp @@ -336,13 +336,6 @@ GraphPattern Visitor::visit(Parser::GroupGraphPatternContext* ctx) { } else { AD_CORRECTNESS_CHECK(ctx->groupGraphPatternSub()); auto [subOps, filters] = visit(ctx->groupGraphPatternSub()); - - if (subOps.empty()) { - reportError(ctx, - "QLever currently doesn't support empty GroupGraphPatterns " - "and WHERE clauses"); - } - pattern._graphPatterns = std::move(subOps); for (auto& filter : filters) { if (auto langFilterData = diff --git a/test/QueryPlannerTest.cpp b/test/QueryPlannerTest.cpp index 36c3273fb8..ae8a24c66b 100644 --- a/test/QueryPlannerTest.cpp +++ b/test/QueryPlannerTest.cpp @@ -897,6 +897,17 @@ TEST(QueryPlanner, NonDistinctVariablesInTriple) { h::IndexScanFromStrings(internalVar(0), "?s", ""))); } +TEST(QueryPlanner, emptyGroupGraphPattern) { + 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())); +} + // __________________________________________________________________________ TEST(QueryPlanner, TooManyTriples) { std::string query = "SELECT * WHERE {"; diff --git a/test/QueryPlannerTestHelpers.h b/test/QueryPlannerTestHelpers.h index 09c0f3a6ff..301e9ff019 100644 --- a/test/QueryPlannerTestHelpers.h +++ b/test/QueryPlannerTestHelpers.h @@ -19,6 +19,7 @@ #include "engine/TextIndexScanForEntity.h" #include "engine/TextIndexScanForWord.h" #include "engine/TransitivePath.h" +#include "engine/Union.h" #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" #include "parser/SparqlParser.h" @@ -71,7 +72,7 @@ inline auto MatchTypeAndOrderedChildren = /// single `IndexScan` with the given `subject`, `predicate`, and `object`, and /// that the `ScanType` of this `IndexScan` is any of the given /// `allowedPermutations`. -inline auto IndexScan = +constexpr auto IndexScan = [](TripleComponent subject, TripleComponent predicate, TripleComponent object, const std::vector& allowedPermutations = {}) @@ -90,8 +91,13 @@ inline auto IndexScan = AD_PROPERTY(IndexScan, getObject, Eq(object)))); }; -inline auto TextIndexScanForWord = [](Variable textRecordVar, - string word) -> QetMatcher { +// Match the `NeutralElementOperation`. +constexpr auto NeutralElement = []() -> QetMatcher { + return MatchTypeAndOrderedChildren<::NeutralElementOperation>(); +}; + +constexpr auto TextIndexScanForWord = [](Variable textRecordVar, + string word) -> QetMatcher { return RootOperation<::TextIndexScanForWord>(AllOf( AD_PROPERTY(::TextIndexScanForWord, getResultWidth, Eq(1 + word.ends_with('*'))), @@ -255,6 +261,9 @@ constexpr auto OrderBy = [](const ::OrderBy::SortedVariables& sortedVariables, AD_PROPERTY(::OrderBy, getSortedVariables, Eq(sortedVariables)))); }; +// Match a `UNION` operation. +constexpr auto Union = MatchTypeAndOrderedChildren<::Union>; + /// Parse the given SPARQL `query`, pass it to a `QueryPlanner` with empty /// execution context, and return the resulting `QueryExecutionTree` QueryExecutionTree parseAndPlan(std::string query, QueryExecutionContext* qec) { diff --git a/test/SparqlAntlrParserTest.cpp b/test/SparqlAntlrParserTest.cpp index 9fb011ad6e..c4f7e1b8d8 100644 --- a/test/SparqlAntlrParserTest.cpp +++ b/test/SparqlAntlrParserTest.cpp @@ -867,9 +867,11 @@ TEST(SparqlParser, GroupGraphPattern) { ExpectParseFails<&Parser::groupGraphPattern>{{}}; auto DummyTriplesMatcher = m::Triples({{Var{"?x"}, "?y", Var{"?z"}}}); - // Empty GraphPatterns are not supported. - expectGroupGraphPatternFails("{ }"); - expectGroupGraphPatternFails("{ SELECT * WHERE { } }"); + // Empty GraphPatterns. + expectGraphPattern("{ }", m::GraphPattern()); + expectGraphPattern( + "{ SELECT * WHERE { } }", + m::GraphPattern(m::SubSelect(::testing::_, m::GraphPattern()))); SparqlTriple abc{Var{"?a"}, "?b", Var{"?c"}}; SparqlTriple def{Var{"?d"}, "?e", Var{"?f"}};