Skip to content

Commit 72572e7

Browse files
committed
GH-0000 Provide LMDB index recommendations in explain plans
1 parent 6e1f1db commit 72572e7

6 files changed

Lines changed: 163 additions & 4 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/evaluationsteps/StatementPatternQueryEvaluationStep.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,10 @@ private JoinStatementWithBindingSetIterator getIteration(BindingSet bindings) {
276276
}
277277

278278
if (iteration instanceof IndexReportingIterator) {
279-
statementPattern.setIndexName(((IndexReportingIterator) iteration).getIndexName());
279+
String indexName = ((IndexReportingIterator) iteration).getIndexName();
280+
statementPattern.setIndexName(indexName);
281+
} else {
282+
statementPattern.setIndexName(null);
280283
}
281284

282285
if (iteration instanceof EmptyIteration) {
@@ -329,7 +332,10 @@ private ConvertStatementToBindingSetIterator getIteration() {
329332
iteration = tripleSource.getStatements((Resource) subject, (IRI) predicate, object, contexts);
330333
}
331334
if (iteration instanceof IndexReportingIterator) {
332-
statementPattern.setIndexName(((IndexReportingIterator) iteration).getIndexName());
335+
String indexName = ((IndexReportingIterator) iteration).getIndexName();
336+
statementPattern.setIndexName(indexName);
337+
} else {
338+
statementPattern.setIndexName(null);
333339
}
334340

335341
if (iteration instanceof EmptyIteration) {

core/sail/base/src/main/java/org/eclipse/rdf4j/sail/TripleSourceIterationWrapper.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
1818
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
19+
import org.eclipse.rdf4j.common.iteration.IndexReportingIterator;
1920
import org.eclipse.rdf4j.query.QueryEvaluationException;
2021

2122
@InternalUseOnly
22-
public class TripleSourceIterationWrapper<T> implements CloseableIteration<T> {
23+
public class TripleSourceIterationWrapper<T> implements CloseableIteration<T>, IndexReportingIterator {
2324

2425
private final CloseableIteration<? extends T> delegate;
2526
private boolean closed = false;
@@ -28,6 +29,14 @@ public TripleSourceIterationWrapper(CloseableIteration<? extends T> delegate) {
2829
this.delegate = Objects.requireNonNull(delegate, "The iterator was null");
2930
}
3031

32+
@Override
33+
public String getIndexName() {
34+
if (delegate instanceof IndexReportingIterator) {
35+
return ((IndexReportingIterator) delegate).getIndexName();
36+
}
37+
return null;
38+
}
39+
3140
/**
3241
* Checks whether the underlying iteration contains more elements.
3342
*

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class LmdbRecordIterator implements RecordIterator {
4545

4646
private final TripleIndex index;
4747

48+
private final String indexName;
49+
4850
private final long subj;
4951
private final long pred;
5052
private final long obj;
@@ -98,6 +100,7 @@ class LmdbRecordIterator implements RecordIterator {
98100
this.keyData = pool.getVal();
99101
this.valueData = pool.getVal();
100102
this.index = index;
103+
this.indexName = computeIndexName(index, subj, pred, obj, context);
101104
if (rangeSearch) {
102105
minKeyBuf = pool.getKeyBuffer();
103106
index.getMinKey(minKeyBuf, subj, pred, obj, context);
@@ -139,6 +142,69 @@ class LmdbRecordIterator implements RecordIterator {
139142
}
140143
}
141144

145+
private static String computeIndexName(TripleIndex index, long subj, long pred, long obj, long context) {
146+
String actual = new String(index.getFieldSeq());
147+
int boundCount = countBound(subj, pred, obj, context);
148+
if (boundCount <= 0) {
149+
return actual;
150+
}
151+
152+
int score = index.getPatternScore(subj, pred, obj, context);
153+
if (score >= boundCount) {
154+
return actual;
155+
}
156+
157+
String recommendation = buildRecommendedIndex(subj, pred, obj, context);
158+
if (recommendation == null || recommendation.equals(actual)) {
159+
return actual;
160+
}
161+
162+
return actual + " (scan; consider " + recommendation + ")";
163+
}
164+
165+
private static int countBound(long subj, long pred, long obj, long context) {
166+
int count = 0;
167+
if (subj >= 0) {
168+
count++;
169+
}
170+
if (pred >= 0) {
171+
count++;
172+
}
173+
if (obj >= 0) {
174+
count++;
175+
}
176+
if (context >= 0) {
177+
count++;
178+
}
179+
return count;
180+
}
181+
182+
private static String buildRecommendedIndex(long subj, long pred, long obj, long context) {
183+
StringBuilder recommendation = new StringBuilder(4);
184+
appendIfBound(recommendation, 's', subj >= 0);
185+
appendIfBound(recommendation, 'p', pred >= 0);
186+
appendIfBound(recommendation, 'o', obj >= 0);
187+
appendIfBound(recommendation, 'c', context >= 0);
188+
189+
if (recommendation.length() == 0) {
190+
return null;
191+
}
192+
193+
for (char component : new char[] { 's', 'p', 'o', 'c' }) {
194+
if (recommendation.indexOf(String.valueOf(component)) < 0) {
195+
recommendation.append(component);
196+
}
197+
}
198+
199+
return recommendation.toString();
200+
}
201+
202+
private static void appendIfBound(StringBuilder builder, char component, boolean bound) {
203+
if (bound) {
204+
builder.append(component);
205+
}
206+
}
207+
142208
@Override
143209
public long[] next() {
144210
long readStamp;
@@ -147,6 +213,7 @@ public long[] next() {
147213
} catch (InterruptedException e) {
148214
throw new SailException(e);
149215
}
216+
150217
try {
151218
if (closed) {
152219
log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null");
@@ -216,6 +283,11 @@ public long[] next() {
216283
}
217284
}
218285

286+
@Override
287+
public String getIndexName() {
288+
return indexName;
289+
}
290+
219291
private boolean matches() {
220292

221293
if (groupMatcher != null) {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.NoSuchElementException;
1515

1616
import org.eclipse.rdf4j.common.iteration.AbstractCloseableIteration;
17+
import org.eclipse.rdf4j.common.iteration.IndexReportingIterator;
1718
import org.eclipse.rdf4j.model.IRI;
1819
import org.eclipse.rdf4j.model.Resource;
1920
import org.eclipse.rdf4j.model.Statement;
@@ -24,7 +25,7 @@
2425
* A statement iterator that wraps a RecordIterator containing statement records and translates these records to
2526
* {@link Statement} objects.
2627
*/
27-
class LmdbStatementIterator extends AbstractCloseableIteration<Statement> {
28+
class LmdbStatementIterator extends AbstractCloseableIteration<Statement> implements IndexReportingIterator {
2829

2930
/*-----------*
3031
* Variables *
@@ -35,6 +36,8 @@ class LmdbStatementIterator extends AbstractCloseableIteration<Statement> {
3536
private final ValueStore valueStore;
3637
private Statement nextElement;
3738

39+
private final String indexName;
40+
3841
/*--------------*
3942
* Constructors *
4043
*--------------*/
@@ -45,6 +48,7 @@ class LmdbStatementIterator extends AbstractCloseableIteration<Statement> {
4548
public LmdbStatementIterator(RecordIterator recordIt, ValueStore valueStore) {
4649
this.recordIt = recordIt;
4750
this.valueStore = valueStore;
51+
this.indexName = recordIt.getIndexName();
4852
}
4953

5054
/*---------*
@@ -135,4 +139,9 @@ private Statement lookAhead() {
135139
public void remove() {
136140
throw new UnsupportedOperationException();
137141
}
142+
143+
@Override
144+
public String getIndexName() {
145+
return indexName;
146+
}
138147
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ interface RecordIterator extends Closeable {
3131
*/
3232
@Override
3333
void close();
34+
35+
default String getIndexName() {
36+
return null;
37+
}
3438
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.eclipse.rdf4j.sail.lmdb;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.File;
6+
7+
import org.eclipse.rdf4j.model.vocabulary.FOAF;
8+
import org.eclipse.rdf4j.model.vocabulary.RDF;
9+
import org.eclipse.rdf4j.query.explanation.Explanation;
10+
import org.eclipse.rdf4j.repository.RepositoryException;
11+
import org.eclipse.rdf4j.repository.sail.SailRepository;
12+
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
13+
import org.eclipse.rdf4j.sail.Sail;
14+
import org.eclipse.rdf4j.sail.SailException;
15+
import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig;
16+
import org.junit.jupiter.api.AfterEach;
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.io.TempDir;
20+
21+
class LmdbExplainIndexRecommendationTest {
22+
23+
@TempDir
24+
File dataDir;
25+
26+
private SailRepository repository;
27+
28+
@BeforeEach
29+
void setUp() throws SailException {
30+
Sail sail = new LmdbStore(dataDir, new LmdbStoreConfig("psoc"));
31+
repository = new SailRepository(sail);
32+
repository.init();
33+
}
34+
35+
@AfterEach
36+
void tearDown() {
37+
if (repository != null) {
38+
repository.shutDown();
39+
}
40+
}
41+
42+
@Test
43+
void recommendsBetterIndexInExplainPlan() {
44+
try (SailRepositoryConnection connection = repository.getConnection()) {
45+
connection.add(connection.getValueFactory().createIRI("http://example.com/alice"), RDF.TYPE, FOAF.PERSON);
46+
47+
Explanation explanation = connection
48+
.prepareTupleQuery("PREFIX foaf: <" + FOAF.NAMESPACE + ">\n" +
49+
"SELECT ?person WHERE { ?person a foaf:Person . }")
50+
.explain(Explanation.Level.Optimized);
51+
52+
String plan = explanation.toString();
53+
54+
assertThat(plan).contains("[index: psoc (scan; consider posc)]");
55+
} catch (RepositoryException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)