diff --git a/src/engine/QueryPlanner.cpp b/src/engine/QueryPlanner.cpp index be7cfe27d5..19c81b7007 100644 --- a/src/engine/QueryPlanner.cpp +++ b/src/engine/QueryPlanner.cpp @@ -1553,6 +1553,9 @@ vector> QueryPlanner::fillDpTab( textLimitIds |= plan.idsOfIncludedTextLimits_; subtrees.push_back(std::move(plan._qet)); }); + // Ensure estimated largest tree is on the right side. + ql::ranges::sort(subtrees, {}, + [](const auto& tree) { return tree->getSizeEstimate(); }); result.at(0).push_back( makeSubtreePlan(_qec, std::move(subtrees))); auto& plan = result.at(0).back(); diff --git a/test/QueryPlannerTest.cpp b/test/QueryPlannerTest.cpp index e2135f80c5..d028af2a6a 100644 --- a/test/QueryPlannerTest.cpp +++ b/test/QueryPlannerTest.cpp @@ -2981,3 +2981,32 @@ TEST(QueryPlanner, Exists) { "GRAPH ?g { ?u ?v ?c}}}", filter); } + +// _____________________________________________________________________________ +TEST(QueryPlanner, CartesianProductJoinChildrenAreOrdered) { + auto qp = makeQueryPlanner(); + { + auto query = + SparqlParser::parseQuery("SELECT * { VALUES ?x {1} VALUES ?y {2 3} }"); + auto qet = qp.createExecutionTree(query); + EXPECT_TRUE(std::dynamic_pointer_cast( + qet.getRootOperation())); + ASSERT_EQ(qet.getRootOperation()->getChildren().size(), 2); + EXPECT_EQ(qet.getRootOperation()->getChildren().at(0)->getCacheKey(), + "VALUES (?x) { (1) }"); + EXPECT_EQ(qet.getRootOperation()->getChildren().at(1)->getCacheKey(), + "VALUES (?y) { (2) (3) }"); + } + { + auto query = + SparqlParser::parseQuery("SELECT * { VALUES ?y {2 3} VALUES ?x {1} }"); + auto qet = qp.createExecutionTree(query); + EXPECT_TRUE(std::dynamic_pointer_cast( + qet.getRootOperation())); + ASSERT_EQ(qet.getRootOperation()->getChildren().size(), 2); + EXPECT_EQ(qet.getRootOperation()->getChildren().at(0)->getCacheKey(), + "VALUES (?x) { (1) }"); + EXPECT_EQ(qet.getRootOperation()->getChildren().at(1)->getCacheKey(), + "VALUES (?y) { (2) (3) }"); + } +}