Skip to content

Commit 158fda7

Browse files
committed
wip
1 parent 9bc05bc commit 158fda7

5 files changed

Lines changed: 198 additions & 0 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.rdf4j.query.algebra.Coalesce;
2323
import org.eclipse.rdf4j.query.algebra.Exists;
2424
import org.eclipse.rdf4j.query.algebra.Filter;
25+
import org.eclipse.rdf4j.query.algebra.FunctionCall;
2526
import org.eclipse.rdf4j.query.algebra.If;
2627
import org.eclipse.rdf4j.query.algebra.Join;
2728
import org.eclipse.rdf4j.query.algebra.LeftJoin;
@@ -87,6 +88,14 @@ private static boolean requiresRightVars(ValueExpr expr, Set<String> rightOnly)
8788
if (expr instanceof If || expr instanceof Coalesce || expr instanceof Exists) {
8889
return false;
8990
}
91+
if (expr instanceof FunctionCall) {
92+
for (ValueExpr arg : ((FunctionCall) expr).getArgs()) {
93+
if (requiresRightVars(arg, rightOnly)) {
94+
return true;
95+
}
96+
}
97+
return false;
98+
}
9099
if (expr instanceof Or) {
91100
Or or = (Or) expr;
92101
return requiresRightVars(or.getLeftArg(), rightOnly) && requiresRightVars(or.getRightArg(), rightOnly);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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.optimizer;
13+
14+
import java.util.HashSet;
15+
import java.util.Set;
16+
17+
import org.eclipse.rdf4j.query.BindingSet;
18+
import org.eclipse.rdf4j.query.Dataset;
19+
import org.eclipse.rdf4j.query.algebra.Bound;
20+
import org.eclipse.rdf4j.query.algebra.Exists;
21+
import org.eclipse.rdf4j.query.algebra.Filter;
22+
import org.eclipse.rdf4j.query.algebra.LeftJoin;
23+
import org.eclipse.rdf4j.query.algebra.Not;
24+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
25+
import org.eclipse.rdf4j.query.algebra.ValueExpr;
26+
import org.eclipse.rdf4j.query.algebra.Var;
27+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
28+
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
29+
30+
/**
31+
* Rewrites Filter(!BOUND(?v)) over a LeftJoin into Filter(Not(Exists(right))) on the left argument.
32+
*/
33+
public class OptionalNotBoundFilterOptimizer implements QueryOptimizer {
34+
35+
@Override
36+
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
37+
tupleExpr.visit(new OptionalNotBoundVisitor());
38+
}
39+
40+
private static final class OptionalNotBoundVisitor extends AbstractSimpleQueryModelVisitor<RuntimeException> {
41+
@Override
42+
public void meet(Filter filter) {
43+
super.meet(filter);
44+
Var notBoundVar = notBoundVar(filter.getCondition());
45+
if (notBoundVar == null || notBoundVar.hasValue()) {
46+
return;
47+
}
48+
if (!(filter.getArg() instanceof LeftJoin)) {
49+
return;
50+
}
51+
LeftJoin leftJoin = (LeftJoin) filter.getArg();
52+
if (leftJoin.getCondition() != null) {
53+
return;
54+
}
55+
Set<String> rightOnly = new HashSet<>(leftJoin.getRightArg().getBindingNames());
56+
rightOnly.removeAll(leftJoin.getLeftArg().getBindingNames());
57+
if (!rightOnly.contains(notBoundVar.getName())) {
58+
return;
59+
}
60+
61+
TupleExpr left = leftJoin.getLeftArg();
62+
TupleExpr right = leftJoin.getRightArg();
63+
64+
filter.setCondition(new Not(new Exists(right.clone())));
65+
filter.setArg(left);
66+
}
67+
}
68+
69+
private static Var notBoundVar(ValueExpr condition) {
70+
if (!(condition instanceof Not)) {
71+
return null;
72+
}
73+
ValueExpr inner = ((Not) condition).getArg();
74+
if (!(inner instanceof Bound)) {
75+
return null;
76+
}
77+
ValueExpr arg = ((Bound) inner).getArg();
78+
if (arg instanceof Var) {
79+
return (Var) arg;
80+
}
81+
return null;
82+
}
83+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class SparqlUoOptimizer implements QueryOptimizer {
4545
private final BeTreeSerializer serializer = new BeTreeSerializer();
4646
private final BeTreeTransformer transformer;
4747
private final OptionalFilterJoinOptimizer optionalFilterJoinOptimizer = new OptionalFilterJoinOptimizer();
48+
private final OptionalNotBoundFilterOptimizer optionalNotBoundFilterOptimizer = new OptionalNotBoundFilterOptimizer();
4849

4950
public SparqlUoOptimizer(EvaluationStatistics evaluationStatistics) {
5051
this(evaluationStatistics, SparqlUoConfig.fromSystemProperties());
@@ -68,6 +69,7 @@ public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings)
6869
tupleExpr.visit(new SparqlUoVisitor());
6970
if (config.enableOptionalFilterJoin()) {
7071
optionalFilterJoinOptimizer.optimize(tupleExpr, dataset, bindings);
72+
optionalNotBoundFilterOptimizer.optimize(tupleExpr, dataset, bindings);
7173
}
7274
}
7375

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class SparqlUoQueryOptimizerPipeline implements QueryOptimizerPipeline {
2929
private final UnionCommonStatementPatternOptimizer unionCommonStatementPatternOptimizer;
3030
private final UnionCommonFilterBindingSetOptimizer unionCommonFilterBindingSetOptimizer;
3131
private final OptionalFilterJoinOptimizer optionalFilterJoinOptimizer;
32+
private final OptionalNotBoundFilterOptimizer optionalNotBoundFilterOptimizer;
3233
private final MinusOptimizer minusOptimizer;
3334
private final ExistsConstantOptimizer existsConstantOptimizer;
3435
private final QueryJoinOptimizer joinOptimizer;
@@ -48,6 +49,7 @@ public SparqlUoQueryOptimizerPipeline(EvaluationStrategy strategy, TripleSource
4849
this.unionCommonStatementPatternOptimizer = new UnionCommonStatementPatternOptimizer(evaluationStatistics);
4950
this.unionCommonFilterBindingSetOptimizer = new UnionCommonFilterBindingSetOptimizer();
5051
this.optionalFilterJoinOptimizer = new OptionalFilterJoinOptimizer();
52+
this.optionalNotBoundFilterOptimizer = new OptionalNotBoundFilterOptimizer();
5153
this.minusOptimizer = new MinusOptimizer(config.enableMinusUnionSplit());
5254
this.existsConstantOptimizer = new ExistsConstantOptimizer();
5355
this.joinOptimizer = new QueryJoinOptimizer(evaluationStatistics, strategy.isTrackResultSize(), tripleSource,
@@ -77,6 +79,7 @@ public Iterable<QueryOptimizer> getOptimizers() {
7779
optimizers.add(unionCommonFilterBindingSetOptimizer);
7880
if (enableOptionalFilterJoin) {
7981
optimizers.add(optionalFilterJoinOptimizer);
82+
optimizers.add(optionalNotBoundFilterOptimizer);
8083
}
8184
if (!statementPatternInserted) {
8285
optimizers.add(unionCommonStatementPatternOptimizer);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 java.util.concurrent.atomic.AtomicBoolean;
17+
18+
import org.eclipse.rdf4j.query.BindingSet;
19+
import org.eclipse.rdf4j.query.QueryLanguage;
20+
import org.eclipse.rdf4j.query.algebra.Exists;
21+
import org.eclipse.rdf4j.query.algebra.LeftJoin;
22+
import org.eclipse.rdf4j.query.algebra.Not;
23+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
24+
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.SparqlUoQueryOptimizerPipeline;
25+
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.sparqluo.SparqlUoConfig;
26+
import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor;
27+
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
28+
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;
29+
import org.eclipse.rdf4j.query.parser.QueryParserUtil;
30+
import org.junit.jupiter.api.Test;
31+
32+
class SparqlUoOptionalFilterRewriteTest {
33+
34+
@Test
35+
void convertsFunctionCallFilterToJoin() {
36+
String query = "SELECT * WHERE { "
37+
+ "?s <urn:p1> ?o . "
38+
+ "OPTIONAL { ?s <urn:p2> ?v . BIND(STR(?v) AS ?vStr) } "
39+
+ "FILTER(CONTAINS(?vStr, \"foo\")) "
40+
+ "}";
41+
42+
TupleExpr expr = optimize(query);
43+
44+
assertThat(containsLeftJoin(expr)).isFalse();
45+
}
46+
47+
@Test
48+
void rewritesNotBoundOptionalToNotExists() {
49+
String query = "SELECT * WHERE { "
50+
+ "?s <urn:p1> ?o . "
51+
+ "OPTIONAL { ?s <urn:p2> ?x . } "
52+
+ "FILTER(!BOUND(?x)) "
53+
+ "}";
54+
55+
TupleExpr expr = optimize(query);
56+
57+
assertThat(containsLeftJoin(expr)).isFalse();
58+
assertThat(containsNotExists(expr)).isTrue();
59+
}
60+
61+
private static TupleExpr optimize(String query) {
62+
ParsedTupleQuery parsedQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null);
63+
TupleExpr expr = parsedQuery.getTupleExpr();
64+
65+
EmptyTripleSource tripleSource = new EmptyTripleSource();
66+
EvaluationStatistics evaluationStatistics = new EvaluationStatistics();
67+
DefaultEvaluationStrategy strategy = new DefaultEvaluationStrategy(tripleSource, null, null, 0L,
68+
evaluationStatistics);
69+
SparqlUoConfig config = SparqlUoConfig.builder().allowNonImprovingTransforms(true).build();
70+
strategy.setOptimizerPipeline(
71+
new SparqlUoQueryOptimizerPipeline(strategy, tripleSource, evaluationStatistics, config));
72+
BindingSet bindings = EmptyBindingSet.getInstance();
73+
strategy.optimize(expr, evaluationStatistics, bindings);
74+
return expr;
75+
}
76+
77+
private static boolean containsLeftJoin(TupleExpr expr) {
78+
AtomicBoolean found = new AtomicBoolean(false);
79+
expr.visit(new AbstractQueryModelVisitor<RuntimeException>() {
80+
@Override
81+
public void meet(LeftJoin node) {
82+
found.set(true);
83+
}
84+
});
85+
return found.get();
86+
}
87+
88+
private static boolean containsNotExists(TupleExpr expr) {
89+
AtomicBoolean found = new AtomicBoolean(false);
90+
expr.visit(new AbstractQueryModelVisitor<RuntimeException>() {
91+
@Override
92+
public void meet(Not node) {
93+
if (node.getArg() instanceof Exists) {
94+
found.set(true);
95+
}
96+
super.meet(node);
97+
}
98+
});
99+
return found.get();
100+
}
101+
}

0 commit comments

Comments
 (0)