Skip to content

Commit 57b79d7

Browse files
committed
wip
1 parent fda9de2 commit 57b79d7

4 files changed

Lines changed: 246 additions & 7 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/QueryModelNormalizerOptimizer.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.eclipse.rdf4j.query.BindingSet;
1818
import org.eclipse.rdf4j.query.Dataset;
1919
import org.eclipse.rdf4j.query.algebra.And;
20+
import org.eclipse.rdf4j.query.algebra.BindingSetAssignment;
2021
import org.eclipse.rdf4j.query.algebra.Difference;
2122
import org.eclipse.rdf4j.query.algebra.EmptySet;
2223
import org.eclipse.rdf4j.query.algebra.Filter;
@@ -86,14 +87,18 @@ public void meet(Join join) {
8687
newUnion.setVariableScopeChange(union.isVariableScopeChange());
8788
join.replaceWith(newUnion);
8889
newUnion.visit(this);
89-
} else if (leftArg instanceof LeftJoin && isWellDesigned((LeftJoin) leftArg)) {
90+
} else if (leftArg instanceof LeftJoin && isWellDesigned((LeftJoin) leftArg)
91+
&& !bindsOptionalVars(rightArg, (LeftJoin) leftArg)
92+
&& !containsBindingSetAssignment(rightArg)) {
9093
// sort left join above normal joins
9194
LeftJoin leftJoin = (LeftJoin) leftArg;
9295
join.replaceWith(leftJoin);
9396
join.setLeftArg(leftJoin.getLeftArg());
9497
leftJoin.setLeftArg(join);
9598
leftJoin.visit(this);
96-
} else if (rightArg instanceof LeftJoin && isWellDesigned((LeftJoin) rightArg)) {
99+
} else if (rightArg instanceof LeftJoin && isWellDesigned((LeftJoin) rightArg)
100+
&& !bindsOptionalVars(leftArg, (LeftJoin) rightArg)
101+
&& !containsBindingSetAssignment(leftArg)) {
97102
// sort left join above normal joins
98103
LeftJoin leftJoin = (LeftJoin) rightArg;
99104
join.replaceWith(leftJoin);
@@ -244,6 +249,50 @@ private boolean isWellDesigned(LeftJoin leftJoin) {
244249
return checkAgainstParent(leftJoin, problemVars);
245250
}
246251

252+
private boolean bindsOptionalVars(TupleExpr otherArg, LeftJoin leftJoin) {
253+
Set<String> optionalVars = VarNameCollector.process(leftJoin.getRightArg());
254+
if (leftJoin.hasCondition()) {
255+
optionalVars = new HashSet<>(optionalVars);
256+
optionalVars.addAll(VarNameCollector.process(leftJoin.getCondition()));
257+
}
258+
259+
optionalVars = retainAll(optionalVars, leftJoin.getLeftArg().getBindingNames());
260+
if (optionalVars.isEmpty()) {
261+
return false;
262+
}
263+
264+
Set<String> otherBindingNames = otherArg.getBindingNames();
265+
for (String var : optionalVars) {
266+
if (otherBindingNames.contains(var)) {
267+
return true;
268+
}
269+
}
270+
return false;
271+
}
272+
273+
private boolean containsBindingSetAssignment(TupleExpr tupleExpr) {
274+
BindingSetAssignmentFinder finder = new BindingSetAssignmentFinder();
275+
tupleExpr.visit(finder);
276+
return finder.found;
277+
}
278+
279+
private static class BindingSetAssignmentFinder extends AbstractQueryModelVisitor<RuntimeException> {
280+
281+
private boolean found;
282+
283+
@Override
284+
public void meet(BindingSetAssignment node) {
285+
found = true;
286+
}
287+
288+
@Override
289+
protected void meetNode(QueryModelNode node) {
290+
if (!found) {
291+
super.meetNode(node);
292+
}
293+
}
294+
}
295+
247296
private Set<String> retainAll(Set<String> problemVars, Set<String> leftBindingNames) {
248297
if (!leftBindingNames.isEmpty() && !problemVars.isEmpty()) {
249298
if (leftBindingNames.size() > problemVars.size()) {

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/sparqluo/BeTreeBuilder.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,47 @@
1212
package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.sparqluo;
1313

1414
import java.util.ArrayList;
15+
import java.util.HashSet;
1516
import java.util.List;
17+
import java.util.Set;
1618

1719
import org.eclipse.rdf4j.query.algebra.Join;
1820
import org.eclipse.rdf4j.query.algebra.LeftJoin;
1921
import org.eclipse.rdf4j.query.algebra.StatementPattern;
2022
import org.eclipse.rdf4j.query.algebra.TupleExpr;
2123
import org.eclipse.rdf4j.query.algebra.Union;
24+
import org.eclipse.rdf4j.query.algebra.helpers.collectors.VarNameCollector;
2225

2326
public class BeTreeBuilder {
2427
private final BeBgpCoalescer coalescer = new BeBgpCoalescer();
2528

2629
public BeGroupNode build(TupleExpr expr) {
2730
BeGroupNode group = new BeGroupNode();
28-
addGroupChildren(expr, group);
31+
addGroupChildren(expr, group, new HashSet<>());
2932
coalescer.coalesce(group);
3033
return group;
3134
}
3235

33-
private void addGroupChildren(TupleExpr expr, BeGroupNode group) {
36+
private void addGroupChildren(TupleExpr expr, BeGroupNode group, Set<String> boundVars) {
3437
if (expr instanceof Join) {
3538
List<TupleExpr> args = new ArrayList<>();
3639
collectJoinArgs((Join) expr, args);
3740
for (TupleExpr arg : args) {
38-
addGroupChildren(arg, group);
41+
addGroupChildren(arg, group, boundVars);
3942
}
4043
return;
4144
}
4245
if (expr instanceof LeftJoin) {
4346
LeftJoin leftJoin = (LeftJoin) expr;
44-
if (leftJoin.getCondition() != null) {
47+
if (leftJoin.getCondition() != null || !canFlattenLeftJoin(leftJoin, boundVars)) {
4548
group.addChild(new BeBarrierNode(expr));
49+
boundVars.addAll(expr.getBindingNames());
4650
return;
4751
}
48-
addGroupChildren(leftJoin.getLeftArg(), group);
52+
addGroupChildren(leftJoin.getLeftArg(), group, boundVars);
4953
BeGroupNode rightGroup = build(leftJoin.getRightArg());
5054
group.addChild(new BeOptionalNode(rightGroup));
55+
boundVars.addAll(leftJoin.getBindingNames());
5156
return;
5257
}
5358
if (expr instanceof Union) {
@@ -59,14 +64,17 @@ private void addGroupChildren(TupleExpr expr, BeGroupNode group) {
5964
unionNode.addBranch(build(branch));
6065
}
6166
group.addChild(unionNode);
67+
boundVars.addAll(union.getBindingNames());
6268
return;
6369
}
6470
if (expr instanceof StatementPattern) {
6571
group.addChild(new BeBgpNode(List.of((StatementPattern) expr)));
72+
boundVars.addAll(expr.getBindingNames());
6673
return;
6774
}
6875

6976
group.addChild(new BeBarrierNode(expr));
77+
boundVars.addAll(expr.getBindingNames());
7078
}
7179

7280
private void collectJoinArgs(Join join, List<TupleExpr> args) {
@@ -101,4 +109,24 @@ private boolean collectUnionArgs(Union union, List<TupleExpr> branches, boolean
101109
}
102110
return scopeChange;
103111
}
112+
113+
private boolean canFlattenLeftJoin(LeftJoin leftJoin, Set<String> boundVars) {
114+
if (boundVars.isEmpty()) {
115+
return true;
116+
}
117+
Set<String> optionalVars = new HashSet<>(VarNameCollector.process(leftJoin.getRightArg()));
118+
if (leftJoin.hasCondition()) {
119+
optionalVars.addAll(VarNameCollector.process(leftJoin.getCondition()));
120+
}
121+
optionalVars.removeAll(leftJoin.getLeftArg().getBindingNames());
122+
if (optionalVars.isEmpty()) {
123+
return true;
124+
}
125+
for (String var : optionalVars) {
126+
if (boundVars.contains(var)) {
127+
return false;
128+
}
129+
}
130+
return true;
131+
}
104132
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.query.algebra.evaluation.impl;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
import org.eclipse.rdf4j.query.QueryLanguage;
17+
import org.eclipse.rdf4j.query.algebra.BindingSetAssignment;
18+
import org.eclipse.rdf4j.query.algebra.Join;
19+
import org.eclipse.rdf4j.query.algebra.LeftJoin;
20+
import org.eclipse.rdf4j.query.algebra.Projection;
21+
import org.eclipse.rdf4j.query.algebra.QueryRoot;
22+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
23+
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.QueryModelNormalizerOptimizer;
24+
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
25+
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;
26+
import org.eclipse.rdf4j.query.parser.QueryParserUtil;
27+
import org.junit.jupiter.api.Test;
28+
29+
public class QueryModelNormalizerOptimizerTest {
30+
31+
@Test
32+
public void testValuesJoinNotMovedAboveOptional() {
33+
String query = String.join("\n",
34+
"PREFIX : <http://example.org/>",
35+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/>",
36+
"SELECT ?s ?o1 ?o2",
37+
"{",
38+
" ?s ?p1 ?o1",
39+
" OPTIONAL { ?s foaf:knows ?o2 }",
40+
"} VALUES (?o2) {",
41+
" (:b)",
42+
"}"
43+
);
44+
45+
ParsedTupleQuery parsedQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null);
46+
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
47+
48+
new QueryModelNormalizerOptimizer().optimize(tupleExpr, null, EmptyBindingSet.getInstance());
49+
50+
TupleExpr optimized = ((QueryRoot) tupleExpr).getArg();
51+
assertThat(optimized).isInstanceOf(Projection.class);
52+
53+
TupleExpr projectionArg = ((Projection) optimized).getArg();
54+
assertThat(projectionArg).isInstanceOf(Join.class);
55+
56+
Join join = (Join) projectionArg;
57+
assertThat(join.getLeftArg()).isInstanceOf(BindingSetAssignment.class);
58+
assertThat(join.getRightArg()).isInstanceOf(LeftJoin.class);
59+
}
60+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.sail.memory;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
import java.util.HashSet;
17+
import java.util.List;
18+
import java.util.Set;
19+
20+
import org.eclipse.rdf4j.model.IRI;
21+
import org.eclipse.rdf4j.model.Value;
22+
import org.eclipse.rdf4j.model.util.Values;
23+
import org.eclipse.rdf4j.model.vocabulary.FOAF;
24+
import org.eclipse.rdf4j.query.BindingSet;
25+
import org.eclipse.rdf4j.query.TupleQuery;
26+
import org.eclipse.rdf4j.query.TupleQueryResult;
27+
import org.eclipse.rdf4j.repository.sail.SailRepository;
28+
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
33+
public class MemoryStoreValuesOptionalQueryTest {
34+
35+
private SailRepository repository;
36+
37+
@BeforeEach
38+
public void setUp() {
39+
repository = new SailRepository(new MemoryStore());
40+
repository.init();
41+
}
42+
43+
@AfterEach
44+
public void tearDown() {
45+
if (repository != null) {
46+
repository.shutDown();
47+
}
48+
}
49+
50+
@Test
51+
public void testPostQueryValuesWithOptionalObjectVar() {
52+
try (SailRepositoryConnection conn = repository.getConnection()) {
53+
IRI a = Values.iri("http://example.org/a");
54+
IRI b = Values.iri("http://example.org/b");
55+
IRI c = Values.iri("http://example.org/c");
56+
57+
conn.add(a, FOAF.NAME, Values.literal("Alan"));
58+
conn.add(a, FOAF.MBOX, Values.literal("alan@example.org"));
59+
conn.add(b, FOAF.NAME, Values.literal("Bob"));
60+
conn.add(b, FOAF.MBOX, Values.literal("bob@example.org"));
61+
conn.add(c, FOAF.NAME, Values.literal("Alice"));
62+
conn.add(c, FOAF.MBOX, Values.literal("alice@example.org"));
63+
conn.add(a, FOAF.KNOWS, b);
64+
conn.add(b, FOAF.KNOWS, c);
65+
66+
String sparql = String.join("\n",
67+
"PREFIX : <http://example.org/>",
68+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/>",
69+
"SELECT ?s ?o1 ?o2",
70+
"{",
71+
" ?s ?p1 ?o1",
72+
" OPTIONAL { ?s foaf:knows ?o2 }",
73+
"} VALUES (?o2) {",
74+
" (:b)",
75+
"}"
76+
);
77+
78+
TupleQuery query = conn.prepareTupleQuery(sparql);
79+
Set<List<Value>> actual = new HashSet<>();
80+
try (TupleQueryResult result = query.evaluate()) {
81+
while (result.hasNext()) {
82+
BindingSet bindingSet = result.next();
83+
actual.add(List.of(
84+
bindingSet.getValue("s"),
85+
bindingSet.getValue("o1"),
86+
bindingSet.getValue("o2")
87+
));
88+
}
89+
}
90+
91+
Set<List<Value>> expected = Set.of(
92+
List.of(a, b, b),
93+
List.of(a, Values.literal("alan@example.org"), b),
94+
List.of(a, Values.literal("Alan"), b),
95+
List.of(c, Values.literal("alice@example.org"), b),
96+
List.of(c, Values.literal("Alice"), b)
97+
);
98+
99+
assertEquals(expected, actual);
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)