Skip to content

Commit 9ae7e17

Browse files
committed
GH-3220 Fix property path object list handling
1 parent 2f6ae3b commit 9ae7e17

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/TupleExprBuilder.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,36 @@ public TupleExpr visit(ASTPathSequence pathSeqNode, Object data) throws VisitorE
14491449
subjVar = mapValueExprToVar(data);
14501450
}
14511451

1452+
List<ValueExpr> objectList = null;
1453+
if (!(data instanceof PathSequenceContext)) {
1454+
ASTObjectList objectListNode = getObjectList(pathSeqNode);
1455+
if (objectListNode != null) {
1456+
@SuppressWarnings("unchecked")
1457+
List<ValueExpr> evaluatedObjectList = (List<ValueExpr>) objectListNode.jjtAccept(this, null);
1458+
objectList = evaluatedObjectList;
1459+
1460+
if (objectList.size() > 1) {
1461+
TupleExpr result = null;
1462+
for (ValueExpr objectItem : objectList) {
1463+
PathSequenceContext objectContext = new PathSequenceContext();
1464+
objectContext.scope = graphPattern.getStatementPatternScope();
1465+
objectContext.contextVar = graphPattern.getContextVar();
1466+
objectContext.startVar = subjVar;
1467+
objectContext.endVar = mapValueExprToVar(objectItem);
1468+
1469+
TupleExpr pathExpr = (TupleExpr) pathSeqNode.jjtAccept(this, objectContext);
1470+
1471+
if (result == null) {
1472+
result = pathExpr;
1473+
} else {
1474+
result = new Join(result, pathExpr);
1475+
}
1476+
}
1477+
return result;
1478+
}
1479+
}
1480+
}
1481+
14521482
List<ASTPathElt> pathElements = pathSeqNode.getPathElements();
14531483
int pathLength = pathElements.size();
14541484

@@ -1470,10 +1500,15 @@ public TupleExpr visit(ASTPathSequence pathSeqNode, Object data) throws VisitorE
14701500

14711501
// We handle this here instead of higher up in the tree visitor because here we have
14721502
// a reference to the "temporary" endVar that needs to be replaced.
1473-
@SuppressWarnings("unchecked")
1474-
List<ValueExpr> objectList = (List<ValueExpr>) getObjectList(pathSeqNode).jjtAccept(this, null);
1503+
List<ValueExpr> finalObjectList = objectList;
1504+
if (finalObjectList == null) {
1505+
@SuppressWarnings("unchecked")
1506+
List<ValueExpr> evaluatedObjectList = (List<ValueExpr>) getObjectList(pathSeqNode)
1507+
.jjtAccept(this, null);
1508+
finalObjectList = evaluatedObjectList;
1509+
}
14751510

1476-
for (ValueExpr objectItem : objectList) {
1511+
for (ValueExpr objectItem : finalObjectList) {
14771512
Var objectVar = mapValueExprToVar(objectItem);
14781513
Var replacement = objectVar;
14791514
if (objectVar.equals(subjVar)) { // corner case for cyclic expressions, see SES-1685

core/queryparser/sparql/src/test/java/org/eclipse/rdf4j/query/parser/sparql/TestPropPathMisbehaviour.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
*******************************************************************************/
1111
package org.eclipse.rdf4j.query.parser.sparql;
1212

13+
import static org.junit.jupiter.api.Assertions.assertEquals;
1314
import static org.junit.jupiter.api.Assertions.assertNotNull;
1415
import static org.junit.jupiter.api.Assertions.assertTrue;
1516

17+
import java.util.List;
18+
import java.util.Set;
19+
import java.util.stream.Collectors;
20+
1621
import org.eclipse.rdf4j.query.algebra.ArbitraryLengthPath;
1722
import org.eclipse.rdf4j.query.algebra.Distinct;
1823
import org.eclipse.rdf4j.query.algebra.Join;
@@ -22,6 +27,7 @@
2227
import org.eclipse.rdf4j.query.algebra.TupleExpr;
2328
import org.eclipse.rdf4j.query.algebra.Union;
2429
import org.eclipse.rdf4j.query.algebra.ZeroLengthPath;
30+
import org.eclipse.rdf4j.query.algebra.helpers.collectors.StatementPatternCollector;
2531
import org.eclipse.rdf4j.query.parser.ParsedQuery;
2632
import org.junit.jupiter.api.AfterEach;
2733
import org.junit.jupiter.api.BeforeEach;
@@ -109,4 +115,30 @@ public void testGH3053() {
109115
"expect Union Right arg to be StatementPattern");
110116
assertTrue(!proj2.isSubquery(), "expect projection to do NOT be a subQuery");
111117
}
118+
119+
@Test
120+
public void testGH3220() {
121+
String query = "select * where { ?i <urn:prop1> / <urn:prop2> ?v1, ?v2 . }";
122+
ParsedQuery parsedQuery = parser.parseQuery(query, "http://example.org/");
123+
124+
assertNotNull(parsedQuery);
125+
126+
List<StatementPattern> statementPatterns = StatementPatternCollector
127+
.process(parsedQuery.getTupleExpr());
128+
129+
List<StatementPattern> prop1Patterns = statementPatterns.stream()
130+
.filter(sp -> sp.getPredicateVar().hasValue()
131+
&& "urn:prop1".equals(sp.getPredicateVar().getValue().stringValue()))
132+
.collect(Collectors.toList());
133+
134+
assertEquals(2, prop1Patterns.size(),
135+
"expected each object list entry to yield its own statement pattern for the first path element");
136+
137+
Set<String> intermediateVarNames = prop1Patterns.stream()
138+
.map(sp -> sp.getObjectVar().getName())
139+
.collect(Collectors.toSet());
140+
141+
assertEquals(2, intermediateVarNames.size(),
142+
"expected unique intermediate variable per object list entry for the first path element");
143+
}
112144
}

0 commit comments

Comments
 (0)