Skip to content

Commit f34206e

Browse files
committed
improvements
1 parent 2134a1c commit f34206e

6 files changed

Lines changed: 630 additions & 683 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 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.ArrayList;
15+
import java.util.LinkedHashSet;
16+
import java.util.List;
17+
import java.util.Set;
18+
19+
import org.eclipse.rdf4j.model.IRI;
20+
import org.eclipse.rdf4j.model.Literal;
21+
import org.eclipse.rdf4j.model.Value;
22+
import org.eclipse.rdf4j.model.base.CoreDatatype;
23+
import org.eclipse.rdf4j.query.BindingSet;
24+
import org.eclipse.rdf4j.query.Dataset;
25+
import org.eclipse.rdf4j.query.algebra.BindingSetAssignment;
26+
import org.eclipse.rdf4j.query.algebra.Compare;
27+
import org.eclipse.rdf4j.query.algebra.Filter;
28+
import org.eclipse.rdf4j.query.algebra.Join;
29+
import org.eclipse.rdf4j.query.algebra.ListMemberOperator;
30+
import org.eclipse.rdf4j.query.algebra.SameTerm;
31+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
32+
import org.eclipse.rdf4j.query.algebra.ValueConstant;
33+
import org.eclipse.rdf4j.query.algebra.ValueExpr;
34+
import org.eclipse.rdf4j.query.algebra.Var;
35+
import org.eclipse.rdf4j.query.algebra.VariableScopeChange;
36+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
37+
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
38+
import org.eclipse.rdf4j.query.impl.MapBindingSet;
39+
40+
/**
41+
* Rewrites safe RDF-term equality IN filters into a finite VALUES semijoin anchor.
42+
*/
43+
final class FilterInValuesOptimizer implements QueryOptimizer {
44+
45+
private static final int MAX_VALUES = 64;
46+
47+
@Override
48+
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
49+
tupleExpr.visit(new Visitor());
50+
}
51+
52+
private static final class Visitor extends AbstractSimpleQueryModelVisitor<RuntimeException> {
53+
54+
private Visitor() {
55+
super(false);
56+
}
57+
58+
@Override
59+
public void meet(Filter filter) {
60+
super.meet(filter);
61+
if (filter.getParentNode() == null || isScopeBoundary(filter)) {
62+
return;
63+
}
64+
65+
BindingSetAssignment assignment = safeValuesAnchor(filter.getCondition());
66+
if (assignment == null
67+
|| !filter.getArg().getAssuredBindingNames().containsAll(assignment.getBindingNames())) {
68+
return;
69+
}
70+
71+
filter.replaceWith(new Join(assignment, filter.getArg().clone()));
72+
}
73+
}
74+
75+
private static boolean isScopeBoundary(Filter filter) {
76+
return filter != null && filter.isVariableScopeChange();
77+
}
78+
79+
private static BindingSetAssignment safeValuesAnchor(ValueExpr condition) {
80+
if (condition instanceof ListMemberOperator) {
81+
return listValuesAnchor((ListMemberOperator) condition);
82+
}
83+
if (condition instanceof Compare compare && compare.getOperator() == Compare.CompareOp.EQ) {
84+
return singleValueAnchor(compare.getLeftArg(), compare.getRightArg());
85+
}
86+
if (condition instanceof SameTerm sameTerm) {
87+
return singleValueAnchor(sameTerm.getLeftArg(), sameTerm.getRightArg());
88+
}
89+
return null;
90+
}
91+
92+
private static BindingSetAssignment listValuesAnchor(ListMemberOperator operator) {
93+
List<ValueExpr> arguments = operator.getArguments();
94+
if (arguments == null || arguments.size() < 2 || !(arguments.getFirst()instanceof Var var)) {
95+
return null;
96+
}
97+
98+
if (var.hasValue() || var.getName() == null) {
99+
return null;
100+
}
101+
102+
LinkedHashSet<Value> values = new LinkedHashSet<>();
103+
for (int i = 1; i < arguments.size(); i++) {
104+
if (!(arguments.get(i) instanceof ValueConstant)) {
105+
return null;
106+
}
107+
Value value = ((ValueConstant) arguments.get(i)).getValue();
108+
if (!isSafeValuesAnchorValue(value) || !values.add(value) || values.size() > MAX_VALUES) {
109+
return null;
110+
}
111+
}
112+
return valuesAnchor(var.getName(), values);
113+
}
114+
115+
private static BindingSetAssignment singleValueAnchor(ValueExpr left, ValueExpr right) {
116+
if (left instanceof Var && right instanceof ValueConstant) {
117+
return singleValueAnchor((Var) left, (ValueConstant) right);
118+
}
119+
if (right instanceof Var && left instanceof ValueConstant) {
120+
return singleValueAnchor((Var) right, (ValueConstant) left);
121+
}
122+
return null;
123+
}
124+
125+
private static BindingSetAssignment singleValueAnchor(Var var, ValueConstant constant) {
126+
if (var.hasValue() || var.getName() == null || !isSafeValuesAnchorValue(constant.getValue())) {
127+
return null;
128+
}
129+
LinkedHashSet<Value> values = new LinkedHashSet<>();
130+
values.add(constant.getValue());
131+
return valuesAnchor(var.getName(), values);
132+
}
133+
134+
private static BindingSetAssignment valuesAnchor(String bindingName, LinkedHashSet<Value> values) {
135+
if (values.isEmpty()) {
136+
return null;
137+
}
138+
139+
BindingSetAssignment assignment = new BindingSetAssignment();
140+
assignment.setBindingNames(Set.of(bindingName));
141+
List<BindingSet> bindingSets = new ArrayList<>(values.size());
142+
for (Value value : values) {
143+
MapBindingSet bindingSet = new MapBindingSet(1);
144+
bindingSet.addBinding(bindingName, value);
145+
bindingSets.add(bindingSet);
146+
}
147+
assignment.setBindingSets(bindingSets);
148+
return assignment;
149+
}
150+
151+
private static boolean isSafeValuesAnchorValue(Value value) {
152+
return value instanceof IRI || value instanceof Literal && !isUnsafeCoreInEqualityValue((Literal) value);
153+
}
154+
155+
private static boolean isUnsafeCoreInEqualityValue(Literal literal) {
156+
CoreDatatype coreDatatype = literal.getCoreDatatype();
157+
if (!coreDatatype.isXSDDatatype()) {
158+
return false;
159+
}
160+
CoreDatatype.XSD xsdDatatype = coreDatatype.asXSDDatatypeOrNull();
161+
return xsdDatatype.isNumericDatatype() || xsdDatatype.isCalendarDatatype()
162+
|| xsdDatatype == CoreDatatype.XSD.BOOLEAN;
163+
}
164+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class StandardQueryOptimizerPipeline implements QueryOptimizerPipeline {
4949
public static final ProjectionRemovalOptimizer PROJECTION_REMOVAL_OPTIMIZER = new ProjectionRemovalOptimizer();
5050
public static final IterativeEvaluationOptimizer ITERATIVE_EVALUATION_OPTIMIZER = new IterativeEvaluationOptimizer();
5151
public static final FilterOptimizer FILTER_OPTIMIZER = new FilterOptimizer();
52+
public static final FilterInValuesOptimizer FILTER_IN_VALUES_OPTIMIZER = new FilterInValuesOptimizer();
5253
public static final OrderLimitOptimizer ORDER_LIMIT_OPTIMIZER = new OrderLimitOptimizer();
5354
public static final ParentReferenceCleaner PARENT_REFERENCE_CLEANER = new ParentReferenceCleaner();
5455
private final EvaluationStatistics evaluationStatistics;
@@ -82,6 +83,7 @@ public Iterable<QueryOptimizer> getOptimizers() {
8283
QUERY_MODEL_NORMALIZER,
8384
PROJECTION_REMOVAL_OPTIMIZER, // Make sure this is after the UnionScopeChangeOptimizer
8485
new FilterOptimizer(evaluationStatistics, false, true),
86+
FILTER_IN_VALUES_OPTIMIZER,
8587
new QueryJoinOptimizer(evaluationStatistics, strategy.isTrackResultSize(), tripleSource),
8688
ITERATIVE_EVALUATION_OPTIMIZER,
8789
new FilterOptimizer(evaluationStatistics),

0 commit comments

Comments
 (0)