Skip to content

Commit b64f565

Browse files
committed
wip
1 parent f3c4827 commit b64f565

7 files changed

Lines changed: 975 additions & 25 deletions

File tree

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ public abstract class AbstractBulkJoinPlanNode implements PlanNode {
4141
protected static final int BULK_SIZE = 1000;
4242
private final List<StatementMatcher.Variable> vars;
4343
private final String varsQueryString;
44+
private final boolean includeInferredStatements;
4445
StackTraceElement[] stackTrace;
4546
protected Function<BindingSet, ValidationTuple> mapper;
4647
ValidationExecutionLogger validationExecutionLogger;
4748

48-
public AbstractBulkJoinPlanNode(List<StatementMatcher.Variable> vars) {
49+
public AbstractBulkJoinPlanNode(List<StatementMatcher.Variable> vars, boolean includeInferredStatements) {
4950
this.vars = vars;
51+
this.includeInferredStatements = includeInferredStatements;
5052
this.varsQueryString = vars.stream()
5153
.map(StatementMatcher.Variable::asSparqlVariable)
5254
.reduce((a, b) -> a + " " + b)
@@ -85,7 +87,7 @@ private void executeQuery(ArrayDeque<ValidationTuple> right, SailConnection conn
8587

8688
// System.out.println(stackTrace[3].getClassName());
8789
try (Stream<? extends BindingSet> stream = connection
88-
.evaluate(parsedQuery, dataset, EmptyBindingSet.getInstance(), includeInferredStatements())
90+
.evaluate(parsedQuery, dataset, EmptyBindingSet.getInstance(), includeInferredStatements)
8991
.stream()) {
9092
stream
9193
.map(mapper)
@@ -95,10 +97,6 @@ private void executeQuery(ArrayDeque<ValidationTuple> right, SailConnection conn
9597

9698
}
9799

98-
protected boolean includeInferredStatements() {
99-
return true;
100-
}
101-
102100
private void updateQuery(TupleExpr parsedQuery, List<BindingSet> newBindindingSet) {
103101
parsedQuery
104102
.visit(new AbstractSimpleQueryModelVisitor<>(false) {
@@ -137,13 +135,13 @@ private List<BindingSet> buildBindingSets(Collection<ValidationTuple> left, Sail
137135

138136
if (!(tuple.getActiveTarget().isResource())) {
139137
hasStatement = previousStateConnection.hasStatement(null, null, tuple.getActiveTarget(),
140-
includeInferredStatements(), dataGraph);
138+
includeInferredStatements, dataGraph);
141139

142140
} else {
143141
hasStatement = previousStateConnection.hasStatement(((Resource) tuple.getActiveTarget()),
144-
null, null, includeInferredStatements(), dataGraph) ||
142+
null, null, includeInferredStatements, dataGraph) ||
145143
previousStateConnection.hasStatement(null, null, tuple.getActiveTarget(),
146-
includeInferredStatements(), dataGraph);
144+
includeInferredStatements, dataGraph);
147145

148146
}
149147

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,14 @@ public class BulkedExternalInnerJoin extends AbstractBulkJoinPlanNode {
5656
private final boolean skipBasedOnPreviousConnection;
5757
private final SailConnection previousStateConnection;
5858
private final String query;
59-
private final boolean includeInferredStatements;
6059
private boolean printed = false;
6160

6261
public BulkedExternalInnerJoin(PlanNode leftNode, SailConnection connection, Resource[] dataGraph,
6362
SparqlFragment query,
6463
boolean skipBasedOnPreviousConnection, SailConnection previousStateConnection,
6564
Function<BindingSet, ValidationTuple> mapper, ConnectionsGroup connectionsGroup,
6665
List<StatementMatcher.Variable> vars) {
67-
super(vars);
66+
super(vars, connectionsGroup.isIncludeInferredStatements());
6867
assert !skipBasedOnPreviousConnection || previousStateConnection != null;
6968

7069
this.leftNode = PlanNodeHelper.handleSorting(this, leftNode, connectionsGroup);
@@ -77,7 +76,6 @@ public BulkedExternalInnerJoin(PlanNode leftNode, SailConnection connection, Res
7776
this.previousStateConnection = previousStateConnection;
7877
this.dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph);
7978
this.dataGraph = dataGraph;
80-
this.includeInferredStatements = connectionsGroup.isIncludeInferredStatements();
8179
}
8280

8381
public static Function<BindingSet, ValidationTuple> getMapper(String a, String c, ConstraintComponent.Scope scope,
@@ -195,11 +193,6 @@ public int depth() {
195193
return leftNode.depth() + 1;
196194
}
197195

198-
@Override
199-
protected boolean includeInferredStatements() {
200-
return includeInferredStatements;
201-
}
202-
203196
@Override
204197
public void getPlanAsGraphvizDot(StringBuilder stringBuilder) {
205198
if (printed) {

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,13 @@ public class BulkedExternalLeftOuterJoin extends AbstractBulkJoinPlanNode {
4242
private final Resource[] dataGraph;
4343
private TupleExpr parsedQuery;
4444
private final String query;
45-
private final boolean includeInferredStatements;
4645
private boolean printed = false;
4746

4847
public BulkedExternalLeftOuterJoin(PlanNode leftNode, SailConnection connection, Resource[] dataGraph,
4948
SparqlFragment query,
5049
Function<BindingSet, ValidationTuple> mapper, ConnectionsGroup connectionsGroup,
5150
List<StatementMatcher.Variable> vars) {
52-
super(vars);
51+
super(vars, connectionsGroup.isIncludeInferredStatements());
5352
leftNode = PlanNodeHelper.handleSorting(this, leftNode, connectionsGroup);
5453
this.leftNode = leftNode;
5554
this.query = query.getNamespacesForSparql()
@@ -59,7 +58,6 @@ public BulkedExternalLeftOuterJoin(PlanNode leftNode, SailConnection connection,
5958
this.mapper = mapper;
6059
this.dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph);
6160
this.dataGraph = dataGraph;
62-
this.includeInferredStatements = connectionsGroup.isIncludeInferredStatements();
6361
}
6462

6563
@Override
@@ -180,11 +178,6 @@ public int depth() {
180178
return leftNode.depth() + 1;
181179
}
182180

183-
@Override
184-
protected boolean includeInferredStatements() {
185-
return includeInferredStatements;
186-
}
187-
188181
@Override
189182
public void getPlanAsGraphvizDot(StringBuilder stringBuilder) {
190183
if (printed) {
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
13+
package org.eclipse.rdf4j.sail.shacl;
14+
15+
import java.io.StringReader;
16+
import java.util.Set;
17+
18+
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
19+
import org.eclipse.rdf4j.model.IRI;
20+
import org.eclipse.rdf4j.model.util.Values;
21+
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
22+
import org.eclipse.rdf4j.query.QueryLanguage;
23+
import org.eclipse.rdf4j.repository.RepositoryConnection;
24+
import org.eclipse.rdf4j.repository.RepositoryException;
25+
import org.eclipse.rdf4j.repository.sail.SailRepository;
26+
import org.eclipse.rdf4j.rio.RDFFormat;
27+
import org.eclipse.rdf4j.sail.NotifyingSail;
28+
import org.eclipse.rdf4j.sail.memory.MemoryStore;
29+
import org.eclipse.rdf4j.sail.shacl.ShaclSail.TransactionSettings.ValidationApproach;
30+
import org.eclipse.rdf4j.sail.shacl.results.ValidationReport;
31+
import org.junit.jupiter.api.Assertions;
32+
33+
abstract class AbstractShaclReasoningCombinationTest {
34+
35+
protected static final IRI DATA_GRAPH = Values.iri("urn:data");
36+
protected static final IRI ONTOLOGY_GRAPH = Values.iri("urn:ontology");
37+
38+
protected static final class ReasoningCase {
39+
final String name;
40+
final String shapesTurtle;
41+
final String dataTurtle;
42+
final String updatePart1;
43+
final String updatePart2;
44+
final boolean conformsWhenEnabled;
45+
final boolean conformsWhenDisabled;
46+
47+
ReasoningCase(String name, String shapesTurtle, String dataTurtle, String updatePart1, String updatePart2,
48+
boolean conformsWhenEnabled, boolean conformsWhenDisabled) {
49+
this.name = name;
50+
this.shapesTurtle = shapesTurtle;
51+
this.dataTurtle = dataTurtle;
52+
this.updatePart1 = updatePart1;
53+
this.updatePart2 = updatePart2;
54+
this.conformsWhenEnabled = conformsWhenEnabled;
55+
this.conformsWhenDisabled = conformsWhenDisabled;
56+
}
57+
58+
boolean expectedConforms(boolean enabled) {
59+
return enabled ? conformsWhenEnabled : conformsWhenDisabled;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return name;
65+
}
66+
}
67+
68+
protected void runAllModes(ReasoningCase testCase, boolean expectedEnabled, boolean rdfsSubClassReasoning,
69+
boolean includeInferredStatements) {
70+
assertSingleTransaction(testCase, expectedEnabled, rdfsSubClassReasoning, includeInferredStatements);
71+
assertBulkValidation(testCase, expectedEnabled, rdfsSubClassReasoning, includeInferredStatements);
72+
assertMultiUpdateTransaction(testCase, expectedEnabled, rdfsSubClassReasoning, includeInferredStatements);
73+
assertShaclValidator(testCase, expectedEnabled, rdfsSubClassReasoning, includeInferredStatements);
74+
}
75+
76+
protected abstract NotifyingSail createDataSail();
77+
78+
protected abstract String ontologyTurtle();
79+
80+
private void assertSingleTransaction(ReasoningCase testCase, boolean expectedEnabled, boolean rdfsSubClassReasoning,
81+
boolean includeInferredStatements) {
82+
SailRepository repository = createRepository(testCase, rdfsSubClassReasoning, includeInferredStatements);
83+
try {
84+
try (RepositoryConnection connection = repository.getConnection()) {
85+
connection.begin(IsolationLevels.NONE);
86+
addTurtle(connection, testCase.dataTurtle, DATA_GRAPH);
87+
commitExpecting(testCase, expectedEnabled, connection, "single transaction");
88+
}
89+
} finally {
90+
repository.shutDown();
91+
}
92+
}
93+
94+
private void assertBulkValidation(ReasoningCase testCase, boolean expectedEnabled, boolean rdfsSubClassReasoning,
95+
boolean includeInferredStatements) {
96+
SailRepository repository = createRepository(testCase, rdfsSubClassReasoning, includeInferredStatements);
97+
try {
98+
loadDataWithoutValidation(repository, testCase.dataTurtle);
99+
try (RepositoryConnection connection = repository.getConnection()) {
100+
connection.begin(ValidationApproach.Bulk);
101+
try {
102+
connection.commit();
103+
Assertions.assertTrue(testCase.expectedConforms(expectedEnabled),
104+
"bulk validation should have failed");
105+
} catch (RepositoryException e) {
106+
connection.rollback();
107+
Throwable cause = e.getCause();
108+
if (!(cause instanceof ShaclSailValidationException)) {
109+
throw e;
110+
}
111+
Assertions.assertFalse(testCase.expectedConforms(expectedEnabled),
112+
"bulk validation unexpectedly failed");
113+
}
114+
}
115+
} finally {
116+
repository.shutDown();
117+
}
118+
}
119+
120+
private void assertMultiUpdateTransaction(ReasoningCase testCase, boolean expectedEnabled,
121+
boolean rdfsSubClassReasoning,
122+
boolean includeInferredStatements) {
123+
SailRepository repository = createRepository(testCase, rdfsSubClassReasoning, includeInferredStatements);
124+
try {
125+
try (RepositoryConnection connection = repository.getConnection()) {
126+
connection.begin(IsolationLevels.NONE);
127+
connection.prepareUpdate(QueryLanguage.SPARQL, testCase.updatePart1).execute();
128+
connection.prepareUpdate(QueryLanguage.SPARQL, testCase.updatePart2).execute();
129+
commitExpecting(testCase, expectedEnabled, connection, "multi-update transaction");
130+
}
131+
} finally {
132+
repository.shutDown();
133+
}
134+
}
135+
136+
private void assertShaclValidator(ReasoningCase testCase, boolean expectedEnabled, boolean rdfsSubClassReasoning,
137+
boolean includeInferredStatements) {
138+
SailRepository shapesRepo = new SailRepository(new MemoryStore());
139+
SailRepository dataRepo = new SailRepository(createDataSail());
140+
try {
141+
shapesRepo.init();
142+
dataRepo.init();
143+
loadShapes(shapesRepo, testCase.shapesTurtle);
144+
loadOntology(dataRepo);
145+
loadDataWithoutValidation(dataRepo, testCase.dataTurtle);
146+
147+
ValidationReport report = ShaclValidator.builder()
148+
.setEclipseRdf4jShaclExtensions(true)
149+
.setCacheSelectNodes(true)
150+
.setParallelValidation(false)
151+
.setRdfsSubClassReasoning(rdfsSubClassReasoning)
152+
.setIncludeInferredStatements(includeInferredStatements)
153+
.withShapes(shapesRepo.getSail())
154+
.build()
155+
.validate(dataRepo.getSail());
156+
157+
Assertions.assertEquals(testCase.expectedConforms(expectedEnabled), report.conforms(),
158+
"ShaclValidator result mismatch");
159+
} finally {
160+
try {
161+
shapesRepo.shutDown();
162+
} finally {
163+
dataRepo.shutDown();
164+
}
165+
}
166+
}
167+
168+
private void commitExpecting(ReasoningCase testCase, boolean expectedEnabled, RepositoryConnection connection,
169+
String label) {
170+
boolean expectedConforms = testCase.expectedConforms(expectedEnabled);
171+
try {
172+
connection.commit();
173+
Assertions.assertTrue(expectedConforms, label + " should have failed");
174+
} catch (RepositoryException e) {
175+
connection.rollback();
176+
Throwable cause = e.getCause();
177+
if (!(cause instanceof ShaclSailValidationException)) {
178+
throw e;
179+
}
180+
Assertions.assertFalse(expectedConforms, label + " should have conformed");
181+
}
182+
}
183+
184+
private SailRepository createRepository(ReasoningCase testCase, boolean rdfsSubClassReasoning,
185+
boolean includeInferredStatements) {
186+
SailRepository repository = new SailRepository(
187+
createShaclSail(rdfsSubClassReasoning, includeInferredStatements));
188+
repository.init();
189+
loadShapes(repository, testCase.shapesTurtle);
190+
loadOntology(repository);
191+
return repository;
192+
}
193+
194+
private ShaclSail createShaclSail(boolean rdfsSubClassReasoning, boolean includeInferredStatements) {
195+
NotifyingSail baseSail = createDataSail();
196+
ShaclSail shaclSail = new ShaclSail(baseSail);
197+
shaclSail.setLogValidationPlans(false);
198+
shaclSail.setCacheSelectNodes(true);
199+
shaclSail.setParallelValidation(false);
200+
shaclSail.setLogValidationViolations(false);
201+
shaclSail.setGlobalLogValidationExecution(false);
202+
shaclSail.setEclipseRdf4jShaclExtensions(true);
203+
shaclSail.setDashDataShapes(false);
204+
shaclSail.setPerformanceLogging(false);
205+
shaclSail.setSerializableValidation(false);
206+
shaclSail.setShapesGraphs(Set.of(RDF4J.SHACL_SHAPE_GRAPH));
207+
shaclSail.setRdfsSubClassReasoning(rdfsSubClassReasoning);
208+
shaclSail.setIncludeInferredStatements(includeInferredStatements);
209+
return shaclSail;
210+
}
211+
212+
private void loadShapes(SailRepository repository, String turtle) {
213+
try (RepositoryConnection connection = repository.getConnection()) {
214+
connection.begin(IsolationLevels.NONE, ValidationApproach.Disabled);
215+
addTurtle(connection, turtle, RDF4J.SHACL_SHAPE_GRAPH);
216+
connection.commit();
217+
}
218+
}
219+
220+
private void loadOntology(SailRepository repository) {
221+
try (RepositoryConnection connection = repository.getConnection()) {
222+
connection.begin(IsolationLevels.NONE, ValidationApproach.Disabled);
223+
addTurtle(connection, ontologyTurtle(), ONTOLOGY_GRAPH);
224+
connection.commit();
225+
}
226+
}
227+
228+
private void loadDataWithoutValidation(SailRepository repository, String turtle) {
229+
try (RepositoryConnection connection = repository.getConnection()) {
230+
connection.begin(IsolationLevels.NONE, ValidationApproach.Disabled);
231+
addTurtle(connection, turtle, DATA_GRAPH);
232+
connection.commit();
233+
}
234+
}
235+
236+
private static void addTurtle(RepositoryConnection connection, String turtle, IRI context) {
237+
try {
238+
connection.add(new StringReader(turtle), "", RDFFormat.TURTLE, context);
239+
} catch (java.io.IOException e) {
240+
throw new RuntimeException(e);
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)