diff --git a/src/engine/ExportQueryExecutionTrees.cpp b/src/engine/ExportQueryExecutionTrees.cpp index f92be076f2..fe58b3204a 100644 --- a/src/engine/ExportQueryExecutionTrees.cpp +++ b/src/engine/ExportQueryExecutionTrees.cpp @@ -13,14 +13,9 @@ #include "util/http/MediaTypes.h" // __________________________________________________________________________ -namespace { -struct IndexWithTable { - size_t index_; - const IdTable& idTable_; -}; - -cppcoro::generator getIdTables(const Result& result) { +cppcoro::generator ExportQueryExecutionTrees::getIdTables( + const Result& result) { if (result.isDataEvaluated()) { co_yield result.idTable(); } else { @@ -29,11 +24,13 @@ cppcoro::generator getIdTables(const Result& result) { } } } + // Return a range that contains the indices of the rows that have to be exported // from the `idTable` given the `LimitOffsetClause`. It takes into account the // LIMIT, the OFFSET, and the actual size of the `idTable` -cppcoro::generator getRowIndices(LimitOffsetClause limitOffset, - const Result& result) { +cppcoro::generator +ExportQueryExecutionTrees::getRowIndices(LimitOffsetClause limitOffset, + const Result& result) { for (const IdTable& idTable : getIdTables(result)) { uint64_t currentOffset = limitOffset.actualOffset(idTable.numRows()); uint64_t upperBound = limitOffset.upperBound(idTable.numRows()); @@ -47,7 +44,6 @@ cppcoro::generator getRowIndices(LimitOffsetClause limitOffset, } } } -} // namespace // _____________________________________________________________________________ cppcoro::generator diff --git a/src/engine/ExportQueryExecutionTrees.h b/src/engine/ExportQueryExecutionTrees.h index 4e8db28e0e..a85e35e546 100644 --- a/src/engine/ExportQueryExecutionTrees.h +++ b/src/engine/ExportQueryExecutionTrees.h @@ -177,4 +177,28 @@ class ExportQueryExecutionTrees { const QueryExecutionTree& qet, const parsedQuery::SelectClause& selectClause, LimitOffsetClause limitAndOffset, CancellationHandle cancellationHandle); + + struct IndexWithTable { + size_t index_; + const IdTable& idTable_; + }; + + static cppcoro::generator getIdTables(const Result& result); + // Return a range that contains the indices of the rows that have to be + // exported from the `idTable` given the `LimitOffsetClause`. It takes into + // account the LIMIT, the OFFSET, and the actual size of the `idTable` + static cppcoro::generator getRowIndices( + LimitOffsetClause limitOffset, const Result& result); + + FRIEND_TEST(ExportQueryExecutionTrees, getIdTablesReturnsSingletonIterator); + FRIEND_TEST(ExportQueryExecutionTrees, getIdTablesMirrorsGenerator); + FRIEND_TEST(ExportQueryExecutionTrees, ensureCorrectSlicingOfSingleIdTable); + FRIEND_TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstIsSkipped); + FRIEND_TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenLastIsSkipped); + FRIEND_TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstAndSecondArePartial); + FRIEND_TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstAndLastArePartial); }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a83d8b0cb2..59ace1df30 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -302,7 +302,7 @@ addLinkAndDiscoverTestSerial(OrderByTest engine) addLinkAndDiscoverTestSerial(ValuesForTestingTest index) -addLinkAndDiscoverTestSerial(ExportQueryExecutionTreeTest index engine parser) +addLinkAndDiscoverTestSerial(ExportQueryExecutionTreesTest index engine parser) addLinkAndDiscoverTestSerial(AggregateExpressionTest parser sparqlExpressions index engine) diff --git a/test/ExportQueryExecutionTreeTest.cpp b/test/ExportQueryExecutionTreesTest.cpp similarity index 77% rename from test/ExportQueryExecutionTreeTest.cpp rename to test/ExportQueryExecutionTreesTest.cpp index 359a9407c6..b432c6b2b3 100644 --- a/test/ExportQueryExecutionTreeTest.cpp +++ b/test/ExportQueryExecutionTreesTest.cpp @@ -210,7 +210,7 @@ static std::string makeXMLHeader( static const std::string xmlTrailer = "\n\n"; // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, Integers) { +TEST(ExportQueryExecutionTrees, Integers) { std::string kg = "

42 .

-42019234865781 .

4012934858173560"; std::string query = "SELECT ?o WHERE {?s ?p ?o} ORDER BY ?o"; @@ -276,7 +276,7 @@ TEST(ExportQueryExecutionTree, Integers) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, Bool) { +TEST(ExportQueryExecutionTrees, Bool) { std::string kg = "

true .

false."; std::string query = "SELECT ?o WHERE {?s ?p ?o} ORDER BY ?o"; @@ -330,7 +330,7 @@ TEST(ExportQueryExecutionTree, Bool) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, UnusedVariable) { +TEST(ExportQueryExecutionTrees, UnusedVariable) { std::string kg = "

true .

false."; std::string query = "SELECT ?o WHERE {?s ?p ?x} ORDER BY ?s"; std::string expectedXml = makeXMLHeader({"o"}) + R"( @@ -366,7 +366,7 @@ TEST(ExportQueryExecutionTree, UnusedVariable) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, Floats) { +TEST(ExportQueryExecutionTrees, Floats) { std::string kg = "

42.2 .

-42019234865.781e12 .

" "4.012934858173560e-12"; @@ -434,7 +434,7 @@ TEST(ExportQueryExecutionTree, Floats) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, Dates) { +TEST(ExportQueryExecutionTrees, Dates) { std::string kg = "

" "\"1950-01-01T00:00:00\"^^."; @@ -493,7 +493,7 @@ TEST(ExportQueryExecutionTree, Dates) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, Entities) { +TEST(ExportQueryExecutionTrees, Entities) { std::string kg = "PREFIX qlever: \n

qlever:o"; std::string query = "SELECT ?o WHERE {?s ?p ?o} ORDER BY ?o"; std::string expectedXml = makeXMLHeader({"o"}) + @@ -540,7 +540,7 @@ TEST(ExportQueryExecutionTree, Entities) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, LiteralWithLanguageTag) { +TEST(ExportQueryExecutionTrees, LiteralWithLanguageTag) { std::string kg = "

\"\"\"Some\"Where\tOver,\"\"\"@en-ca."; std::string query = "SELECT ?o WHERE {?s ?p ?o} ORDER BY ?o"; std::string expectedXml = makeXMLHeader({"o"}) + @@ -589,7 +589,7 @@ TEST(ExportQueryExecutionTree, LiteralWithLanguageTag) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, LiteralWithDatatype) { +TEST(ExportQueryExecutionTrees, LiteralWithDatatype) { std::string kg = "

\"something\"^^"; std::string query = "SELECT ?o WHERE {?s ?p ?o} ORDER BY ?o"; std::string expectedXml = makeXMLHeader({"o"}) + @@ -637,7 +637,7 @@ TEST(ExportQueryExecutionTree, LiteralWithDatatype) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, UndefinedValues) { +TEST(ExportQueryExecutionTrees, UndefinedValues) { std::string kg = "

"; std::string query = "SELECT ?o WHERE {?s

OPTIONAL {?s ?o}} ORDER BY ?o"; @@ -676,7 +676,7 @@ TEST(ExportQueryExecutionTree, UndefinedValues) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, BlankNode) { +TEST(ExportQueryExecutionTrees, BlankNode) { std::string kg = "

_:blank"; std::string objectQuery = "SELECT ?o WHERE {?s ?p ?o } ORDER BY ?o"; std::string expectedXml = makeXMLHeader({"o"}) + @@ -702,7 +702,7 @@ TEST(ExportQueryExecutionTree, BlankNode) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, TextIndex) { +TEST(ExportQueryExecutionTrees, TextIndex) { std::string kg = "

\"alpha beta\". \"alphax betax\". "; std::string objectQuery = "SELECT ?o WHERE {

?t. ?text ql:contains-entity ?t .?text " @@ -728,7 +728,7 @@ TEST(ExportQueryExecutionTree, TextIndex) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, MultipleVariables) { +TEST(ExportQueryExecutionTrees, MultipleVariables) { std::string kg = "

"; std::string objectQuery = "SELECT ?p ?o WHERE { ?p ?o } ORDER BY ?p ?o"; std::string expectedXml = makeXMLHeader({"p", "o"}) + @@ -765,7 +765,7 @@ TEST(ExportQueryExecutionTree, MultipleVariables) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, BinaryExport) { +TEST(ExportQueryExecutionTrees, BinaryExport) { std::string kg = "

31 . 42"; std::string query = "SELECT ?p ?o WHERE { ?p ?o } ORDER BY ?p ?o"; std::string result = @@ -790,7 +790,7 @@ TEST(ExportQueryExecutionTree, BinaryExport) { } // ____________________________________________________________________________ -TEST(ExportQueryExecutionTree, CornerCases) { +TEST(ExportQueryExecutionTrees, CornerCases) { std::string kg = "

"; std::string query = "SELECT ?p ?o WHERE { ?p ?o } ORDER BY ?p ?o"; std::string constructQuery = @@ -907,3 +907,264 @@ INSTANTIATE_TEST_SUITE_P(StreamableMediaTypes, StreamableMediaTypesFixture, // TODO Unit tests that also test for the export of text records from // the text index and thus systematically fill the coverage gaps. + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, getIdTablesReturnsSingletonIterator) { + IdTable idTable{1, ad_utility::makeUnlimitedAllocator()}; + idTable.push_back({Id::makeFromInt(42)}); + idTable.push_back({Id::makeFromInt(1337)}); + + Result result{std::move(idTable), {}, LocalVocab{}}; + auto generator = ExportQueryExecutionTrees::getIdTables(result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + ASSERT_EQ(iterator->size(), 2); + EXPECT_EQ(iterator->at(0)[0], Id::makeFromInt(42)); + EXPECT_EQ(iterator->at(1)[0], Id::makeFromInt(1337)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, getIdTablesMirrorsGenerator) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + + IdTable idTable2{1, ad_utility::makeUnlimitedAllocator()}; + idTable2.push_back({Id::makeFromInt(42)}); + idTable2.push_back({Id::makeFromInt(1337)}); + + co_yield std::move(idTable2); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getIdTables(result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + ASSERT_EQ(iterator->size(), 3); + EXPECT_EQ(iterator->at(0)[0], Id::makeFromInt(1)); + EXPECT_EQ(iterator->at(1)[0], Id::makeFromInt(2)); + EXPECT_EQ(iterator->at(2)[0], Id::makeFromInt(3)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + ASSERT_EQ(iterator->size(), 2); + EXPECT_EQ(iterator->at(0)[0], Id::makeFromInt(42)); + EXPECT_EQ(iterator->at(1)[0], Id::makeFromInt(1337)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, ensureCorrectSlicingOfSingleIdTable) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getRowIndices( + LimitOffsetClause{._limit = 1, ._offset = 1}, result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(2)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstIsSkipped) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + + IdTable idTable2{1, ad_utility::makeUnlimitedAllocator()}; + idTable2.push_back({Id::makeFromInt(4)}); + idTable2.push_back({Id::makeFromInt(5)}); + + co_yield std::move(idTable2); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getRowIndices( + LimitOffsetClause{._limit = std::nullopt, ._offset = 3}, result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(4)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(5)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenLastIsSkipped) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + + IdTable idTable2{1, ad_utility::makeUnlimitedAllocator()}; + idTable2.push_back({Id::makeFromInt(4)}); + idTable2.push_back({Id::makeFromInt(5)}); + + co_yield std::move(idTable2); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getRowIndices( + LimitOffsetClause{._limit = 3}, result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(1)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(2)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(3)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstAndSecondArePartial) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + + IdTable idTable2{1, ad_utility::makeUnlimitedAllocator()}; + idTable2.push_back({Id::makeFromInt(4)}); + idTable2.push_back({Id::makeFromInt(5)}); + + co_yield std::move(idTable2); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getRowIndices( + LimitOffsetClause{._limit = 3, ._offset = 1}, result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(2)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(3)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(4)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +} + +// _____________________________________________________________________________ +TEST(ExportQueryExecutionTrees, + ensureCorrectSlicingOfIdTablesWhenFirstAndLastArePartial) { + auto tableGenerator = []() -> cppcoro::generator { + IdTable idTable1{1, ad_utility::makeUnlimitedAllocator()}; + idTable1.push_back({Id::makeFromInt(1)}); + idTable1.push_back({Id::makeFromInt(2)}); + idTable1.push_back({Id::makeFromInt(3)}); + + co_yield std::move(idTable1); + + IdTable idTable2{1, ad_utility::makeUnlimitedAllocator()}; + idTable2.push_back({Id::makeFromInt(4)}); + idTable2.push_back({Id::makeFromInt(5)}); + + co_yield std::move(idTable2); + + IdTable idTable3{1, ad_utility::makeUnlimitedAllocator()}; + idTable3.push_back({Id::makeFromInt(6)}); + idTable3.push_back({Id::makeFromInt(7)}); + idTable3.push_back({Id::makeFromInt(8)}); + idTable3.push_back({Id::makeFromInt(9)}); + + co_yield std::move(idTable3); + }(); + + Result result = Result::createResultAsMasterConsumer( + std::make_shared( + Result{std::move(tableGenerator), {}, LocalVocab{}}), + []() {}); + auto generator = ExportQueryExecutionTrees::getRowIndices( + LimitOffsetClause{._limit = 5, ._offset = 2}, result); + + auto iterator = generator.begin(); + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(3)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(4)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(5)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(6)); + + ++iterator; + ASSERT_NE(iterator, generator.end()); + EXPECT_EQ(iterator->idTable_.at(iterator->index_)[0], Id::makeFromInt(7)); + + ++iterator; + EXPECT_EQ(iterator, generator.end()); +}