Skip to content

Commit 75b4a79

Browse files
committed
wip
1 parent 07b65a4 commit 75b4a79

5 files changed

Lines changed: 567 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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.Collection;
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.BindingSetAssignment;
20+
import org.eclipse.rdf4j.query.algebra.Join;
21+
import org.eclipse.rdf4j.query.algebra.StatementPattern;
22+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
23+
import org.eclipse.rdf4j.query.algebra.Var;
24+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
25+
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
26+
27+
/**
28+
* Reorders joins to avoid an early cartesian product between two {@link BindingSetAssignment}s (VALUES) when the result
29+
* is immediately joined with a {@link StatementPattern} that can be evaluated efficiently with a single bound side.
30+
*/
31+
public class BindingSetAssignmentJoinOrderOptimizer implements QueryOptimizer {
32+
33+
@Override
34+
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
35+
if (UnorderedSliceDetector.hasUnorderedSlice(tupleExpr)) {
36+
return;
37+
}
38+
tupleExpr.visit(new BindingSetAssignmentJoinOrderVisitor());
39+
}
40+
41+
private static final class BindingSetAssignmentJoinOrderVisitor
42+
extends AbstractSimpleQueryModelVisitor<RuntimeException> {
43+
44+
@Override
45+
public void meet(Join join) throws RuntimeException {
46+
super.meet(join);
47+
48+
StatementPattern statementPattern;
49+
Join bindingSetJoin;
50+
if (join.getLeftArg() instanceof Join && join.getRightArg() instanceof StatementPattern) {
51+
bindingSetJoin = (Join) join.getLeftArg();
52+
statementPattern = (StatementPattern) join.getRightArg();
53+
} else if (join.getRightArg() instanceof Join && join.getLeftArg() instanceof StatementPattern) {
54+
bindingSetJoin = (Join) join.getRightArg();
55+
statementPattern = (StatementPattern) join.getLeftArg();
56+
} else {
57+
return;
58+
}
59+
60+
if (!(bindingSetJoin.getLeftArg() instanceof BindingSetAssignment)
61+
|| !(bindingSetJoin.getRightArg() instanceof BindingSetAssignment)) {
62+
return;
63+
}
64+
65+
BindingSetAssignment leftValues = (BindingSetAssignment) bindingSetJoin.getLeftArg();
66+
BindingSetAssignment rightValues = (BindingSetAssignment) bindingSetJoin.getRightArg();
67+
if (!areDisjoint(leftValues.getBindingNames(), rightValues.getBindingNames())) {
68+
return;
69+
}
70+
71+
if (!statementPatternUsesAny(statementPattern, leftValues.getBindingNames())
72+
|| !statementPatternUsesAny(statementPattern, rightValues.getBindingNames())) {
73+
return;
74+
}
75+
76+
BindingSetAssignment firstValues = chooseFirst(statementPattern, leftValues, rightValues);
77+
BindingSetAssignment secondValues = firstValues == leftValues ? rightValues : leftValues;
78+
79+
StatementPattern statementPatternClone = statementPattern.clone();
80+
Join inner = new Join((TupleExpr) firstValues.clone(), statementPatternClone);
81+
Join outer = new Join(inner, (TupleExpr) secondValues.clone());
82+
83+
join.replaceWith(outer);
84+
outer.visit(this);
85+
}
86+
87+
private static boolean areDisjoint(Set<String> left, Set<String> right) {
88+
for (String name : left) {
89+
if (right.contains(name)) {
90+
return false;
91+
}
92+
}
93+
return true;
94+
}
95+
96+
private static boolean statementPatternUsesAny(StatementPattern statementPattern, Set<String> bindingNames) {
97+
for (Var var : new Var[] { statementPattern.getSubjectVar(), statementPattern.getPredicateVar(),
98+
statementPattern.getObjectVar(), statementPattern.getContextVar() }) {
99+
if (var == null || var.hasValue()) {
100+
continue;
101+
}
102+
if (bindingNames.contains(var.getName())) {
103+
return true;
104+
}
105+
}
106+
return false;
107+
}
108+
109+
private static BindingSetAssignment chooseFirst(StatementPattern statementPattern, BindingSetAssignment left,
110+
BindingSetAssignment right) {
111+
int leftScore = positionScore(statementPattern, left.getBindingNames());
112+
int rightScore = positionScore(statementPattern, right.getBindingNames());
113+
if (leftScore != rightScore) {
114+
return leftScore < rightScore ? left : right;
115+
}
116+
117+
int leftSize = estimateSize(left);
118+
int rightSize = estimateSize(right);
119+
if (leftSize != rightSize) {
120+
return leftSize <= rightSize ? left : right;
121+
}
122+
123+
return left;
124+
}
125+
126+
private static int positionScore(StatementPattern statementPattern, Set<String> names) {
127+
Var subject = statementPattern.getSubjectVar();
128+
if (subject != null && !subject.hasValue() && names.contains(subject.getName())) {
129+
return 0;
130+
}
131+
Var object = statementPattern.getObjectVar();
132+
if (object != null && !object.hasValue() && names.contains(object.getName())) {
133+
return 1;
134+
}
135+
Var predicate = statementPattern.getPredicateVar();
136+
if (predicate != null && !predicate.hasValue() && names.contains(predicate.getName())) {
137+
return 2;
138+
}
139+
Var context = statementPattern.getContextVar();
140+
if (context != null && !context.hasValue() && names.contains(context.getName())) {
141+
return 3;
142+
}
143+
return Integer.MAX_VALUE;
144+
}
145+
146+
private static int estimateSize(BindingSetAssignment assignment) {
147+
Iterable<BindingSet> bindingSets = assignment.getBindingSets();
148+
if (bindingSets instanceof Collection<?>) {
149+
return ((Collection<?>) bindingSets).size();
150+
}
151+
return Integer.MAX_VALUE;
152+
}
153+
}
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 org.eclipse.rdf4j.query.BindingSet;
15+
import org.eclipse.rdf4j.query.Dataset;
16+
import org.eclipse.rdf4j.query.algebra.BinaryValueOperator;
17+
import org.eclipse.rdf4j.query.algebra.Exists;
18+
import org.eclipse.rdf4j.query.algebra.Filter;
19+
import org.eclipse.rdf4j.query.algebra.Join;
20+
import org.eclipse.rdf4j.query.algebra.NAryValueOperator;
21+
import org.eclipse.rdf4j.query.algebra.StatementPattern;
22+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
23+
import org.eclipse.rdf4j.query.algebra.UnaryValueOperator;
24+
import org.eclipse.rdf4j.query.algebra.ValueExpr;
25+
import org.eclipse.rdf4j.query.algebra.Var;
26+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
27+
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
28+
29+
/**
30+
* Pulls up Filters containing {@link Exists} over joins where the right operand does not introduce new bindings.
31+
* <p>
32+
* This reduces the number of EXISTS evaluations when the join can act as a pre-filter.
33+
*/
34+
public class ExistsFilterPullUpOptimizer implements QueryOptimizer {
35+
36+
@Override
37+
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
38+
tupleExpr.visit(new ExistsFilterPullUpVisitor());
39+
}
40+
41+
private static final class ExistsFilterPullUpVisitor extends AbstractSimpleQueryModelVisitor<RuntimeException> {
42+
@Override
43+
public void meet(Join join) throws RuntimeException {
44+
super.meet(join);
45+
46+
if (!(join.getLeftArg() instanceof Filter)) {
47+
return;
48+
}
49+
50+
Filter filter = (Filter) join.getLeftArg();
51+
ValueExpr condition = filter.getCondition();
52+
if (!containsExists(condition)) {
53+
return;
54+
}
55+
56+
TupleExpr filterArg = filter.getArg();
57+
if (filterArg == null) {
58+
return;
59+
}
60+
61+
TupleExpr rightArg = join.getRightArg();
62+
if (rightArg == null) {
63+
return;
64+
}
65+
66+
if (!(rightArg instanceof StatementPattern)) {
67+
return;
68+
}
69+
70+
if (!introducesNoNewBindings(filterArg, (StatementPattern) rightArg)) {
71+
return;
72+
}
73+
74+
Join rewrittenJoin = new Join(filterArg, rightArg);
75+
rewrittenJoin.setMergeJoin(join.isMergeJoin());
76+
Filter pulledUp = new Filter(rewrittenJoin, condition);
77+
join.replaceWith(pulledUp);
78+
}
79+
80+
private static boolean introducesNoNewBindings(TupleExpr leftArg, StatementPattern rightArg) {
81+
for (Var var : new Var[] { rightArg.getSubjectVar(), rightArg.getPredicateVar(), rightArg.getObjectVar(),
82+
rightArg.getContextVar() }) {
83+
if (var == null || var.hasValue()) {
84+
continue;
85+
}
86+
if (!leftArg.getBindingNames().contains(var.getName())) {
87+
return false;
88+
}
89+
}
90+
return true;
91+
}
92+
93+
private static boolean containsExists(ValueExpr expr) {
94+
if (expr == null) {
95+
return false;
96+
}
97+
if (expr instanceof Exists) {
98+
return true;
99+
}
100+
if (expr instanceof UnaryValueOperator) {
101+
return containsExists(((UnaryValueOperator) expr).getArg());
102+
}
103+
if (expr instanceof BinaryValueOperator) {
104+
BinaryValueOperator binary = (BinaryValueOperator) expr;
105+
return containsExists(binary.getLeftArg()) || containsExists(binary.getRightArg());
106+
}
107+
if (expr instanceof NAryValueOperator) {
108+
for (ValueExpr arg : ((NAryValueOperator) expr).getArguments()) {
109+
if (containsExists(arg)) {
110+
return true;
111+
}
112+
}
113+
return false;
114+
}
115+
return false;
116+
}
117+
}
118+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class SparqlUoQueryOptimizerPipeline implements QueryOptimizerPipeline {
4242
private final OptionalBindLeftJoinOptimizer optionalBindLeftJoinOptimizer;
4343
private final MinusOptimizer minusOptimizer;
4444
private final ExistsConstantOptimizer existsConstantOptimizer;
45+
private final ExistsFilterPullUpOptimizer existsFilterPullUpOptimizer;
46+
private final BindingSetAssignmentJoinOrderOptimizer bindingSetAssignmentJoinOrderOptimizer;
4547
private final ExistsSemiJoinOptimizer existsSemiJoinOptimizer;
4648
private final NotExistsSemiJoinOptimizer notExistsSemiJoinOptimizer;
4749
private final QueryJoinOptimizer joinOptimizer;
@@ -70,6 +72,8 @@ public SparqlUoQueryOptimizerPipeline(EvaluationStrategy strategy, TripleSource
7072
this.optionalBindLeftJoinOptimizer = new OptionalBindLeftJoinOptimizer();
7173
this.minusOptimizer = new MinusOptimizer(config.enableMinusUnionSplit());
7274
this.existsConstantOptimizer = new ExistsConstantOptimizer();
75+
this.existsFilterPullUpOptimizer = new ExistsFilterPullUpOptimizer();
76+
this.bindingSetAssignmentJoinOrderOptimizer = new BindingSetAssignmentJoinOrderOptimizer();
7377
this.existsSemiJoinOptimizer = new ExistsSemiJoinOptimizer(evaluationStatistics,
7478
config.allowNonImprovingTransforms());
7579
this.notExistsSemiJoinOptimizer = new NotExistsSemiJoinOptimizer(evaluationStatistics,
@@ -112,6 +116,8 @@ public Iterable<QueryOptimizer> getOptimizers() {
112116
}
113117
if (optimizer instanceof FilterOptimizer) {
114118
optimizers.add(optimizer);
119+
optimizers.add(existsFilterPullUpOptimizer);
120+
optimizers.add(bindingSetAssignmentJoinOrderOptimizer);
115121
optimizers.add(unionCommonFilterBindingSetOptimizer);
116122
if (!statementPatternInserted) {
117123
if (enableUnionCommonPullUp) {

0 commit comments

Comments
 (0)