Skip to content

Commit fefa3f9

Browse files
authored
Merge main into develop (#5379)
2 parents 33ad2f2 + 13e0df6 commit fefa3f9

File tree

3 files changed

+283
-21
lines changed

3 files changed

+283
-21
lines changed

core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/ProjectionElemList.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.eclipse.rdf4j.query.algebra;
1212

1313
import java.util.ArrayList;
14+
import java.util.Arrays;
1415
import java.util.Collection;
1516
import java.util.Collections;
1617
import java.util.LinkedHashSet;
@@ -148,7 +149,7 @@ public ProjectionElemList clone() {
148149
clone.elements[i].setParentNode(clone);
149150
}
150151

151-
clone.elementsList = List.of(clone.elements);
152+
clone.elementsList = Arrays.asList(clone.elements);
152153

153154
return clone;
154155
}

core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/collectors/StatementPatternCollector.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
package org.eclipse.rdf4j.query.algebra.helpers.collectors;
1313

14+
import java.util.ArrayDeque;
1415
import java.util.ArrayList;
16+
import java.util.Deque;
1517
import java.util.List;
1618

1719
import org.eclipse.rdf4j.query.algebra.Filter;
@@ -50,33 +52,32 @@ public void meet(Filter node) {
5052

5153
@Override
5254
public void meet(Join node) throws RuntimeException {
53-
TupleExpr leftArg = node.getLeftArg();
54-
TupleExpr rightArg = node.getRightArg();
55-
56-
// INSERT clause is often a deeply nested join. Recursive approach may cause stack overflow. Attempt
57-
// non-recursive (or at least less-recursive) approach first.
58-
while (true) {
59-
if (leftArg instanceof Join && !(rightArg instanceof Join)) {
60-
rightArg.visit(this);
55+
if (!(node.getLeftArg() instanceof Join || node.getRightArg() instanceof Join)) {
56+
super.meet(node);
57+
return;
58+
}
6159

62-
Join join = (Join) leftArg;
63-
leftArg = join.getLeftArg();
64-
rightArg = join.getRightArg();
60+
Deque<TupleExpr> stack = new ArrayDeque<>();
61+
TupleExpr current = node;
6562

66-
} else if (rightArg instanceof Join && !(leftArg instanceof Join)) {
67-
leftArg.visit(this);
63+
while (true) {
64+
// Drill down the leftmost spine, pushing right branches onto the stack
65+
while (current instanceof Join) {
66+
Join join = (Join) current;
67+
stack.push(join.getRightArg()); // defer right side
68+
current = join.getLeftArg(); // continue with left side
69+
}
6870

69-
Join join = (Join) rightArg;
70-
leftArg = join.getLeftArg();
71-
rightArg = join.getRightArg();
71+
// current is a leaf (not a Join)
72+
current.visit(this);
7273

73-
} else {
74-
leftArg.visit(this);
75-
rightArg.visit(this);
74+
// When the stack is empty, we have visited every deferred right branch
75+
if (stack.isEmpty()) {
7676
return;
7777
}
78+
// Pop the next right branch to process
79+
current = stack.pop();
7880
}
79-
8081
}
8182

8283
@Override

core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/QueryPlanRetrievalTest.java

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.eclipse.rdf4j.model.vocabulary.FOAF;
2828
import org.eclipse.rdf4j.model.vocabulary.RDF;
2929
import org.eclipse.rdf4j.model.vocabulary.RDFS;
30+
import org.eclipse.rdf4j.query.GraphQuery;
3031
import org.eclipse.rdf4j.query.Query;
3132
import org.eclipse.rdf4j.query.TupleQuery;
3233
import org.eclipse.rdf4j.query.explanation.Explanation;
@@ -101,6 +102,60 @@ public class QueryPlanRetrievalTest {
101102
" }\n" +
102103
"} GROUP BY ?countryID ?year";
103104

105+
public static final String CONSTRUCT = "PREFIX epo: <http://data.europa.eu/a4g/ontology#>\n" +
106+
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" +
107+
"PREFIX legal: <https://www.w3.org/ns/legal#>\n" +
108+
"PREFIX dcterms: <http://purl.org/dc/terms#>\n" +
109+
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" +
110+
"PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" +
111+
"\n" +
112+
"CONSTRUCT {" +
113+
"\n" +
114+
" ?proc a epo:Procedure .\n" +
115+
" ?proc epo:hasProcedureType ?p .\n" +
116+
" ?stat epo:concernsSubmissionsForLot ?lot .\n" +
117+
" ?stat a epo:SubmissionStatisticalInformation .\n" +
118+
" ?stat epo:hasReceivedTenders ?bidders .\n" +
119+
" ?resultnotice a epo:ResultNotice .\n" +
120+
" ?resultnotice epo:hasDispatchDate ?ddate .\n" +
121+
" ?proc epo:hasProcurementScopeDividedIntoLot ?lot .\n" +
122+
" ?resultnotice epo:refersToRole ?buyerrole .\n" +
123+
" ?resultnotice epo:refersToProcedure ?proc .\n" +
124+
"}" +
125+
" WHERE {\n"
126+
+
127+
"\n" +
128+
" ?proc a epo:Procedure .\n" +
129+
" ?proc epo:hasProcedureType ?p .\n" +
130+
" ?stat epo:concernsSubmissionsForLot ?lot .\n" +
131+
" ?stat a epo:SubmissionStatisticalInformation .\n" +
132+
" ?stat epo:hasReceivedTenders ?bidders .\n" +
133+
" ?resultnotice a epo:ResultNotice .\n" +
134+
" ?resultnotice epo:hasDispatchDate ?ddate .\n" +
135+
" ?proc epo:hasProcurementScopeDividedIntoLot ?lot .\n" +
136+
" ?resultnotice epo:refersToRole ?buyerrole .\n" +
137+
" ?resultnotice epo:refersToProcedure ?proc .\n" +
138+
"\n" +
139+
" \tFILTER ( ?p != <http://publications.europa.eu/resource/authority/procurement-procedure-type/neg-wo-call>)\n"
140+
+
141+
"\t\tBIND(year(xsd:dateTime(?ddate)) AS ?year) .\n" +
142+
"\n" +
143+
"\n" +
144+
" {\n" +
145+
" SELECT DISTINCT ?buyerrole ?countryID WHERE {\n" +
146+
" ?org epo:hasBuyerType ?buytype .\n" +
147+
" FILTER (?buytype != <http://publications.europa.eu/resource/authority/buyer-legal-type/eu-int-org> )\n"
148+
+
149+
"\n" +
150+
" ?buyerrole epo:playedBy ?org .\n" +
151+
" ?org legal:registeredAddress ?orgaddress .\n" +
152+
" ?orgaddress epo:hasCountryCode ?countrycode .\n" +
153+
" ?countrycode dc:identifier ?countryID .\n" +
154+
"\n" +
155+
" }\n" +
156+
" }\n" +
157+
"} GROUP BY ?countryID ?year";
158+
104159
public static final String UNION_QUERY = "select ?a where {?a a ?type. {?a ?b ?c, ?c2. {?c2 a ?type1}UNION{?c2 a ?type2}} UNION {?type ?d ?c}}";
105160

106161
ValueFactory vf = SimpleValueFactory.getInstance();
@@ -1534,6 +1589,211 @@ public void testArbitraryLengthPath() {
15341589

15351590
}
15361591

1592+
@Test
1593+
public void constructQueryTest() {
1594+
1595+
String expected = "Reduced\n" +
1596+
" MultiProjection\n" +
1597+
" ProjectionElemList\n" +
1598+
" ProjectionElem \"proc\" AS \"subject\"\n" +
1599+
" ProjectionElem \"_const_f5e5585a_uri\" AS \"predicate\"\n" +
1600+
" ProjectionElem \"_const_be18ee7b_uri\" AS \"object\"\n" +
1601+
" ProjectionElemList\n" +
1602+
" ProjectionElem \"proc\" AS \"subject\"\n" +
1603+
" ProjectionElem \"_const_9c756f6b_uri\" AS \"predicate\"\n" +
1604+
" ProjectionElem \"p\" AS \"object\"\n" +
1605+
" ProjectionElemList\n" +
1606+
" ProjectionElem \"stat\" AS \"subject\"\n" +
1607+
" ProjectionElem \"_const_25686184_uri\" AS \"predicate\"\n" +
1608+
" ProjectionElem \"lot\" AS \"object\"\n" +
1609+
" ProjectionElemList\n" +
1610+
" ProjectionElem \"stat\" AS \"subject\"\n" +
1611+
" ProjectionElem \"_const_f5e5585a_uri\" AS \"predicate\"\n" +
1612+
" ProjectionElem \"_const_ea79e75_uri\" AS \"object\"\n" +
1613+
" ProjectionElemList\n" +
1614+
" ProjectionElem \"stat\" AS \"subject\"\n" +
1615+
" ProjectionElem \"_const_98c73a3c_uri\" AS \"predicate\"\n" +
1616+
" ProjectionElem \"bidders\" AS \"object\"\n" +
1617+
" ProjectionElemList\n" +
1618+
" ProjectionElem \"resultnotice\" AS \"subject\"\n" +
1619+
" ProjectionElem \"_const_f5e5585a_uri\" AS \"predicate\"\n" +
1620+
" ProjectionElem \"_const_77e914ad_uri\" AS \"object\"\n" +
1621+
" ProjectionElemList\n" +
1622+
" ProjectionElem \"resultnotice\" AS \"subject\"\n" +
1623+
" ProjectionElem \"_const_1b0b00ca_uri\" AS \"predicate\"\n" +
1624+
" ProjectionElem \"ddate\" AS \"object\"\n" +
1625+
" ProjectionElemList\n" +
1626+
" ProjectionElem \"proc\" AS \"subject\"\n" +
1627+
" ProjectionElem \"_const_9c3f1eec_uri\" AS \"predicate\"\n" +
1628+
" ProjectionElem \"lot\" AS \"object\"\n" +
1629+
" ProjectionElemList\n" +
1630+
" ProjectionElem \"resultnotice\" AS \"subject\"\n" +
1631+
" ProjectionElem \"_const_6aa9a9c_uri\" AS \"predicate\"\n" +
1632+
" ProjectionElem \"buyerrole\" AS \"object\"\n" +
1633+
" ProjectionElemList\n" +
1634+
" ProjectionElem \"resultnotice\" AS \"subject\"\n" +
1635+
" ProjectionElem \"_const_183bd06d_uri\" AS \"predicate\"\n" +
1636+
" ProjectionElem \"proc\" AS \"object\"\n" +
1637+
" Extension\n" +
1638+
" Group (countryID, year)\n" +
1639+
" Join (HashJoinIteration)\n" +
1640+
" ╠══ Extension [left]\n" +
1641+
" ║ ├── Join (JoinIterator)\n" +
1642+
" ║ │ ╠══ StatementPattern (costEstimate=0.71, resultSizeEstimate=0) [left]\n" +
1643+
" ║ │ ║ s: Var (name=resultnotice)\n" +
1644+
" ║ │ ║ p: Var (name=_const_183bd06d_uri, value=http://data.europa.eu/a4g/ontology#refersToProcedure, anonymous)\n"
1645+
+
1646+
" ║ │ ║ o: Var (name=proc)\n" +
1647+
" ║ │ ╚══ Join (JoinIterator) [right]\n" +
1648+
" ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0) [left]\n" +
1649+
" ║ │ │ s: Var (name=proc)\n" +
1650+
" ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous)\n"
1651+
+
1652+
" ║ │ │ o: Var (name=_const_be18ee7b_uri, value=http://data.europa.eu/a4g/ontology#Procedure, anonymous)\n"
1653+
+
1654+
" ║ │ └── Join (JoinIterator) [right]\n" +
1655+
" ║ │ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0) [left]\n" +
1656+
" ║ │ ║ s: Var (name=resultnotice)\n" +
1657+
" ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous)\n"
1658+
+
1659+
" ║ │ ║ o: Var (name=_const_77e914ad_uri, value=http://data.europa.eu/a4g/ontology#ResultNotice, anonymous)\n"
1660+
+
1661+
" ║ │ ╚══ Join (JoinIterator) [right]\n" +
1662+
" ║ │ ├── StatementPattern (costEstimate=1.12, resultSizeEstimate=0) [left]\n" +
1663+
" ║ │ │ s: Var (name=proc)\n" +
1664+
" ║ │ │ p: Var (name=_const_9c3f1eec_uri, value=http://data.europa.eu/a4g/ontology#hasProcurementScopeDividedIntoLot, anonymous)\n"
1665+
+
1666+
" ║ │ │ o: Var (name=lot)\n" +
1667+
" ║ │ └── Join (JoinIterator) [right]\n" +
1668+
" ║ │ ╠══ StatementPattern (costEstimate=0.75, resultSizeEstimate=0) [left]\n"
1669+
+
1670+
" ║ │ ║ s: Var (name=stat)\n" +
1671+
" ║ │ ║ p: Var (name=_const_25686184_uri, value=http://data.europa.eu/a4g/ontology#concernsSubmissionsForLot, anonymous)\n"
1672+
+
1673+
" ║ │ ║ o: Var (name=lot)\n" +
1674+
" ║ │ ╚══ Join (JoinIterator) [right]\n" +
1675+
" ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0) [left]\n"
1676+
+
1677+
" ║ │ │ s: Var (name=stat)\n" +
1678+
" ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous)\n"
1679+
+
1680+
" ║ │ │ o: Var (name=_const_ea79e75_uri, value=http://data.europa.eu/a4g/ontology#SubmissionStatisticalInformation, anonymous)\n"
1681+
+
1682+
" ║ │ └── Join (JoinIterator) [right]\n" +
1683+
" ║ │ ╠══ Filter [left]\n" +
1684+
" ║ │ ║ ├── Compare (!=)\n" +
1685+
" ║ │ ║ │ Var (name=p)\n" +
1686+
" ║ │ ║ │ ValueConstant (value=http://publications.europa.eu/resource/authority/procurement-procedure-type/neg-wo-call)\n"
1687+
+
1688+
" ║ │ ║ └── StatementPattern (costEstimate=2.24, resultSizeEstimate=0)\n"
1689+
+
1690+
" ║ │ ║ s: Var (name=proc)\n" +
1691+
" ║ │ ║ p: Var (name=_const_9c756f6b_uri, value=http://data.europa.eu/a4g/ontology#hasProcedureType, anonymous)\n"
1692+
+
1693+
" ║ │ ║ o: Var (name=p)\n" +
1694+
" ║ │ ╚══ Join (JoinIterator) [right]\n" +
1695+
" ║ │ ├── StatementPattern (costEstimate=2.24, resultSizeEstimate=0) [left]\n"
1696+
+
1697+
" ║ │ │ s: Var (name=stat)\n" +
1698+
" ║ │ │ p: Var (name=_const_98c73a3c_uri, value=http://data.europa.eu/a4g/ontology#hasReceivedTenders, anonymous)\n"
1699+
+
1700+
" ║ │ │ o: Var (name=bidders)\n" +
1701+
" ║ │ └── Join (JoinIterator) [right]\n" +
1702+
" ║ │ ╠══ StatementPattern (costEstimate=2.24, resultSizeEstimate=0) [left]\n"
1703+
+
1704+
" ║ │ ║ s: Var (name=resultnotice)\n" +
1705+
" ║ │ ║ p: Var (name=_const_1b0b00ca_uri, value=http://data.europa.eu/a4g/ontology#hasDispatchDate, anonymous)\n"
1706+
+
1707+
" ║ │ ║ o: Var (name=ddate)\n" +
1708+
" ║ │ ╚══ StatementPattern (costEstimate=2.24, resultSizeEstimate=0) [right]\n"
1709+
+
1710+
" ║ │ s: Var (name=resultnotice)\n" +
1711+
" ║ │ p: Var (name=_const_6aa9a9c_uri, value=http://data.europa.eu/a4g/ontology#refersToRole, anonymous)\n"
1712+
+
1713+
" ║ │ o: Var (name=buyerrole)\n" +
1714+
" ║ └── ExtensionElem (year)\n" +
1715+
" ║ FunctionCall (http://www.w3.org/2005/xpath-functions#year-from-dateTime)\n" +
1716+
" ║ FunctionCall (http://www.w3.org/2001/XMLSchema#dateTime)\n" +
1717+
" ║ Var (name=ddate)\n" +
1718+
" ╚══ Distinct (new scope) [right]\n" +
1719+
" Projection\n" +
1720+
" ╠══ ProjectionElemList\n" +
1721+
" ║ ProjectionElem \"buyerrole\"\n" +
1722+
" ║ ProjectionElem \"countryID\"\n" +
1723+
" ╚══ Join (JoinIterator)\n" +
1724+
" ├── StatementPattern (costEstimate=1.25, resultSizeEstimate=0) [left]\n" +
1725+
" │ s: Var (name=org)\n" +
1726+
" │ p: Var (name=_const_beb18915_uri, value=https://www.w3.org/ns/legal#registeredAddress, anonymous)\n"
1727+
+
1728+
" │ o: Var (name=orgaddress)\n" +
1729+
" └── Join (JoinIterator) [right]\n" +
1730+
" ╠══ StatementPattern (costEstimate=1.12, resultSizeEstimate=0) [left]\n" +
1731+
" ║ s: Var (name=orgaddress)\n" +
1732+
" ║ p: Var (name=_const_2f7de0e1_uri, value=http://data.europa.eu/a4g/ontology#hasCountryCode, anonymous)\n"
1733+
+
1734+
" ║ o: Var (name=countrycode)\n" +
1735+
" ╚══ Join (JoinIterator) [right]\n" +
1736+
" ├── Filter [left]\n" +
1737+
" │ ╠══ Compare (!=)\n" +
1738+
" │ ║ Var (name=buytype)\n" +
1739+
" │ ║ ValueConstant (value=http://publications.europa.eu/resource/authority/buyer-legal-type/eu-int-org)\n"
1740+
+
1741+
" │ ╚══ StatementPattern (costEstimate=2.24, resultSizeEstimate=0)\n" +
1742+
" │ s: Var (name=org)\n" +
1743+
" │ p: Var (name=_const_1abd8d4b_uri, value=http://data.europa.eu/a4g/ontology#hasBuyerType, anonymous)\n"
1744+
+
1745+
" │ o: Var (name=buytype)\n" +
1746+
" └── Join (JoinIterator) [right]\n" +
1747+
" ╠══ StatementPattern (costEstimate=2.24, resultSizeEstimate=0) [left]\n"
1748+
+
1749+
" ║ s: Var (name=buyerrole)\n" +
1750+
" ║ p: Var (name=_const_beb855c2_uri, value=http://data.europa.eu/a4g/ontology#playedBy, anonymous)\n"
1751+
+
1752+
" ║ o: Var (name=org)\n" +
1753+
" ╚══ StatementPattern (costEstimate=2.24, resultSizeEstimate=0) [right]\n"
1754+
+
1755+
" s: Var (name=countrycode)\n" +
1756+
" p: Var (name=_const_a825a5f4_uri, value=http://purl.org/dc/elements/1.1/identifier, anonymous)\n"
1757+
+
1758+
" o: Var (name=countryID)\n" +
1759+
" ExtensionElem (_const_f5e5585a_uri)\n" +
1760+
" ValueConstant (value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type)\n" +
1761+
" ExtensionElem (_const_be18ee7b_uri)\n" +
1762+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#Procedure)\n" +
1763+
" ExtensionElem (_const_9c756f6b_uri)\n" +
1764+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#hasProcedureType)\n" +
1765+
" ExtensionElem (_const_25686184_uri)\n" +
1766+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#concernsSubmissionsForLot)\n" +
1767+
" ExtensionElem (_const_ea79e75_uri)\n" +
1768+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#SubmissionStatisticalInformation)\n"
1769+
+
1770+
" ExtensionElem (_const_98c73a3c_uri)\n" +
1771+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#hasReceivedTenders)\n" +
1772+
" ExtensionElem (_const_77e914ad_uri)\n" +
1773+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#ResultNotice)\n" +
1774+
" ExtensionElem (_const_1b0b00ca_uri)\n" +
1775+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#hasDispatchDate)\n" +
1776+
" ExtensionElem (_const_9c3f1eec_uri)\n" +
1777+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#hasProcurementScopeDividedIntoLot)\n"
1778+
+
1779+
" ExtensionElem (_const_6aa9a9c_uri)\n" +
1780+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#refersToRole)\n" +
1781+
" ExtensionElem (_const_183bd06d_uri)\n" +
1782+
" ValueConstant (value=http://data.europa.eu/a4g/ontology#refersToProcedure)\n";
1783+
SailRepository sailRepository = new SailRepository(new MemoryStore());
1784+
addData(sailRepository);
1785+
1786+
try (SailRepositoryConnection connection = sailRepository.getConnection()) {
1787+
GraphQuery query = connection.prepareGraphQuery(CONSTRUCT);
1788+
String actual = query.explain(Explanation.Level.Optimized).toString();
1789+
1790+
assertThat(actual).isEqualToNormalizingNewlines(expected);
1791+
1792+
}
1793+
sailRepository.shutDown();
1794+
1795+
}
1796+
15371797
@Test
15381798
public void testHaving() {
15391799

0 commit comments

Comments
 (0)