Skip to content

Commit e831cc8

Browse files
committed
fixes
1 parent d57102f commit e831cc8

2 files changed

Lines changed: 62 additions & 18 deletions

File tree

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbLftjPreparedPlanCache.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,29 @@ synchronized void clear() {
4444
}
4545

4646
static String normalizedKey(List<StatementPattern> patterns, Set<String> configuredIndexes) {
47-
Map<String, String> aliases = new LinkedHashMap<>();
4847
StringBuilder builder = new StringBuilder(configuredIndexes.size() * 6 + patterns.size() * 32);
4948
builder.append("indexes=");
5049
configuredIndexes.stream().sorted().forEach(indexName -> builder.append(indexName).append(','));
5150
builder.append(";patterns=");
52-
int[] nextAlias = { 0 };
53-
for (StatementPattern pattern : patterns) {
54-
builder.append('[').append(pattern.getScope().name()).append(';');
55-
appendTerm(builder, pattern.getSubjectVar(), aliases, nextAlias);
56-
appendTerm(builder, pattern.getPredicateVar(), aliases, nextAlias);
57-
appendTerm(builder, pattern.getObjectVar(), aliases, nextAlias);
58-
appendTerm(builder, pattern.getContextVar(), aliases, nextAlias);
59-
builder.append(']');
60-
}
51+
patterns.stream()
52+
.map(LmdbLftjPreparedPlanCache::patternKey)
53+
.sorted()
54+
.forEach(builder::append);
55+
return builder.toString();
56+
}
57+
58+
private static String patternKey(StatementPattern pattern) {
59+
StringBuilder builder = new StringBuilder(48);
60+
builder.append('[').append(pattern.getScope().name()).append(';');
61+
appendTerm(builder, pattern.getSubjectVar());
62+
appendTerm(builder, pattern.getPredicateVar());
63+
appendTerm(builder, pattern.getObjectVar());
64+
appendTerm(builder, pattern.getContextVar());
65+
builder.append(']');
6166
return builder.toString();
6267
}
6368

64-
private static void appendTerm(StringBuilder builder, Var var, Map<String, String> aliases, int[] nextAlias) {
69+
private static void appendTerm(StringBuilder builder, Var var) {
6570
if (var == null) {
6671
builder.append("null;");
6772
return;
@@ -74,8 +79,7 @@ private static void appendTerm(StringBuilder builder, Var var, Map<String, Strin
7479
builder.append("hidden;");
7580
return;
7681
}
77-
String alias = aliases.computeIfAbsent(var.getName(), ignored -> "v" + nextAlias[0]++);
78-
builder.append(alias).append(';');
82+
builder.append("var=").append(var.getName()).append(';');
7983
}
8084

8185
private static String valueKey(Value value) {

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbLftjOptimizerTest.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
1616

1717
import java.util.HashMap;
18+
import java.util.List;
1819
import java.util.Map;
1920
import java.util.Set;
2021

@@ -38,24 +39,45 @@
3839

3940
class LmdbLftjOptimizerTest {
4041

42+
private static final SimpleValueFactory VF = SimpleValueFactory.getInstance();
43+
4144
@Test
42-
void optimizeShouldReusePreparedPlanAcrossEquivalentVariableRenames() {
45+
void optimizeShouldNotReusePreparedPlanAcrossEquivalentVariableRenames() {
4346
TestQueryAccess queryAccess = new TestQueryAccess();
4447
LmdbLftjOptimizer optimizer = new LmdbLftjOptimizer(
4548
new LmdbLftjTripleSource(new EmptyTripleSource(), queryAccess));
4649

4750
TupleExpr first = new QueryRoot(cycle("a", "b", "c"));
4851
TupleExpr second = new QueryRoot(cycle("x", "y", "z"));
52+
Set<String> expectedBindingNames = second.getBindingNames();
53+
54+
optimizer.optimize(first, (Dataset) null, EmptyBindingSet.getInstance());
55+
optimizer.optimize(second, (Dataset) null, EmptyBindingSet.getInstance());
56+
57+
assertEquals(2, queryAccess.cachedPlanPuts,
58+
"renamed visible variables must produce a fresh prepared plan");
59+
assertEquals(0, queryAccess.cachedPlanHits,
60+
"prepared-plan reuse across renamed variables leaks stale binding names");
61+
assertEquals(List.of("x", "y", "z"), lftjNode(second).plan().variableOrder());
62+
assertEquals(expectedBindingNames, lftjNode(second).plan().bindingNames());
63+
}
64+
65+
@Test
66+
void optimizeShouldReusePreparedPlanAcrossEquivalentJoinReorders() {
67+
TestQueryAccess queryAccess = new TestQueryAccess();
68+
LmdbLftjOptimizer optimizer = new LmdbLftjOptimizer(
69+
new LmdbLftjTripleSource(new EmptyTripleSource(), queryAccess));
70+
71+
TupleExpr first = new QueryRoot(distinctPredicateCycle("a", "b", "c"));
72+
TupleExpr second = new QueryRoot(reorderedDistinctPredicateCycle("a", "b", "c"));
4973

5074
optimizer.optimize(first, (Dataset) null, EmptyBindingSet.getInstance());
5175
optimizer.optimize(second, (Dataset) null, EmptyBindingSet.getInstance());
5276

5377
assertEquals(1, queryAccess.cachedPlanPuts,
54-
"first cyclic plan should populate the prepared-plan cache once");
78+
"equivalent cyclic shapes should share one prepared plan entry");
5579
assertEquals(1, queryAccess.cachedPlanHits,
56-
"equivalent cyclic shape should reuse the cached prepared plan on the second optimize");
57-
assertInstanceOf(LmdbLftjTupleExpr.class, assertInstanceOf(QueryRoot.class, first).getArg());
58-
assertInstanceOf(LmdbLftjTupleExpr.class, assertInstanceOf(QueryRoot.class, second).getArg());
80+
"commuted join order should still hit the normalized prepared-plan cache");
5981
}
6082

6183
private TupleExpr cycle(String a, String b, String c) {
@@ -69,6 +91,24 @@ private StatementPattern statementPattern(String subjectName, String objectName)
6991
return new StatementPattern(new Var(subjectName), new Var("pred", FOAF.KNOWS), new Var(objectName));
7092
}
7193

94+
private TupleExpr distinctPredicateCycle(String a, String b, String c) {
95+
StatementPattern pattern1 = new StatementPattern(new Var(a), new Var("p1", VF.createIRI("urn:p1")), new Var(b));
96+
StatementPattern pattern2 = new StatementPattern(new Var(b), new Var("p2", VF.createIRI("urn:p2")), new Var(c));
97+
StatementPattern pattern3 = new StatementPattern(new Var(c), new Var("p3", VF.createIRI("urn:p3")), new Var(a));
98+
return new Join(new Join(pattern1, pattern2), pattern3);
99+
}
100+
101+
private TupleExpr reorderedDistinctPredicateCycle(String a, String b, String c) {
102+
StatementPattern pattern1 = new StatementPattern(new Var(a), new Var("p1", VF.createIRI("urn:p1")), new Var(b));
103+
StatementPattern pattern2 = new StatementPattern(new Var(b), new Var("p2", VF.createIRI("urn:p2")), new Var(c));
104+
StatementPattern pattern3 = new StatementPattern(new Var(c), new Var("p3", VF.createIRI("urn:p3")), new Var(a));
105+
return new Join(new Join(pattern2, pattern3), pattern1);
106+
}
107+
108+
private LmdbLftjTupleExpr lftjNode(TupleExpr tupleExpr) {
109+
return assertInstanceOf(LmdbLftjTupleExpr.class, assertInstanceOf(QueryRoot.class, tupleExpr).getArg());
110+
}
111+
72112
private static final class EmptyTripleSource implements TripleSource {
73113

74114
@Override

0 commit comments

Comments
 (0)