Skip to content

Commit 736e4b1

Browse files
authored
GH-5189 cache Value objects retrieved from parent sail (#5190)
2 parents 3068e99 + 8fa48c5 commit 736e4b1

7 files changed

Lines changed: 133 additions & 44 deletions

File tree

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ValidationQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ private String getFullQueryString() {
232232
} else {
233233
extraVariablesString = "";
234234
}
235-
} else
235+
} else {
236236
extraVariablesString = "";
237+
}
237238

238239
if (scope_validationReport == ConstraintComponent.Scope.propertyShape
239240
&& propertyShapeWithValue_validationReport) {

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AllTargetsPlanNode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ protected void init() {
7171

7272
@Override
7373
public void localClose() {
74-
if (iterator != null)
74+
if (iterator != null) {
7575
iterator.close();
76+
}
7677
}
7778

7879
@Override

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicate.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
2222
import org.eclipse.rdf4j.model.IRI;
2323
import org.eclipse.rdf4j.model.Resource;
24-
import org.eclipse.rdf4j.model.Statement;
2524
import org.eclipse.rdf4j.model.Value;
2625
import org.eclipse.rdf4j.sail.SailConnection;
2726
import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection;
@@ -36,6 +35,7 @@ public class FilterByPredicate implements PlanNode {
3635
private final Set<IRI> filterOnPredicates;
3736
final PlanNode parent;
3837
private final On on;
38+
private final ConnectionsGroup connectionsGroup;
3939
private boolean printed = false;
4040
private ValidationExecutionLogger validationExecutionLogger;
4141
private final Resource[] dataGraph;
@@ -53,6 +53,7 @@ public FilterByPredicate(SailConnection connection, Set<IRI> filterOnPredicates,
5353
assert this.connection != null;
5454
this.filterOnPredicates = filterOnPredicates;
5555
this.on = on;
56+
this.connectionsGroup = connectionsGroup;
5657
}
5758

5859
@Override
@@ -77,18 +78,11 @@ void calculateNext() {
7778
return;
7879
}
7980

80-
filterOnPredicates = FilterByPredicate.this.filterOnPredicates.stream()
81-
.map(predicate -> {
82-
try (var stream = connection
83-
.getStatements(null, predicate, null, true, dataGraph)
84-
.stream()) {
85-
return stream.map(Statement::getPredicate)
86-
.findAny()
87-
.orElse(null);
88-
}
89-
}
90-
)
91-
.filter(Objects::nonNull)
81+
filterOnPredicates = FilterByPredicate.this.filterOnPredicates
82+
.stream()
83+
.map(iri -> connectionsGroup.getSailSpecificValue(iri,
84+
ConnectionsGroup.StatementPosition.predicate, connection
85+
))
9286
.collect(Collectors.toList());
9387

9488
}

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicateObject.java

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
2121
import org.eclipse.rdf4j.model.IRI;
2222
import org.eclipse.rdf4j.model.Resource;
23-
import org.eclipse.rdf4j.model.Statement;
2423
import org.eclipse.rdf4j.model.Value;
24+
import org.eclipse.rdf4j.model.vocabulary.RDF;
2525
import org.eclipse.rdf4j.sail.SailConnection;
2626
import org.eclipse.rdf4j.sail.SailException;
2727
import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection;
@@ -42,10 +42,12 @@ public class FilterByPredicateObject implements PlanNode {
4242
private final FilterOn filterOn;
4343
private final PlanNode parent;
4444
private final boolean returnMatching;
45+
private final ConnectionsGroup connectionsGroup;
4546
private StackTraceElement[] stackTrace;
4647
private boolean printed = false;
4748
private ValidationExecutionLogger validationExecutionLogger;
4849
private final Resource[] dataGraph;
50+
boolean typeFilterWithInference;
4951

5052
private final Cache<Resource, Boolean> cache;
5153

@@ -68,6 +70,11 @@ public FilterByPredicateObject(SailConnection connection, Resource[] dataGraph,
6870
cache = CacheBuilder.newBuilder().maximumSize(10000).build();
6971
}
7072

73+
this.connectionsGroup = connectionsGroup;
74+
if (includeInferred && connectionsGroup.getRdfsSubClassOfReasoner() != null
75+
&& RDF.TYPE.equals(filterOnPredicate)) {
76+
typeFilterWithInference = true;
77+
}
7178
// this.stackTrace = Thread.currentThread().getStackTrace();
7279
}
7380

@@ -148,31 +155,32 @@ void calculateNext() {
148155

149156
private void internResources() {
150157
if (filterOnObject == null) {
151-
152-
try (var stream = connection
153-
.getStatements(null, FilterByPredicateObject.this.filterOnPredicate, null, includeInferred,
154-
dataGraph)
155-
.stream()) {
156-
filterOnPredicate = stream.map(Statement::getPredicate).findAny().orElse(null);
157-
}
158-
158+
filterOnPredicate = connectionsGroup.getSailSpecificValue(
159+
FilterByPredicateObject.this.filterOnPredicate,
160+
ConnectionsGroup.StatementPosition.predicate, connection
161+
);
159162
if (filterOnPredicate == null) {
160163
filterOnObject = new Resource[0];
161164
} else {
162-
filterOnObject = FilterByPredicateObject.this.filterOnObject.stream()
163-
.map(object -> {
164-
try (var stream = connection
165-
.getStatements(null, filterOnPredicate, object, includeInferred, dataGraph)
166-
.stream()) {
167-
return stream.map(Statement::getObject)
168-
.map(o -> ((Resource) o))
169-
.findAny()
170-
.orElse(null);
171-
}
172-
}
173-
)
174-
.filter(Objects::nonNull)
175-
.toArray(Resource[]::new);
165+
if (typeFilterWithInference) {
166+
filterOnObject = FilterByPredicateObject.this.filterOnObject.stream()
167+
.flatMap(type -> connectionsGroup.getRdfsSubClassOfReasoner()
168+
.backwardsChain(type)
169+
.stream())
170+
.distinct()
171+
.map(object -> connectionsGroup.getSailSpecificValue(object,
172+
ConnectionsGroup.StatementPosition.object, connection
173+
))
174+
.filter(Objects::nonNull)
175+
.toArray(Resource[]::new);
176+
} else {
177+
filterOnObject = FilterByPredicateObject.this.filterOnObject.stream()
178+
.map(object -> connectionsGroup.getSailSpecificValue(object,
179+
ConnectionsGroup.StatementPosition.object, connection
180+
))
181+
.filter(Objects::nonNull)
182+
.toArray(Resource[]::new);
183+
}
176184
}
177185

178186
}
@@ -237,8 +245,8 @@ private Boolean matchesCached(Resource subject, IRI filterOnPredicate, Resource[
237245

238246
private boolean matchesUnCached(Resource subject, IRI filterOnPredicate, Resource[] filterOnObject) {
239247
for (Resource object : filterOnObject) {
240-
if (connection.hasStatement(subject, filterOnPredicate, object, includeInferred,
241-
dataGraph)) {
248+
if (connection.hasStatement(subject, filterOnPredicate, object,
249+
includeInferred && !typeFilterWithInference, dataGraph)) {
242250
return true;
243251
}
244252
}

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LeftOuterJoin.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,13 @@ void calculateNext() {
119119
@Override
120120
public void localClose() {
121121
try {
122-
if (leftIterator != null)
122+
if (leftIterator != null) {
123123
leftIterator.close();
124+
}
124125
} finally {
125-
if (rightIterator != null)
126+
if (rightIterator != null) {
126127
rightIterator.close();
128+
}
127129
}
128130
}
129131

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/NonUniqueTargetLang.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ private void calculateNext() {
166166

167167
@Override
168168
public void localClose() {
169-
if (parentIterator != null)
169+
if (parentIterator != null) {
170170
parentIterator.close();
171+
}
171172
}
172173

173174
@Override

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/wrapper/data/ConnectionsGroup.java

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313

1414
import java.util.Map;
1515
import java.util.concurrent.ConcurrentHashMap;
16+
import java.util.concurrent.ExecutionException;
1617

1718
import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
1819
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
20+
import org.eclipse.rdf4j.model.IRI;
21+
import org.eclipse.rdf4j.model.Resource;
22+
import org.eclipse.rdf4j.model.Statement;
23+
import org.eclipse.rdf4j.model.Value;
24+
import org.eclipse.rdf4j.model.util.Values;
1925
import org.eclipse.rdf4j.sail.Sail;
2026
import org.eclipse.rdf4j.sail.SailConnection;
27+
import org.eclipse.rdf4j.sail.SailException;
2128
import org.eclipse.rdf4j.sail.shacl.ShaclSailConnection;
2229
import org.eclipse.rdf4j.sail.shacl.Stats;
2330
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter;
@@ -27,6 +34,9 @@
2734
import org.slf4j.Logger;
2835
import org.slf4j.LoggerFactory;
2936

37+
import com.google.common.cache.Cache;
38+
import com.google.common.cache.CacheBuilder;
39+
3040
/**
3141
* @apiNote since 3.0. This feature is for internal use only: its existence, signature or behavior may change without
3242
* warning from one release to the next.
@@ -52,6 +62,11 @@ public class ConnectionsGroup implements AutoCloseable {
5262
// used to cache Select plan nodes so that we don't query a store for the same data during the same validation step.
5363
private final Map<PlanNode, BufferedSplitter> nodeCache = new ConcurrentHashMap<>();
5464

65+
private final Cache<Value, Value> INTERNED_VALUE_CACHE = CacheBuilder.newBuilder()
66+
.concurrencyLevel(Runtime.getRuntime().availableProcessors() * 2)
67+
.maximumSize(10000)
68+
.build();
69+
5570
public ConnectionsGroup(SailConnection baseConnection,
5671
SailConnection previousStateConnection, Sail addedStatements, Sail removedStatements,
5772
Stats stats, RdfsSubClassOfReasonerProvider rdfsSubClassOfReasonerProvider,
@@ -95,6 +110,67 @@ public SailConnection getRemovedStatements() {
95110
return removedStatements;
96111
}
97112

113+
public enum StatementPosition {
114+
subject,
115+
predicate,
116+
object
117+
}
118+
119+
/**
120+
* This method is a performance optimization for converting a more general value object, like RDF.TYPE, to the
121+
* specific Value object that the underlying sail would use for that node. It uses a cache to avoid querying the
122+
* store for the same value multiple times during the same validation.
123+
*
124+
* @param value the value object to be converted
125+
* @param statementPosition the position of the statement (subject, predicate, or object)
126+
* @param connection the SailConnection used to retrieve the specific Value object
127+
* @param <T> the type of the value
128+
* @return the specific Value object used by the underlying sail, or the original value if no specific Value is
129+
* found
130+
* @throws SailException if an error occurs while retrieving the specific Value object
131+
*/
132+
public <T extends Value> T getSailSpecificValue(T value, StatementPosition statementPosition,
133+
SailConnection connection) {
134+
try {
135+
136+
Value t = INTERNED_VALUE_CACHE.get(value, () -> {
137+
138+
switch (statementPosition) {
139+
case subject:
140+
try (var statements = connection.getStatements(((Resource) value), null, null, false).stream()) {
141+
Resource ret = statements.map(Statement::getSubject).findAny().orElse(null);
142+
if (ret == null) {
143+
return value;
144+
}
145+
return ret;
146+
}
147+
case predicate:
148+
try (var statements = connection.getStatements(null, ((IRI) value), null, false).stream()) {
149+
IRI ret = statements.map(Statement::getPredicate).findAny().orElse(null);
150+
if (ret == null) {
151+
return value;
152+
}
153+
return ret;
154+
}
155+
case object:
156+
try (var statements = connection.getStatements(null, null, value, false).stream()) {
157+
Value ret = statements.map(Statement::getObject).findAny().orElse(null);
158+
if (ret == null) {
159+
return value;
160+
}
161+
return ret;
162+
}
163+
}
164+
165+
throw new IllegalStateException("Unknown statement position: " + statementPosition);
166+
167+
});
168+
return ((T) t);
169+
} catch (ExecutionException e) {
170+
throw new SailException(e);
171+
}
172+
}
173+
98174
@Override
99175
public void close() {
100176
if (addedStatements != null) {
@@ -143,9 +219,15 @@ public PlanNode getCachedNodeFor(PlanNode planNode) {
143219

144220
}
145221

222+
/**
223+
* Returns the RdfsSubClassOfReasoner if it is enabled. If it is not enabled this method will return null.
224+
*
225+
* @return RdfsSubClassOfReasoner or null
226+
*/
146227
public RdfsSubClassOfReasoner getRdfsSubClassOfReasoner() {
147-
if (rdfsSubClassOfReasonerProvider == null)
228+
if (rdfsSubClassOfReasonerProvider == null) {
148229
return null;
230+
}
149231
return rdfsSubClassOfReasonerProvider.getRdfsSubClassOfReasoner();
150232
}
151233

0 commit comments

Comments
 (0)